Technical Article

Kody kreskowe w plikach PDF za pomocą Delphi: QR, PDF417, DataMatrix

Kod kreskowy na etykiecie wysyłkowej lub fakturze ma jedno zadanie: zostać odczytanym przez skaner za pierwszym razem. To, czy przejdzie tę próbę pomyślnie, rozstrzyga się na długo przed tym, jak paczka trafi na rampę załadowczą. Decyduje o tym sposób, w jaki symbol został umieszczony na stronie. Najczęstszym błędem w potoku raportowania Delphi jest renderowanie kodu kreskowego jako mapy bitowej w innym module i umieszczanie tego obrazu w pliku PDF. Wygląda to dobrze na ekranie przy jednym poziomie powiększenia, a następnie ulega degradacji w każdym innym miejscu.

Alternatywą jest narysowanie symbolu jako zawartości wektorowej, bezpośrednio na stronie. Biblioteka PDFlibPas udostępnia rodzinę wywołań rysowania przeznaczonych do tego celu, obejmującą dwuwymiarowe symbole matrycowe QR, PDF417 i DataMatrix, rodziny liniowe Code128 i GS1-128 oraz USPS Intelligent Mail na potrzeby automatyzacji pocztowej. Argument za grafiką wektorową nie ma charakteru estetycznego. Chodzi o to, czy kreski znajdą się dokładnie tam, gdzie oczekuje tego skaner.

Dlaczego wektor wygrywa z umieszczoną mapą bitową

Kod kreskowy to wzorzec kresek i odstępów, a w dwóch wymiarach – siatka ciemnych i jasnych modułów. Dekoder działa poprzez pomiar stosunku tych szerokości. Wszystko, co zniekształca te proporcje, jest szumem, który zmniejsza budżet tolerancji błędów symbolu. Zrastrowany obraz kodu kreskowego niesie ze sobą stałą liczbę pikseli. Gdy plik PDF jest renderowany na drukarce, której punkty nie dzielą się równomiernie na siatkę obrazu, rasteryzator musi przeprowadzić ponowne próbkowanie (resampling), a krawędzie modułów, które powinny być ostre, rozmywają się na dwa piksele urządzenia. Wąska kreska może ulec pogrubieniu, sąsiedni odstęp – zwężeniu, a stosunek szerokości, na którym polega dekoder, ulega zachwianiu.

Ten sam symbol narysowany jako zawartość wektorowa to zestaw wypełnionych prostokątów opisanych we współrzędnych przestrzeni użytkownika PDF. Nie ma tu stałej siatki pikseli, z którą trzeba walczyć. W czasie drukowania urządzenie renderuje każdy prostokąt w rozdzielczości, którą faktycznie dysponuje, dzięki czemu każda krawędź modułu jest tak ostra, jak pozwala na to sprzęt, przy dowolnej skali i rozmiarze druku. Skalowanie symbolu wektorowego w górę dla etykiety paletowej lub zmniejszanie go dla małej paczki pozostawia geometrię nienaruszoną. Ta precyzja zapewnia wysoki współczynnik odczytu przy pierwszej próbie, co jest jedynym celem umieszczenia kodu kreskowego na stronie.

Kody QR i cztery poziomy korekcji błędów

QR to dwuwymiarowy symbol matrycowy odczytywany w obu osiach jednocześnie, dlatego pakuje dużo danych w mały kwadrat. Jego tolerancja na uszkodzenia wynika z korekcji błędów Reeda-Solomona, oferowanej na czterech poziomach. Poziom L odzyskuje około 7 procent słów kodowych, M – około 15 procent, Q – około 25 procent, a H – około 30 procent. Wyższa korekcja nie jest darmowa. Słowa odzyskiwania zajmują pojemność modułów, więc dla stałej ilości danych wyższy poziom wymusza gęstszy lub fizycznie większy symbol.

Ten kompromis zależy od środowiska, w którym symbol będzie funkcjonował. Czysty dokument cyfrowy, który będzie skanowany wyłącznie z ekranu, może pozostać przy poziomie L i zachować kompaktowe rozmiary. Etykieta, która będzie drukowana, obsługiwana, narażona na otarcia i potencjalnie częściowo zaklejona taśmą, wymaga poziomu Q lub H, ponieważ dodatkowa nadmiarowość pozwala dekoderowi zrekonstruować ładunek z symbolu, który nie jest już nieskazitelny. Metoda DrawQRCode przyjmuje pozycję i rozmiar SymbolSize, który ustala szerokość i wysokość rysowania, a także wartość EncodeOptions określającą tryb danych (0 dla trybu automatycznego lub warianty numeryczne, alfanumeryczne, ISO-8859-1 i UTF-8) oraz DrawOptions dla orientacji.

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;

Sam poziom korekcji jest dobierany przez enkoder tak, aby dane zmieściły się w żądanym symbolu. Jeśli potrzebujesz gwarantowanego wysokiego poziomu dla trudnych środowisk, zaprojektuj odpowiednio większy symbol, aby enkoder miał wystarczający budżet modułów do przeznaczenia na nadmiarowość, zamiast być zmuszonym do jego zmniejszenia.

PDF417 dla dokumentów tożsamości i etykiet wysyłkowych

PDF417 to dwuwymiarowy wielowierszowy (stosowy) kod kreskowy. Każdy wiersz to krótki kod liniowy, a wiersze układają się jeden na drugim, tworząc blok. Z tego powodu pojawia się on na prawach jazdy, kartach pokładowych i etykietach wysyłkowych kurierów, gdzie większa ilość danych musi zmieścić się na prostokątnej powierzchni. Jego korekcja błędów działa w skali od 0 do 8. Każdy krok w górę z grubsza podwaja liczbę słów kodowych korekcji, więc poziom 5 niesie znacznie większą nadmiarowość niż poziom 1, kosztem większej liczby słów kodowych na stronie.

Kształt bloku PDF417 jest regulowany, co ma znaczenie, ponieważ etykieta ma stałą powierzchnię do wypełnienia. Metoda DrawPDF417SymbolEx udostępnia mechanizmy sterujące, których nie ma w wywołaniu podstawowym. FixedColumns i FixedRows blokują liczbę kolumn i wierszy danych (wartość 0 oznacza decyzję enkodera). ErrorLevel przyjmuje -1 dla trybu automatycznego lub jawną wartość od 0 do 8. ModuleSize to szerokość najwęższego elementu w bieżącej jednostce miary, a HeightWidthRatio ustawia wysokość każdego modułu w stosunku do jego szerokości, co pozwala na spłaszczenie lub wydłużenie bloku w zależności od dostępnego miejsca.

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

Zablokowanie kolumn to standardowy zabieg w szablonach etykiet. Stała liczba kolumn nadaje blokowi przewidywalną szerokość, dzięki czemu otaczający go układ nie przesuwa się, gdy kodowany ładunek zmienia długość w poszczególnych dokumentach, podczas gdy enkoder dodaje kolejne wiersze w dół, aby pomieścić różnice.

DataMatrix dla małych oznaczeń

DataMatrix to symbol, po który należy sięgnąć, gdy oznaczenie musi być małe. Jest to kompaktowa siatka dwuwymiarowa wykorzystująca ECC 200 (nowoczesny schemat Reeda-Solomona) i pozostaje czytelna przy rozmiarach, w których kod QR dla tych samych danych byłby nieporęczny. To czyni go standardowym wyborem do bezpośredniego znakowania części, małych komponentów elektronicznych i gęstych etykiet logistycznych.

Metoda DrawDataMatrixSymbol przyjmuje parametr ModuleSize określający skok punktu, Encoding o wartości 1 dla ASCII oraz SymbolSize, który wynosi 0 dla trybu automatycznego lub przyjmuje jeden ze standardowych wymiarów kwadratowych i prostokątnych, od 10x10 do 132x132. Parametr Options łączy orientację z szerokością strefy ciszy (quiet-zone), gdzie dodanie wartości od 100 do 400 ustawia białą ramkę o szerokości od jednego do czterech modułów. Strefa ciszy nie jest dekoracją. Dekoder potrzebuje tego czystego marginesu, aby znaleźć wzorzec wyszukiwania symbolu, a symbol ściśnięty z innym nadrukiem to symbol, którego nie uda się odczytać.

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

Gdzie wciąż rządzą kody 1D

Symbole dwuwymiarowe przyciągają uwagę, ale kody liniowe wciąż posiadają ogromne udziały w handlu detalicznym i logistyce, a powodem jest zainstalowana baza skanerów laserowych odczytujących pojedyncze przejście wiązki. Code128 to wół roboczy dla danych alfanumerycznych, a jego wydajność wynika z trzech zestawów znaków. Zestaw A obejmuje znaki sterujące i wielkie litery, zestaw B pokrywa pełny zakres drukowalnych znaków ASCII, a zestaw C to ten, który ma znaczenie dla liczb. Podzbiór C koduje parę cyfr w pojedynczym znaku symbolu, więc ciąg danych numerycznych zajmuje połowę znaków w porównaniu z zestawem A lub B. To najbardziej zwarty sposób zapisu długiego kodu numerycznego, a implementacja Code128 w PDFlibPas automatycznie łączy zestawy B i C, aby to osiągnąć.

GS1-128, standard znany dawniej jako EAN-128, bazuje na Code128 poprzez dodanie identyfikatorów zastosowań (Application Identifiers) – prefiksów w nawiasach, które informują system odbiorczy, czy kolejne cyfry to numer seryjny, kod partii czy data ważności. Struktura ta jest oznaczana przez FNC1, specjalny znak niebędący danymi, który flaguje symbol jako zakodowany w standardzie GS1 i oddziela pola o zmiennej długości. W PDFlibPas rysuje się symbol GS1-128 za pomocą metody DrawBarcode, używając typu Code128 i umieszczając dosłowny znacznik [FNC1] w ciągu danych w miejscu, w którym zaczyna się każdy identyfikator zastosowania.

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;

W przypadku poczty, standard USPS Intelligent Mail (zwany również OneCode) koduje dane rutowania i śledzenia w pojedynczym kodzie kreskowym o modulowanej wysokości na potrzeby automatyzacji pocztowej. Metoda DrawIntelligentMailBarcode przyjmuje jawne parametry geometryczne dla szerokości kreski, pełnej wysokości kreski, wysokości elementu śledzącego oraz szerokości odstępu, a dane wejściowe przekazywane są jako ciąg składający się z 20, 25, 29 lub 31 wyłącznie cyfr. Jawne wysokości kresek i elementów śledzących wynikają z faktu, że symbol niesie informacje w tym, czy każda kreska jest pełna, czy jest wydłużeniem górnym (ascender) czy dolnym (descender), a czytnik pocztowy zależy od ścisłego przestrzegania tych wysokości.

Rysowanie na stronie i pomiar na potrzeby układu

Każde pokazane tutaj wywołanie rysuje bezpośrednio w zawartości aktualnie wybranej strony – tej samej powierzchni, która przyjmuje tekst i obrazy, więc kod kreskowy jest generowany jako część normalnego procesu tworzenia dokumentu, a nie importowany jako oddzielny zasób. Ponieważ symbole są zawartością wektorową, kodowane przez nie dane oraz zajmowana geometria są znane w momencie rysowania, co pozwala na ich deterministyczne rozmieszczenie.

Projektowanie układu dla rodzin liniowych zyskuje na wcześniejszym wykonaniu pomiaru. Metoda GetBarcodeWidth zwraca całkowitą szerokość wyrenderowanego kodu kreskowego dla danej szerokości wąskiej kreski i typu kodu, dzięki czemu można zarezerwować dokładne miejsce w poziomie przed zatwierdzeniem rysowania, zamiast zgadywać i odkrywać nałożenie elementów po zbudowaniu strony. Symbole 2D są prostsze w rozmieszczaniu, ponieważ ich rozmiar określa się bezpośrednio poprzez SymbolSize lub ModuleSize, a symbol wypełnia ten obszar. W obu przypadkach dyscyplina jest taka sama: określ fizyczny rozmiar na podstawie środowiska skanowania, upewnij się, że symbol mieści się w przewidzianym miejscu i pozwól geometrii wektorowej zachować ostrość każdej krawędzi od podglądu na ekranie po ostateczny wydruk.

Szerszy proces budowania stron, w który wpasowują się te kody kreskowe, został opisany w naszym artykule na temat ekstrakcji tekstu, obrazów i czcionek, a przewodnik po scalaniu i dzieleniu dużych plików PDF z bezpośrednim dostępem pokazuje, jak wydajnie składać dokumenty o dużej objętości. Oba te rozwiązania naturalnie łączą się z prezentowanym tu interfejsem API rysowania, który jest dostarczany jako część biblioteki Delphi PDF Library dla Delphi i C++Builder, obok interfejsów API do obsługi tekstu, grafiki, formularzy i podpisów omówionych w innych miejscach tego bloga.