zpl_renderer 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/COMPATIBILITY.md +64 -0
- data/LICENSE.txt +24 -0
- data/README.md +147 -0
- data/exe/zpl-render +95 -0
- data/lib/zpl_renderer/binary.rb +63 -0
- data/lib/zpl_renderer/decoder.rb +42 -0
- data/lib/zpl_renderer/engine.rb +87 -0
- data/lib/zpl_renderer/errors.rb +14 -0
- data/lib/zpl_renderer/options.rb +139 -0
- data/lib/zpl_renderer/renderer.rb +23 -0
- data/lib/zpl_renderer/sanitizer.rb +156 -0
- data/lib/zpl_renderer/validation.rb +185 -0
- data/lib/zpl_renderer/version.rb +5 -0
- data/lib/zpl_renderer.rb +85 -0
- data/renderer/Cargo.lock +2473 -0
- data/renderer/Cargo.toml +25 -0
- data/renderer/src/main.rs +310 -0
- data/vendor/bin/SHA256SUMS +1 -0
- data/vendor/bin/x86_64-unknown-linux-gnu/zpl-renderer +0 -0
- metadata +113 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b9d98e662dece76d83d75273549dc7cea4326b6f49a4dddc351f6e8a925ab3cd
|
|
4
|
+
data.tar.gz: 22544d0cc68b30c89eba29762898eb6fede5eaf6317bd3d1373a1dd55c59e5b3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9e160e478a204168f49cd191c0275f8b2537cadd164161e185814778517f7d968b56a92409701a9dfbf51356d2536bf4e5d228701eb65fb1af5aa9c082398258
|
|
7
|
+
data.tar.gz: e7afe50cff6da9eb11371dbdf9a0b64ff36642305091220d92a0d79d1468bbea4328bed8ed075bf8ef58b0afe49e39b8ca7ae01f4e506753b42c368313032303
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
- Fix right-edge fields being cut off when `^PW` is narrower than the canvas
|
|
6
|
+
(rotated serials at `^FO788` / `^FO791` under `^PW800`)
|
|
7
|
+
- Add `clip_to_print_width` option for printer-accurate clipping
|
|
8
|
+
- Decode PNG pixels in specs to regression-test right-edge clipping
|
|
9
|
+
- Keep fixtures synthetic only; do not commit real customer ZPL
|
|
10
|
+
|
|
11
|
+
## 0.1.1
|
|
12
|
+
|
|
13
|
+
- Sanitize malformed `^FO...,--` coordinates using last section-divider Y
|
|
14
|
+
- Add synthetic carrier return/outbound fixture with barcode decode coverage
|
|
15
|
+
|
|
16
|
+
## 0.1.0
|
|
17
|
+
|
|
18
|
+
- Initial offline ZPL to PNG/PDF renderer for Ruby 3.1+
|
|
19
|
+
- Bundled Rust renderer based on labelize
|
|
20
|
+
- Code 128, Code 39, EAN-13, QR, and related shipping-label barcode support
|
|
21
|
+
- Strict compatibility validation and scanability warnings
|
|
22
|
+
- Independent ZXing-based decode helper for CI/QA
|
|
23
|
+
- CLI: `zpl-render`
|
data/COMPATIBILITY.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ZPL Compatibility Matrix (v0.1)
|
|
2
|
+
|
|
3
|
+
`zpl_renderer` targets **common shipping-label ZPL** at **203 / 300 / 600 DPI**.
|
|
4
|
+
It is offline and does not claim bit-identical output with every Zebra firmware,
|
|
5
|
+
installed font, or graphics store.
|
|
6
|
+
|
|
7
|
+
## Supported commands
|
|
8
|
+
|
|
9
|
+
| Category | Commands |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Label control | `^XA` `^XZ` `^PW` `^PO` `^LH` `^LR` `^CI` `^MU` `^PR` `^PQ` `^JM` `^MD` `^MN` `^MT` `^PM` |
|
|
12
|
+
| Text / fonts | `^FO` `^FT` `^FD` `^FS` `^A` `^CF` `^FB` `^FR` `^FH` `^FN` `^FW` `^FV` |
|
|
13
|
+
| Barcodes | `^BC` `^BE` `^B2` `^B3` `^B7` `^BO` `^BX` `^BQ` `^BD` `^BY` |
|
|
14
|
+
| Graphics | `^GB` `^GC` `^GD` `^GF` `^GS` `~DG` `^IL` `^XG` |
|
|
15
|
+
| Stored formats | `^DF` `^XF` |
|
|
16
|
+
|
|
17
|
+
## Barcode scanability guidance
|
|
18
|
+
|
|
19
|
+
| DPI | Recommended `^BY` module width | QR magnification (`^BQ` 3rd param) |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| 203 (8 dpmm) | >= 2 dots | >= 2 (prefer 4-6) |
|
|
22
|
+
| 300 (12 dpmm) | >= 3 dots | >= 3 (prefer 5-8) |
|
|
23
|
+
| 600 (24 dpmm) | >= 4 dots | >= 4 (prefer 6-10) |
|
|
24
|
+
|
|
25
|
+
Leave quiet zones around barcodes. Avoid clipping symbols with tight `^PW`/`^LL`
|
|
26
|
+
or overlapping reverse fields (`^FR`).
|
|
27
|
+
|
|
28
|
+
## Strict mode
|
|
29
|
+
|
|
30
|
+
By default `strict: true`:
|
|
31
|
+
|
|
32
|
+
- missing `^XA` / `^XZ` raises
|
|
33
|
+
- empty barcode `^FD` data raises
|
|
34
|
+
- known unsupported RFID / wireless / advanced commands raise
|
|
35
|
+
|
|
36
|
+
Set `strict: false` to render best-effort and collect warnings via
|
|
37
|
+
`ZplRenderer.validate`.
|
|
38
|
+
|
|
39
|
+
## Explicitly unsupported (first release)
|
|
40
|
+
|
|
41
|
+
RFID (`^RF` family), wireless/config (`^W*`), advanced serialization helpers
|
|
42
|
+
beyond common shipping usage, and printer-private extensions.
|
|
43
|
+
|
|
44
|
+
## Accuracy contract
|
|
45
|
+
|
|
46
|
+
1. Rendered barcode/QR payloads must decode with an independent ZXing engine.
|
|
47
|
+
2. PDF page size equals the requested physical label size.
|
|
48
|
+
3. PNG dimensions equal `ceil(mm * dpmm)` for width and height.
|
|
49
|
+
4. Pixel-perfect printer firmware matching is **not** guaranteed.
|
|
50
|
+
|
|
51
|
+
## Carrier ZPL sanitization
|
|
52
|
+
|
|
53
|
+
Before render/validate, the gem rewrites known carrier quirks:
|
|
54
|
+
|
|
55
|
+
- `^FO464,--^GB2,126,2` becomes `^FO464,<last-section-divider-y>`
|
|
56
|
+
when a prior full-width horizontal rule exists; otherwise Y falls back to `0`.
|
|
57
|
+
- A `^PW` narrower than the requested canvas is widened to the canvas width, and
|
|
58
|
+
`^LH` is shifted by the same centering offset the engine would have applied.
|
|
59
|
+
Without this, fields near the right edge (carrier templates often put rotated
|
|
60
|
+
serials at `^FO788` / `^FO791` under `^PW800`) are clipped off an 813-dot
|
|
61
|
+
4 inch canvas. Pass `clip_to_print_width: true` to keep printer-accurate
|
|
62
|
+
clipping instead. Labels using `^POI` are left untouched.
|
|
63
|
+
|
|
64
|
+
Use `ZplRenderer.sanitize(zpl, canvas_dots: 813)` to inspect repairs without rendering.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Waqas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
This gem bundles a renderer that depends on the MIT-licensed labelize project
|
|
24
|
+
(https://github.com/GOODBOY008/labelize).
|
data/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# zpl_renderer
|
|
2
|
+
|
|
3
|
+
Offline ZPL to PNG/PDF renderer for Ruby and Rails.
|
|
4
|
+
|
|
5
|
+
Add the gem, render labels, and get barcodes/QR codes that scanners can decode.
|
|
6
|
+
No Labelary account, no network calls, no printer hardware required.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
# Gemfile
|
|
12
|
+
gem "zpl_renderer"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Precompiled renderer binaries ship for common platforms (Linux/macOS/Windows).
|
|
20
|
+
If your platform is missing a binary, build it once:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cd renderer && cargo build --release
|
|
24
|
+
bundle exec rake build_renderer
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or point to a binary explicitly:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export ZPL_RENDERER_BINARY=/absolute/path/to/zpl-renderer
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require "zpl_renderer"
|
|
37
|
+
|
|
38
|
+
zpl = <<~ZPL
|
|
39
|
+
^XA
|
|
40
|
+
^FO50,50^A0N,40,40^FDHello^FS
|
|
41
|
+
^FO50,120^BY3
|
|
42
|
+
^BCN,100,Y,N,N
|
|
43
|
+
^FDTRACK123456789^FS
|
|
44
|
+
^FO50,280^BQN,2,6
|
|
45
|
+
^FDQA,https://example.com/track/ABC123^FS
|
|
46
|
+
^XZ
|
|
47
|
+
ZPL
|
|
48
|
+
|
|
49
|
+
png = ZplRenderer.render(zpl, format: :png, width: 4, height: 6, dpi: 203)
|
|
50
|
+
File.binwrite("label.png", png)
|
|
51
|
+
|
|
52
|
+
pdf = ZplRenderer.render(zpl, format: :pdf, width: 4, height: 6, dpi: 300)
|
|
53
|
+
File.binwrite("label.pdf", pdf)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Rails
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
png = ZplRenderer.render(@label.zpl, format: :png, width: 4, height: 6, dpi: 203)
|
|
60
|
+
send_data png,
|
|
61
|
+
type: ZplRenderer.content_type(:png),
|
|
62
|
+
disposition: "inline",
|
|
63
|
+
filename: "label.png"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
See [`examples/rails_controller.rb`](examples/rails_controller.rb).
|
|
67
|
+
|
|
68
|
+
### CLI
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
zpl-render label.zpl -o label.png --dpi 203 --width 4 --height 6
|
|
72
|
+
zpl-render label.zpl -o label.pdf --format pdf --dpi 300
|
|
73
|
+
zpl-render --validate-only label.zpl
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Options
|
|
77
|
+
|
|
78
|
+
| Option | Default | Notes |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `format` | `:png` | `:png` or `:pdf` |
|
|
81
|
+
| `width` / `height` | `4` / `6` | Inches by default |
|
|
82
|
+
| `unit` | `:inches` | `:inches` or `:mm` |
|
|
83
|
+
| `dpi` | `203` | `203`, `300`, or `600` |
|
|
84
|
+
| `dpmm` | derived | `8`, `12`, or `24` (overrides `dpi`) |
|
|
85
|
+
| `index` | `0` | Multi-label `^XA...^XZ` selection |
|
|
86
|
+
| `timeout` | `30` | Seconds |
|
|
87
|
+
| `strict` | `true` | Raise on unsupported commands |
|
|
88
|
+
| `clip_to_print_width` | `false` | Emulate printer `^PW` clipping instead of widening |
|
|
89
|
+
|
|
90
|
+
Canvas size is printer-dot accurate: `ceil(mm * dpmm)`.
|
|
91
|
+
|
|
92
|
+
## Accuracy and barcodes
|
|
93
|
+
|
|
94
|
+
This gem targets **common shipping-label ZPL**, not every Zebra firmware quirk.
|
|
95
|
+
|
|
96
|
+
Supported first-release command set and barcode guidance live in
|
|
97
|
+
[`COMPATIBILITY.md`](COMPATIBILITY.md).
|
|
98
|
+
|
|
99
|
+
Carrier quirks are sanitized automatically before render: bad field origins such
|
|
100
|
+
as `^FO464,--`, and `^PW` values narrower than the canvas that would clip
|
|
101
|
+
rotated text at the right edge. Inspect repairs with
|
|
102
|
+
`ZplRenderer.sanitize(zpl, canvas_dots: 813)`.
|
|
103
|
+
|
|
104
|
+
Barcode/QR payloads are independently verified in CI with a ZXing-based decoder:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
hits = ZplRenderer.decode_png(png)
|
|
108
|
+
hits.map(&:text)
|
|
109
|
+
# => ["TRACK123456789", "https://example.com/track/ABC123"]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Scanner tips
|
|
113
|
+
|
|
114
|
+
- Prefer `^BY3` (or wider) at 203 DPI
|
|
115
|
+
- Prefer QR magnification `>= 4` for handheld scanners
|
|
116
|
+
- Keep quiet zones clear of text/graphics
|
|
117
|
+
- Avoid clipping barcodes with tiny `^PW` / label widths
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
bundle install
|
|
123
|
+
source "$HOME/.cargo/env" # if needed
|
|
124
|
+
bundle exec rake build_renderer
|
|
125
|
+
bundle exec rake golden
|
|
126
|
+
bundle exec rspec
|
|
127
|
+
cd renderer && cargo test
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Fixtures under `spec/fixtures/` must stay synthetic. Do not commit real customer
|
|
131
|
+
or production ZPL/PNG samples.
|
|
132
|
+
|
|
133
|
+
## Packaging
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
bundle exec rake build_renderer
|
|
137
|
+
gem build zpl_renderer.gemspec
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Platform CI builds native binaries for Linux/macOS/Windows and packages them under
|
|
141
|
+
`vendor/bin/<rust-triple>/`.
|
|
142
|
+
|
|
143
|
+
Publishing to RubyGems is intentional and separate from local gem builds.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT. Renderer engine depends on [labelize](https://github.com/GOODBOY008/labelize) (MIT).
|
data/exe/zpl-render
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
require_relative "../lib/zpl_renderer"
|
|
6
|
+
|
|
7
|
+
options = {
|
|
8
|
+
format: :png,
|
|
9
|
+
width: 4.0,
|
|
10
|
+
height: 6.0,
|
|
11
|
+
dpi: 203,
|
|
12
|
+
index: 0,
|
|
13
|
+
unit: :inches,
|
|
14
|
+
strict: true,
|
|
15
|
+
output: nil
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
parser = OptionParser.new do |opts|
|
|
19
|
+
opts.banner = "Usage: zpl-render [options] [INPUT.zpl]"
|
|
20
|
+
|
|
21
|
+
opts.on("-o", "--output PATH", "Output file (default: stdout for bytes, or INPUT.png/pdf)") do |v|
|
|
22
|
+
options[:output] = v
|
|
23
|
+
end
|
|
24
|
+
opts.on("-f", "--format FORMAT", "png or pdf (default: png)") do |v|
|
|
25
|
+
options[:format] = v.to_sym
|
|
26
|
+
end
|
|
27
|
+
opts.on("-w", "--width N", Float, "Label width (default: 4)") { |v| options[:width] = v }
|
|
28
|
+
opts.on("-H", "--height N", Float, "Label height (default: 6)") { |v| options[:height] = v }
|
|
29
|
+
opts.on("--dpi N", Integer, "203, 300, or 600 (default: 203)") { |v| options[:dpi] = v }
|
|
30
|
+
opts.on("--dpmm N", Integer, "8, 12, or 24 (overrides --dpi)") { |v| options[:dpmm] = v }
|
|
31
|
+
opts.on("--index N", Integer, "Label index for multi-label ZPL (default: 0)") { |v| options[:index] = v }
|
|
32
|
+
opts.on("--unit UNIT", "inches or mm (default: inches)") { |v| options[:unit] = v.to_sym }
|
|
33
|
+
opts.on("--[no-]strict", "Raise on unsupported commands (default: true)") { |v| options[:strict] = v }
|
|
34
|
+
opts.on("-V", "--validate-only", "Validate ZPL and exit") { options[:validate_only] = true }
|
|
35
|
+
opts.on("-v", "--version", "Print version") do
|
|
36
|
+
puts "zpl_renderer #{ZplRenderer::VERSION}"
|
|
37
|
+
exit 0
|
|
38
|
+
end
|
|
39
|
+
opts.on("--help", "Show help") do
|
|
40
|
+
puts opts
|
|
41
|
+
exit 0
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
parser.parse!
|
|
46
|
+
|
|
47
|
+
input_path = ARGV.shift
|
|
48
|
+
zpl =
|
|
49
|
+
if input_path
|
|
50
|
+
File.read(input_path)
|
|
51
|
+
elsif !$stdin.tty?
|
|
52
|
+
$stdin.read
|
|
53
|
+
else
|
|
54
|
+
warn parser.help
|
|
55
|
+
exit 1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
render_kwargs = {
|
|
59
|
+
format: options[:format],
|
|
60
|
+
width: options[:width],
|
|
61
|
+
height: options[:height],
|
|
62
|
+
dpi: options[:dpi],
|
|
63
|
+
index: options[:index],
|
|
64
|
+
unit: options[:unit],
|
|
65
|
+
strict: options[:strict]
|
|
66
|
+
}
|
|
67
|
+
render_kwargs[:dpmm] = options[:dpmm] if options[:dpmm]
|
|
68
|
+
|
|
69
|
+
if options[:validate_only]
|
|
70
|
+
result = ZplRenderer.validate(zpl, **render_kwargs)
|
|
71
|
+
result.warnings.each { |w| warn "warning: #{w}" }
|
|
72
|
+
if result.ok?
|
|
73
|
+
puts "OK"
|
|
74
|
+
exit 0
|
|
75
|
+
else
|
|
76
|
+
result.errors.each { |e| warn "error: #{e}" }
|
|
77
|
+
exit 2
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
bytes = ZplRenderer.render(zpl, **render_kwargs)
|
|
82
|
+
|
|
83
|
+
output = options[:output]
|
|
84
|
+
if output.nil? && input_path
|
|
85
|
+
ext = options[:format] == :pdf ? ".pdf" : ".png"
|
|
86
|
+
output = input_path.sub(/\.[^.]+\z/, "") + ext
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if output
|
|
90
|
+
File.binwrite(output, bytes)
|
|
91
|
+
warn "Wrote #{output} (#{bytes.bytesize} bytes)"
|
|
92
|
+
else
|
|
93
|
+
$stdout.binmode
|
|
94
|
+
$stdout.write(bytes)
|
|
95
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module ZplRenderer
|
|
6
|
+
# Locates the bundled zpl-renderer native binary for the current platform.
|
|
7
|
+
module Binary
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
BINARY_NAME = "zpl-renderer"
|
|
11
|
+
|
|
12
|
+
def path
|
|
13
|
+
candidates.find { |candidate| File.file?(candidate) && File.executable?(candidate) } ||
|
|
14
|
+
raise(BinaryMissingError, missing_message)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def platform_triple
|
|
18
|
+
case RUBY_PLATFORM
|
|
19
|
+
when /x86_64-linux-musl/ then "x86_64-unknown-linux-musl"
|
|
20
|
+
when /aarch64-linux-musl|arm64-linux-musl/ then "aarch64-unknown-linux-musl"
|
|
21
|
+
when /x86_64-linux/ then "x86_64-unknown-linux-gnu"
|
|
22
|
+
when /aarch64-linux|arm64-linux/ then "aarch64-unknown-linux-gnu"
|
|
23
|
+
when /x86_64-darwin/ then "x86_64-apple-darwin"
|
|
24
|
+
when /arm64-darwin|aarch64-darwin/ then "aarch64-apple-darwin"
|
|
25
|
+
when /x64-mingw|x86_64-mingw|x64-mswin|x86_64-mswin/ then "x86_64-pc-windows-msvc"
|
|
26
|
+
else
|
|
27
|
+
RUBY_PLATFORM
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def candidates
|
|
32
|
+
root = File.expand_path("../..", __dir__)
|
|
33
|
+
name = windows? ? "#{BINARY_NAME}.exe" : BINARY_NAME
|
|
34
|
+
triple = platform_triple
|
|
35
|
+
|
|
36
|
+
[
|
|
37
|
+
ENV["ZPL_RENDERER_BINARY"],
|
|
38
|
+
File.join(root, "vendor", "bin", triple, name),
|
|
39
|
+
File.join(root, "vendor", "bin", name),
|
|
40
|
+
File.join(root, "renderer", "target", "release", name),
|
|
41
|
+
File.join(root, "renderer", "target", "debug", name)
|
|
42
|
+
].compact
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def windows?
|
|
46
|
+
!!(RUBY_PLATFORM =~ /mswin|mingw|cygwin/)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def missing_message
|
|
50
|
+
<<~MSG
|
|
51
|
+
Could not find the zpl-renderer binary for #{platform_triple}.
|
|
52
|
+
|
|
53
|
+
Tried:
|
|
54
|
+
#{candidates.map { |c| " - #{c}" }.join("\n")}
|
|
55
|
+
|
|
56
|
+
Build it with:
|
|
57
|
+
cd renderer && cargo build --release
|
|
58
|
+
|
|
59
|
+
Or set ZPL_RENDERER_BINARY to an absolute path.
|
|
60
|
+
MSG
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
|
|
6
|
+
module ZplRenderer
|
|
7
|
+
# Independent barcode/QR decoder used by tests and optional QA hooks.
|
|
8
|
+
class Decoder
|
|
9
|
+
Hit = Struct.new(:symbology, :text, keyword_init: true)
|
|
10
|
+
|
|
11
|
+
def initialize(binary_path: nil)
|
|
12
|
+
@binary_path = binary_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def decode_png(png_bytes)
|
|
16
|
+
stdout, stderr, status = Open3.capture3(
|
|
17
|
+
binary_path,
|
|
18
|
+
"--decode",
|
|
19
|
+
stdin_data: png_bytes,
|
|
20
|
+
binmode: true
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
unless status.success?
|
|
24
|
+
message = stderr.to_s.strip
|
|
25
|
+
message = "decode failed with status #{status.exitstatus}" if message.empty?
|
|
26
|
+
raise RenderError, message
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
JSON.parse(stdout).map do |row|
|
|
30
|
+
Hit.new(symbology: row.fetch("format"), text: row.fetch("text"))
|
|
31
|
+
end
|
|
32
|
+
rescue Errno::ENOENT
|
|
33
|
+
raise BinaryMissingError, Binary.missing_message
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def binary_path
|
|
39
|
+
@binary_path || Binary.path
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "timeout"
|
|
6
|
+
|
|
7
|
+
module ZplRenderer
|
|
8
|
+
# Invokes the offline Rust renderer binary over stdin/stdout.
|
|
9
|
+
class Engine
|
|
10
|
+
PROTOCOL_VERSION = 1
|
|
11
|
+
|
|
12
|
+
def initialize(binary_path: nil)
|
|
13
|
+
@binary_path = binary_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render(zpl, options)
|
|
17
|
+
request = JSON.generate(
|
|
18
|
+
protocol: PROTOCOL_VERSION,
|
|
19
|
+
zpl: zpl.to_s,
|
|
20
|
+
format: options.format.to_s,
|
|
21
|
+
width_mm: options.width_mm,
|
|
22
|
+
height_mm: options.height_mm,
|
|
23
|
+
dpmm: options.dpmm,
|
|
24
|
+
index: options.index
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
stdout = +"".b
|
|
28
|
+
stderr = +"".b
|
|
29
|
+
status = nil
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
Timeout.timeout(options.timeout) do
|
|
33
|
+
Open3.popen3(binary_path, "--json") do |stdin, out, err, wait_thr|
|
|
34
|
+
stdin.binmode
|
|
35
|
+
out.binmode
|
|
36
|
+
err.binmode
|
|
37
|
+
|
|
38
|
+
stdin.write(request)
|
|
39
|
+
stdin.close
|
|
40
|
+
|
|
41
|
+
stdout = out.read
|
|
42
|
+
stderr = err.read
|
|
43
|
+
status = wait_thr.value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
rescue Timeout::Error
|
|
47
|
+
raise TimeoutError, "render timed out after #{options.timeout}s"
|
|
48
|
+
rescue Errno::ENOENT
|
|
49
|
+
raise BinaryMissingError, Binary.missing_message
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
unless status&.success?
|
|
53
|
+
message = stderr.to_s.strip
|
|
54
|
+
message = "renderer exited with status #{status&.exitstatus}" if message.empty?
|
|
55
|
+
raise_for_message!(message)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if stdout.bytesize > options.max_output_bytes
|
|
59
|
+
raise OutputTooLargeError,
|
|
60
|
+
"output is #{stdout.bytesize} bytes; max_output_bytes=#{options.max_output_bytes}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
stdout
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def binary_path
|
|
69
|
+
@binary_path || Binary.path
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def raise_for_message!(message)
|
|
73
|
+
case message
|
|
74
|
+
when /unsupported|not supported/i
|
|
75
|
+
raise UnsupportedCommandError, message
|
|
76
|
+
when /barcode|invalid data|checksum/i
|
|
77
|
+
raise InvalidBarcodeError, message
|
|
78
|
+
when /timeout/i
|
|
79
|
+
raise TimeoutError, message
|
|
80
|
+
when /index out of range|no labels/i
|
|
81
|
+
raise RenderError, message
|
|
82
|
+
else
|
|
83
|
+
raise RenderError, message
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ZplRenderer
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class ConfigurationError < Error; end
|
|
7
|
+
class ValidationError < Error; end
|
|
8
|
+
class UnsupportedCommandError < ValidationError; end
|
|
9
|
+
class InvalidBarcodeError < ValidationError; end
|
|
10
|
+
class RenderError < Error; end
|
|
11
|
+
class TimeoutError < RenderError; end
|
|
12
|
+
class BinaryMissingError < RenderError; end
|
|
13
|
+
class OutputTooLargeError < RenderError; end
|
|
14
|
+
end
|