Technical Article

Barcodes in PDF met Delphi: QR, PDF417, DataMatrix

Een barcode op een verzendlabel of een factuur heeft één taak: bij de eerste poging worden gelezen door een scanner. Of hij die poging overleeft, wordt lang voordat het pakket het dok bereikt beslist. Het wordt beslist door hoe het symbool op de pagina is geplaatst. De meest voorkomende fout in een Delphi-rapportagepijplijn is om de barcode elders als een bitmap te renderen en die afbeelding in de PDF te plaatsen. Het ziet er op het scherm bij één zoomniveau prima uit, maar de kwaliteit verslechtert overal elders.

Het alternatief is om het symbool als vectorinhoud rechtstreeks op de pagina te tekenen. PDFlibPas ontsluit hiervoor een reeks tekenaanroepen, die betrekking hebben op de 2D-matrixsymbolen QR, PDF417 en DataMatrix, de lineaire families Code128 en GS1-128, en USPS Intelligent Mail voor postautomatisering. Het argument voor vector is niet esthetisch. Het gaat erom of de balken landen waar de scanner ze verwacht.

Waarom vector wint van een geplaatste bitmap

Een barcode is een patroon van balken en spaties, of in twee dimensies een raster van donkere en lichte modules. De decoder meet de verhouding van die breedtes. Alles wat de verhoudingen vervormt is ruis die ten koste gaat van het foutbudget van het symbool. Een gerasterde barcode-afbeelding bevat vaste pixels. Wanneer de PDF wordt gerenderd naar een printer waarvan de punten niet gelijkmatig over het afbeeldingsraster zijn verdeeld, moet de rasterizer herschalen. Randen van modules die scherp zouden moeten zijn, worden dan over twee apparaatpixels verdeeld. Een smalle balk kan dikker worden, een aangrenzende spatie kan dunner worden, en de breedteverhouding waarop de decoder vertrouwt raakt ontregeld.

Getekend als vectorinhoud is hetzelfde symbool een set gevulde rechthoeken die zijn beschreven in PDF-gebruikersruimtecoördinaten. Er is geen vast pixelraster om tegen te vechten. Tijdens het afdrukken rendert het apparaat elke rechthoek op de werkelijke resolutie, zodat elke modulegrens zo scherp is als de hardware toestaat, op elke schaal en elk afdrukformaat. Schaalt u een vectorsymbool op voor een palletlabel of verkleint u het voor een pakket, dan blijft de geometrie exact. Die precisie zorgt ervoor dat de barcode direct de eerste keer goed gelezen wordt, wat het hele doel is van het plaatsen van een barcode op de pagina.

QR-codes en the four correction levels

QR is a 2D matrix symbol read in both axes at once, which is why it packs a lot of data into a small square. Its damage tolerance comes from Reed-Solomon error correction, offered at four levels. Level L recovers roughly 7 percent of the codewords, M about 15 percent, Q about 25 percent, and H about 30 percent. Higher correction is not free. The recovery codewords occupy module capacity, so for a fixed amount of data a higher level forces a denser or physically larger symbol.

De afweging is afhankelijk van de omgeving waarin het symbool wordt gebruikt. Een schoon digitaal document dat alleen vanaf een scherm wordt gescand kan op niveau L blijven en zo compact mogelijk blijven. Een label dat wordt afgedrukt, gehanteerd, beschadigd en mogelijk gedeeltelijk door tape wordt bedekt, vereist Q of H. Die extra redundantie stelt een decoder in staat de gegevens te reconstrueren uit een symbool dat niet meer ongeschonden is. DrawQRCode accepteert de positie en een SymbolSize die de getekende breedte en hoogte vastlegt, plus een EncodeOptions-waarde die de gegevensmodus selecteert (0 voor automatisch, of numerieke, alfanumerieke, ISO-8859-1 en UTF-8-varianten) en een DrawOptions-waarde voor de oriëntatie.

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;

Het correctieniveau zelf wordt door de encoder gekozen om de gegevens in het door u gevraagde symbool te passen. Als u een gegarandeerd hoog niveau nodig heeft voor een zware omgeving, kies dan een ruim formaat voor het symbool, zodat de encoder budget heeft voor redundantie in plaats van gedwongen te worden dit te verkleinen om te passen.

PDF417 voor ID's en verzendlabels

PDF417 is een gestapeld lineair symbool. Elke rij is een korte lineaire barcode, en de rijen stapelen zich op tot een blok, wat verklaart waarom het voorkomt op rijbewijzen, instapkaarten en verzendlabels waar een bredere hoeveelheid gegevens in een rechthoekige indeling moet passen. De foutcorrectie werkt op een schaal van 0 tot 8. Elke stap verdubbelt ruwweg het aantal correctiecodewoorden, zodat niveau 5 veel meer redundantie bevat dan niveau 1, ten koste van meer codewoorden op de pagina.

De vorm van een PDF417-blok is aanpasbaar, en dat is belangrijk omdat het label een vast oppervlak heeft om te vullen. DrawPDF417SymbolEx biedt de instellingen die de basisfunctionaliteit mist. FixedColumns en FixedRows fixeren het aantal gegevenskolommen en -rijen, waarbij 0 betekent dat de encoder beslist. ErrorLevel accepteert -1 voor automatisch of een expliciete waarde van 0 tot 8. ModuleSize is de breedte van het smalste element in de huidige maateenheid, en HeightWidthRatio bepaalt hoe hoog elke module is ten opzichte van zijn breedte. Zo maakt u het blok kort en breed, of juist hoog en smal, passend bij de beschikbare ruimte.

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

Het vastzetten van het aantal kolommen is de gebruikelijke instelling voor een labelsjabloon. Een constant aantal kolommen geeft het blok een voorspelbare breedte, zodat de omliggende lay-out niet verschuift als de gecodeerde gegevens in lengte variëren van het ene document naar het volgende, terwijl de encoder naar beneden toe rijen toevoegt om het verschil op te vangen.

DataMatrix voor kleine markeringen

DataMatrix is het aangewezen symbool wanneer de markering klein moet zijn. Het is een compact 2D-raster dat ECC 200 gebruikt, de moderne Reed-Solomon-methode, en het blijft leesbaar op formaten waar een QR-symbool voor dezelfde gegevens te groot zou zijn. Dit maakt het de standaardkeuze voor directe productmarkering, kleine elektronische componenten en compacte logistieke labels.

DrawDataMatrixSymbol accepteert een ModuleSize voor de pixelgrootte, een Encoding van 1 voor ASCII, en een SymbolSize die 0 is voor automatisch of een van de standaard vierkante of rechthoekige afmetingen (van 10x10 tot 132x132). De parameter Options combineert oriëntatie met de breedte van de stiltezone (quiet zone), waarbij het toevoegen van 100 tot 400 een witte rand van één tot vier modules instelt. De stiltezone is geen versiering. Een decoder heeft die vrije marge nodig om het patroon van het symbool te lokaliseren, en een symbool dat direct tegen andere inkt is geplaatst is onleesbaar.

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

Waar 1D-barcodes nog steeds heersen

Tweedimensionale symbolen trekken de aandacht, maar lineaire barcodes zijn nog steeds dominant in retail en logistiek. De reden is de basis van laserscanners die een enkele sweep lezen. Code128 is het werkpaard voor alfanumerieke gegevens, en de efficiëntie ervan komt voort uit drie tekensets. Set A omvat besturingstekens en hoofdletters, set B omvat het volledige ASCII-bereik en set C is degene die belangrijk is voor getallen. Subset C codeert een paar cijfers in een enkel symboolteken, zodat een reeks numerieke gegevens de helft van de symbooltekens in beslag neemt in vergelijking met set A of B. Dat is de meest compacte manier om een lange numerieke barcode weer te geven, en de Code128-implementatie van PDFlibPas combineert de B- en C-sets automatisch om dit te bereiken.

GS1-128, de standaard die voorheen EAN-128 heette, bouwt voort op Code128 door applicatie-identificaties (Application Identifiers) te bevatten: de voorvoegsels tussen haakjes die een ontvangend systeem vertellen of de volgende cijfers een serienummer, een batchcode of een vervaldatum zijn. De structuur wordt gemarkeerd door FNC1, een speciaal niet-gegevens-teken dat het symbool markeert als GS1-gecodeerd en velden met variabele lengte scheidt. In PDFlibPas tekent u een GS1-128-symbool met DrawBarcode met behulp van het Code128-type en de letterlijke [FNC1]-markering in de gegevensreeks waar elke applicatie-identificatie begint.

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;

Voor post, USPS Intelligent Mail, ook wel OneCode genoemd, codeert routerings- en trackinggegevens in een enkele in hoogte gemoduleerde barcode voor postautomatisering. DrawIntelligentMailBarcode vereist expliciete geometrie voor de balkbreedte, the full bar height, the tracker height, and the space width, met de gegevens geleverd als een string van 20, 25, 29 of 31 cijfers. De expliciete balk- en trackerhoogten zijn noodzakelijk omdat het symbool informatie bevat in de vraag of elke balk een volledige balk, een stijgende balk (ascender) of een dalende balk (descender) is. De postlezer is ervan afhankelijk dat die hoogten exact aan de specificaties voldoen.

Tekenen op de pagina en meten voor de lay-out

Elke hier getoonde aanroep tekent op de momenteel geselecteerde pagina, hetzelfde oppervlak dat uw tekst en afbeeldingen ontvangt. Een barcode wordt dus geproduceerd als onderdeel van de normale documentgeneratie en niet geïmporteerd als een afzonderlijk bestand. Omdat de symbolen vectorinhoud zijn, zijn zowel de gecodeerde gegevens als de ingenomen ruimte bekend op het moment van tekenen, waardoor u ze deterministisch kunt plaatsen.

Bij het bepalen van de lay-out voor de lineaire families is het voordelig om vooraf te meten. GetBarcodeWidth retourneert de totale getekende breedte van een barcode bij een gegeven minimale balkbreedte en barcodetype, zodat u de exacte horizontale ruimte kunt reserveren voordat u gaan tekenen, in plaats van te gokken en overlap te ontdekken nadat de pagina is gebouwd. De 2D-symbolen zijn eenvoudiger te plaatsen omdat u hun getekende formaat rechtstreeks instelt via SymbolSize of ModuleSize, waarna het symbool die ruimte vult. Hoe dan ook is de discipline hetzelfde. Bepaal het fysieke formaat op basis van de scanomgeving, controleer of het symbool in de beschikbare ruimte past en laat de vectorgeometrie elke rand scherp houden van schermvoorbeeld tot uiteindelijke afdruk.

Voor de bredere pagina-opbouw waarin deze barcodes worden geïntegreerd, de technieken in ons artikel over tekst-, afbeeldings- en lettertype-extractie behandelen het teruglezen van inhoud uit een PDF, en toont de handleiding over samenvoegen en splitsen van grote PDF's met directe toegang hoe u documenten met een hoog volume efficiënt assembleert. Beide sluiten aan bij de teken-API die hier wordt beschreven en die wordt geleverd als onderdeel van de Delphi PDF Library voor Delphi en C++Builder, naast de tekst-, grafische, formulier- en handtekening-API's die elders op deze blog worden behandeld.