Teknisk artikel

Streckkoder i PDF med Delphi: QR, PDF417, DataMatrix

En streckkod på en fraktetikett (shipping label) eller en faktura har ett jobb, vilket är att bli läst av en skanner vid första försöket (first pass). Huruvida den överlever det försöket avgörs långt innan paketet når lastkajen (dock). Det avgörs av hur symbolen sattes på sidan. Det vanligaste misstaget i en Delphi-rapporteringspipeline är att rendera streckkoden som en bitmapp (bitmap) någon annanstans och placera den bilden i PDF:en. Den ser bra ut på skärmen vid en zoomnivå och försämras sedan överallt annars

Alternativet är att rita symbolen som vektorinnehåll, direkt på sidan. PDFlibPas exponerar en familj av ritningsanrop för exakt detta, täckande 2D-matrissymbolerna QR, PDF417, och DataMatrix, de linjära familjerna genom Code128 och GS1-128, och USPS Intelligent Mail för postautomation. Argumentet för vektor är inte estetiskt. Det handlar om ifall strecken (bars) landar där skannern förväntar sig dem

Varför vektor slår en placerad bitmapp

En streckkod är ett mönster av streck och mellanrum (spaces), eller i två dimensioner ett rutnät (grid) av mörka och ljusa moduler. Avkodaren (decoder) fungerar genom att mäta förhållandet (ratio) mellan de bredderna. Allt som förvränger förhållandena är brus som äter upp symbolens felbudget. En rastrerad (rasterized) streckkodsbild bär på fasta pixlar. När PDF:en renderas till en skrivare vars punkter (dots) inte delas jämnt in i bildens rutnät måste rastreraren sampla om (resample), och modulkanter som borde vara skarpa blir spridda över två enhetspixlar. Ett smalt streck kan bli tjockare, ett intilliggande mellanrum kan bli tunnare, och det breddförhållande som avkodaren förlitar sig på driver iväg

Ritad som vektorinnehåll är samma symbol en uppsättning fyllda rektanglar beskrivna i PDF:ens användarrymdskoordinater (user-space coordinates). Det finns inget fast pixelrutnät att kämpa mot. Vid utskriftstillfället renderar enheten varje rektangel vid den upplösning den faktiskt har, så varje modulkant är så skarp som hårdvaran tillåter, i vilken skala (scale) och vilken utskriftsstorlek som helst. Skala upp en vektorsymbol för en palletikett (pallet label) eller krymp den för ett paket och geometrin förblir exakt. Den precisionen är vad som håller avläsningsfrekvensen vid första försöket hög, vilket är hela poängen med att sätta en streckkod på sidan

QR-koder och de fyra korrigeringsnivåerna

QR är en 2D-matrissymbol som läses i båda axlarna samtidigt, vilket är varför den packar mycket data i en liten kvadrat. Dess skadetolerans (damage tolerance) kommer från Reed-Solomon-felkorrigering, som erbjuds på fyra nivåer. Nivå L återställer ungefär 7 procent av kodorden, M ungefär 15 procent, Q ungefär 25 procent, och H ungefär 30 procent. Högre korrigering är inte gratis. Återställningskodorden (recovery codewords) upptar modulkapacitet, så för en fast mängd data tvingar en högre nivå fram en tätare (denser) eller fysiskt större symbol

Avvägningen (trade) är en fråga om den miljö symbolen kommer att leva i. Ett rent digitalt dokument som bara någonsin kommer att skannas från en skärm kan sitta på L och förbli kompakt (compact). En etikett som kommer att skrivas ut, hanteras, skrapas (scuffed) och möjligen delvis täckas av tejp vill ha Q eller H, eftersom den extra redundansen är vad som låter en avkodare rekonstruera (reconstruct) nyttolasten (payload) från en symbol som inte längre är i nyskick (pristine). DrawQRCode tar positionen och en SymbolSize som fixerar den ritade bredden och höjden, plus ett EncodeOptions-värde som väljer dataläge (0 för automatiskt, eller varianter för numeriskt, alfanumeriskt, ISO-8859-1 och UTF-8) samt ett DrawOptions-värde för orientering

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;

Själva korrigeringsnivån (correction level) väljs av kodaren för att passa in datan i den symbol du bad om. Om du behöver en garanterat hög nivå för en hård miljö, dimensionera symbolen generöst så att kodaren har modulbudgeten (module budget) att spendera på redundans snarare än att tvingas ned för att få plats

PDF417 för ID:n och fraktetiketter

PDF417 är en staplad (stacked) linjär symbol. Varje rad är en kort linjär streckkod, och raderna staplas för att bilda ett block, vilket är varför den dyker upp på körkort, boardingkort och fraktetiketter (shipping labels) hos transportörer där en bredare remsa av data måste få plats på en rektangulär yta. Dess felkorrigering löper på en skala från 0 till 8. Varje steg ungefär fördubblar antalet korrigeringskodord, så nivå 5 bär mycket mer redundans än nivå 1, på bekostnad av fler kodord på sidan

Formen på ett PDF417-block är justerbar, och det spelar roll eftersom etiketten har en fast yta att fylla. DrawPDF417SymbolEx exponerar de kontroller som det grundläggande anropet inte gör. FixedColumns och FixedRows nålar fast datakolumnantalet och radantalet, där 0 betyder låt kodaren bestämma. ErrorLevel tar -1 för automatiskt eller en explicit 0 till 8. ModuleSize är bredden på det smalaste elementet i den nuvarande måttenheten (measurement unit), och HeightWidthRatio ställer in hur hög varje modul är relativt dess bredd, vilket är hur man gör blocket kort och brett eller långt och smalt för att matcha det utrymme man har

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

Att fixera kolumner är den vanliga hävstången (lever) på en etikettmall. Ett konstant kolumnantal ger blocket en förutsägbar bredd, så att den omgivande layouten inte skiftar när den kodade nyttolasten (encoded payload) ändrar längd från ett dokument till nästa, medan kodaren lägger till rader nedåt för att absorbera skillnaden

DataMatrix för små markeringar (small marks)

DataMatrix är symbolen att sträcka sig efter när markeringen måste vara liten. Det är ett kompakt 2D-rutnät (2D grid) som använder ECC 200, det moderna Reed-Solomon-schemat, och det förblir läsbart i storlekar där en QR-symbol för samma data skulle vara klumpig. Det gör den till standardvalet för direkt märkning av delar (direct part marking), små elektroniska komponenter, och täta logistiketiketter

DrawDataMatrixSymbol tar en ModuleSize för punktavståndet (dot pitch), en Encoding på 1 för ASCII, och en SymbolSize som är antingen 0 för automatisk eller en av standardmåtten, kvadratiska eller rektangulära, från 10x10 upp till 132x132. Parametern Options kombinerar orientering med bredden på tyst zon (quiet-zone width), där att addera 100 till 400 sätter en till fyra moduler vit kant (white border). Den tysta zonen är inte dekoration. En avkodare behöver den klara marginalen (clear margin) för att hitta symbolens sökmönster (finder pattern), och en symbol inkilad (crammed) mot annat bläck är en symbol som misslyckas med att fångas (acquire)

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

Där 1D-streckkoder fortfarande regerar

Tvådimensionella symboler får uppmärksamheten, men linjära streckkoder äger fortfarande stora delar av detaljhandel och logistik, och anledningen är den installerade basen av laserskannrar som läser i en enda svepning (sweep). Code128 är arbetshästen för alfanumerisk data, och dess effektivitet kommer från tre teckenuppsättningar. Uppsättning A täcker kontrolltecken och versaler, uppsättning B täcker hela det utskrivbara ASCII-området, och uppsättning C är den som spelar roll för siffror. Delmängd (Subset) C kodar in ett par siffror i ett enda symboltecken, så en radda numerisk data tar hälften av de symboltecken den skulle i uppsättning A eller B. Det är det mest kompakta sättet att lägga ner en lång numerisk streckkod, och PDFlibPas Code128-implementering kombinerar B- och C-uppsättningarna automatiskt för att nå dit

GS1-128, standarden som tidigare hette EAN-128, bygger på Code128 genom att bära Applikationsidentifierare (Application Identifiers), de klamrade prefixen (bracketed prefixes) som talar om för ett mottagande system ifall de efterföljande siffrorna är ett serienummer, en batchkod eller ett utgångsdatum. Strukturen är markerad av FNC1, ett speciellt icke-data-tecken (non-data character) som flaggar symbolen som GS1-kodad och separerar fält med variabel längd. I PDFlibPas ritar du en GS1-128-symbol med DrawBarcode med hjälp av Code128-typen och den bokstavliga (literal) markören [FNC1] placerad i datasträngen där varje applikationsidentifierare börjar

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;

För post, USPS Intelligent Mail, även kallad OneCode, kodas routing- och spårningsdata in i en enda höjdmodulerad (height-modulated) streckkod för postautomation. DrawIntelligentMailBarcode tar explicit geometri för streckbredd, den fulla streckhöjden (bar height), spårarhöjden (tracker height), och mellanrumsbredden (space width), med datan tillhandahållen som en sträng på 20, 25, 29 eller 31 enbart siffror. De explicita streck- och spårarhöjderna (bar and tracker heights) existerar eftersom symbolen bär information i huruvida varje streck är ett fullt streck, en uppstickare (ascender), eller en nerstickare (descender), och postläsaren är beroende av att dessa höjder hålls till specifikationen

Att rita in i sidan och mäta för layout

Varje anrop som visas här ritar in i innehållet (content) på den för närvarande valda sidan, samma yta som tar emot din text och dina bilder, så en streckkod produceras som en del av normal dokumentgenerering snarare än importerad som en separat tillgång (asset). Eftersom symbolerna är vektorinnehåll, är datan de kodar och geometrin de upptar båda kända vid ritningstillfället (draw time), vilket är vad som låter dig placera dem deterministiskt

Layout för de linjära familjerna drar nytta av att mäta först. GetBarcodeWidth returnerar den totala ritade bredden för en streckkod för en given smal-streck-bredd (narrow-bar width) och streckkodstyp, så att du kan reservera det exakta horisontella utrymmet (horizontal space) innan du åtar dig ritningen (commit the draw), i stället för att gissa och upptäcka en överlappning efter att sidan är byggd. 2D-symbolerna är enklare att placera eftersom du sätter deras ritade storlek direkt genom SymbolSize eller ModuleSize, och symbolen fyller det fotavtrycket (footprint). Oavsett vilket är disciplinen densamma. Bestäm den fysiska storleken utifrån skanningsmiljön, bekräfta att symbolen passar det fack du har, och låt vektorgeometrin hålla varje kant skarp från skärmförhandsgranskning (screen preview) till slutgiltig utskrift

För det bredare sidbyggande arbetsflödet som dessa streckkoder släpps ner i, täcker teknikerna i vår artikel om text-, bild- och typsnittsextraktion att läsa ut innehåll tillbaka från en PDF, och guiden till stor PDF-sammanslagning och delning med direktåtkomst visar hur man sätter ihop högvolymdokument effektivt. Båda parar ihop (pair) sig naturligt med det ritnings-API som beskrivs här, vilket skeppas (ships) som en del av Delphi PDF Library för Delphi och C++Builder jämte de text-, grafik-, formulär- och signatur-API:er som täcks på andra ställen på denna blogg