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 объединяет ориентацию с шириной свободной зоны, где прибавление от 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 и содержит идентификаторы применения, представляющие собой префиксы в скобках, которые указывают системе, чем являются последующие цифры: серийным номером, кодом партии или датой окончания срока годности. Структура размечается специальным символом 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 цифры. Настройка высоты штрихов необходима потому, что символ кодирует информацию с помощью типа штрихов (полный штрих, восходящий или нисходящий), и почтовый считыватель требует строгого соответствия этих параметров спецификации

Рисование на странице и расчет макета

Каждый из описанных здесь вызовов выполняет рисование непосредственно на текущей выбранной странице, на той же поверхности, где размещаются текст и изображения. Это позволяет создавать штрихкод в рамках обычного формирования документа без импорта сторонних ресурсов. Поскольку символы являются векторными, закодированные данные и занимаемая ими геометрия известны уже в момент рисования, что позволяет размещать их с высокой точностью

Для линейных штрихкодов полезно выполнить предварительное измерение. Функция GetBarcodeWidth возвращает полную ширину штрихкода для заданной ширины тонкого штриха и типа символа. Это позволяет зарезервировать точное горизонтальное пространство перед рисованием вместо размещения наугад с последующим обнаружением наложений. Двухмерные символы позиционировать проще, так как их размер задается напрямую через SymbolSize или ModuleSize, и они заполняют выделенную область. В любом случае подход одинаков: выберите физический размер исходя из требований сканирования, убедитесь в наличии свободного места и позвольте векторной геометрии сохранить четкость каждой границы от предварительного просмотра до печати

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