Техническая статья

Создание сканируемых штрих-кодов в PDF с помощью HotPDF в Delphi

Штрих-код — это не картинка, которой вы украшаете документ. Это измерение, а сканер — это инструмент, который его считывает. Это переосмысление определяет почти все в том, как вы должны рисовать его в PDF. Полосы не несут информации своей чернотой; информация находится в соотношении ширины полос к ширине пробелов, и считыватель восстанавливает ее, замеряя переходы при перемещении лазера или датчика. Сожмите эту геометрию, размойте ее или загромоздите поля, и вы получите нечто, что выглядит в точности как штрих-код, а сканируется как пятно. HotPDF дает вам два способа поместить его на страницу, и разница между ними заключается именно в разнице между контролем над этой геометрией и отказом от него

Страница PDF с сеткой линейных штрих-кодов в различных символиках, нарисованная с помощью HotPDF в Delphi
Символики линейных штрих-кодов, нарисованные в одном PDF с помощью HotPDF

Что может кодировать HotPDF

HotPDF рисует линейные (одномерные) символики, и этот набор шире, чем требуется большинству проектов. Перечисление THPDFBarcodeType охватывает семейство Code 2 of 5 в его interleaved, industrial и matrix формах; Code 39 и его расширенный вариант; три подмножества Code 128: A, B и C; Code 93 обычный и расширенный; MSI; PostNet; Codabar; группу розничной торговли UPC и EAN, а именно EAN-8, EAN-13, UPC-A, сжатые UPC-E0 и UPC-E1, а также дополнительные 2- и 5-значные дополнения UPC; и подмножества GS1-128 (EAN-128). Этого достаточно, чтобы покрыть этикетки цепочки поставок, точки розничных продаж и старые промышленные коды, которые все еще используются на складах

Чего он не рисует, так это двумерное семейство. Здесь нет QR, Data Matrix или PDF417. Они кодируют байты в сетке со своей собственной математикой исправления ошибок, и если в требованиях назван один из них, то это неподходящий инструмент, и вы должны знать об этом до того, как начнете строить код вокруг него, а не после. Для одномерных кодов практический вопрос более узок: какая символика принимает данные, которые у вас есть на самом деле, потому что кодировки не взаимозаменяемы

Ограничения данных реальны, и они проявляются во время генерации. Варианты Code 2 of 5 и MSI принимают только цифры. Code 39 содержит заглавные буквы, цифры и небольшое количество знаков препинания; если вам нужны строчные буквы или полный диапазон ASCII, то это расширенный Code 39 или подмножество Code 128. Code 128C упаковывает две цифры в каждый символ для плотности, поэтому ему нужна числовая строка четной длины и ничего более. EAN-13 ожидает двенадцать цифр и вычисляет тринадцатую как контрольную; EAN-8 ожидает семь и вычисляет восьмую; UPC-A принимает двенадцать. Передайте символике данные, которые она не может представить, и вы не получите полезного исключения, вы получите штрих-код, кодирующий мусор, что еще хуже, потому что он выглядит нормально, пока кто-то не отсканирует его на кассе

Два пути рисования, два уровня контроля

Метод, который следует использовать в рабочей среде, — DrawBarcode, применяемый к объекту страницы. Он принимает символику, позицию, высоту и один параметр, который важнее остальных: MUnit, ширина модуля. Модуль — это ширина самой узкой полосы, основа, кратной которой является каждая другая мера в коде, и здесь она выражена в пунктах. Все, что касается того, отсканируется ли напечатанный результат, сводится к этому единственному целому числу

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'label.pdf';
    Pdf.BeginDoc;

    // BCType, X, Y, Height, MUnit (module width in points), angle,
    // data, UseCheckSum, bar color, background color.
    Pdf.CurrentPage.DrawBarcode(
      bcCodeEAN13,           // symbology
      72, 680,               // X, Y in points from the bottom-left
      60,                    // bar height
      1,                     // MUnit: 1pt narrowest bar
      0,                     // no rotation
      '123456789012',        // 12 digits; the 13th is the check
      True,                  // append the modulo-10 check digit
      clBlack, clWhite);     // bars black, background white

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Два аргумента заслуживают более внимательного рассмотрения. UseCheckSum добавляет контрольную цифру по модулю 10, которую ожидает символика, и для розничных кодов вам почти всегда нужно установить значение True; отключайте это только тогда, когда ваши данные уже содержат предварительно вычисленную проверку, иначе вы получите удвоенную цифру. Цвета полос и фона — это последние два параметра, и соблазн проявить здесь творческий подход — это ловушка, которая обсуждается ниже. Обратите также внимание на начало координат: как и при любом другом вызове рисования в HotPDF, X и Y измеряются от нижнего левого угла страницы в пунктах, при этом Y растет вверх, — это то же соглашение, которое рассматривается в примере Hello World

Второй путь — это DirectDrawBarcode, который принимает данные и ограничивающую рамку X, Y, Width, Height, и масштабирует шаблон полос для заполнения этой ширины. Это удобно для размещения кодов в сетке, потому что вы указываете площадь, а метод подгоняет под нее полосы. Это удобство также является его недостатком. Когда вы задаете ширину, вы больше не устанавливаете размер модуля; метод делит пространство, которое вы выделяете, на столько полос, сколько нужно для данных, и самая узкая полоса становится тем, что получается в результате этого деления. Запросите плотную строку Code 128 внутри слишком узкого прямоугольника, и модули сожмутся до значений, которые сканер не сможет разобрать, и это произойдет без уведомлений. Для всего, что должно надежно сканироваться, отдавайте предпочтение DrawBarcode и осознанно устанавливайте MUnit. Оставьте DirectDrawBarcode для предварительного просмотра и для макетов, где вы измерили, что полученные полосы остаются читаемыми

Ширина модуля — это решение, связанное с разрешением

Вот арифметика, определяющая, будет ли ваша этикетка работать. Лазерный сканер и камера имеют мельчайшие элементы, которые они могут различать, и узкая полоса должна комфортно превышать их после печати. Широко цитируемым нижним пределом для линейных кодов общего назначения является узкая полоса в 13 мил, около 0,33 мм, и многие руководства по розничной торговле и промышленности рассматривают это как минимум, а не как цель. Переведем это в единицы измерения PDF: один пункт равен 1/72 дюйма, что составляет около 0,353 мм, поэтому один пункт ширины модуля находится как раз на этом нижнем уровне. Вот почему MUnit := 1 — наименьшее значение, которому следует доверять для кода, предназначенного для реального сканера, и почему удвоение его до 2 дает запас, который почти ничего не стоит на этикетке, где есть место

Теперь свяжите это с разрешением вывода, потому что модуль также должен "пережить" принтер. На лазерном принтере с разрешением 300 DPI одна точка устройства составляет 1/300 дюйма, поэтому модуль в один пункт имеет ширину около четырех точек. Четырех точек едва хватает для четкого края; растекание тонера и небольшая ошибка совмещения ухудшают ситуацию, и полоса, размер которой в вашем PDF-файле составлял один пункт, на печати получается толще или тоньше, чем допускает спецификация. Увеличьте модуль до 2 пунктов, и у вас будет восемь точек для работы, которые поглощают этот шум. Правило, которое стоит усвоить: ширина модуля, которую вы устанавливаете в пунктах, должна соответствовать целому, удобному количеству точек устройства при вашем реальном разрешении печати, а не при том, которое вы хотели бы иметь. Код, который безупречно сканируется с экрана и дает сбой на складском принтере, почти всегда не проходит эту проверку

Свободная зона — часть символа

Самой распространенной причиной, по которой правильно закодированный штрих-код не сканируется, является свободная зона, пустое поле с каждой стороны от полос. Сканеры используют эту пустоту, чтобы найти, где начинается и где заканчивается код; без нее считыватель не может отличить первую полосу от того, что находится рядом с ней на странице. Стандарты весьма конкретны. Большинство линейных символик требуют свободную зону, превышающую ширину модуля как минимум в десять раз с каждой стороны, а розничные коды UPC и EAN требуют девять модулей слева и семь справа. При модуле в один пункт это примерно десять пунктов, около одной седьмой дюйма, гарантированного белого пространства по бокам от полос

HotPDF рисует полосы и ничего больше. Он не резервирует для вас свободную зону, а это значит, что ответственность лежит на вас, и об этом легко забыть. Режим сбоя неуловим: вы размещаете штрих-код вплотную к границе ячейки таблицы, или вы позволяете макету страницы разместить логотип рядом с ним, и код, который прошел все тесты на пустой странице, перестает сканироваться в тот момент, когда он попадает в реальный документ. Явно закладывайте поля. Перед вызовом DrawBarcode оставьте по крайней мере десять значений ширины модуля чистого пространства с обеих сторон и относитесь к любой графике, линии или тексту, которые вторгаются в эту полосу, как к дефекту, а не как к косметическому выбору

Цвет, контраст и удобочитаемая для человека строка

Цвета полос и фона существуют для того, чтобы вы могли соответствовать палитре бренда, и это самый быстрый способ сломать рабочий код. Сканеры считывают контраст, обычно с помощью красного света, и ожидают темные полосы на светлом поле. Черное на белом — единственная комбинация, которую следует использовать без тестирования. Темно-синий или темно-зеленый на белом могут пройти; любая комбинация с низким контрастом яркости, и в частности красные полосы, которые сканер красного света видит как фон, — нет. Если дизайнер просит сделать цветные штрих-коды, честным ответом будет то, что полосы останутся черными, а цвет будет нанесен на другое место этикетки

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

Короткая привычка проверки

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

Показанные здесь методы DrawBarcode и DirectDrawBarcode являются частью компонента HotPDF для Delphi и C++Builder