require 'tmpdir' require 'rqrcode' DPI = 203 SRC_PDF = ARGV[0] || '/home/randy/EPH266757312_adresne_stitky_a6.pdf' OUT_PS = ARGV[1] || '/tmp/test-label-v2.ps' # barcode crop region in the source label, in native pixels at 203dpi BARCODE_BOX = { x0: 388, y0: 222, x1: 772, y1: 322 } def read_pbm(path) bytes = File.binread(path) header = /\AP4\s+(?:#[^\n]*\n\s*)*(\d+)\s+(\d+)\s/.match(bytes) raise "bad pbm #{path}" unless header width, height = header[1].to_i, header[2].to_i bytes_per_row = (width + 7) / 8 body = bytes.byteslice(header.end(0), bytes_per_row * height) { width: width, height: height, bytes_per_row: bytes_per_row, body: body } end def pbm_bit(pbm, x, y) byte = pbm[:body].getbyte(y * pbm[:bytes_per_row] + (x / 8)) (byte >> (7 - (x % 8))) & 1 end # Packs a [x,y] => 0/1 (1 = black) pixel getter into row-padded, bit-inverted # (PostScript DeviceGray 1bpc: 0=black) bytes ready for the `image` operator. def pack_for_ps_image(width, height) bytes_per_row = (width + 7) / 8 rows = Array.new(height) { "\x00".b * bytes_per_row } height.times do |y| row = rows[y].dup.b width.times do |x| next unless yield(x, y) == 1 byte_index = x / 8 bit = 0x80 >> (x % 8) row.setbyte(byte_index, row.getbyte(byte_index) | bit) end rows[y] = row end packed = rows.join inverted = packed.bytes.map { |b| (~b) & 0xFF }.pack('C*') { width: width, height: height, hex: inverted.unpack1('H*').upcase } end def ps_image_block(x_pt, y_pt, w_pt, h_pt, img) <<~PS gsave #{x_pt} #{y_pt} translate #{w_pt} #{h_pt} scale #{img[:width]} #{img[:height]} 1 [#{img[:width]} 0 0 -#{img[:height]} 0 #{img[:height]}] { <#{img[:hex]}> } image grestore PS end Dir.mktmpdir do |dir| pbm_path = File.join(dir, 'label.pbm') system('gs', '-q', '-dNOPAUSE', '-dBATCH', '-dSAFER', '-sDEVICE=pbmraw', "-r#{DPI}", '-dFirstPage=1', '-dLastPage=1', "-sOutputFile=#{pbm_path}", SRC_PDF) or raise 'gs failed' pbm = read_pbm(pbm_path) bw = BARCODE_BOX[:x1] - BARCODE_BOX[:x0] bh = BARCODE_BOX[:y1] - BARCODE_BOX[:y0] barcode_img = pack_for_ps_image(bw, bh) do |x, y| pbm_bit(pbm, BARCODE_BOX[:x0] + x, BARCODE_BOX[:y0] + y) end qr = RQRCode::QRCode.new('TEST-LABEL-PRINTSERVER') modules = qr.modules quiet = 4 qsize = modules.size + quiet * 2 qr_img = pack_for_ps_image(qsize, qsize) do |x, y| mx, my = x - quiet, y - quiet next 0 if mx.negative? || my.negative? || mx >= modules.size || my >= modules.size modules[my][mx] ? 1 : 0 end # physical sizes on the A6 (298x420pt) page barcode_pt_w = 220 barcode_pt_h = barcode_pt_w * bh / bw qr_pt = 100 ps = <<~PS %!PS << /PageSize [298 420] >> setpagedevice 1 setlinewidth 10 10 278 400 rectstroke /Helvetica-Bold findfont 24 scalefont setfont 30 380 moveto (TEST LABEL) show /Helvetica findfont 9 scalefont setfont 30 362 moveto (Generic print-quality test page - safe to reprint anytime) show /Helvetica-Bold findfont 13 scalefont setfont 30 330 moveto (Odosielatel: Test Sender, BA) show 30 312 moveto (Adresat: Test Recipient, KE) show /Helvetica findfont 8 scalefont setfont 30 292 moveto (Fine print 8pt: quick brown fox 0123456789) show #{ps_image_block(30, 210, barcode_pt_w, barcode_pt_h, barcode_img)} /Helvetica findfont 8 scalefont setfont 30 198 moveto (1D: reused known-good Code128 from a real courier label) show #{ps_image_block(30, 60, qr_pt, qr_pt, qr_img)} /Helvetica findfont 8 scalefont setfont 140 100 moveto (QR: TEST-LABEL-PRINTSERVER) show 140 88 moveto (rendered natively via rqrcode) show showpage PS File.write(OUT_PS, ps) puts "wrote #{OUT_PS}" end