Files
labelprint/lib/receipt_builder.rb
T
randy 148f7dc248
deploy / deploy (push) Successful in 10s
Unified /labels page (badges, edit, preview), mnemonic ESC/POS parser for the raw receipt field
- Saved labels/receipts now show on one page instead of split across
  Shipping labels and Receipts, each with a badge for its type/printer,
  Edit (name+content, in place), Print, and Delete.
- ZPL labels get a real visual preview via Labelary's public render API;
  ESC/POS ones show their raw content (no good general preview for that).
- The Receipts free-text field only ever encoded whatever was typed as
  literal printable characters -- pasting mnemonic ESC/POS notation
  (ESC/GS/LF as command names, 0xNN as raw bytes) just printed as text
  instead of being interpreted. Added a small line-based parser for that
  notation (ReceiptBuilder#raw appends already-resolved bytes verbatim),
  verified byte-for-byte against an example with align/bold/barcode/cut.
2026-09-13 11:21:46 +02:00

114 lines
2.7 KiB
Ruby

# 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
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