快递面单、仓库库位标签、商品条码——物流场景里的标签,绝大多数出自同一类硬件:Zebra(斑马)标签打印机,以及大量兼容或模拟它的机型。这类机器说的语言叫 ZPL(Zebra Programming Language)。
和小票机的 ESC/POS 一样,ZPL 不是图片格式,而是一种紧凑的文本语言:你描述「什么内容放在什么位置」,打印机全速渲染。一张 4×6 英寸的面单用 ZPL 描述约 500 字节;同样的内容渲染成位图是几百 KB,打得还慢。
心智模型:点阵画布
每张标签都写在 ^XA(开始)和 ^XZ(结束)之间,坐标单位是点(dot),而点取决于打印头的 DPI:
- 203 dpi(最常见)→ 8 点/毫米 → 4×6 英寸标签是 812 × 1218 点
- 300 dpi → 12 点/毫米 → 同一张标签是 1218 × 1824 点
这很重要:按 203 dpi 写的 ZPL 拿到 300 dpi 机器上会缩成三分之二大小。动手前先确定目标 DPI。
一张真实的面单
^XA
^PW812
^LL1218
^CI28
^CF0,45
^FO40,40^FDPrintBase 物流^FS
^FO40,100^GB732,3,3^FS
^CF0,30
^FO40,140^FD收件人:^FS
^CF0,38
^FO40,185^FD王小明^FS
^FO40,235^FD市场街 1 号^FS
^FO40,285^FD上海市 200001^FS
^FO40,420^BY3
^BCN,160,Y,N,N
^FDSF1234567890123^FS
^FO560,140^BQN,2,7
^FDQA,https://example.com/track/SF123^FS
^XZ高频指令逐个看:
| 指令 | 作用 |
|---|---|
^PW / ^LL | 打印宽度 / 标签长度(单位:点) |
^FO x,y | 定位下一个元素的原点 |
^CF0,45 | 默认字体与字号(0 号是矢量字体) |
^FD ... ^FS | 字段内容与结束符 |
^GB w,h,t | 画框/线——这里是一条横线 |
^BCN,160,Y,N,N | Code 128 条码,高 160 点,带可读数字 |
^BQN,2,7 | 二维码,放大系数 7(QA, 前缀设置纠错与自动模式) |
^CI28 | 切换 UTF-8 编码。注意:中文还要求打印机装有中文字库(字库卡或 Flash 字体),没有字库的机器发中文会打不出来 |
不浪费标签纸的预览方法
ZPL 开发最有用的一个技巧:Labelary 在线预览(labelary.com)能把 ZPL 渲染成 PNG,还提供免费 API——POST 你的 ZPL,返回图片。把它接进测试流程,条码错位在 CI 里就能发现,而不是浪费一卷标签纸之后。
把 ZPL 送进打印机
直连 TCP。 Zebra 机器监听 9100 端口,netcat 或 Node 的 socket 都能直接写入。很好用——直到打印机在仓库 NAT 后面、后端在云上。
Windows 驱动。 驱动会把内容渲染成位图,ZPL 的所有优点——速度、精确模块宽度的清晰条码、极小的载荷——全部丢失。驱动适合打办公文档,不适合标签流水线。
云端 RAW 直通。 打印机旁的电脑跑一个轻量 Agent,向云端保持出站连接;后端把 ZPL 通过 HTTPS API 发出,Agent 逐字节写进打印机。ZPL 是纯 ASCII 文本,用 PrintBase 就是一次调用:
const zpl = buildLabel(order); // 上面的模板填入订单数据
const res = await fetch('https://api.printbase.cloud/v1/print-jobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PRINTBASE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
printer_code: 1000000, // 仓库那台 Zebra 的设备码
content_type: 'raw',
content: Buffer.from(zpl).toString('base64'),
}),
});
const job = await res.json();
// { id: "job_abc", status: "queued" }WMS 创建发货单后调这一下,几秒后标签就在仓库里出纸——后端在哪个云、仓库在哪个城市都无所谓。状态沿 queued → dispatched → printing → completed 回传,失败带原因码(PRINTER_OFFLINE、PRINT_ERROR 等),配合 Webhook 可以在面单打印失败的瞬间收到通知,避免订单没贴单就发出。
说句实在话:国内快递平台(菜鸟、拼多多、抖音电商等)的电子面单很多直接给成品 PDF,这种情况别用 ZPL 重画——把 PDF 原样以 content_type: "pdf" 发到同一个接口就行。ZPL 的价值在于标签由你自己生成的场景:库位标签、商品条码、自定义吊牌。
实战坑清单
- 整体偏小(或偏大):DPI 不匹配。确认打印头 DPI,按比例换算点坐标。
- 条码糊、扫不出:永远别把条码渲染成图片,用
^BC/^BQ让打印机按精确模块宽度画。上线前用手机扫一遍验证。 - 打着打着偏位:跑一次打印机的介质校准(media calibration),让它重新学习标签间隙。
- 中文打不出:先
^CI28,再确认机器装了中文字库;没有字库的机型考虑用 PDF 方案。
