Yu Wu Hsien - Profile Picture
YU WU HSIEN

Just simple folk, with HTML, trying to make a living.

自建物流標籤列印系統:從 Loftware 到 GoDex 的轉移之路

公司的物流部門一直使用 Loftware 的 IoT 標籤列印系統來處理出貨標籤。這套系統功能完整、穩定可靠,但有個致命的問題 — 太貴了

授權費加上標籤機的租賃費用,每年是一筆不小的開支。營運部門開始思考:既然我們有自己的 ERP 系統,能不能自己開發標籤列印功能,搭配相對便宜的標籤機來處理?

於是,這個任務落到了我頭上。

需求與選型

營運部選擇了 GoDex G530+ 作為標籤機。這是一台桌上型熱感應標籤機,價格親民,支援 100mm 寬的標籤紙。我們的物流標籤規格是 100mm x 120mm,分為上方 80mm 的主標籤和下方 40mm 的客戶簽收聯,中間有撕開線。

技術方案上,我決定採用 Web 列印 的方式。相較於直接發送 EZPL 指令到印表機,Web 列印的優勢在於:

  • 使用 HTML/CSS 排版,開發效率高
  • 不需要額外安裝列印軟體
  • 可以在瀏覽器預覽列印結果
  • 跨平台相容性較好

聽起來很美好,但現實總是比想像中複雜。

驅動程式的坑

第一個問題出現在安裝印表機時。我在 M4 晶片的 MacBook Pro 上連接 GoDex G530+,macOS 可以偵測到印表機,但在選擇驅動程式時,找不到 GoDex 的選項。

我從 GoDex 官網下載了 macOS 驅動並安裝,但新增印表機時依然看不到 GoDex 驅動。無奈之下,我選擇了看起來最相關的 Zebra ZPL Label Printer

印表機確實可以印東西了,但問題接踵而來:

  • 內容無法滿版,總是有很大的邊距
  • 紙張尺寸選項跟實際標籤不符
  • 列印方向似乎不對,需要旋轉 180 度

我花了大量時間調整 CSS,嘗試各種 @page 設定、transform: rotate(180deg)、不同的紙張尺寸設定。有時候內容會完全空白,有時候會跑版,有時候會跨越標籤邊界。

問題的根源

在反覆除錯的過程中,我開始懷疑問題不在 CSS,而在驅動程式本身。ZPL 是 Zebra 印表機的語言,而 GoDex 用的是 EZPL。雖然有部分相容,但紙張尺寸定義、列印方向、邊距計算都可能不同。

用終端機檢查系統狀態:

bash
lpstat -p -d
# lpstat: 沒有加入目標。沒有系統預設目標

system_profiler SPPrintersDataType
# Status: The printers list is empty.

奇怪的是,印表機明明可以印,但系統卻說沒有印表機。這讓我意識到印表機可能沒有正確註冊到 CUPS 系統。

找到真正的問題

檢查 GoDex 驅動的安裝位置:

bash
ls /Library/Printers/GoDEX/PPDs/
# godex-g530.ppd  godex-g530+.ppd  ...(一堆 PPD 檔案)

ls /Library/Printers/PPDs/Contents/Resources/ | grep -i godex
# (空的)

問題找到了!GoDex 的 PPD 檔案被安裝到 /Library/Printers/GoDEX/PPDs/,但 macOS 只會讀取 /Library/Printers/PPDs/Contents/Resources/ 裡的 PPD 檔案。

PPD(PostScript Printer Description) 是印表機的描述檔,定義了紙張尺寸、解析度、功能選項等參數。錯誤的 PPD 就像給印表機看了一份別人的說明書,自然無法正確運作。

解決方案

建立符號連結,讓 macOS 找到正確的 PPD:

bash
sudo ln -s /Library/Printers/GoDEX/PPDs/godex-g530.ppd \
    /Library/Printers/PPDs/Contents/Resources/godex-g530.ppd

sudo ln -s /Library/Printers/GoDEX/PPDs/godex-g530+.ppd \
    /Library/Printers/PPDs/Contents/Resources/godex-g530+.ppd

重新新增印表機,這次在「選取軟體」中搜尋 godex,終於看到了 GODEX G530+, 1.1.14

選擇正確的驅動後,之前的問題一掃而空:

  • ✅ 紙張尺寸正確
  • ✅ 不需要手動旋轉
  • ✅ 邊距合理

CSS 列印控制

驅動問題解決後,接下來是排版。物流標籤需要精確控制尺寸,讓 80mm 的主標籤和 40mm 的簽收聯正好對齊撕開線。

@page vs body

CSS 列印有兩個重要的概念需要區分:

css
@page {
    margin: 2mm;    /* 控制紙張邊緣的留白 */
}

body {
    margin: 0;      /* 控制內容與列印區域的距離 */
}

簡單說,@page 控制「紙」,body 控制「內容」。對於標籤列印,@page margin 決定了印表機實際可列印的區域。

固定高度分割

為了精確控制 80mm/40mm 的分割,我使用固定高度的 div 包裹:

css
.label-page {
    width: 100%;
    height: 116mm;  /* 120mm - 4mm 邊距 */
    page-break-after: always;
    page-break-inside: avoid;
}

.main-label {
    height: 78mm;   /* 80mm - 2mm 邊距 */
    overflow: hidden;
}

.receipt-label {
    height: 38mm;   /* 40mm - 2mm 邊距 */
    overflow: hidden;
}

page-break-after: always 確保每張標籤獨立一頁,page-break-inside: avoid 防止內容被分割到不同頁。

條碼與 QR Code

標籤上需要印條碼(給掃描槍讀取)和 QR Code(給司機 App 掃描)。我使用 milon/barcode 這個 Laravel 套件來產生。

條碼:PNG vs SVG

最初使用 PNG 格式:

php
DNS1D::getBarcodePNG($delivery->number, 'C128', 1.5, 33)

列印出來的條碼掃描器讀不到。原因是條紋太細,加上 PNG 在縮放時會模糊。

改用 SVG 後問題解決:

php
DNS1D::getBarcodeSVG($delivery->number, 'C128', 1.5, 28, 'black', false)

SVG 是向量圖,縮放不會失真,條紋邊緣銳利,掃描器可以正確讀取。

QR Code:還是用 PNG

有趣的是,QR Code 用 SVG 反而出問題。因為 SVG 的 QR Code 大小是根據內容長度動態產生的,很難用 CSS 精確控制尺寸,容易超出容器或被截斷。

最終的選擇:

類型格式原因
條碼(1D)SVG條紋寬度固定,SVG 清晰
QR Code(2D)PNG用 CSS 控制尺寸較穩定
php
// 條碼
{!! DNS1D::getBarcodeSVG($number, 'C128', 1.5, 28, 'black', false) !!}

// QR Code
<img src="data:image/png;base64,{{ DNS2D::getBarcodePNG($url, 'QRCODE', 4, 4) }}"
     style="width: 12mm; height: 12mm;">

成果與心得

經過一番折騰,標籤列印系統終於上線運作。相較於 Loftware 的年費,一台 GoDex G530+ 的價格大概只要兩三個月的授權費。更重要的是,我們現在完全掌控了標籤的設計與列印邏輯,未來要調整格式或新增欄位都很容易。