Initial commit: ZPL-native PDF printing, drop CUPS dependency
This commit is contained in:
commit
2ea45b5f21
15 files changed
+626
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
.session_secret
|
||||||
|
public/files/*
|
||||||
|
!public/files/.gitkeep
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gem 'sinatra'
|
||||||
|
gem 'sinatra-contrib'
|
||||||
|
gem 'puma'
|
||||||
|
gem 'rqrcode'
|
||||||
|
gem 'logger'
|
||||||
|
gem 'ostruct'
|
||||||
|
|
||||||
|
gem 'rackup', "~> 2.1"
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
base64 (0.3.0)
|
||||||
|
chunky_png (1.4.0)
|
||||||
|
logger (1.7.0)
|
||||||
|
multi_json (1.21.1)
|
||||||
|
mustermann (3.1.1)
|
||||||
|
nio4r (2.7.5)
|
||||||
|
ostruct (0.6.3)
|
||||||
|
puma (8.0.2)
|
||||||
|
nio4r (~> 2.0)
|
||||||
|
rack (3.2.6)
|
||||||
|
rack-protection (4.2.1)
|
||||||
|
base64 (>= 0.1.0)
|
||||||
|
logger (>= 1.6.0)
|
||||||
|
rack (>= 3.0.0, < 4)
|
||||||
|
rack-session (2.1.2)
|
||||||
|
base64 (>= 0.1.0)
|
||||||
|
rack (>= 3.0.0)
|
||||||
|
rackup (2.3.1)
|
||||||
|
rack (>= 3)
|
||||||
|
rqrcode (3.2.0)
|
||||||
|
chunky_png (~> 1.0)
|
||||||
|
rqrcode_core (~> 2.0)
|
||||||
|
rqrcode_core (2.1.0)
|
||||||
|
sinatra (4.2.1)
|
||||||
|
logger (>= 1.6.0)
|
||||||
|
mustermann (~> 3.0)
|
||||||
|
rack (>= 3.0.0, < 4)
|
||||||
|
rack-protection (= 4.2.1)
|
||||||
|
rack-session (>= 2.0.0, < 3)
|
||||||
|
tilt (~> 2.0)
|
||||||
|
sinatra-contrib (4.2.1)
|
||||||
|
multi_json (>= 0.0.2)
|
||||||
|
mustermann (~> 3.0)
|
||||||
|
rack-protection (= 4.2.1)
|
||||||
|
sinatra (= 4.2.1)
|
||||||
|
tilt (~> 2.0)
|
||||||
|
tilt (2.7.0)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
aarch64-linux
|
||||||
|
armv7l-linux-eabihf
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
logger
|
||||||
|
ostruct
|
||||||
|
puma
|
||||||
|
rackup (~> 2.1)
|
||||||
|
rqrcode
|
||||||
|
sinatra
|
||||||
|
sinatra-contrib
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
4.0.14
|
||||||
@@ -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
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"printers":[
|
||||||
|
{
|
||||||
|
"uri": "file:///dev/usb/lp0",
|
||||||
|
"model": "E4204B",
|
||||||
|
"dimensions": "100x150",
|
||||||
|
"name": "Datamax-ONeil_E4204B_Mark_III",
|
||||||
|
"driver": "zpl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "file:///dev/usb/lp1",
|
||||||
|
"model": "QL-700",
|
||||||
|
"dimensions": "62",
|
||||||
|
"name": "Brother QL-700",
|
||||||
|
"driver": "brother_ql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "file:///dev/usb/lp2",
|
||||||
|
"model": "QL-1100",
|
||||||
|
"dimensions": "102",
|
||||||
|
"name": "Brother QL-1100",
|
||||||
|
"driver": "brother_ql"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "file:///dev/usb/lp3",
|
||||||
|
"model": "QL-560",
|
||||||
|
"dimensions": "12+19",
|
||||||
|
"name": "Brother QL-560",
|
||||||
|
"driver": "brother_ql"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
require 'bundler'
|
||||||
|
|
||||||
|
Bundler.require
|
||||||
|
|
||||||
|
require './app'
|
||||||
|
run Sinatra::Application
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
# gemset.gem.gems generated gem export file. Note that any env variable settings will be missing. Append these after using a ';' field separator
|
||||||
|
|
||||||
|
error_highlight -v0.3.0
|
||||||
|
did_you_mean -v1.6.1
|
||||||
|
abbrev -v0.1.0
|
||||||
|
awesome_print -v1.9.2
|
||||||
|
base64 -v0.1.1
|
||||||
|
benchmark -v0.2.0
|
||||||
|
bigdecimal -v3.1.2
|
||||||
|
bigdecimal -v3.1.1
|
||||||
|
bundler -v2.3.10
|
||||||
|
bundler -v2.3.6
|
||||||
|
cgi -v0.3.2
|
||||||
|
cgi -v0.3.1
|
||||||
|
chunky_png -v1.4.0
|
||||||
|
csv -v3.2.2
|
||||||
|
cupsffi -v0.1.9
|
||||||
|
date -v3.2.2
|
||||||
|
delegate -v0.2.0
|
||||||
|
digest -v3.1.0
|
||||||
|
domain_name -v0.5.20190701
|
||||||
|
drb -v2.1.0
|
||||||
|
english -v0.7.1
|
||||||
|
erb -v2.2.3
|
||||||
|
etc -v1.3.0
|
||||||
|
fcntl -v1.0.1
|
||||||
|
ffi -v1.15.5
|
||||||
|
fiddle -v1.1.0
|
||||||
|
fileutils -v1.6.0
|
||||||
|
find -v0.1.1
|
||||||
|
forwardable -v1.3.2
|
||||||
|
getoptlong -v0.1.1
|
||||||
|
http-accept -v2.1.1
|
||||||
|
http-accept -v1.7.0
|
||||||
|
http-cookie -v1.0.4
|
||||||
|
io-console -v0.5.11
|
||||||
|
io-console -v0.5.10
|
||||||
|
io-nonblock -v0.1.0
|
||||||
|
io-wait -v0.2.1
|
||||||
|
ipaddr -v1.2.4
|
||||||
|
ipaddr -v1.2.3
|
||||||
|
irb -v1.4.1
|
||||||
|
json -v2.6.1
|
||||||
|
logger -v1.5.1
|
||||||
|
logger -v1.5.0
|
||||||
|
mime-types -v3.4.1
|
||||||
|
mime-types-data -v3.2022.0105
|
||||||
|
multi_json -v1.15.0
|
||||||
|
mustermann -v2.0.2
|
||||||
|
mustermann -v1.1.1
|
||||||
|
mutex_m -v0.1.1
|
||||||
|
net-http -v0.2.0
|
||||||
|
net-imap -v0.2.3
|
||||||
|
net-protocol -v0.1.2
|
||||||
|
netrc -v0.11.0
|
||||||
|
nio4r -v2.5.8
|
||||||
|
nkf -v0.1.1
|
||||||
|
observer -v0.1.1
|
||||||
|
open-uri -v0.2.0
|
||||||
|
open3 -v0.1.1
|
||||||
|
openssl -v3.0.0
|
||||||
|
optparse -v0.2.0
|
||||||
|
ostruct -v0.5.4
|
||||||
|
ostruct -v0.5.3
|
||||||
|
ostruct -v0.5.2
|
||||||
|
pathname -v0.2.0
|
||||||
|
pp -v0.3.0
|
||||||
|
prettyprint -v0.1.1
|
||||||
|
pstore -v0.1.1
|
||||||
|
psych -v4.0.3
|
||||||
|
puma -v5.6.5
|
||||||
|
puma -v5.6.2
|
||||||
|
puma -v5.6.1
|
||||||
|
racc -v1.6.0
|
||||||
|
rack -v2.2.4
|
||||||
|
rack -v2.2.3
|
||||||
|
rack-protection -v2.2.2
|
||||||
|
rack-protection -v2.2.0
|
||||||
|
rack-protection -v2.1.0
|
||||||
|
rbs -v2.2.2
|
||||||
|
rbs -v2.1.0
|
||||||
|
rdoc -v6.4.0
|
||||||
|
readline -v0.0.3
|
||||||
|
readline-ext -v0.1.4
|
||||||
|
reline -v0.3.1
|
||||||
|
reline -v0.3.0
|
||||||
|
resolv -v0.2.1
|
||||||
|
resolv-replace -v0.1.0
|
||||||
|
rest-client -v2.1.0
|
||||||
|
rinda -v0.1.1
|
||||||
|
rqrcode -v2.1.2
|
||||||
|
rqrcode -v2.1.1
|
||||||
|
rqrcode -v2.1.0
|
||||||
|
rqrcode_core -v1.2.0
|
||||||
|
ruby2_keywords -v0.0.5
|
||||||
|
rubygems-update -v3.3.10
|
||||||
|
rubygems-update -v3.3.6
|
||||||
|
securerandom -v0.2.0
|
||||||
|
securerandom -v0.1.1
|
||||||
|
set -v1.0.2
|
||||||
|
shellwords -v0.1.0
|
||||||
|
sinatra -v2.2.2
|
||||||
|
sinatra -v2.2.0
|
||||||
|
sinatra -v2.1.0
|
||||||
|
sinatra-contrib -v2.2.2
|
||||||
|
sinatra-contrib -v2.2.0
|
||||||
|
sinatra-contrib -v2.1.0
|
||||||
|
singleton -v0.1.1
|
||||||
|
stringio -v3.0.1
|
||||||
|
strscan -v3.0.1
|
||||||
|
syslog -v0.1.0
|
||||||
|
tempfile -v0.1.2
|
||||||
|
tilt -v2.0.11
|
||||||
|
tilt -v2.0.10
|
||||||
|
time -v0.2.0
|
||||||
|
timeout -v0.2.0
|
||||||
|
tmpdir -v0.1.2
|
||||||
|
tsort -v0.1.0
|
||||||
|
typeprof -v0.21.2
|
||||||
|
un -v0.2.0
|
||||||
|
unf -v0.1.4
|
||||||
|
unf_ext -v0.0.8.1
|
||||||
|
unf_ext -v0.0.8
|
||||||
|
uri -v0.11.0
|
||||||
|
weakref -v0.1.1
|
||||||
|
yaml -v0.2.0
|
||||||
|
zlib -v2.1.1
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
applications:
|
||||||
|
- name: bootstrap
|
||||||
|
memory: 128M
|
||||||
|
instances: 1
|
||||||
|
url: bootstrap.${target-base}
|
||||||
|
path: .
|
||||||
Whitespace-only changes.
Binary file not shown.
@@ -0,0 +1,115 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<div class="card" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Brother Label Designer</h5>
|
||||||
|
<p class="card-text">A web service to print labels on Brother QL label printers.</p>
|
||||||
|
<a href="http://rpi-printserver.local:8560/labeldesigner" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">QL-560</a>
|
||||||
|
<a href="http://rpi-printserver.local:8700/labeldesigner" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">QL-700</a>
|
||||||
|
<a href="http://rpi-printserver.local:9100/labeldesigner" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">QL-1100</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Sticker Factory</h5>
|
||||||
|
<p class="card-text">Print images, text and cats on a Brother QL label printer</p>
|
||||||
|
<a href="http://rpi-printserver.local:8989" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Go</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Cups</h5>
|
||||||
|
<p class="card-text">The standards-based, open source printing system developed by Apple for iOS®, iPadOS®, and macOS®. CUPS uses IPP Everywhere™ to support printing to local and network printers.</p>
|
||||||
|
<a href="https://rpi-printserver.local:631/admin" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Go</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Cockpit</h5>
|
||||||
|
<p class="card-text">Cockpit is a web-based graphical interface for servers, intended for everyone</p>
|
||||||
|
<a href="https://rpi-printserver.local:9090" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Go</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">ZPL Viewer</h5>
|
||||||
|
<p class="card-text">An useful tool for labels in ZPL format.</p>
|
||||||
|
<a href="https://labelary.com/viewer.html" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Go</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Creality Sonic Pad</h5>
|
||||||
|
<p class="card-text">3D Printer</p>
|
||||||
|
<a href="http://spad-9088.local" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Fluidd</a>
|
||||||
|
<a href="http://spad-9088.local:8819" class="btn btn-outline-secondary" target="_blank" rel="noopener noreferrer">Mainsail</a>
|
||||||
|
<a href="http://spad-9088.local:7125" class="btn btn-outline-secondary" target="_blank" rel="noopener noreferrer">Moonraker</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Bigtreetech Pad 7 CNC</h5>
|
||||||
|
<p class="card-text">CNC</p>
|
||||||
|
<a href="http://btt-pad7-cnc.local" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Mainsail</a>
|
||||||
|
<a href="http://btt-pad7-cnc.local:7125" class="btn btn-outline-secondary" target="_blank" rel="noopener noreferrer">Moonraker</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Bigtreetech Pad 7 CAN</h5>
|
||||||
|
<p class="card-text">CAN</p>
|
||||||
|
<a href="http://rpi-btt-pad7-can.local" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Mainsail</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Print server</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<link href="data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAD9/f0AAAAAAOzs7ADl5eUA/Pz8ACsZBgD+/v4AAwMDAPv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWZmZmZmIxERZmZmZmZmERFmZmZmZmYREWZmZoZmZhERZmYUVmZmERFmYREWZmYREWYRERFmZhERZhYREXZmERFmZmYRZmYREWZmZhFmZhERZmZmERFmERFmZmYHEWYREWZmZmZmZhERZmZmZmZmERFmZmZmZmYREWZmZmZmZhHAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAA" rel="icon" type="image/x-icon" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||||
|
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/">Print server</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNavDropdown">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/upload">Shipping labels</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="row">
|
||||||
|
<% if @alert_success then %>
|
||||||
|
<div class="alert alert-success"><%=@alert_success%></div>
|
||||||
|
<% end %>
|
||||||
|
<% if @alert_danger then %>
|
||||||
|
<div class="alert alert-danger"><%=@alert_danger%></div>
|
||||||
|
<% end %>
|
||||||
|
<% if @alert_warning then %>
|
||||||
|
<div class="alert alert-warning"><%=@alert_warning%></div>
|
||||||
|
<% end %>
|
||||||
|
<%= yield %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone-min.js"></script>
|
||||||
|
<script>
|
||||||
|
Dropzone.options.UploadForm = {
|
||||||
|
paramFile: "file",
|
||||||
|
accept: function(file, done) {
|
||||||
|
done ();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<form action='/login/attempt' method='post' class="form-inline well">
|
||||||
|
<input type="text" name='username' placeholder="username" autofocus>
|
||||||
|
<input class="btn primary" type='submit' value='log in'>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<form action="/upload" class="dropzone" id="upload-form" method="POST" enctype="multipart/form-data">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="formFile" class="form-label">Upload .pdf or .zpl to print label</label>
|
||||||
|
<input name="file" class="form-control" type="file" id="formFile" accept=".pdf,.zpl" />
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-secondary" type="submit">Upload</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form action="/test-print" method="POST" class="mt-3">
|
||||||
|
<button class="btn btn-outline-secondary" type="submit">Print test label</button>
|
||||||
|
</form>
|
||||||
Reference in new issue
Block a user