Technical Article

在 Delphi 中使用 PDF 產生條碼:QR、PDF417、DataMatrix

出貨標籤或發票上的條碼只有一項工作,那就是在第一次掃描時就被讀取;它能否順利通過掃描,早在包裹到達碼頭之前就已經決定了;這取決於該符號是如何被放置在頁面上的;Delphi 報表管線中最常見的錯誤是在其他地方將條碼轉譯為點陣圖,並將該影像放入 PDF 中;它在螢幕上的某個縮放比例下看起來很好,然後在其他所有地方都會降級

替代方案是將符號作為向量內容直接繪製到頁面中;PDFlibPas 為此提供了繪圖呼叫系列,涵蓋了 2D 矩陣符號 QR、PDF417 和 DataMatrix,以及透過 Code128 和 GS1-128 的線性系列,還有用於郵政自動化的 USPS Intelligent Mail;向量的論點並非基於美學;它關係到條紋是否落在掃描器預期的位置

為什麼向量優於放置的點陣圖

條碼是條紋和空白的圖案,在二維中則是深色和淺色模組的網格;解碼器透過測量這些寬度的比例來工作;任何扭曲比例的因素都是會消耗符號誤差預算的雜訊;點陣化的條碼影像帶有固定像素;當 PDF 被轉譯到其列印點無法均勻分割影像網格的印表機時,點陣化器必須重新取樣,且原本應該清晰的模組邊緣會分佈在兩個裝置像素上;窄條紋可能會變肥,相鄰的空白可能會變窄,且解碼器所依賴的寬度比例會發生漂移

繪製為向量內容時,相同的符號是在 PDF 使用者空間座標中描述的一組填滿矩形;沒有固定的像素網格需要對抗;在列印時,裝置會以其自身的解析度轉譯每個矩形,因此在任何比例和列印大小下,每個模組邊緣都與硬體所允許的一樣清晰;將向量符號放大以用於棧板標籤,或將其縮小以用於包裹,其幾何結構都會保持精確;這種精確度是保持首次掃描讀取率高的關鍵,這正是在頁面上放置條碼的全部意義

QR 碼與四個更正等級

QR 是在兩個軸上同時讀取的 2D 矩陣符號,這就是為什麼它將大量資料打包到一個小正方形中;它的損壞容忍度來自 Reed-Solomon 錯誤更正(提供四個等級);Level L 大約復原 7% 的碼字,M 大約為 15%,Q 大約為 25%,而 H 大約為 30%;更高的更正等級並非免費的;復原碼字會佔用模組容量,因此對於固定數量的資料,更高的等級會迫使符號變得更密集或物理上更大

這種權衡是關於符號將存在的環境問題;只會從螢幕掃描的乾淨數位檔案可以保持在 L 等級並保持精簡;將會被列印、搬運、磨損且可能部分被膠帶覆蓋的標籤需要 Q 或 H,因為額外的冗餘是讓解碼器從不再完好的符號中重建酬載(Payload)的關鍵;DrawQRCode 接受位置和固定繪製寬度與高度的 SymbolSize,加上選擇資料模式的 EncodeOptions 值(0 代表自動,或是數值、英數字、ISO-8859-1 和 UTF-8 變體),以及用於方向的 DrawOptions

var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.NewDocument;
    Pdf.SetPageSize('A4');
    Pdf.SetMeasurementUnits(1);   // 1 = millimetres
    Pdf.NewPage;

    // 30 mm square QR, automatic encoding, normal orientation
    Pdf.DrawQRCode(20, 20, 30, 'https://www.loslab.com/', 0, 0);

    Pdf.SaveToFile('Label_QR.pdf');
  finally
    Pdf.Free;
  end;
end;

更正等級本身由編碼器選擇,以使資料符合您所要求的符號;如果您在惡劣環境中需要有保障的高等級,請慷慨地調整符號大小,以便編碼器有足夠的模組預算用於冗餘,而不是被迫縮小以符合空間

用於身分識別和出貨標籤的 PDF417

PDF417 是一個堆疊的線性符號;每一列都是一個簡短的線性條碼,且各列堆疊形成一個區塊,這就是為什麼它會出現在駕照、登機證和承運商出貨標籤上的原因(在這些地方,較寬的資料條必須放在矩形版面中);它的錯誤更正以 0 到 8 的等級運作;每一步大約會使更正碼字的數量翻倍,因此 5 級攜帶的冗餘遠多於 1 級,代價是頁面上會有更多碼字

PDF417 區塊的形狀是可調整的,這非常重要,因為標籤有固定的填入區域;DrawPDF417SymbolEx 公開了基本呼叫所沒有的控制項;FixedColumnsFixedRows 固定資料欄位計數和列計數,其中 0 表示讓編碼器決定;ErrorLevel 接受自動的 -1 或顯式的 0 到 8;ModuleSize 是目前測量單位中最窄元素的寬度,而 HeightWidthRatio 設定每個模組相對於其寬度的高度,這就是您製作矮寬或高窄區塊以符合現有空間的方式

// Fixed 10 data columns, automatic rows, error level 5,
// module 0.30 mm wide, rows three times the module width tall
Pdf.DrawPDF417SymbolEx(20, 60, 'PDF417 PAYLOAD 0123456789',
  0,        // Options: 0 = normal orientation
  10,       // FixedColumns
  0,        // FixedRows: 0 = automatic
  5,        // ErrorLevel: 0 to 8
  0.30,     // ModuleSize, in the current measurement unit
  3.0);     // HeightWidthRatio

固定欄位是標籤範本上的常用手段;恆定的欄位計數使區塊具有可預測的寬度,因此當編碼的酬載長度在不同檔案之間發生變化時,周圍的版面不會偏移,同時編碼器會向下增加列以吸收差異

用於微小標記的 DataMatrix

當標記必須很小時,DataMatrix 是首選的符號;它是一個使用 ECC 200(現代 Reed-Solomon 配置)的緊湊 2D 網格,且在相同資料的 QR 符號會顯得尷尬的大小下,它仍然保持可讀;這使其成為直接零件標記、微小電子元件以及密集物流標籤的標準選擇

DrawDataMatrixSymbol 接受用於點距的 ModuleSize、用於 ASCII 的 Encoding 值 1,以及自動為 0 或標準正方形和矩形尺寸之一的 SymbolSize(從 10x10 直到 132x132);Options 參數結合了方向與安靜區(Quiet zone)寬度,其中加入 100 到 400 會設定 1 到 4 個模組的白色邊界;安靜區並非裝飾;解碼器需要該清晰的邊緣來尋找符號的尋找圖案,而緊貼著其他油墨的符號則是無法取得的符號

// Auto-sized ASCII DataMatrix, 0.5 mm module, normal orientation
// with a one-module quiet zone (Options 0 + 100)
Pdf.DrawDataMatrixSymbol(20, 110, 0.5, 'DMX-SN-4408812',
  1,        // Encoding: 1 = ASCII
  0,        // SymbolSize: 0 = automatic
  100);     // Options: normal + one-module quiet zone

1D 條碼仍然佔據主導地位的領域

二維符號受到了關注,但線性條碼仍然佔據零售和物流的大部分領域,原因在於讀取單次掃描的雷射掃描器的安裝基數;Code128 是英數字資料的主力,其效率來自三個字元集;字元集 A 涵蓋控制字元和大寫,字元集 B 涵蓋完整的可列印 ASCII 範圍,而字元集 C 是對數字重要的字元集;子集 C 在單個符號字元中編碼一對數字,因此連續的數字資料所佔用的符號字元是其在字元集 A 或 B 中所需的一半;這是鋪設長數字條碼最緊湊的方式,且 PDFlibPas Code128 實現會自動結合 B 和 C 字元集以達到此目的

GS1-128(舊稱 EAN-128 的標準)建置在 Code128 之上,它攜帶應用識別碼(Application Identifier,即括號前綴,告訴接收系統後續數字是序號、批號還是有效期限);該結構由 FNC1 標記(FNC1 是一個特殊的非資料字元,它將符號標記為 GS1 編碼並分隔可變長度欄位);在 PDFlibPas 中,您使用 DrawBarcode 繪製 GS1-128 符號,該方法使用 Code128 型態以及放置在資料字串中每個應用識別碼開始處的字面量 [FNC1] 標記

var
  W: Double;
begin
  // Code128, with FNC1 markers this becomes a GS1-128 symbol.
  // AI 21 (serial) = ABC123, AI 20 (variant) = 13
  Pdf.DrawBarcode(20, 150, 60, 18, '[FNC1]21ABC123[FNC1]2013',
    3,        // Barcode: 3 = Code128
    0);       // Options: 0 = default drawing

  // Measure the rendered width for a 0.30 mm narrow bar before laying out
  W := Pdf.GetBarcodeWidth(0.30, '[FNC1]21ABC123[FNC1]2013', 3);
end;

對於郵件,USPS Intelligent Mail(也稱為 OneCode)在單個高度調變條碼中編碼路由和追蹤資料,以用於郵政自動化;DrawIntelligentMailBarcode 獲取條紋寬度、完整條紋高度、追蹤器高度和間距寬度的顯式幾何結構,資料以 20、25、29 或 31 位僅數字的字串形式提供;顯式條紋和追蹤器高度之所以存在,是因為該符號攜帶的資訊取決於每個條紋是完整條紋、上伸條紋還是下伸條紋,且郵政讀取器依賴於保持在規範內的這些高度

繪製到頁面中並測量以進行配置

此處顯示的每個呼叫都會繪製到目前選定頁面的內容中(即接收您的文字和影像的相同表面),因此條碼是作為一般檔案產生的一部分產生的,而不是作為單獨的資產匯入;因為符號是向量內容,它們編碼的資料和它們佔用的幾何結構在繪製時都是已知的,這使您可以確定地放置它們

線性系列的配置受益於先進行測量;GetBarcodeWidth 傳回指定窄條紋寬度和條碼型態的條碼總繪製寬度,因此您可以在進行繪製之前保留確切的水平空間,而不是在建置頁面後進行猜測並發現重疊;2D 符號更容易放置,因為您直接透過 SymbolSizeModuleSize 設定其繪製大小,且符號會填入該版面配置;無論哪種方式,規範都是相同的:根據掃描環境決定物理大小、確認符號符合您擁有的插槽,並讓向量幾何結構保持每個邊緣清晰(從螢幕預覽到最終列印)

對於這些條碼所嵌入的更廣泛頁面建置工作流程,我們關於文字、影像和字型提取的文章涵蓋了從 PDF 中讀取回內容的技術,而具有直接連入的大型 PDF 合併與分割指南則顯示了如何高效地組合大容量檔案;兩者都與此處描述的繪圖 API 自然結合,這些 API 作為 Delphi 和 C++Builder 的 Delphi PDF Library 的一部分出貨,並與本部落格其他地方介紹的文字、繪圖、表單和簽章 API 搭配