Technical Article

Штрих-коди в PDF з Delphi: QR, PDF417, DataMatrix

Штрих-код на транспортній етикетці або рахунку-фактурі має одне завдання - бути зчитаним сканером із першої спроби. Чи пройде він це зчитування, вирішується задовго до того, як посилка потрапить на вантажну рампу. Це вирішується тим, як саме символ був нанесений на сторінку. Найпоширеніша помилка в конвеєрі звітів Delphi - це рендеринг штрих-коду як растрового зображення в іншому місці та вставлення цього зображення в PDF. Він виглядає нормально на екрані при одному масштабі, але втрачає якість у всіх інших випадках.

Альтернатива полягає в малюванні символу як векторного вмісту безпосередньо на сторінці. Бібліотека PDFlibPas пропонує сімейство викликів малювання саме для цього, охоплюючи двовимірні матричні символи QR, PDF417 та DataMatrix, лінійні сімейства Code128 та GS1-128, а також USPS Intelligent Mail для поштової автоматизації. Аргумент на користь вектора є не естетичним. Він стосується того, чи потраплять штрихи саме туди, де їх очікує побачити сканер.

Чому вектор кращий за вставлене растрове зображення

Штрих-код - це візерунок із штрихів та пробілів, або в двох вимірах - сітка з темних та світлих модулів. Декодер працює шляхом вимірювання співвідношення цих ширин. Усе, що спотворює це співвідношення, є шумом, який витрачає запас стійкості символу до помилок. Растрове зображення штрих-коду містить фіксовані пікселі. Коли PDF відтворюється на принтері, крапки якого не діляться порівну на сітку зображення, растратор змушений виконувати ресемплінг, і краї модулів, які мали бути чіткими, розмиваються на два пікселі пристрою. Вузький штрих може потовщати, сусідній пробіл - стати тоншим, і співвідношення ширин, на яке покладається декодер, зсувається.

Отриманий як векторний вміст, той самий символ є набором заповнених прямокутників, описаних у координатах простору користувача PDF. Немає потреби боротися з фіксованою сіткою пікселів. Під час друку пристрій відображає кожен прямокутник із тією роздільною здатністю, яку він дійсно має, тому кожен край модуля залишається настільки чітким, наскільки це дозволяє обладнання, за будь-якого масштабу та будь-якого розміру друку. Масштабуйте векторний символ для транспортної етикетки або зменшуйте його для невеликої посилки - геометрія залишається точною. Саме ця точність забезпечує високий показник зчитування з першої спроби, що є головною метою розміщення штрих-коду на сторінці.

QR-коди та чотири рівні корекції

QR - це двовимірний матричний символ, який зчитується одночасно в обох напрямках, тому він упаковує багато даних у маленький квадрат. Його стійкість до пошкоджень забезпечується корекцією помилок Ріда-Соломона, яка пропонується на чотирьох рівнях. Рівень L відновлює приблизно 7 відсотків кодових слів, M - близько 15 відсотків, Q - близько 25 відсотків, а H - близько 30 відсотків. Вищий рівень корекції не є безкоштовним. Кодові слова відновлення займають ємність модулів, тому для фіксованого обсягу даних вищий рівень змушує робити символ щільнішим або фізично більшим.

Вибір є питанням про середовище, у якому житиме символ. Чистий цифровий документ, який скануватиметься лише з екрана, може використовувати рівень L і залишатися компактним. Етикетка, яку друкуватимуть, переноситимуть, стиратимуть або навіть частково заклеюватимуть скотчем, вимагає рівня Q або H, оскільки додаткова надмірність дозволяє декодеру відновити корисне навантаження з пошкодженого символу. Метод 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 відкриває елементи керування, яких немає в базовому виклику. Параметри FixedColumns та FixedRows фіксують кількість стовпців та рядків даних, причому 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 (сучасну схему Ріда-Соломона), і вона залишається придатною для зчитування при розмірах, коли QR-код із тими самими даними був би незручним. Це робить її стандартним вибором для прямого маркування деталей, дрібних електронних компонентів та щільних логістичних етикеток.

Метод DrawDataMatrixSymbol приймає ModuleSize для кроку крапки, Encoding із значенням 1 для ASCII та SymbolSize, яке дорівнює 0 для автоматичного підбору або одному зі стандартних квадратних чи прямокутних розмірів (від 10x10 до 132x132). Параметр Options поєднує орієнтацію із шириною вільної зони (quiet zone), де додавання від 100 до 400 визначає білу рамку шириною від одного до чотирьох модулів. Вільна зона - це не прикраса. Декодеру потрібен цей чистий простір, щоб знайти шаблон пошуку символу, а символ, розташований впритул до іншого тексту чи зображення, не зможе правильно розпізнатися.

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

Де досі панують лінійні штрих-коди

Двовимірні коди привертають багато уваги, але лінійні штрих-коди досі охоплюють величезну частину роздрібної торгівлі та логістики. Причиною є велика кількість лазерних сканерів, що зчитують один промінь. Code128 є робочою конячкою для буквено-цифрових даних, і його ефективність забезпечується трьома наборами символів. Набір A містить керуючі символи та великі літери, набір B охоплює повний діапазон ASCII для друку, а набір C призначений для чисел. Піднабір C кодує пару цифр в один символ штрих-коду, тому послідовність числових даних займає вдвічі менше символів, ніж у наборі A або B. Це найкомпактніший спосіб розміщення довгого числового коду, і реалізація Code128 в PDFlibPas автоматично поєднує набори B та C для досягнення цього.

Стандарт GS1-128 (колишня назва EAN-128) базується на Code128 і містить ідентифікатори застосування (Application Identifiers) - префікси в дужках, які повідомляють системі приймання, чи є наступні цифри серійним номером, кодом партії або датою закінчення терміну придатності. Структура позначається символом FNC1 - спеціальним символом без даних, який вказує на кодування GS1 та розділяє поля змінної довжини. В PDFlibPas ви малюєте символ GS1-128 через DrawBarcode з типом 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 символ. Точні параметри висоти штрихів та трекера важливі, оскільки символ несе інформацію в тому, чи є кожен штрих повним, виступаючим вгору (ascender) чи вниз (descender), і поштовий сканер залежить від точної відповідності цих висот специфікації.

Малювання на сторінці та вимірювання для макета

Кожен показаний тут виклик малює безпосередньо на вмісті поточної вибраної сторінки - тій самій поверхні, що отримує ваш текст та зображення. Тому штрих-код створюється як частина звичайного процесу генерації документа, а не імпортується як окремий ресурс. Оскільки ці символи є векторними, дані для кодування та займана ними геометрія відомі під час малювання, що дозволяє розміщувати їх детерміновано.

Для лінійних сімейств корисно спочатку виконати вимірювання. Метод GetBarcodeWidth повертає загальну ширину штрих-коду для заданої ширини вузького штриха та типу штрих-коду, тому ви можете зарезервувати точний простір по горизонталі перед початком малювання, а не вгадувати і виявляти накладання після створення сторінки. Двовимірні символи простіше розміщувати, оскільки ви задаєте їхній розмір безпосередньо через SymbolSize або ModuleSize, і символ заповнює цей простір. У будь-якому випадку підхід однаковий: визначте фізичний розмір виходячи з умов сканування, переконайтеся, що символ відповідає наявному простору, і дозвольте векторній геометрії зберегти чіткість кожного краю від попереднього перегляду на екрані до остаточного друку.

Для ширшого процесу побудови сторінок, у який інтегруються ці штрих-коди, методи з нашої статті про вилучення тексту, зображень та шрифтів охоплюють читання вмісту назад із PDF, а посібник із об'єднання та розділення великих PDF з прямим доступом показує, як ефективно збирати великі документи. Обидва рішення поєднуються з описаним тут API малювання, яке постачається як частина бібліотеки Delphi PDF Library для Delphi та C++Builder разом з API тексту, графіки, форм та підписів, описаними в інших статтях цього блогу.