From d2900361b37be5a03e3bce81ce7fe082779430cd Mon Sep 17 00:00:00 2001 From: Roman Priesol Date: Sun, 13 Sep 2026 12:09:52 +0200 Subject: [PATCH] Add Epson test page: text sizes, EAN-13, QR, and a raster image ReceiptBuilder#image implements GS v 0 raster printing (no image library needed for the demo -- a procedural 8px checkerboard proves it end to end). Skipped attempting the printer's real-time clock: unsure the TM-T20II actually has a battery-backed RTC (that's usually an option on higher-end models), not worth guessing at an undocumented command. --- app.rb | 64 ++++++++++++++++++++++++++++++++++++++++++ lib/receipt_builder.rb | 10 +++++++ views/epson.erb | 4 +++ 3 files changed, 78 insertions(+) diff --git a/app.rb b/app.rb index 29e075c..5f0de52 100644 --- a/app.rb +++ b/app.rb @@ -107,6 +107,60 @@ helpers do receipt.print!(PRINTER_DEVICE_EPSON) end + # Procedural 64x64 checkerboard (8px squares) -- no image library needed, + # just to prove GS v 0 raster printing works end to end. + def checkerboard_bitmap(size_px = 64, block_px = 8) + width_bytes = size_px / 8 + bytes = "".b + size_px.times do |y| + row_block = y / block_px + width_bytes.times do |xb| + byte = 0 + 8.times do |bit| + x = xb * 8 + bit + col_block = x / block_px + byte |= (1 << (7 - bit)) if (row_block + col_block).even? + end + bytes << byte.chr + end + end + bytes + end + + # Demonstrates most of what the printer can do: text sizes/styles, EAN-13, + # QR code, and a raster image -- mirrors /test-print's role for the Datamax. + def print_epson_test_page + receipt = ReceiptBuilder.new + receipt.align(:center).bold.size(:big).text("TM-T20II TEST\n").size(:normal).bold(false) + receipt.align(:left).text(('-' * 48) + "\n") + + receipt.text("Normal text\n") + receipt.bold.text("Bold text\n").bold(false) + receipt.underline.text("Underlined text\n").underline(false) + receipt.size(:wide).text("Double width\n").size(:normal) + receipt.size(:high).text("Double height\n").size(:normal) + receipt.size(:big).text("Double both\n").size(:normal) + receipt.text(('-' * 48) + "\n") + + receipt.align(:center) + receipt.text("EAN-13 barcode:\n") + receipt.ean13('2012345678902') + receipt.feed(1) + + receipt.text("QR code:\n") + receipt.qr('https://git.priesol.net/randy/home-gitops') + receipt.feed(1) + + receipt.text("Raster image:\n") + receipt.image(64, 64, checkerboard_bitmap) + receipt.feed(1) + + receipt.align(:left).text(('-' * 48) + "\n") + receipt.text("Printed: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}\n") + receipt.feed(3).cut + receipt.print!(PRINTER_DEVICE_EPSON) + end + ESCPOS_MNEMONICS = { 'ESC' => "\x1B", 'GS' => "\x1D", 'FS' => "\x1C", 'DLE' => "\x10", 'LF' => "\x0A", 'CR' => "\x0D", 'HT' => "\x09", 'NUL' => "\x00" @@ -313,6 +367,16 @@ get '/epson' do erb :epson end +post '/epson/test-print' do + begin + print_epson_test_page + flash 'Test page sent to Epson printer', 'success' + rescue => e + flash "Epson test print failed (#{e.message})", 'danger' + end + redirect '/epson' +end + post '/epson/print' do begin print_receipt(title: params[:title].to_s, body: params[:body].to_s, qr: params[:qr].to_s) diff --git a/lib/receipt_builder.rb b/lib/receipt_builder.rb index a7ae044..c5ac887 100644 --- a/lib/receipt_builder.rb +++ b/lib/receipt_builder.rb @@ -93,6 +93,16 @@ class ReceiptBuilder 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 diff --git a/views/epson.erb b/views/epson.erb index 0f82d31..1304e13 100644 --- a/views/epson.erb +++ b/views/epson.erb @@ -15,6 +15,10 @@ +
+ +
+
Free text (ESC/POS mnemonic notation)