# v-zpl-printer **Repository Path**: mengdj/v-zpl-printer ## Basic Information - **Project Name**: v-zpl-printer - **Description**: ZPL渲染和虚拟打印 - **Primary Language**: C++ - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-27 - **Last Updated**: 2026-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # v-zpl-printer (vzpl) 原生 C++17 实现的 **ZPL 标签渲染库**,面向 VS2022 / CMake。除以下两处外**不引入第三方库** (无 FreeType、Skia、libpng、libharu、zxing):`stb_image_write.h`(public domain,仅用于 PNG 压缩); `src/barcode/` 下的 `DataBarTables.inc`、`Pdf417Tables.inc`、`AztecTables.inc`(常数表**机械抽取** 自 libzint,BSD-3-Clause,见第 8 节)。 把 ZPL 文本渲染为位图 / PNG / BMP / 多页 PDF,并以 C ABI 导出,供 C++、C#、Python、Java、Go 等直接调用;输出还可通过 **sink 回调**送到内存、网络或数据库。 --- ## 1. 已实现能力 ### 输出 | 形式 | 说明 | |---|---| | 内存图像 | `vzpl_render` 返回 8bpp 灰度位图(0=黑 255=白) | | PNG(默认) | 1bpp 灰度,deflate stored 块(合规但未压缩) | | PNG8 | 8bpp 灰度,经 stb_image_write 的真 DEFLATE 压缩,体积约为 1bpp 版的 **1/7** | | BMP | 1bpp,4 字节行对齐 | | PDF | 多页,每页一张标签,RunLengthDecode 压缩,页面尺寸按 dpmm 换算 | | MONO_RAW | 1bpp 原始位流(bit=1 为白),可直接投递打印机固件 | | **sink 回调** | 逐标签渲染 → 编码 → 由调用方决定去向(不落盘) | | 批量 / 流式 | `vzpl_render_batch`(每标签一个文件)、`vzpl_render_stream`(真流式回调) | | **虚拟打印机** | 内建 TCP 服务(Zebra 9100 裸端口),可选独立程序 `vzpl_printer.exe`,见 4.5 | ### ZPL 命令覆盖 - **布局类**:`^XA` `^XZ` `^FS` `^FO` `^FT` `^LH` `^LT` `^LS` `^FW` `^FX` `^MU`(mm/inch 换算)`^LR`(等价于对后续字段加 `^FR`)`^PO`(180° 反转)`^PM`(左右镜像) - **字段类**:`^FD` `^FV` `^SN` `^FH` `^FR` `^FB`(块自动折行/对齐)`^CF` `^FP`(竖排/自右向左 + 附加字距)`^FN` `^FL` `^GS`(® © ™ 等图形符号) `^FC`(字段时钟,支持全部 RTC 格式字符)`^KD` `^PA`(缺字形策略)`^SF`(字段序列化) `^TB`(文本块:整块旋转 + 按块宽折行、按块高截断) - **字体类**:`^A*`(内置 5x7 点阵;方向 N/R/I/B,缩放由高宽参数决定) - **图形类**:`^GB` `^GC` `^GE` `^GD` `^GF`(`A` ASCII 十六进制 / `B` 二进制) - `^GF` 的 `C`(Zebra 私有压缩)算法未公开,遇到会告警并跳过 - **一维条码**:`^BY` + `^BC`(Code128) `^B3`(Code39) `^B2`(ITF) `^BE`(EAN-13) `^B8`(EAN-8) `^BU`(UPC-A) `^BA`(Code93) `^BK`(Codabar) `^B9`(UPC-E) `^BL`(LOGMARS) - 校验位自动计算、可读解释行(`f` 参数为 `Y`)、宽窄比(`^BY` 的 ratio) - **`^BS` UPC/EAN 2 位 / 5 位扩展码**(数据长度决定:`<=2` 位用 EAN-2,否则按 EAN-5 左补 0 / 截短到 5 位);奇偶表与结构与 zxing 编码器**逐模块一致**(41 个用例零差异) - 注意:zxing-cpp 的读取器**不把 add-on 作为独立格式暴露**(它连自己生成的 EAN-2 图也解不出), 所以该码制的判据是"与 zxing 编码器逐模块一致"而非解码 - **`^BR` GS1 DataBar**: - `b=1..5` 已实现:全向(b=1) / 截断(b=2) / 堆积(b=3) / 堆积全向(b=4) / 受限 DataBar(b=5)。 DataBar-14 与 Limited 均已与 `zxing-cpp` **逐模块比对一致**,四种类型都能被 zxing 解码 - `b=7..10`(UPC-A / UPC-E / EAN-13 / EAN-8)复用既有 EAN/UPC 编码器,已可解码 - `b=6`(扩展码 Expanded)与 `b=11..12`(UCC/EAN-128 复合码)**尚未实现**,会明确告警并跳过(见 6.3) - **二维码**:`^BQ` QR Code(模式 2,自动选版与掩码) - **二维矩阵码**:`^BX` Data Matrix ECC 200(ASCII 码字集 + GF(256) RS 纠错 + 标准模块置放; 支持 10x10..26x26 自动/强制尺寸,含 `符号边长 % 4 == 0` 时的右下角 2x2 固定深色块) - **Aztec**:`^B0` / `^BO`(紧凑 1..4 层 / 全范围 1..32 层 / Aztec Rune;`d` 选纠错与层、 `b` 放大因子);ECIC(`c=Y`)、菜单符号(`e=Y`)、结构化追加(`f`、`g`) 未实现并告警 - **PDF417**:`^B7`(安全级 0..8、列 1..30、行 3..90、`t=Y` 截断);`c×r >= 928` 或 `^FD` 超 3K 时按手册**不打印**并告警 - **MicroPDF417**:`^BF`(`m` 模式 0..33,行列组合按手册 Table 5 校验) - 三者均已通过 zxing-cpp 独立解码验证,且分两层验收(编码器矩阵 + 渲染链路,见第 5 节) ### 输出扩展(0.1.0 新增) | 接口 | 用途 | |---|---| | `vzpl_render_to_sink` | 逐标签渲染→编码→写 sink;真流式,峰值内存只驻留一张标签 | | `vzpl_render_pdf_to_sink` | 多页 PDF 一次性编码后整体写入 sink | | `vzpl_encode` / `vzpl_buffer_free` | 只要字节,不落盘 | | `vzpl_register_encoder` | 注册自定义编码器(编号 ≥ `VZPL_ENC_USER_BASE`) | | `vzpl_render_stream_ex` | 真流式回调,可选 `MONO1`/`GRAY8`,附标签序号与当前标签 ZPL | | `vzpl_set_command_hook` | **指令钩子**:新增指令 / 覆盖内置解析 / 过滤 | | `vzpl_set_command_drop_list` | 丢弃名单:命中指令不解析也不告警 | | `vzpl_render_mono` | 一步拿到 **1bit 点阵**(`VZPL_FMT_MONO1`),无需"先渲染灰度再编码"两段调用 | | `vzpl_render_n` | 单标签渲染:可指定像素格式,并支持**显式长度**(含 NUL 的二进制 ZPL) | | `vzpl_render_stream_n` / `vzpl_render_to_sink_n` / `vzpl_render_pdf_to_sink_n` | 上述流式/sink 接口的显式长度版本 | `zplLength` 传 `VZPL_LEN_AUTO`(0) 表示按 NUL 结尾字符串处理(兼容既有调用);ZPL 中含 `^GFB` 二进制数据(可能有 `0x00`)时**必须**传真实长度,否则其后的标签会被 NUL 截断。 指令钩子回调签名: ```c int hook(void* user, const char* prefix, const char* code, const char* args, int labelIndex, VzplRenderContext* ctx); // 返回 0 = 交给内置解析(默认) // 1 = 已处理,跳过内置解析(新增指令 或 覆盖同名内置指令) // -1 = 丢弃该指令(不解析、不告警) ``` `VzplRenderContext` 提供受控的画布与字段状态访问:画布尺寸、读/写像素、填充矩形、 画框/线/圆、绘制文本(UTF-8)、读写字段落点与字格、读 `^BY` 模块参数、追加警告。 示例(把厂商私有指令 `^ZZ` 画成一块矩形): ```c static int onCommand(void* user, const char* prefix, const char* code, const char* args, int index, VzplRenderContext* ctx) { if (strcmp(code, "ZZ") == 0) { int w = 0, h = 0; vzpl_ctx_canvas_size(ctx, &w, &h); vzpl_ctx_fill_rect(ctx, 20, 20, 60, 40, 0); /* 0 = 黑 */ return 1; /* 已处理 */ } return 0; } vzpl_set_command_hook(onCommand, NULL); ``` ### 图像处理 - 整页旋转 0/90/180/270、水平缩放(`widthScale`,适配 32 位列标签数据) - 按内容自动裁剪高度(`autoHeight`) - 线程安全:渲染无全局可变状态;`vzpl_last_error/warnings` 为线程局部 (指令钩子与编码器注册表是进程级,请在并发渲染前完成设置) --- ## 2. 目录结构 ``` include/vzpl/vzpl.h C ABI 公共头(唯一对外接口) src/api/vzpl_api.cpp 导出层(参数校验、错误码、内存管理、钩子与上下文) src/core/Image.{h,cpp} 灰度画布与图元(矩形/圆/椭圆/线/位图) src/core/Parser.{h,cpp} ZPL 命令流拆分(含 ^GFB 二进制段) src/core/Renderer.{h,cpp} 核心:渲染状态、execute() 分派、flushField()、renderZpl() src/core/RendererInternal.h 内部头:FieldKind / PendingField / LabelRenderer src/core/CommandHook.{h,cpp} 指令钩子注册表与渲染上下文 src/core/render/Command*.cpp 指令实现,按 7 类分文件(布局/字段/字体/一维码/二维码/图形/忽略) src/font/BitmapFont.* 内置 5x7 点阵字库与文本绘制 src/barcode/LinearBarcode.* 一维码编码(11 种码制,含 ^BS 扩展码) src/barcode/DataBar.* GS1 DataBar 编码(^BR b=1..5) src/barcode/DataBarTables.inc DataBar 常数表(抽取自 libzint,文件头含许可声明) src/barcode/LinearMatrix.{h,cpp} 多行线性码的类型校验与行高分配(^B4/^BB/^BZ 等共用) src/barcode/MatrixBarcode.{h,cpp} Aztec / PDF417 / MicroPDF417 编码(^B0 ^BO ^B7 ^BF) src/barcode/AztecTables.inc Aztec 常数表(机械抽取自 libzint) src/barcode/Pdf417Tables.inc PDF417/MicroPDF417 码字图案表(机械抽取自 libzint) src/barcode/QrCode.* QR Code(⚠ 见已知缺陷) src/output/ImageWriter.* PNG / BMP / PDF 编码 src/output/StbImageWrite.cpp stb_image_write 实现单元(PNG8 真压缩) third_party/stb/ stb_image_write.h v1.16(public domain,单头文件) tools/vzpl_test.cpp 测试与演示程序(样例渲染、QR 矩阵导出) tools/verify_qr.py QR 解码脚本(⚠ 与编码器共享同一前提,见已知缺陷) tools/verify_matrix.py Aztec/PDF417/MicroPDF417 矩阵 zxing 解码(22 用例) tools/verify_matrix_e2e.py 上述三者的端到端验收:ZPL→PNG→zxing(19 用例) tools/verify_barcodes.py 一维码 Tier B 判据自校验(zint 逐模块比对) tools/zint_oracle.py zint 交叉比对的 ctypes 封装(--selftest 32/32) tools/gen_pdf417_tables.py 生成 src/barcode/Pdf417Tables.inc tools/gen_aztec_tables.py 生成 src/barcode/AztecTables.inc tools/verify_output_api.py 输出扩展接口回归(sink / 编码器注册 / 真流式) samples/csharp|python|java|go/ 各语言封装与示例 ``` --- ## 3. 构建 ### 方式一:VS2022 直接打开(推荐) `文件 → 打开 → 文件夹`,选择本目录即可。 ### 方式二:命令行 ```powershell cmake --preset vs2022-x64 cmake --build --preset release ``` 或手动: ```powershell cmake -S . -B build -G "Visual Studio 17 2022" -A x64 cmake --build build --config Release ``` 产物(`build/Release/`): - `vzpl.dll` + `vzpl.lib`(导入库)—— 动态库,C ABI - `vzpl_static.lib` —— 静态库,直接链接 - `vzpl_test.exe` —— 测试程序 ### 可选开关 | 选项 | 默认 | 说明 | |---|---|---| | `VZPL_BUILD_TESTS` | ON | 构建 `vzpl_test` | | `VZPL_BUILD_NET` | ON | 虚拟打印机 TCP 服务(`include/vzpl/vzpl_net.h`),并额外产出 `vzpl_printer.exe`;关闭后 `vzpl_net_*` 符号不存在 | > 若 `build/` 是旧路径下的缓存,请在 VS 里重新打开文件夹或删除 `build/` 后重建。 --- ## 4. 快速开始 ### 4.1 C++ ```cpp #include "vzpl/vzpl.h" int main() { const char* zpl = "^XA^PW812^LL406^FO50,50^A0N,40,30^FDHello ZPL^FS^XZ"; if (vzpl_render_png(zpl, nullptr, "label.png") != VZPL_OK) { printf("%s\n", vzpl_last_error()); return 1; } printf("警告: %s\n", vzpl_last_warnings()); return 0; } ``` 链接:`#pragma comment(lib, "vzpl.lib")`(或链接 `vzpl_static.lib` 并把 `VZPL_STATIC` 加到预处理器定义)。 ### 4.2 输出到内存 / 网络(不落盘) ```cpp static int onWrite(void* user, const unsigned char* data, size_t len) { ((std::vector*)user)->insert(...); // 或 send()/fwrite() return 0; } VzplSink sink = { &buffer, onWrite, NULL, NULL }; vzpl_render_to_sink(zpl, nullptr, VZPL_ENC_PNG8, &sink); ``` ### 4.3 C# / Python / Java / Go 见 `samples/` 下各语言的封装与可运行示例(Python 用 ctypes、Java 用 JNA、Go 用 cgo,均只依赖该语言自带或单一常见依赖)。 ### 4.5 虚拟打印机(Zebra 9100 裸端口) 把本库当作一台网络标签打印机:上位机按 Zebra 惯例把 ZPL 原始字节直接发到 9100 端口 (不发命令、不等应答),**关闭连接即一个作业**;服务端渲染后落盘或交给回调。 ```powershell # 独立程序(VZPL_BUILD_NET=ON 时产出) build\Release\vzpl_printer.exe --port 9100 --spool D:\labels --pdf # 发一个作业 python -c "import socket;s=socket.create_connection(('127.0.0.1',9100));s.sendall(open('label.zpl','rb').read());s.close()" ``` ```c /* 也可以把服务嵌进自己的程序(进程内,回调式) */ #include "vzpl/vzpl_net.h" static int onJob(void* user, const char* remote, const unsigned char* data, size_t length) { /* data/length 为该作业的原始字节,可能含 NUL,务必按长度处理 */ return 0; } vzpl_net_set_spool_dir("D:\\labels", 1); /* 可选:同时落盘 PNG + PDF */ vzpl_net_start(0, 16, onJob, NULL); /* 0 = 由系统分配端口 */ printf("listening on %d\n", vzpl_net_port()); ... vzpl_net_stop(); ``` 要点: - **端口语义**:`<0` 用默认 9100、`0` 由系统分配(`vzpl_net_port()` 取回)、`>0` 指定端口 - **并发**:每个连接一个线程;渲染走线程安全的库入口,含 NUL 的作业走 `_n`(显式长度)接口 - **安全**:默认只监听 `127.0.0.1`;单作业字节上限默认 64MB(`vzpl_net_set_max_job_bytes`), 超限作业被丢弃并计入 `vzpl_net_failed_count()` - **错误**:`vzpl_net_last_error()`;符号仅在 `VZPL_BUILD_NET=ON` 的构建中存在 ### 4.4 关键 API | 函数 | 说明 | |---|---| | `vzpl_render` | ZPL → 内存图像(首个标签) | | `vzpl_render_png` / `vzpl_render_bmp` / `vzpl_render_pdf` | ZPL → 文件 | | `vzpl_render_batch` | 每个标签输出一个 PNG/BMP,路径模板用 `%d` | | `vzpl_render_stream` / `_ex` | 逐标签真流式回调 | | `vzpl_render_to_sink` / `vzpl_render_pdf_to_sink` | 编码后写入调用方提供的 sink | | `vzpl_encode` / `vzpl_buffer_free` | 单张图像编码到内存 | | `vzpl_register_encoder` | 注册自定义编码器 | | `vzpl_set_command_hook` / `vzpl_set_command_drop_list` | 指令钩子与丢弃名单 | | `vzpl_label_count` | 统计 `^XA` 数量(粗糙计数,非渲染页数) | | `vzpl_measure_zpl` | 测量单字段 ZPL 片段的渲染尺寸(与渲染同口径,见 4.6) | | `vzpl_last_error` / `vzpl_last_warnings` | 错误 / 警告详情(线程局部) | | `vzpl_image_free` | 释放渲染返回的图像 | 错误码:`VZPL_OK` `VZPL_ERR_ARG` `VZPL_ERR_PARSE` `VZPL_ERR_RENDER` `VZPL_ERR_IO` `VZPL_ERR_UNSUPPORT` `VZPL_ERR_NOMEM`。 ### 4.6 字段尺寸测量(`vzpl_measure_zpl`) 设计端需要画布元素框与预览**位置、大小完全一致**,为此提供“单字段尺寸测量”: ```c VzplMeasure m; vzpl_measure_zpl("^FO0,0^BY3,2,60^BCN,60,Y,N,N^FD1234567890^FS", VZPL_LEN_AUTO, NULL, &m); /* m.offsetX/offsetY = 内容相对 ^FO 的偏移;m.width/height = 内容包围盒尺寸(dot) */ ``` **适用范围**:文本 `^A0`、一维条码(`^BC` 等 12 种)、二维码 `^BQ`、Data Matrix `^BX`(ECC200)。 `^GB`/`^GE`/`^GD`/`^GS`/`^GF` 的尺寸由命令参数或图像本身决定,**无需测量**;一维条码 12 种码制 共用一个接口(传单字段 ZPL 片段即可,片段不含 `^XA`/`^XZ`/`^PW`/`^LL`)。 **`^FO` 对齐口径(本库固定为口径 A)**:`^FO` 对齐**内容墨迹**——`offsetX/offsetY` 为墨迹相对 `^FO` 原点的偏移(静区、字体留白**不计入** `width/height`,故 offset 可能 > 0),`width/height` 为墨迹最小包围盒。核心保证(与预览逐点一致): ``` vzpl_measure_zpl(片段) 的 (offsetX,offsetY,width,height) == 该片段(外包 ^XA^PW^LL^XZ)经 vzpl_render 渲染 → 扫描墨点最小包围盒 → 换算到 ^FO 原点 ``` 测量与 `vzpl_render` **共用同一套解析 / 编码 / 字体**(走同一个 `LabelRenderer`),因此二者天然 一致、新增指令/码制自动支持,不存在两套实现。`modules`/`version` 为可选展示字段,当前未计算 (填 0);`advanceX`/`advanceY` 在未支持含静区占位尺寸时回退为 `width`/`height`。测量**只返回 未旋转(N)结果**:调用方在 90°/270° 自行交换宽高,180° 宽高不变。错误:`out` 为空 / 片段为空 / 未知码制 → `VZPL_ERR_ARG`;条码数据非法 / 未测到内容 → `VZPL_ERR_RENDER`。 --- ## 5. 测试与校验 ```powershell # 渲染内置样例(PNG + 多页 PDF) build\Release\vzpl_test.exe build\out # 渲染自己的 ZPL build\Release\vzpl_test.exe -in label.zpl -out out --pdf # 字段尺寸测量自测(覆盖需求第七节表格,断言「测量值 == 渲染扫描值」) build\Release\vzpl_test.exe --measure-test # 输出扩展接口回归(16 项:sink / 编码器注册 / 内存编码 / 真流式) python tools\verify_output_api.py --dll=build\Release\vzpl.dll # QR 矩阵导出并独立解码比对(6 个用例;先校验定位/定时图案等结构,再按 ISO/IEC 18004 # 解析格式信息/掩码/交织/载荷。结构校验是必须的——早期只解码数据,漏检过定位图案残缺) build\Release\vzpl_test.exe --qr-dump build\qrdump python tools\verify_qr.py build\qrdump # Data Matrix 导出并交 zxing 解码(10 个用例,覆盖 10x10..24x24 与各种编码分支) build\Release\vzpl_test.exe --dm-dump build\dmdump python tools\verify_datamatrix.py build\dmdump # 需要 pip install zxing-cpp # 矩阵码(Aztec / PDF417 / MicroPDF417)分两层验收,缺一不可: # 编码器层 —— 直接把导出的模块矩阵交 zxing 解码,断言格式 + 文本(22 个用例) build\Release\vzpl_test.exe --matrix-dump build\matrixdump python tools\verify_matrix.py build\matrixdump # 渲染链路层 —— ZPL → vzpl_render_png → zxing,验参数解析、贴图与拒绝策略 # (9 正例出图可解 + 10 反例必须告警,共 19 项);只跑上面那层会漏掉 # "编码器写好了但 ^B0/^B7/^BF 没接到渲染器"这种故障 python tools\verify_matrix_e2e.py --dll=build\Release\vzpl.dll # 一维码与 zint 交叉比对(Tier B 判据:逐模块零差异) python tools\zint_oracle.py --selftest # zint 参照工具自校验 32/32 python tools\verify_barcodes.py --self-test # 判据自校验(含注入坏模块必须报错)12/12 python tools\compare_matrix_oracle.py # PDF417/MicroPDF417 与 zint 布局参考比对 ``` > `compare_matrix_oracle.py` 的 `[REF]` 不是失败:PDF417 行宽 `35 + 17×(列数+1)`、 > 行列布局本就不唯一,zint 的自动布局与本项目不同属正常,**硬判据是 `verify_matrix.py` 的 zxing 解码**。 **一维码交叉验证**:本项目的一维码使用 `zxing-cpp` 独立解码验证——把本库渲染出的条码交给 zxing 解码,能读回原文才算通过。已通过:Code128 / Code39 / ITF / EAN-13 / EAN-8 / UPC-A / Code93 / Codabar / LOGMARS,以及 `^BR` 的 `b=7..10`。 例外是 `^BS` 扩展码:zxing-cpp 的读取器不把 add-on 当独立格式(**它连自己生成的 EAN-2 图 都解不出**,已实测),因此改用**与 zxing 编码器逐模块对比**作为判据——EAN-2 全部 10 个取值 (覆盖 4 种奇偶)+ EAN-5 全部 10 个校验和,共 41 个用例零差异。 --- ## 6. 已知缺陷与限制 ### 6.1 QR 定位图案残缺(已修复,保留记录) 上一版 `^BQ` 产出的 QR **任何标准扫码器都读不出**(zxing 对 6/6 全部返回失败)。 根因:定时图案的循环写成了 `for (i = 0; i < size; ++i)`,把整条第 6 行/第 6 列都按 "隔一亮一暗"重画,**覆盖了三个定位图案的右边框与下边框**(12 处模块被挖空)。 定位图案残缺后标准扫码器连码都定位不到;而本仓库自带的 `verify_qr.py` 按固定偏移读格式 信息、按之字形读数据,从不校验定位图案形状,因此一直"6/6 通过"——**编码器与自写解码器 共享了同一个错误前提,属于假通过**。 修复:定时图案只画两个定位图案之间(`i = 8 .. size-9`)。修复后 zxing **6/6 全部正确解码**, 端到端(`vzpl_render` → 图片 → zxing)同样 6/6 通过。 ### 6.2 UPC-E 校验位 7/8/9 与 zxing 存在分歧 本库采用 ISO/IEC 15420 公布的奇偶模式表(每个模式 3 个奇位;号制 1 复用同一张表并反转校验位 序号),校验位 0–6 与 zxing 完全一致;7/8/9 不一致(zxing 的校验位 8 只有 2 个奇位, 违反该表的不变量)。代码内已加不变量自校验。需用真实扫码器或 ISO 原文最终裁定。 ### 6.3 未实现的条码码制 `^B1`(Code11) `^B4`(Code49) `^B5`(Planet) `^BI`(Industrial 2of5) `^BJ`(Standard 2of5) `^BP`(Plessey) `^BB`(CODABLOCK F) `^BD`(UPS MaxiCode) `^BT`(TLC39) `^BZ`(POSTNET/Planet/IMail) —— 识别到后跳过并通过 `vzpl_last_warnings` 告警(不会静默丢弃)。 (**已实现**:`^BX` Data Matrix、`^BS`、`^BR b=7..10`、`^B0`/`^BO` Aztec、`^B7` PDF417、 `^BF` MicroPDF417。) **`^BR` 的扩展码(`b=6`)与复合码(`b=11..12`)尚未实现**(`b=1..5` 已于 0.1.6 实现)。 DataBar Expanded 除了数据字符宽度表之外,还需要 GS1 AI 解析、数值/二元压缩与 Expanded 专用的 finder 序列与权重表,计划沿用同一条已验证路径(从权威实现抽取表 → 实现 → 逐模块/解码验收)。 在此之前这两类会明确告警,不会产出可疑条码。 **验证源与分层验收(本项目的硬约束)**:任何码制都必须交**外部独立实现**判定,自己写解码器验 自己的编码器会重演 QR 那种假通过(见 6.1)。按外部参考的能力分三层: - **Tier A — zxing-cpp 可解**:Aztec / PDF417 / MicroPDF417 / DataMatrix / QR, 以及 `^BR` 的 DataBar 系列。判据 = **zxing 解码 + `BarcodeFormat` 断言 + 文本比对**,三者缺一不可。 - **Tier B — zint 逐模块比对**:Code11 / Code49 / Planet / MSI / Plessey / CODABLOCK F / Industrial·Standard 2of5 / POSTNET / MaxiCode / DataBar Expanded。zxing 既不编码也不解码这些, 改用本机自建的 zint(见 `tools/zint_oracle.py`)作权威参照,**逐模块零差异**才算通过。 - **Tier C — 无任何外部参考**:TLC39(zxing / zint / bwip-js 三者皆无),只能分段验收; 分段也过不了就按"宁可告警也不产出错码"保持告警。 > 注意 zxing-cpp 对 **MaxiCode 解不了**(曾误判为可解,已实测推翻),故 `^BD` 归 Tier B; > 而 **CODABLOCK F 会被 zxing 误报成 Code 128**(假阳性),同样不能用 zxing 当判据。 **Data Matrix(`^BX`)已实现并验证**(ECC 200,zxing 解码 10/10)。当前范围与限制: - 编码只用 **ASCII 码字集**(任意字节输入都合法,只是比 C40/Text/Base256 体积略大); 数字成对压缩正常,`>= 0x80` 用 235 上位移位 - 符号限 **单数据区**正方符号 **10x10 .. 26x26**(最多 44 个数据码字);更大的符号含多个 数据区与对齐图案,其映射表尚未实现,超出会**明确报错**而非产出可疑符号 - 不支持矩形符号(`^BX` 的 `a=2`)与非 200 的规格级别(`s=0/50/80/100/140`),均会告警 - 多块符号(本范围内均为单块)的交织逻辑与更大型号共用一套代码,已按标准实现 **PDF417 / MicroPDF417 / Aztec(`^B7` / `^BF` / `^B0`、`^BO`)已实现并验证**(0.1.7): PDF417 与 MicroPDF417 的 3×929 码字图案表、Aztec 的 16 张常数表均由 `tools/gen_*_tables.py` **机械抽取自 libzint**(非手抄,表头保留来源与许可),可重新生成审计。 限制:`^B0` 不支持 ECIC(`c=Y`)、菜单符号(`e=Y`)、结构化追加(`f`、`g`); `^B7` 不支持序列化,`c×r >= 928` 或 `^FD` 超 3K 字符时按手册**不打印**; `^BF` 的行列组合与数据量上限按手册 Table 5 校验。上述越界均**明确告警并跳过**。 ### 6.4 字体与编码 - 内置 5x7 点阵近似 Zebra A–H 位图字体;**中文/UTF-8 多字节字符渲染为占位方框** - `^CI` 仅记录不生效;`^CW` 与 `^A@` 的字体名被忽略(无字体光栅器) ### 6.5 其它 - PNG 默认仍是 1bpp + stored deflate(无压缩);需要小体积请用 `VZPL_ENC_PNG8` - `^GF` 的 `C` 为 Zebra 私有压缩,算法未公开,无法实现 - `^GFB` 的二进制数据若含 `0x00`:老接口(`const char*`)仍会被 NUL 截断,请改用 `_n` 系列 (`vzpl_render_n` / `vzpl_render_stream_n` / `vzpl_render_to_sink_n` / `vzpl_render_pdf_to_sink_n`) 传真实长度;`vzpl_render_file` 内部已改为按文件实际长度渲染,不再截断 - 默认画布为 `dpmm*4 × dpmm*6` 点(注释写"4×6 英寸"但实为 4×6 mm), 建议始终显式写 `^PW`/`^LL` - 虚拟打印机 TCP 服务已实现(见 4.5),但只支持 IPv4 环回监听;如需对外网卡监听需再放开 `INADDR_LOOPBACK`(出于安全默认只监听本机) --- ## 7. 变更要点(0.1.7) 1. **新增 `^B0`/`^BO` Aztec、`^B7` PDF417、`^BF` MicroPDF417 三种二维矩阵码**(此前 §6.3 列为"未实现会告警"): - **Aztec**:紧凑 1..4 层 / 全范围 1..32 层 / Aztec Rune;`d` 参数按手册映射 (`0` 默认、`01..99` 最小纠错百分比、`101..104` 紧凑层、`201..232` 全范围层、`300` Rune); 覆盖 upper/lower/mixed/punct/digit/byte 五模式切换、位填充、模式消息、GF(2^4/2^6/2^8/2^10/2^12) Reed-Solomon、层数据布局与 spiral 参考格放置。 - **PDF417**:安全级 0..8、列 1..30、行 3..90、`t=Y` 截断;按手册拒绝 `c×r >= 928`、`^FD` 超 3K 字符(不打印符号)。 - **MicroPDF417**:`m` 模式 0..33,行列组合与数据量按手册 Table 5 校验。 - **渲染接线**:`^B0`/`^BO`/`^B7`/`^BF` 此前**只有编码器、零调用方**,写 ZPL 仍会告警 "暂不支持条码类型"(矩阵 dump 测试全绿也暴露不了)。本次接上 `drawBarcode2DField` 分发, 并让 `^BO` 进入二维码识别白名单(此前它连"暂不支持条码类型"都走不到)。 - **非方形模块贴图**:PDF417/MicroPDF417 的横向模块宽取 `^BY` 的 `w`、纵向行高取 `^B7`/`^BF` 的 `h`(前者是每行高度、后者是总高度),`blitBarcodeMatrix` 增加 `moduleSizeY` 参数; `^BQ`/`^BX` 走默认值,产物字节不变。 - **不支持的参数一律告警跳过**:`^B0` 的 ECIC(`c=Y`)、菜单符号(`e=Y`)、结构化追加(`f`、`g`) 本项目不实现,静默用默认值出图会画出与请求不符的符号。 2. **表来源与合规**:新增 `src/barcode/Pdf417Tables.inc`(3×929 码字图案表)与 `src/barcode/AztecTables.inc`(16 张常数表),均**机械抽取自 libzint**(BSD-3-Clause), 非手抄,文件头保留来源路径与许可;配套 `tools/gen_pdf417_tables.py`、`tools/gen_aztec_tables.py` 保证可重新生成审计。 3. **新增两层验收**(`verify_matrix.py` 22 项验编码器、`verify_matrix_e2e.py` 19 项验渲染链路) 与 Tier B 交叉比对工具(`zint_oracle.py --selftest` 32/32、`verify_barcodes.py --self-test` 12/12、 `compare_matrix_oracle.py`)。第 5 节与 6.3 的分层验收判据已同步改写。 4. **公共基建**:抽出 `blitBarcodeRows`(多行、每行高度可不同,供 `^BR` 堆积 DataBar 与后续 `^B4`/`^BB`/`^BZ` 共用)与 `src/barcode/LinearMatrix.{h,cpp}`(多行线性码类型校验)。 5. **自查修复**:`^BD` 曾被误标为 CODABLOCK(ZPL 中并无 `^Codablock` 指令,`^BD` 是 UPS MaxiCode), 6 处同源错误(枚举、switch、测试用例、文档注释)已全部更正。 6. **判据纠错**:曾把 `zint_oracle.py --selftest` 中**特定输入**的参考几何(PDF417 10x103、 MicroPDF417 11x55)误当成所有用例的期望值,导致 4 个用例因"行列布局与 zint 不同"被判失败; 实测 zxing 10/10 全部可解。现已明确:**Tier A 硬判据是 zxing 解码 + 格式断言**, zint 逐模块比对降级为参考(`[REF]` 非 `[FAIL]`)。 ## 7.0 变更要点(0.1.6) 1. **新增 `^BR` 的 GS1 DataBar 本体与受限码(`b=1..5`)**:全向(b=1) / 截断(b=2) / 堆积(b=3) / 堆积全向(b=4) / 受限 DataBar(b=5)。实现要点:13 位数按 4537077 拆左右两对、每对再按 1597 拆两个数据字符,数据字符宽度用 ISO/IEC 24724:2011 Annex B 的**组合算法生成(非查表)**; 校验字符为宽度加权和取模 79(≥8、≥72 各跳一格)后拆成左右校验字符;Limited 取模 89 查 Annex C 图案表;堆积分隔图案按 5.3.2.1(行高 5:7)与 5.3.2.2(上下分隔)。数据支持 `(01)` 前缀、少于 13 位左补 0、给定 14 位时校验并忽略校验位。 2. **验证**:DataBar-14 与 Limited 分别与 `zxing-cpp` **逐模块比对零差异**(4+2 个 GTIN); 四种类型(全向/堆积/堆积全向/受限)都能被 zxing 正确解码;`b=6` 与 `b=11/12` 明确告警。 3. 表来源与合规:`src/barcode/DataBarTables.inc` 的常数表**机械抽取**自 libzint (BSD-3-Clause),非手抄,文件头保留原版权与许可声明(见第 8 节)。 4. **自查修复**:`DataBar Limited` 的内部长度自校验最初写错常数(先按 96、再按 74), 导致该码制被整体拒绝而画不出来;现取实测且与 zxing 产物一致的值 79 模块 (起 2 + 左对 26 + 校验图案 18 + 右对 26 + 止 7),消息中同时给出实测值便于排障。 ## 7.1 变更要点(0.1.5) 1. **新增 `^BS` UPC/EAN 2 位 / 5 位扩展码**(此前未实现):按手册实现 `^BSo,h,f,g`,数据按 "不足左补 0 / 超出截短到 2 或 5 位"规整,解释行支持上方(`g`)与下方(`f`)。 EAN-2 的奇偶由数值 mod 4 决定、EAN-5 的奇偶由校验和 `(3*(d0+d2+d4)+9*(d1+d3)) mod 10` 决定; 两张表与结构均从 zxing 编码器反推核对后实现,**41 个用例逐模块零差异**。 2. **新增 `^BR` GS1 DataBar**:按手册实现 `^BRa,b,c,d,e,f`,其中 `b=7..10` (UPC-A / UPC-E / EAN-13 / EAN-8)复用既有已验证的 EAN/UPC 编码器,已可解码; `b=1..6`(DataBar 本体)与 `b=11..12`(UCC/EAN-128 复合码)尚未实现,明确告警并跳过。 3. **修复一维码渲染未使用实际模块宽**:渲染时直接取 `^BY` 的模块宽,导致 `^BR` 的放大系数 `c` 只影响编码器内部判断、不影响出图宽度。现改用编码器实际采用的模块宽(其余码制的 模块宽仍等于 `^BY`,产物字节不变,已回归确认)。 4. 验证 17/17:EAN-2/EAN-5 逐模块比对、数据规整、`^BR` 四个类型解码、未实现类型告警、 放大系数生效;回归 QR 6/6、Data Matrix 10/10、输出接口 16/16、样例条码 5/5。 ## 7.2 变更要点(0.1.4) 1. **实现虚拟打印机 TCP 服务**(此前只是 `VZPL_BUILD_NET` 预留,且引用了并不存在的源文件): 新增 `include/vzpl/vzpl_net.h` 的 10 个 `vzpl_net_*` 接口、`src/net/TcpPrintServer.cpp` 实现、 导出层 `src/api/vzpl_net_api.cpp`,并在开启选项时产出可独立运行的 `vzpl_printer.exe`。 语义遵循 Zebra 9100 裸端口(一条连接 = 一个作业,关闭连接即结束)。 2. **修正构建系统的一个潜在缺陷**:对象库 `vzpl_objects` 原先不带 `VZPL_STATIC`/`VZPL_EXPORTS` 编译,公共符号被当成 `__declspec(dllimport)`。内部模块一旦调用公共接口(虚拟打印机即如此), 静态库链接就会因 `__imp_*` 未解析而失败。现统一让对象库"不装饰"。 3. 端到端验证 19/19:监听/落盘(PNG+多页 PDF)/多标签作业/含 `0x00` 的 `^GFB` 作业/分片发送/ 空连接/回调/系统分配端口/重复 start 被拒/重复 stop 安全/端口复用/超限作业防护。 ## 7.3 变更要点(0.1.3) 1. **新增 `^BX` Data Matrix(ECC 200)真编码**,并经 zxing 独立解码验证 **10/10**; 内置样例加入 Data Matrix 后,`barcode.png` 的 **5 个条码全部可解** (Code128 / Data Matrix / QR Code / EAN-13 / UPC-A)。 2. 定位了两个关键实现点(都曾导致符号不可解码): - 置放算法必须作用在**数据区网格**(边长 = 符号边长 − 2)上,固定图案另行叠加; - **占用状态必须与位值分离**——写成"亮位"的模块若被判为未占用,遍历会重复置放, 后续位索引整体错位。另需按标准处理 `符号边长 % 4 == 0` 时右下角 **2x2 固定深色块**。 3. 新增 `--dm-dump` 与 `tools/verify_datamatrix.py`(zxing 独立解码,10 个用例)。 4. ⚠ `barcode.png` / `labels.pdf` 产物随样例改动而变化。 ## 7.4 变更要点(0.1.2) 1. **修正内置样例的标签高度**:`barcode.png` 的样例把纠错等级 H、倍率 5 的 QR 放在 `^LL406` 的标签里,符号被底边截断导致扫不出来。改为 `^LL520` 后,用 zxing 扫该图 **4 个条码全部可解**(Code128 / QR Code / EAN-13 / UPC-A),此前只有 3 个。 2. 二维码绘制路径抽出共用函数 `blitBarcodeMatrix`(供后续 PDF417/Data Matrix/Aztec 复用), 行为不变(回归:QR 矩阵 6/6、样例条码解码结果不变)。 3. `^BX` 的一版实现因未通过独立验证已回退,详情与后续路径见 6.3。 ## 7.5 变更要点(0.1.1 相对上一版) 1. **修复 `^BQ` 定位图案残缺**(定时图案覆盖定位图案边框,见 6.1):上一版产出的 QR **任何标准扫码器都读不出**,修复后 zxing 6/6 正确解码。 2. **新增接口**:`vzpl_render_mono`(一步取 1bit 点阵)、`vzpl_render_n`、 `vzpl_render_stream_n`、`vzpl_render_to_sink_n`、`vzpl_render_pdf_to_sink_n` (显式长度,含 NUL 的二进制 ZPL 不再被截断);`vzpl_render_file` 同步改为按实际长度渲染。 既有接口签名与行为均未改变。 3. ⚠ **QR 产物字节会变化**:定位图案边框补全后,`^BQ` 的输出与上一版不同(上一版不可用)。 ## 7.6 变更要点(0.1.0 相对更早版本) 1. **修复 EAN-13 / UPC-A 校验位算错**(权重方向写反):旧版对约 4/5 的数据会生成 **扫不出来的条码**。实测同一 ZPL 旧二进制 0 个条码可解,新版全部正确解出。 ⚠ **该修复会改变 `^BE`/`^BU`/`^B8` 的既有输出字节**。 2. **修复 PDF 乱码**:RunLengthDecode 的两个分支写反,导致所有合规阅读器都显示为乱码。 ⚠ **该修复会改变 PDF 产物字节**。 3. 新增 4 种一维码真编码:Code93、Codabar、UPC-E、LOGMARS(均经 zxing 交叉验证)。 4. 新增版面/字段/时钟/文本块指令,共 20 余条(见第 1 节)。 5. 新增输出扩展:sink、PNG8、MONO_RAW、`vzpl_encode`、编码器注册、真流式。 6. 新增指令钩子:新增/覆盖/过滤指令。 7. 未支持指令不再静默丢弃,改为写入 `vzpl_last_warnings`。 8. 引入 `stb_image_write.h`(public domain),仅用于 PNG8 压缩。 --- ## 8. 许可与合规说明 - 除 `third_party/stb/stb_image_write.h`(public domain)与 `src/barcode/DataBarTables.inc`、 `src/barcode/Pdf417Tables.inc`、`src/barcode/AztecTables.inc` 外,本库为独立实现,不含第三方源码。 - `src/barcode/DataBarTables.inc`、`Pdf417Tables.inc`、`AztecTables.inc` 的常数表**机械抽取**自 [libzint](https://github.com/zint/zint)(BSD-3-Clause 风格许可),已按许可要求在各文件头保留 原作者版权与来源说明;生成脚本 `tools/gen_*_tables.py` 可随时重新生成核对。 算法实现(`DataBar.cpp`、`MatrixBarcode.cpp`)为独立编写,其中的分支与上游逐条对应,便于核对。 Zebra 官方字体文件未被打包(内置点阵字形为自绘近似)。 - 输出位图使用 `1bpp`(bit=1 表示白/不打印),与打印机"墨点"语义一致。 - 若在商业产品中使用,请自行确认 ZPL、Zebra 相关商标与字体的使用条款。