zebra-day 0.0.32__py3-none-any.whl → 1.0.2__py3-none-any.whl
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.
- zebra_day/__init__.py +35 -0
- zebra_day/bin/fetch_zebra_config.py +15 -0
- zebra_day/bin/generate_coord_grid_zpl.py +50 -0
- zebra_day/bin/print_zpl_from_file.py +21 -0
- zebra_day/bin/probe_new_label_dimensions.py +75 -0
- zebra_day/bin/scan_for_networed_zebra_printers_curl.sh +2 -1
- zebra_day/bin/zserve.py +701 -259
- zebra_day/cli/__init__.py +240 -0
- zebra_day/cli/cognito.py +121 -0
- zebra_day/cli/gui.py +255 -0
- zebra_day/cli/printer.py +168 -0
- zebra_day/cli/template.py +176 -0
- zebra_day/cmd_mgr.py +35 -0
- zebra_day/etc/label_styles/blank.zpl +0 -0
- zebra_day/etc/label_styles/cornersStripOf4Squares_1inX1in.zpl +55 -0
- zebra_day/etc/label_styles/corners_1inX2in.zpl +28 -0
- zebra_day/etc/label_styles/corners_20cmX30cm.zpl +6 -0
- zebra_day/etc/label_styles/corners_smallTube.zpl +7 -0
- zebra_day/etc/label_styles/corners_unspecifiedDimensions.zpl +15 -0
- zebra_day/etc/label_styles/plate_1inX0.25inHD.zpl +9 -0
- zebra_day/etc/label_styles/smallTubeWdotHD_prod.zpl +8 -0
- zebra_day/etc/label_styles/smallTubeWdot_corners.zpl +7 -0
- zebra_day/etc/label_styles/smallTubeWdot_prod.zpl +8 -0
- zebra_day/etc/label_styles/smallTubeWdot_prodAlt1.zpl +6 -0
- zebra_day/etc/label_styles/smallTubeWdot_prodAlt1b.zpl +3 -0
- zebra_day/etc/label_styles/smallTubeWdot_prodV2.zpl +8 -0
- zebra_day/etc/label_styles/smallTubeWdot_reagent.zpl +29 -0
- zebra_day/etc/label_styles/stripOf4Squares_1inX1in.zpl +32 -0
- zebra_day/etc/label_styles/test_800dX800dCoordinateArray.zpl +1 -0
- zebra_day/etc/label_styles/tmps/tmp_zpl_templates.here +0 -0
- zebra_day/etc/label_styles/tube_20mmX30mmA.zpl +7 -0
- zebra_day/etc/label_styles/tube_2inX0.5in.zpl +15 -0
- zebra_day/etc/label_styles/tube_2inX0.5inHD.zpl +15 -0
- zebra_day/etc/label_styles/tube_2inX1inHD.zpl +22 -0
- zebra_day/etc/label_styles/tube_2inX1inHDv3.zpl +21 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.022846_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.033657_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.039597_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.047295_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.055804_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:50:25.061337_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.073326_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.081950_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.088251_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.096501_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.104767_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.110364_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.118239_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.125950_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.349866_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.361085_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.558323_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:24.565756_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/{2023-10-25_02:19:04.139607_printer_config.json → 2026-02-01_01:51:29.739070_printer_config.json} +4 -3
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.753796_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.760201_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.768747_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.775312_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.782533_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.789287_printer_config.json +1 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.794230_printer_config.json +3 -0
- zebra_day/etc/old_printer_config/2026-02-01_01:51:29.800021_printer_config.json +5 -0
- zebra_day/etc/printer_config.json +3 -54
- zebra_day/etc/printer_config.template.json +3 -2
- zebra_day/etc/tmp_printers0.json +5 -0
- zebra_day/etc/tmp_printers374.json +5 -0
- zebra_day/etc/tmp_printers383.json +5 -0
- zebra_day/etc/tmp_printers450.json +5 -0
- zebra_day/etc/tmp_printers504.json +5 -0
- zebra_day/etc/tmp_printers608.json +5 -0
- zebra_day/etc/tmp_printers657.json +5 -0
- zebra_day/etc/tmp_printers838.json +5 -0
- zebra_day/etc/tmp_printers839.json +5 -0
- zebra_day/etc/tmp_printers933.json +5 -0
- zebra_day/etc/tmp_printers957.json +5 -0
- zebra_day/exceptions.py +88 -0
- zebra_day/files/hold +0 -0
- zebra_day/files/test_png_17696.png +0 -0
- zebra_day/files/test_png_23477.png +0 -0
- zebra_day/files/test_png_28157.png +0 -0
- zebra_day/files/test_png_35832.png +0 -0
- zebra_day/files/test_png_36400.png +0 -0
- zebra_day/files/test_png_40816.png +0 -0
- zebra_day/files/test_png_49564.png +0 -0
- zebra_day/files/test_png_53848.png +0 -0
- zebra_day/files/test_png_62542.png +0 -0
- zebra_day/files/test_png_91597.png +0 -0
- zebra_day/files/test_png_93633.png +0 -0
- zebra_day/files/tmpbjo3k7q1.png +0 -0
- zebra_day/files/tmpigtr4pwy.png +0 -0
- zebra_day/files/zpl_label_tube_2inX1in_2026-02-01_01:51:24.370964.png +0 -0
- zebra_day/logging_config.py +74 -0
- zebra_day/logs/.hold +0 -0
- zebra_day/logs/print_requests.log +2 -0
- zebra_day/paths.py +143 -0
- zebra_day/print_mgr.py +489 -103
- zebra_day/static/datschund.css +63 -43
- zebra_day/static/datschund.png +0 -0
- zebra_day/static/daylily.png +0 -0
- zebra_day/static/favicon.svg +20 -0
- zebra_day/static/general.css +99 -0
- zebra_day/static/js/zebra_modern.js +172 -0
- zebra_day/static/lsmc.css +354 -0
- zebra_day/static/moon.jpeg +0 -0
- zebra_day/static/oakland.css +0 -32
- zebra_day/static/popday_daylily.css +1 -1
- zebra_day/static/style.css +39 -0
- zebra_day/static/zebra_modern.css +771 -0
- zebra_day/templates/base.html +36 -0
- zebra_day/templates/bpr.html +72 -0
- zebra_day/templates/build_new_config.html +36 -0
- zebra_day/templates/build_print_request.html +32 -0
- zebra_day/templates/chg_ui_style.html +19 -0
- zebra_day/templates/edit_template.html +128 -0
- zebra_day/templates/edit_zpl.html +37 -0
- zebra_day/templates/index.html +82 -0
- zebra_day/templates/legacy/base.html +37 -0
- zebra_day/templates/legacy/bpr.html +72 -0
- zebra_day/templates/legacy/build_new_config.html +36 -0
- zebra_day/templates/legacy/build_print_request.html +32 -0
- zebra_day/templates/legacy/chg_ui_style.html +19 -0
- zebra_day/templates/legacy/edit_template.html +128 -0
- zebra_day/templates/legacy/edit_zpl.html +37 -0
- zebra_day/templates/legacy/index.html +82 -0
- zebra_day/templates/legacy/list_prior_configs.html +24 -0
- zebra_day/templates/legacy/print_result.html +30 -0
- zebra_day/templates/legacy/printer_details.html +25 -0
- zebra_day/templates/legacy/printer_status.html +70 -0
- zebra_day/templates/legacy/save_result.html +17 -0
- zebra_day/templates/legacy/send_print_request.html +34 -0
- zebra_day/templates/legacy/simple_print.html +94 -0
- zebra_day/templates/legacy/view_pstation_json.html +29 -0
- zebra_day/templates/list_prior_configs.html +24 -0
- zebra_day/templates/modern/base.html +98 -0
- zebra_day/templates/modern/config.html +141 -0
- zebra_day/templates/modern/dashboard.html +160 -0
- zebra_day/templates/modern/print_request.html +141 -0
- zebra_day/templates/modern/print_result.html +88 -0
- zebra_day/templates/modern/printer_detail.html +117 -0
- zebra_day/templates/modern/printers.html +133 -0
- zebra_day/templates/modern/save_result.html +46 -0
- zebra_day/templates/modern/template_editor.html +172 -0
- zebra_day/templates/modern/templates.html +122 -0
- zebra_day/templates/print_result.html +30 -0
- zebra_day/templates/printer_details.html +25 -0
- zebra_day/templates/printer_status.html +70 -0
- zebra_day/templates/save_result.html +17 -0
- zebra_day/templates/send_print_request.html +34 -0
- zebra_day/templates/simple_print.html +94 -0
- zebra_day/templates/view_pstation_json.html +29 -0
- zebra_day/web/__init__.py +9 -0
- zebra_day/web/app.py +171 -0
- zebra_day/web/auth.py +172 -0
- zebra_day/web/middleware.py +159 -0
- zebra_day/web/routers/__init__.py +2 -0
- zebra_day/web/routers/api.py +163 -0
- zebra_day/web/routers/ui.py +1051 -0
- zebra_day/zpl_renderer.py +273 -0
- zebra_day-1.0.2.dist-info/METADATA +786 -0
- zebra_day-1.0.2.dist-info/RECORD +179 -0
- {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/WHEEL +1 -1
- zebra_day-1.0.2.dist-info/entry_points.txt +4 -0
- zebra_day/etc/.blind +0 -1
- zebra_day/etc/current_style.txt +0 -1
- zebra_day/etc/label_styles/test_2inX1in.zpl +0 -22
- zebra_day/etc/label_styles/tmps/labware_2inX1in.na.2023-10-24_12:49:21.045127.zpl +0 -21
- zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.8888.2023-10-24_16:02:16.443911.zpl +0 -21
- zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.zpl +0 -21
- zebra_day/etc/label_styles/tmps/test_2inX1in.na.2023-10-24_12:45:37.002774.zpl +0 -22
- zebra_day/etc/old_printer_config/2023-10-24_16:06:06.931764_printer_config.json +0 -67
- zebra_day/etc/printer_config.json~ +0 -67
- zebra_day/files/tmp_2olihg4.png +0 -0
- zebra_day/files/tmpveojoyvn.png +0 -0
- zebra_day/files/zpl_label_labware_2inX1in_2023-10-25_02:30:08.093631.png +0 -0
- zebra_day/files/zpl_label_test_2inX1in_2023-10-24_15:54:29.343124.png +0 -0
- zebra_day/files/zpl_label_test_2inX1in_2023-10-24_16:01:45.670132.png +0 -0
- zebra_day/static/beyonce.css +0 -227
- zebra_day/static/datschund_on_moon.css +0 -164
- zebra_day/static/medicalsci.css +0 -144
- zebra_day/static/moar_zebra.css +0 -133
- zebra_day/static/popday.css +0 -140
- zebra_day/static/popday_dark.css +0 -140
- zebra_day/static/popday_dog.css +0 -140
- zebra_day-0.0.32.dist-info/METADATA +0 -14
- zebra_day-0.0.32.dist-info/RECORD +0 -53
- zebra_day-0.0.32.dist-info/entry_points.txt +0 -2
- /zebra_day/{etc/label_styles/blank_0inX0in.zpl → bin/__init__.py} +0 -0
- /zebra_day/etc/label_styles/{labware_2inX1in.zpl → generic_2inX1in.zpl} +0 -0
- {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info/licenses}/LICENSE +0 -0
- {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local ZPL-to-PNG renderer using Pillow and zint-bindings.
|
|
3
|
+
|
|
4
|
+
Replaces external Labelary API dependency with offline rendering.
|
|
5
|
+
Supports the ZPL commands used in zebra_day templates:
|
|
6
|
+
- ^XA/^XZ: Label start/end
|
|
7
|
+
- ^FO: Field origin (positioning)
|
|
8
|
+
- ^FD/^FS: Field data/separator
|
|
9
|
+
- ^A0N/^ADN: Font selection
|
|
10
|
+
- ^BY: Barcode field default
|
|
11
|
+
- ^B3N: Code 39 barcode
|
|
12
|
+
- ^BCN: Code 128 barcode
|
|
13
|
+
- ^BQN: QR code
|
|
14
|
+
- ^BXN: Data Matrix
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import io
|
|
19
|
+
import logging
|
|
20
|
+
import re
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import zint
|
|
28
|
+
ZINT_AVAILABLE = True
|
|
29
|
+
except ImportError:
|
|
30
|
+
ZINT_AVAILABLE = False
|
|
31
|
+
|
|
32
|
+
_log = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
# Default label dimensions for 4x6 inch label at 8 dpmm (203 dpi)
|
|
35
|
+
DEFAULT_LABEL_WIDTH_DOTS = 812 # 4 inches * 203 dpi
|
|
36
|
+
DEFAULT_LABEL_HEIGHT_DOTS = 1218 # 6 inches * 203 dpi
|
|
37
|
+
|
|
38
|
+
# Barcode type mappings
|
|
39
|
+
BARCODE_TYPES = {
|
|
40
|
+
'B3N': 'CODE39', # Code 39
|
|
41
|
+
'BCN': 'CODE128', # Code 128
|
|
42
|
+
'BQN': 'QRCODE', # QR Code
|
|
43
|
+
'BXN': 'DATAMATRIX', # Data Matrix
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class FontSpec:
|
|
49
|
+
"""Font specification from ZPL ^A command."""
|
|
50
|
+
height: int = 30
|
|
51
|
+
width: int = 20
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class BarcodeSpec:
|
|
56
|
+
"""Barcode specification from ZPL ^BY command."""
|
|
57
|
+
module_width: int = 2
|
|
58
|
+
ratio: float = 3.0
|
|
59
|
+
height: int = 10
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class RenderState:
|
|
64
|
+
"""Current rendering state while parsing ZPL."""
|
|
65
|
+
x: int = 0
|
|
66
|
+
y: int = 0
|
|
67
|
+
font: FontSpec = field(default_factory=FontSpec)
|
|
68
|
+
barcode: BarcodeSpec = field(default_factory=BarcodeSpec)
|
|
69
|
+
current_barcode_type: str | None = None
|
|
70
|
+
barcode_height: int = 40
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _get_font(height: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
|
|
74
|
+
"""Get a font at the specified height. Falls back to default if unavailable."""
|
|
75
|
+
try:
|
|
76
|
+
# Try common monospace fonts
|
|
77
|
+
for font_name in ['DejaVuSansMono.ttf', 'Menlo.ttc', 'Courier New.ttf', 'monospace']:
|
|
78
|
+
try:
|
|
79
|
+
return ImageFont.truetype(font_name, height)
|
|
80
|
+
except OSError:
|
|
81
|
+
continue
|
|
82
|
+
# Fallback to default
|
|
83
|
+
return ImageFont.load_default()
|
|
84
|
+
except Exception:
|
|
85
|
+
return ImageFont.load_default()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _render_barcode(barcode_type: str, data: str, height: int = 40, module_width: int = 2) -> Image.Image | None:
|
|
89
|
+
"""Render a barcode using zint-bindings."""
|
|
90
|
+
if not ZINT_AVAILABLE:
|
|
91
|
+
_log.warning("zint-bindings not available, cannot render barcode")
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
import tempfile
|
|
95
|
+
import os
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
symbol = zint.Symbol()
|
|
99
|
+
|
|
100
|
+
# Map barcode type to zint symbology
|
|
101
|
+
if barcode_type == 'CODE39':
|
|
102
|
+
symbol.symbology = zint.Symbology.CODE39
|
|
103
|
+
elif barcode_type == 'CODE128':
|
|
104
|
+
symbol.symbology = zint.Symbology.CODE128
|
|
105
|
+
elif barcode_type == 'QRCODE':
|
|
106
|
+
symbol.symbology = zint.Symbology.QRCODE
|
|
107
|
+
elif barcode_type == 'DATAMATRIX':
|
|
108
|
+
symbol.symbology = zint.Symbology.DATAMATRIX
|
|
109
|
+
else:
|
|
110
|
+
_log.warning("Unknown barcode type: %s", barcode_type)
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
symbol.height = height
|
|
114
|
+
symbol.scale = max(1, module_width)
|
|
115
|
+
symbol.show_text = False # ZPL typically handles text separately
|
|
116
|
+
|
|
117
|
+
# Create temp file for output
|
|
118
|
+
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
|
|
119
|
+
temp_path = f.name
|
|
120
|
+
|
|
121
|
+
symbol.outfile = temp_path
|
|
122
|
+
symbol.encode(data)
|
|
123
|
+
symbol.print() # Write to outfile
|
|
124
|
+
|
|
125
|
+
# Load the image
|
|
126
|
+
if os.path.exists(temp_path):
|
|
127
|
+
img = Image.open(temp_path).convert('RGBA')
|
|
128
|
+
os.unlink(temp_path) # Clean up temp file
|
|
129
|
+
return img
|
|
130
|
+
return None
|
|
131
|
+
except Exception as e:
|
|
132
|
+
_log.warning("Failed to render barcode: %s", e)
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _parse_font_command(cmd: str) -> FontSpec:
|
|
137
|
+
"""Parse ^A0N or ^ADN font command."""
|
|
138
|
+
# ^A0N,height,width or ^ADN,height,width
|
|
139
|
+
parts = cmd.split(',')
|
|
140
|
+
height = int(parts[1]) if len(parts) > 1 and parts[1].strip() else 30
|
|
141
|
+
width = int(parts[2]) if len(parts) > 2 and parts[2].strip() else height // 2
|
|
142
|
+
return FontSpec(height=height, width=width)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _parse_barcode_default(cmd: str) -> BarcodeSpec:
|
|
146
|
+
"""Parse ^BY command for barcode defaults."""
|
|
147
|
+
# ^BY[module_width],[ratio],[height]
|
|
148
|
+
parts = cmd.split(',')
|
|
149
|
+
module_width = int(parts[0]) if parts[0].strip() else 2
|
|
150
|
+
ratio = float(parts[1]) if len(parts) > 1 and parts[1].strip() else 3.0
|
|
151
|
+
height = int(parts[2]) if len(parts) > 2 and parts[2].strip() else 10
|
|
152
|
+
return BarcodeSpec(module_width=module_width, ratio=ratio, height=height)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _parse_position(cmd: str) -> tuple[int, int]:
|
|
156
|
+
"""Parse ^FO command for field origin."""
|
|
157
|
+
# ^FOx,y
|
|
158
|
+
parts = cmd.split(',')
|
|
159
|
+
x = int(parts[0]) if parts[0].strip() else 0
|
|
160
|
+
y = int(parts[1]) if len(parts) > 1 and parts[1].strip() else 0
|
|
161
|
+
return x, y
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _parse_barcode_command(cmd: str) -> int:
|
|
165
|
+
"""Parse barcode command (^B3N, ^BCN, etc.) and return height."""
|
|
166
|
+
# Format: ^B3N,orientation,height,... or ^BCN,orientation,height,...
|
|
167
|
+
parts = cmd.split(',')
|
|
168
|
+
# Height is usually the 3rd parameter (index 2) for most barcode commands
|
|
169
|
+
if len(parts) > 2 and parts[2].strip():
|
|
170
|
+
try:
|
|
171
|
+
return int(parts[2])
|
|
172
|
+
except ValueError:
|
|
173
|
+
pass
|
|
174
|
+
return 40 # Default height
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def render_zpl_to_png(
|
|
178
|
+
zpl_string: str,
|
|
179
|
+
output_path: str | Path,
|
|
180
|
+
width: int = DEFAULT_LABEL_WIDTH_DOTS,
|
|
181
|
+
height: int = DEFAULT_LABEL_HEIGHT_DOTS,
|
|
182
|
+
) -> str:
|
|
183
|
+
"""
|
|
184
|
+
Render ZPL string to PNG image.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
zpl_string: ZPL format string to render
|
|
188
|
+
output_path: Path to save the PNG file
|
|
189
|
+
width: Label width in dots (default: 812 for 4" at 203 dpi)
|
|
190
|
+
height: Label height in dots (default: 1218 for 6" at 203 dpi)
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Path to the saved PNG file
|
|
194
|
+
"""
|
|
195
|
+
output_path = Path(output_path)
|
|
196
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
197
|
+
|
|
198
|
+
# Create white background image
|
|
199
|
+
img = Image.new('RGB', (width, height), 'white')
|
|
200
|
+
draw = ImageDraw.Draw(img)
|
|
201
|
+
|
|
202
|
+
state = RenderState()
|
|
203
|
+
|
|
204
|
+
# Parse ZPL commands - split on ^ character
|
|
205
|
+
commands = re.split(r'\^', zpl_string)
|
|
206
|
+
|
|
207
|
+
pending_text: str | None = None
|
|
208
|
+
|
|
209
|
+
for cmd in commands:
|
|
210
|
+
cmd = cmd.strip()
|
|
211
|
+
if not cmd:
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
# Label start/end - ignore
|
|
215
|
+
if cmd.startswith('XA') or cmd.startswith('XZ'):
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
# Field origin - set position
|
|
219
|
+
if cmd.startswith('FO'):
|
|
220
|
+
state.x, state.y = _parse_position(cmd[2:])
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
# Font commands
|
|
224
|
+
if cmd.startswith('A0N') or cmd.startswith('ADN'):
|
|
225
|
+
state.font = _parse_font_command(cmd)
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
# Barcode default
|
|
229
|
+
if cmd.startswith('BY'):
|
|
230
|
+
state.barcode = _parse_barcode_default(cmd[2:])
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
# Barcode type commands
|
|
234
|
+
for bc_cmd, bc_type in BARCODE_TYPES.items():
|
|
235
|
+
if cmd.startswith(bc_cmd):
|
|
236
|
+
state.current_barcode_type = bc_type
|
|
237
|
+
state.barcode_height = _parse_barcode_command(cmd)
|
|
238
|
+
break
|
|
239
|
+
else:
|
|
240
|
+
# Not a barcode command, check for field data
|
|
241
|
+
if cmd.startswith('FD'):
|
|
242
|
+
# Extract data between FD and FS
|
|
243
|
+
data = cmd[2:]
|
|
244
|
+
if data.endswith('FS'):
|
|
245
|
+
data = data[:-2]
|
|
246
|
+
|
|
247
|
+
# If there's a pending barcode type, render barcode
|
|
248
|
+
if state.current_barcode_type:
|
|
249
|
+
bc_img = _render_barcode(
|
|
250
|
+
state.current_barcode_type,
|
|
251
|
+
data,
|
|
252
|
+
height=state.barcode_height,
|
|
253
|
+
module_width=state.barcode.module_width,
|
|
254
|
+
)
|
|
255
|
+
if bc_img:
|
|
256
|
+
img.paste(bc_img, (state.x, state.y), bc_img if bc_img.mode == 'RGBA' else None)
|
|
257
|
+
state.current_barcode_type = None
|
|
258
|
+
else:
|
|
259
|
+
# Render text
|
|
260
|
+
font = _get_font(state.font.height)
|
|
261
|
+
draw.text((state.x, state.y), data, fill='black', font=font)
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
# Field separator - just a marker, usually handled with FD
|
|
265
|
+
if cmd.startswith('FS'):
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
# Save the image
|
|
269
|
+
img.save(str(output_path), 'PNG')
|
|
270
|
+
_log.info("Label image saved as %s", output_path)
|
|
271
|
+
|
|
272
|
+
return str(output_path)
|
|
273
|
+
|