# Minimal chainable ESC/POS builder for the Epson TM-T20II. # # Written instead of pulling in the `escpos` gem: the gem's diacritics # handling needed the same manual UTF-8->CP852 `.encode` call anyway, and a # couple of its helpers didn't behave as documented against this printer -- # fewer moving parts doing it directly. class ReceiptBuilder INIT = "\x1B\x40".freeze CP852 = "\x1B\x74\x12".freeze # code page for Slovak diacritics ALIGN_LEFT = "\x1B\x61\x00".freeze ALIGN_CENT = "\x1B\x61\x01".freeze ALIGN_RIGH = "\x1B\x61\x02".freeze BOLD_ON = "\x1B\x45\x01".freeze BOLD_OFF = "\x1B\x45\x00".freeze UNDER_ON = "\x1B\x2D\x01".freeze UNDER_OFF = "\x1B\x2D\x00".freeze TXT_NORM = "\x1D\x21\x00".freeze TXT_BIG = "\x1D\x21\x11".freeze # double height and width TXT_HIGH = "\x1D\x21\x01".freeze # double height TXT_WIDE = "\x1D\x21\x10".freeze # double width CUT = "\x1D\x56\x00".freeze def initialize @buffer = "".b init end def init @buffer << INIT << CP852 self end def text(str) @buffer << str.encode('CP852', invalid: :replace, undef: :replace, replace: '?') self end # Appends already-encoded/raw bytes as-is (e.g. pre-parsed ESC/POS command # sequences), bypassing the CP852 text encoding `text` applies. def raw(bytes) @buffer << bytes self end def align(mode) @buffer << case mode when :center then ALIGN_CENT when :right then ALIGN_RIGH else ALIGN_LEFT end self end def bold(enable = true) @buffer << (enable ? BOLD_ON : BOLD_OFF) self end def underline(enable = true) @buffer << (enable ? UNDER_ON : UNDER_OFF) self end def size(mode) @buffer << case mode when :big then TXT_BIG when :high then TXT_HIGH when :wide then TXT_WIDE else TXT_NORM end self end # Native ESC/POS QR code def qr(data, size: 6) qr_data = data.b store_len = qr_data.bytesize + 3 @buffer << "\x1D\x28\x6B\x03\x00\x31\x43" << size.chr @buffer << "\x1D\x28\x6B\x03\x00\x31\x45\x30" # error correction L @buffer << "\x1D\x28\x6B" << [store_len % 256, store_len / 256].pack('CC') << "\x31\x50\x30" << qr_data @buffer << "\x1D\x28\x6B\x03\x00\x31\x51\x30" # print self end # Native ESC/POS EAN-13 def ean13(code) @buffer << "\x1D\x6B\x02" << code.b << "\x00" self end # Raster bit image (GS v 0) -- bytes must already be packed 1bpp, # row-major, (width_px/8 rounded up) bytes per row, MSB-first, 1=black. def image(width_px, height_px, bytes) width_bytes = (width_px + 7) / 8 @buffer << "\x1D\x76\x30\x00" @buffer << [width_bytes % 256, width_bytes / 256, height_px % 256, height_px / 256].pack('C4') @buffer << bytes self end def feed(lines = 3) @buffer << ("\n" * lines) self end def cut @buffer << CUT self end def render @buffer end def print!(device) File.open(device, 'wb') { |f| f.write(@buffer) } end end