Blog

Mã vạch GS1-128 với độ chính xác 0,1 mm trong JSON: hướng dẫn thực tế

GS1-128 trông đơn giản cho đến khi scanner ngừng đọc ở 240 dpi. Hướng dẫn thực dụng về độ chính xác chiều dài tổng thể, X-dimension, quiet zone và vì sao HTML/CSS làm việc này khó hơn.

Nếu bạn vận chuyển hàng hóa vật lý, sớm hay muộn bạn sẽ phải in một mã vạch GS1-128 để scanner cầm tay thật đọc được trong ánh sáng kho thật, ở khoảng cách thật. Nghe có vẻ nhàm chán. Thực tế, đây là một trong những kiểu lỗi ồn ào hơn trong tạo PDF.

Bài này giải thích “độ chính xác 0,1 mm” thật sự nghĩa là gì với mã vạch GS1-128, vì sao renderer dựa trên HTML/CSS khó đạt mức đó, và bộ quy tắc thiết kế nhỏ giúp mã vạch in đúng ngay từ lần đầu trên DHL, FedEx, USPS và cả scanner nhập kho của Amazon.

“Độ chính xác mã vạch” thật sự nghĩa là gì

GS1-128, trước đây là UCC/EAN-128, mã hóa dữ liệu bằng độ rộng thanh và khoảng trắng theo tỷ lệ chặt chẽ. Đơn vị nguyên tử là X-dimension, tức độ rộng của thanh hoặc khoảng trắng hẹp nhất. Mọi thứ còn lại là bội số của X: 1X, 2X, 3X, 4X cho pattern nội bộ của Code 128.

Scanner giải mã bằng cách đo tương quan độ rộng. Nếu độ rộng sau khi in bị lệch, giải mã sẽ fail.

Hai kiểu lỗi phổ biến nhất trong production:

  1. X-dimension không nhất quán trong cùng symbol: renderer làm tròn sub-pixel khác nhau giữa các thanh liền kề. Ba thanh đầu là 8 px, rồi giữa chừng xuất hiện một thanh 7 px. Scanner nhìn thấy pattern không nhất quán.
  2. Sai chiều dài tổng thể hoặc scaling: renderer scale symbol sau khi render, làm méo X-dimension xuống dưới mức tối thiểu của GS1, thường là 0,495 mm ở hệ số phóng đại 1,0x.

Cả hai cùng biểu hiện như nhau: scanner beep được trên một sample đơn, nhưng batch production có tỷ lệ reject 1/30. QA không bắt được vì scanner dev thường dễ tính hơn scanner trong kho.

Quy tắc 0,1 mm

Độ chính xác liên quan là chiều dài tổng thể của mã vạch lệch không quá 0,1 mm so với mục tiêu trong spec. KHÔNG phải “mỗi thanh rộng 0,1 mm”; thanh thường rộng 0,495 mm hoặc hơn. Giới hạn 0,1 mm nằm ở độ chính xác kích thước cộng dồn của toàn symbol.

Với một GS1-128 điển hình chứa 18 ký tự số:

  • Symbol có khoảng 120 thanh và khoảng trắng
  • Chiều dài tổng thể ở phóng đại 1,0x: khoảng 58 mm
  • Sai số tổng thể 0,1 mm = khoảng 0,17% độ chính xác trên toàn chiều dài
  • Tính ra ngân sách nhất quán chỉ khoảng 0,001 mm mỗi thanh, thấp hơn rất nhiều so với độ rộng của một thanh đơn

Đó là lý do “renderer đặt một thanh 7 px ở nơi đáng ra là 7,4 px” có thể gây lỗi nghiêm trọng. Làm tròn sub-pixel cộng dồn qua 120 thanh, và bạn có thể vượt sai số 0,1 mm đâu đó giữa thanh 50 và thanh 80.

Vì sao HTML/CSS khó ở đây

Đường đi nhiều đội vấp phải: encode dữ liệu GS1-128 thành chuỗi, tạo SVG hoặc tệ hơn là từng thanh <div>, embed vào HTML, rồi render PDF bằng Puppeteer hoặc Prince.

Mỗi mắt xích trong chuỗi đó đều có thể đưa sai lệch vào.

1. Browser rasterisation làm tròn

Ngay cả SVG trong HTML cũng bị painter của browser làm tròn sub-pixel, trừ khi đặt shape-rendering="crispEdges", VÀ container xung quanh nằm đúng biên pixel nguyên, VÀ DPI PDF nhân sạch vào độ rộng thanh. Ba điều kiện này đều rất dễ bị phá vỡ vô tình.

2. CSS layout shifts

Một transform: scale(0.95) đâu đó trong stylesheet, được thêm 6 tháng trước để sửa một vấn đề layout khác, sẽ âm thầm làm méo mọi barcode trên trang. Không có cảnh báo. PDF nhìn vẫn ổn. Scanner thì không.

3. Quantization riêng của PDF emitter

Khi browser serialize sang PDF, nó chuyển kết quả đã paint thành PDF drawing operators. Một số emitter, đặc biệt là Chromium, snap vào một lưới nội bộ cố định của PDF. Với một SVG barcode đặt tại tọa độ không khớp lưới đó, output gần đúng nhưng tích lũy sai số.

4. Encoding bằng font còn tệ hơn

Một số đội dùng font Code 128 ({ font: "Code128" }, gõ dữ liệu rồi hy vọng). Font là vector và về lý thuyết có thể scale, nhưng font hinting ở kích thước nhỏ sẽ dịch độ rộng theo hướng làm chữ đẹp hơn với người đọc, đúng là thứ scanner không cần.

Cách tiếp cận structured rendering

gPdf nhận dữ liệu, tính pattern thanh/khoảng trắng theo spec GS1-128, rồi emit PDF vector primitives trực tiếp: không HTML, không dịch SVG, không font hinting.

{
  "pages": [{
    "size": "label_100_150",
    "elements": [
      {
        "type": "barcode",
        "format": "gs1128",
        "content": "(00)123456789012345678",
        "x": 4,
        "y": 8,
        "width": 58.0,
        "height": 18.0,
        "barcode_text": { "enabled": true, "position": "bottom" }
      }
    ]
  }]
}

Với element barcode, widthchiều dài tổng thể của symbol tính bằng mm, tức thứ bạn đo bằng thước cặp trên nhãn đã in. Đây là núm chỉnh đúng, và là điều width: 58.0 bảo đảm:

  • Renderer tính X-dimension bằng cách chia chiều dài mục tiêu cho số thanh của symbol, vốn được quyết định hoàn toàn bởi dữ liệu.
  • Mọi thanh được vẽ với cùng X-dimension chính xác.
  • Các độ rộng đó được ghi vào PDF bằng tọa độ floating-point (đơn vị PDF = 1/72 inch, mịn hơn mức cần thiết cho độ phân giải scan).
  • Không font hinting, không CSS pixel rounding, không méo do layout pass.

Kết quả: chiều dài tổng thể nằm trong sai số 0,1 mm so với mục tiêu yêu cầu trên mọi printer không tự thêm scaling, và phần lớn printer mặc định không làm vậy.

Thực tế nên in thế nào

Ba quy tắc tránh được 95% lỗi scanner trong production.

Quy tắc 1: chỉ định chiều dài tổng thể, không chỉ định X-dimension

Trường width là núm chỉnh đúng vì nó đo được: bạn có thể lấy thước cặp đo nhãn đã in và xác thực trực tiếp. Nếu chỉ định X-dimension, chiều dài symbol phụ thuộc vào độ dài dữ liệu được encode, làm QA nhãn khó hơn: SKU khác nhau thì barcode rộng khác nhau.

Với phần lớn kích thước nhãn:

  • Nhãn vận chuyển 4x6 inch: rộng 100 mm, GS1-128 nên khoảng 58-72 mm
  • Nhãn compliance 4x4 inch: khoảng 45-58 mm
  • Nhãn carton 2x1 inch, như Amazon UPC: không phải vùng GS1-128, dùng UPC-A

Quy tắc 2: luôn có quiet zone

GS1-128 cần quiet zone ít nhất 10X ở cả hai bên, tức khoảng trắng rỗng để scanner tìm mép symbol. Ở phóng đại 1,0x (X = 0,495 mm), đó là ít nhất 4,95 mm khoảng trống.

Failure mode kinh điển: developer đặt barcode ở x: 0 để chen cạnh element khác trên nhãn, scanner không tìm thấy điểm bắt đầu. Renderer nên tự dành quiet zone (gPdf làm vậy); hãy kiểm tra renderer của bạn.

Quy tắc 3: test trên scanner mục tiêu thật, không phải scanner dev

Camera điện thoại của bạn dễ tính hơn scanner công nghiệp Honeywell hoặc Zebra. Đường QA chuẩn:

  1. In 50 nhãn trên printer production thật, ở tốc độ production.
  2. Cho chúng chạy qua đúng loại scanner production, ở đúng tốc độ băng chuyền.
  3. Read rate dưới 99% nghĩa là có gì đó lệch, thường là tính nhất quán của X-dimension.

Đừng gửi nhãn vào production cho đối tác logistics chỉ vì “scan ổn trên camera MacBook của tôi”.

Thực tế nhiều định dạng

Nhãn của bạn có thể cần nhiều hơn GS1-128. Các tổ hợp phổ biến:

Symbol Dùng cho Nguồn spec
GS1-128 Đơn vị logistics, GTIN + serial + lot GS1 General Specifications
QR với FNC1 Ecommerce có thể scan bằng mobile ISO/IEC 18004
Data Matrix Dược phẩm (DSCSA / EU FMD) ISO/IEC 16022
PDF417 Bằng lái xe, boarding pass ISO/IEC 15438
Aztec Vé vận tải ISO/IEC 24778
MaxiCode Riêng UPS ISO/IEC 16023

Renderer chỉ xử lý GS1-128 rồi cuối cùng cũng đẩy bạn sang công cụ thứ hai. Chúng tôi bundle cả sáu định dạng trong một renderer vì workflow shipping/logistics gần như luôn cần ít nhất hai.

Xử lý scanner rejection trong production

Nếu bạn đã gặp vấn đề scanner rejection trong production, thứ tự chẩn đoán nên là:

  1. Lấy mẫu nhãn lỗi: đừng chỉ dựa vào metric tổng hợp. Lấy đúng nhãn vật lý đã fail.
  2. Đo bằng thước cặp: chiều dài tổng thể và X-dimension. Nếu không nằm trong tolerance của spec, đó là bug.
  3. Kiểm tra human-readable text bên dưới: scanner fail phần bars thường thử OCR fallback. Nếu cả hai fail, symbol thật sự malformed.
  4. Xác thực quiet zone: đo khoảng trắng hai bên. Nếu có thứ gì in quá gần, như logo, đường chia hoặc barcode khác, đó là vấn đề.
  5. Thử model scanner khác: một số scanner có quirk firmware. Nếu model A đọc được mà model B không đọc, vấn đề là interoperability, không nhất thiết là renderer.
  6. So với nhãn tham chiếu known-good: vendor thường công bố ảnh tham chiếu đúng spec. Nếu nhãn của bạn không khớp trực quan, lần ngược từ khác biệt đó.

TL;DR

Độ chính xác GS1-128 không phải là bạn in được thanh mỏng đến đâu; đó là X-dimension có giữ nhất quán trên toàn symbol trong một phần nhỏ của millimetre hay không. Renderer dựa trên HTML/CSS đưa sub-pixel drift vào nhiều giai đoạn của pipeline; renderer có cấu trúc emit trực tiếp PDF vector primitives bỏ qua các nguồn drift đó.

Nếu PDF stack hiện tại của bạn tạo tỷ lệ scanner rejection 1-5%, đó là dấu hiệu rất rõ. Playground có thể render sample GS1-128 với trường width đặt đúng spec nhãn của bạn; hãy đo bản in bằng thước cặp và so sánh.