Initial commit: ZPL-native PDF printing, drop CUPS dependency

This commit is contained in:
randy committed 2026-09-12 00:22:35 +02:00
commit 2ea45b5f21
15 files changed
+626

No files matched your search

+138
View File
@@ -0,0 +1,138 @@
# /usr/bin/env ruby
require 'sinatra'
require "sinatra/reloader"
require 'tmpdir'
set :protection, :except => :frame_options
set :environment, :production
set :bind, "::"
set :port, 4567
if !File.exist?('config.json') || !File.readable?('config.json')
raise RuntimeException, 'Could not read config.json'
end
PRINTER_DEVICE = '/dev/usb/lp0'
PRINTER_DPI = 203 # DatamaxONeil E4204B Mark III native head resolution
PRINTER_DARKNESS = 8 # lower than factory default to stop thermal dot-gain from fusing barcode bars
TEST_LABEL_PDF = File.join(File.dirname(__FILE__), 'test-label.pdf')
configure do
enable :static
set :session_secret, File.read(File.join(File.dirname(__FILE__), '.session_secret')).strip
enable :sessions
set :views, File.join(File.dirname(__FILE__), 'views')
set :public_folder, File.join(File.dirname(__FILE__), 'public')
set :files, File.join(settings.public_folder, 'files')
set :unallowed_paths, ['.', '..']
end
configure :production do
enable :reloader
end
helpers do
def flash(message = '', level)
case level
when "success"
session[:alert_success] = message
when "danger"
session[:alert_danger] = message
when "warning"
session[:alert_warning] = message
end
end
# Renders a PDF to ZPL ^GFA graphic labels at the printer's native DPI,
# bypassing CUPS/Gutenprint's raster pipeline which blurs text and 1px-shifts
# output compared to native ZPL rendering.
def pdf_to_zpl(pdf_path, dpi: PRINTER_DPI)
Dir.mktmpdir do |dir|
pattern = File.join(dir, 'page-%03d.pbm')
ok = system('gs', '-q', '-dNOPAUSE', '-dBATCH', '-dSAFER',
'-sDEVICE=pbmraw', "-r#{dpi}",
'-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
"-sOutputFile=#{pattern}", pdf_path)
raise 'ghostscript rendering failed' unless ok
Dir.glob(File.join(dir, 'page-*.pbm')).sort.map { |f| pbm_to_zpl_label(f) }.join
end
end
def pbm_to_zpl_label(pbm_path)
bytes = File.binread(pbm_path)
header = /\AP4\s+(?:#[^\n]*\n\s*)*(\d+)\s+(\d+)\s/.match(bytes)
raise "unreadable PBM header in #{pbm_path}" unless header
width, height = header[1].to_i, header[2].to_i
bytes_per_row = (width + 7) / 8
total_bytes = bytes_per_row * height
body = bytes.byteslice(header.end(0), total_bytes)
hexdata = body.unpack1('H*').upcase
"^XA\n^MD#{PRINTER_DARKNESS}\n^PW#{width}\n^LL#{height}\n^FO0,0^GFA,#{total_bytes},#{total_bytes},#{bytes_per_row},#{hexdata}\n^XZ\n"
end
end
before do
@alert_success = session.delete(:alert_success)
@alert_danger = session.delete(:alert_danger)
@alert_warning = session.delete(:alert_warning)
end
get '/' do
erb :index
end
get '/upload' do
erb :upload
end
post '/upload' do
if params[:file]
tempfile = params[:file][:tempfile]
filename = params[:file][:filename]
filetype = File.extname filename
filepath = File.join(settings.files, filename)
File.open(filepath, 'wb') do |f|
f.write tempfile.read
end
case filetype
when ".pdf"
begin
zpl = pdf_to_zpl(filepath)
File.open(PRINTER_DEVICE, 'wb') { |f| f.write(zpl) }
flash "PDF: Upload successful (#{filename})", "success"
rescue => e
flash "PDF: Failed to print (#{e.message})", "danger"
end
when ".zpl"
File.open(PRINTER_DEVICE, 'wb') { |f| f.write(File.binread(filepath)) }
flash "ZPL: Upload successful (#{filename})", "success"
else
flash "#{filetype} is not supported. Only .pdf and .zpl!", "false"
end
File.delete(filepath) if File.exist?(filepath)
else
flash 'You have to choose a file', "warning"
end
redirect '/upload'
end
post '/test-print' do
begin
zpl = pdf_to_zpl(TEST_LABEL_PDF)
File.open(PRINTER_DEVICE, 'wb') { |f| f.write(zpl) }
flash 'Test label sent to printer', 'success'
rescue => e
flash "Test print failed (#{e.message})", 'danger'
end
redirect '/upload'
end