Technical Article

Mã vạch trong PDF với Delphi: QR, PDF417, DataMatrix

Một mã vạch trên nhãn vận chuyển hoặc hóa đơn chỉ có một nhiệm vụ duy nhất, đó là được máy quét đọc thành công ngay từ lượt quét đầu tiên. Việc nó có vượt qua được lượt quét đó hay không được quyết định từ rất lâu trước khi kiện hàng cập bến bốc dỡ. Nó được quyết định bởi cách ký hiệu đó được đưa lên trang giấy. Sai lầm phổ biến nhất trong quy trình lập báo cáo bằng Delphi là dựng mã vạch dưới dạng một ảnh bitmap ở một nơi khác rồi đặt hình ảnh đó vào tài liệu PDF. Nó trông có vẻ ổn trên màn hình ở một mức thu phóng nhất định nhưng sẽ bị giảm chất lượng ở mọi nơi khác.

Giải pháp thay thế là vẽ ký hiệu dưới dạng nội dung vector, trực tiếp lên trang giấy. PDFlibPas cung cấp một tập hợp các lệnh gọi vẽ chính xác cho việc này, bao gồm các ký hiệu ma trận 2D như QR, PDF417, và DataMatrix, các nhóm mã vạch tuyến tính Code128 và GS1-128, và USPS Intelligent Mail cho tự động hóa bưu chính. Lý do lựa chọn vector không phải vì tính thẩm mỹ. Mà là vì các thanh mã vạch có nằm đúng vị trí máy quét mong đợi hay không.

Tại sao vector vượt trội hơn một ảnh bitmap được đặt sẵn

Một mã vạch là một mẫu các thanh và khoảng trống, hoặc trong không gian hai chiều là một lưới các mô-đun tối và sáng. Bộ giải mã hoạt động bằng cách đo tỷ lệ của các độ rộng đó. Bất kỳ điều gì làm sai lệch tỷ lệ đều là nhiễu ảnh hưởng đến khả năng sửa lỗi của ký hiệu. Một hình ảnh mã vạch được raster hóa chứa các pixel cố định. Khi tệp PDF được hiển thị ra một máy in có các điểm in không chia đều cho lưới hình ảnh, bộ raster hóa phải lấy mẫu lại, và các cạnh của mô-đun vốn cần sắc nét sẽ bị phân tán trên hai pixel của thiết bị. Một thanh hẹp có thể bị làm dày lên, một khoảng trống bên cạnh có thể bị thu hẹp lại, và tỷ lệ độ rộng mà bộ giải mã dựa vào sẽ bị sai lệch.

Được vẽ dưới dạng nội dung vector, cùng một ký hiệu là một tập hợp các hình chữ nhật được tô màu được mô tả trong hệ tọa độ không gian người dùng PDF (user-space coordinates). Không có lưới pixel cố định nào để xung đột. Tại thời điểm in, thiết bị sẽ hiển thị từng hình chữ nhật ở độ phân giải thực tế của nó, do đó mọi cạnh của mô-đun đều sắc nét ở mức tối đa phần cứng cho phép, ở bất kỳ tỷ lệ và kích thước in nào. Hãy phóng to một ký hiệu vector cho nhãn pallet hoặc thu nhỏ nó cho một bưu kiện và cấu trúc hình học vẫn giữ nguyên độ chính xác. Độ chính xác đó là thứ duy trì tỷ lệ đọc thành công ngay từ lượt quét đầu tiên ở mức cao, đây là mục đích chính của việc đưa mã vạch lên trang giấy.

Mã QR và bốn cấp độ sửa lỗi

QR là ký hiệu ma trận 2D được đọc trên cả hai trục cùng một lúc, đó là lý do tại sao nó đóng gói rất nhiều dữ liệu vào một hình vuông nhỏ. Khả năng chịu hư hại của nó đến từ tính năng sửa lỗi Reed-Solomon, được cung cấp ở bốn cấp độ. Cấp độ L phục hồi khoảng 7% số từ mã (codewords), M khoảng 15%, Q khoảng 25%, và H khoảng 30%. Tính năng sửa lỗi cao hơn không phải là miễn phí. Các từ mã phục hồi chiếm dung lượng mô-đun, vì vậy đối với một lượng dữ liệu cố định, cấp độ cao hơn buộc ký hiệu phải dày đặc hơn hoặc có kích thước vật lý lớn hơn.

Sự đánh đổi này là câu hỏi về môi trường mà ký hiệu sẽ tồn tại. Một tài liệu kỹ thuật số sạch sẽ chỉ được quét từ màn hình có thể đặt ở mức L và giữ nguyên sự nhỏ gọn. Một chiếc nhãn sẽ được in ấn, xử lý, chà xát và có thể bị băng keo che phủ một phần sẽ cần mức độ Q hoặc H, vì độ dư thừa bổ sung là thứ cho phép bộ giải mã tái cấu trúc lại dữ liệu từ một ký hiệu không còn nguyên vẹn. DrawQRCode nhận vị trí và một SymbolSize xác định chiều rộng và chiều cao được vẽ, cùng một giá trị EncodeOptions chọn chế độ dữ liệu (0 cho tự động, hoặc các biến thể số, chữ số, ISO-8859-1, và UTF-8) và một giá trị DrawOptions cho hướng hiển thị.

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;

Mức độ sửa lỗi sẽ được chọn bởi bộ mã hóa để khớp dữ liệu vào ký hiệu bạn yêu cầu. Nếu bạn cần một cấp độ cao được đảm bảo cho một môi trường khắc nghiệt, hãy định kích thước ký hiệu một cách rộng rãi để bộ mã hóa có đủ dung lượng mô-đun chi cho sự dư thừa thay vì bị buộc phải hạ thấp cấp độ xuống để vừa vặn.

PDF417 cho giấy tờ tùy thân và nhãn vận chuyển

PDF417 là một ký hiệu tuyến tính xếp chồng. Mỗi hàng là một mã vạch tuyến tính ngắn, và các hàng xếp chồng lên nhau để tạo thành một khối, đó là lý do tại sao nó xuất hiện trên giấy phép lái xe, thẻ lên máy bay và nhãn vận chuyển của nhà vận chuyển nơi một dải dữ liệu rộng hơn phải nằm trong một diện tích hình chữ nhật. Tính năng sửa lỗi của nó hoạt động trên thang điểm từ 0 đến 8. Mỗi bước tăng gấp đôi số lượng từ mã sửa lỗi, vì vậy cấp độ 5 mang lại độ dư thừa nhiều hơn nhiều so với cấp độ 1, với chi phí là nhiều từ mã hơn trên trang.

Hình dạng của một khối PDF417 có thể điều chỉnh được, và điều đó rất quan trọng vì nhãn in có một diện tích cố định để điền dữ liệu. Phương thức DrawPDF417SymbolEx cung cấp các tính năng kiểm soát mà lệnh gọi cơ bản không có. FixedColumnsFixedRows cố định số lượng cột và số lượng hàng dữ liệu, với giá trị 0 có nghĩa là hãy để bộ mã hóa quyết định. ErrorLevel nhận giá trị -1 cho tự động hoặc một giá trị tường minh từ 0 đến 8. ModuleSize là chiều rộng của phần tử hẹp nhất trong đơn vị đo lường hiện tại, và HeightWidthRatio thiết lập chiều cao của từng mô-đun so với chiều rộng của nó, đây là cách bạn làm cho khối ngắn và rộng hoặc cao và hẹp để phù hợp với không gian bạn có.

// 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

Cố định các cột là phương pháp thường dùng trên một mẫu nhãn in. Số lượng cột không đổi giúp khối có chiều rộng có thể dự đoán trước, do đó bố cục xung quanh không bị dịch chuyển khi độ dài dữ liệu được mã hóa thay đổi từ tài liệu này sang tài liệu tiếp theo, trong khi bộ mã hóa tự động thêm các hàng xuống phía dưới để hấp thụ sự chênh lệch.

DataMatrix cho các bản in kích thước nhỏ

DataMatrix là ký hiệu cần nghĩ tới khi hình in yêu cầu kích thước nhỏ. Nó là một lưới 2D nhỏ gọn sử dụng ECC 200, cơ chế Reed-Solomon hiện đại, và nó vẫn có thể đọc được ở các kích thước mà một ký hiệu QR chứa cùng lượng dữ liệu sẽ trở nên quá lớn. Điều đó làm cho nó trở thành lựa chọn tiêu chuẩn cho việc đánh dấu bộ phận trực tiếp, các linh kiện điện tử nhỏ và các nhãn hậu cần mật độ cao.

Phương thức DrawDataMatrixSymbol nhận một ModuleSize cho khoảng cách điểm (dot pitch), một Encoding bằng 1 cho ASCII, và một SymbolSize bằng 0 cho tự động hoặc một trong các kích thước hình vuông và hình chữ nhật tiêu chuẩn, từ 10x10 đến 132x132. Tham số Options kết hợp hướng hiển thị với chiều rộng vùng yên tĩnh (quiet-zone), trong đó cộng thêm từ 100 đến 400 thiết lập một viền trắng từ một đến bốn mô-đun. Vùng yên tĩnh không phải là phần trang trí. Bộ giải mã cần biên độ trống đó để tìm mẫu định vị của ký hiệu, và một ký hiệu nằm quá sát các vệt mực khác là một ký hiệu mà máy quét không thể nhận diện được.

// 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

Nơi mã vạch 1D vẫn thống trị

Các ký hiệu hai chiều thu hút nhiều sự chú ý, nhưng các mã vạch tuyến tính vẫn sở hữu phần lớn thị phần bán lẻ và hậu cần, và lý do là sự phổ biến của các máy quét laser đọc bằng một đường quét duy nhất. Code128 là công cụ đắc lực cho dữ liệu chữ và số, và tính hiệu quả của nó đến từ ba tập ký tự. Tập A bao gồm các ký tự điều khiển và chữ in hoa, tập B bao gồm toàn bộ dải ASCII có thể in được, và tập C là tập quan trọng đối với các con số. Tập con C mã hóa một cặp chữ số trong một ký tự ký hiệu đơn lẻ, do đó một chuỗi dữ liệu số chỉ chiếm một nửa số ký tự ký hiệu so với tập A hoặc B. Đó là cách nhỏ gọn nhất để bố trí một mã vạch số dài, và triển khai Code128 của PDFlibPas kết hợp các tập B và C một cách tự động để đạt được điều đó.

GS1-128, tiêu chuẩn trước đây có tên là EAN-128, phát triển dựa trên Code128 bằng cách mang theo các Định danh Ứng dụng (Application Identifiers), các tiền tố trong dấu ngoặc đơn cho hệ thống nhận biết các chữ số tiếp theo là số sê-ri, mã lô sản xuất hay ngày hết hạn. Cấu trúc được đánh dấu bằng FNC1, một ký tự đặc biệt không chứa dữ liệu giúp đánh dấu ký hiệu là mã hóa GS1 và phân tách các trường có độ dài thay đổi. Trong PDFlibPas, bạn vẽ một ký hiệu GS1-128 bằng phương thức DrawBarcode sử dụng kiểu Code128 và mã đánh dấu [FNC1] được đặt trong chuỗi dữ liệu nơi mỗi định danh ứng dụng bắt đầu.

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;

Đối với thư tín, USPS Intelligent Mail, còn được gọi là OneCode, mã hóa dữ liệu định tuyến và theo dõi trong một mã vạch điều biến chiều cao duy nhất cho tự động hóa bưu chính. DrawIntelligentMailBarcode nhận các thông số hình học rõ ràng cho chiều rộng thanh, chiều cao thanh đầy đủ, chiều cao phần định vị (tracker), và chiều rộng khoảng trống, với dữ liệu được cung cấp dưới dạng chuỗi gồm 20, 25, 29 hoặc 31 chữ số. Các chiều cao thanh và chiều cao định vị rõ ràng tồn tại vì ký hiệu mang thông tin trong việc mỗi thanh là một thanh đầy đủ, thanh hướng lên (ascender) hay thanh hướng xuống (descender), và máy đọc bưu chính phụ thuộc vào các chiều cao đó được giữ đúng theo đặc tả.

Vẽ lên trang giấy và đo lường cho bố cục

Mọi lệnh gọi hiển thị ở đây đều vẽ vào nội dung của trang hiện được chọn, cùng bề mặt nhận văn bản và hình ảnh của bạn, do đó mã vạch được tạo ra như một phần của quá trình tạo tài liệu thông thường thay vì được nhập vào như một tài sản riêng biệt. Bởi vì các ký hiệu là nội dung vector, dữ liệu chúng mã hóa và cấu trúc hình học chúng chiếm giữ đều được xác định tại thời điểm vẽ, đó là điều cho phép bạn đặt chúng một cách chính xác.

Bố cục cho các nhóm tuyến tính sẽ có lợi hơn nếu được đo lường trước tiên. GetBarcodeWidth trả về tổng chiều rộng được vẽ của một mã vạch cho một chiều rộng thanh hẹp và kiểu mã vạch nhất định, do đó bạn có thể dự trữ không gian ngang chính xác trước khi thực hiện thao tác vẽ, thay vì đoán mò và phát hiện ra sự chồng chéo sau khi trang đã được dựng. Các ký hiệu 2D dễ bố trí hơn vì bạn thiết lập kích thước vẽ của chúng trực tiếp thông qua SymbolSize hoặc ModuleSize, và ký hiệu sẽ lấp đầy diện tích đó. Dù bằng cách nào, nguyên tắc vẫn giống nhau. Xác định kích thước vật lý từ môi trường quét, xác nhận ký hiệu khớp với vị trí bạn có, và để cấu trúc hình học vector giữ cho mọi cạnh sắc nét từ lúc xem trước trên màn hình đến bản in cuối cùng.

Đối với luồng công việc xây dựng trang rộng lớn hơn mà các mã vạch này tham gia vào, các kỹ thuật trong bài viết của chúng tôi về trích xuất văn bản, hình ảnh và font chữ trình bày cách đọc nội dung ngược lại từ PDF, và hướng dẫn về hợp nhất và chia tách PDF lớn bằng quyền truy cập trực tiếp trình bày cách lắp ráp các tài liệu dung lượng lớn một cách hiệu quả. Cả hai đều kết hợp tự nhiên với API vẽ được mô tả ở đây, vốn đi kèm như một phần của Delphi PDF Library dành cho Delphi và C++Builder bên cạnh các API văn bản, đồ họa, biểu mẫu và chữ ký được đề cập ở những bài viết khác trên blog này.