Een team voor claimverwerking had dertig jaar aan papieren dossiers die door een sheet-fed scanner (velleninvoer-scanner) gingen. De scanner spuugde één JPEG per pagina uit in een map, genaamd 0001.jpg, 0002.jpg, enzovoort. Wat het archief daadwerkelijk nodig had, was één PDF per dossier (case file), met de pagina's in de juiste volgorde, zodat een beoordelaar een enkel document kon openen in plaats van door honderd miniatuurafbeeldingen te moeten klikken. Die laatste stap, het omzetten van een genummerde stapel scans in een enkele geordende PDF, is hier de taak
De PDFium-component handelt dit direct af. Naast renderen en tekstextractie kan de component een PDF vanaf nul opbouwen (from scratch): maak een leeg document, voeg een lege pagina toe waarvan u de grootte zelf bepaalt, plaats een afbeelding op die pagina in user-space coördinaten en sla op. De gehele pijplijn bevindt zich op de TPdf-component, dus een batchconverter is een loop (lus) over bestandsnamen plus een handvol aanroepen
De vorm van de conversie
Voor elke scan moeten er drie dingen gebeuren. U bepaalt de paginagrootte, u plaatst de afbeelding op de pagina waarbij u een marge overlaat, en u gaat door naar de volgende pagina. De PDFium-component biedt voor elk van deze stappen één methode: AddPage maakt een lege pagina op een opgegeven formaat, AddImage (of AddPicture als u al een TPicture vasthoudt) tekent de bitmap op de huidige pagina, en PageNumber vertelt de component op welke pagina daaropvolgende tekenaanroepen (draw calls) zich moeten richten
Het ene detail dat mensen op het verkeerde been zet, is het coördinatensysteem. De PDF user space plaatst de oorsprong in de linkerbenedenhoek van de pagina, waarbij Y naar boven toe toeneemt. Dit is het tegenovergestelde van de schermcoördinaten waar Delphi-ontwikkelaars uit reflex naar grijpen. De X, Y die u aan AddImage doorgeeft, is de linkerbenedenhoek van de afbeeldingsrechthoek, en Width, Height vormen de plaatsingsgrootte in punten, niet de pixelgrootte van het bronbestand. Als u dat omdraait (get that backwards), belanden uw scans buiten de pagina of ondersteboven ten opzichte van waar u ze had verwacht
Het document maken en een pagina per scan toevoegen
Begin met een leeg document. CreateDocument wijst een verse PDF toe en laat de component actief, er is dus geen afzonderlijke open-stap. Van daaruit doorloopt u de lijst met gescande bestanden, en voor elk bestand voegt u een pagina toe, maakt u deze actueel en plaatst u de afbeelding. De pagina-afmetingen zijn hier A4 in punten (595 × 842 staand/portrait), het standaard velformaat voor gearchiveerde correspondentie
procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
PageW = 595.0; // A4 width in points
PageH = 842.0; // A4 height in points
Margin = 36.0; // half-inch border around each scan
var
I: Integer;
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // new, empty, already active
for I := 0 to Files.Count - 1 do
begin
Pdf.AddPage(I + 1, PageW, PageH); // 1-based page index
Pdf.PageNumber := I + 1; // make the new page current
PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
end;
Pdf.SaveAs(OutputPath);
finally
Pdf.Free;
end;
end;
Elke iteratie maakt een pagina en stelt PageNumber onmiddellijk in op die pagina. Die tweede regel is belangrijk: AddPage voegt de pagina in, maar de tekenmethoden werken in op de pagina die op dat moment huidig (current) is. Het instellen van PageNumber is dus wat AddImage richt op de pagina die u zojuist hebt gemaakt. Sla dit over en uw afbeeldingen stapelen zich op wat voor pagina er daarvoor dan ook toevallig geladen was
Er gaat één aanname schuil in die loop: de volgorde van Files. Een scanner noemt pagina's 0001.jpg tot en met 0100.jpg, maar een mapopsomming (directory enumeration) retourneert ze niet altijd gesorteerd. Op het moment dat u page9.jpg naast page10.jpg treft, plaatst een gewone stringsortering pagina 10 vóór pagina 9. Sorteer de lijst expliciet vóór de loop (lus) en geef de voorkeur aan namen die tijdens de scan met nullen zijn opgevuld (zero-padded), zodat de lexicale volgorde overeenkomt met de paginavolgorde. De paginavolgorde is het enige dat een beoordelaar (reviewer) onmiddellijk opvalt, en het is de goedkoopste fout om te voorkomen
Een scan plaatsen met behoud van de beeldverhouding (aspect ratio)
Een scan heeft zelden dezelfde vorm als de pagina. Als u deze uitrekt om het vel te vullen, vervormt u de tekst; als u de scan op volledige pixelgrootte plaatst, loopt deze over. De oplossing is om te schalen aan de hand van de kleinste van de twee verhoudingen: op basis van breedte (width-fit) of op basis van hoogte (height-fit), en te centreren wat overblijft. Omdat de oorsprong zich linksonder bevindt, betekent centreren dat de resterende ruimte gelijkmatig wordt verdeeld en aan zowel X als Y wordt toegevoegd
procedure TArchiveForm.PlaceScan(Pdf: TPdf; const FileName: string;
PageW, PageH, Margin: Double);
var
Pic: TPicture;
AvailW, AvailH, Scale, DrawW, DrawH, X, Y: Double;
begin
Pic := TPicture.Create;
try
Pic.LoadFromFile(FileName); // BMP, JPG, PNG, etc. via the VCL graphics units
AvailW := PageW - 2 * Margin;
AvailH := PageH - 2 * Margin;
// Fit inside the margins without distorting the scan.
Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
DrawW := Pic.Width * Scale;
DrawH := Pic.Height * Scale;
// Center: leftover space split evenly. Y measured from the page bottom.
X := (PageW - DrawW) / 2;
Y := (PageH - DrawH) / 2;
Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
finally
Pic.Free;
end;
end;
Dit laadt het bestand één keer om de pixelafmetingen te lezen, berekent één enkele uniforme schaal en geeft de plaatsingsrechthoek door aan AddImage. AddImage accepteert direct een bestandspad en leidt het door dezelfde afbeeldingspijplijn als AddPicture, dus elk formaat dat de VCL graphics-units herkennen, werkt zonder uitzonderingsregels (special-casing). Als u de afbeelding al gedecodeerd (decoded) in een TPicture uit een voorbeeldvenster hebt, roept u AddPicture(Pic, X, Y, DrawW, DrawH) op met dezelfde rechthoek en slaat u de tweede leesbewerking van het bestand over
Het decoderen overslaan voor JPEG-scans
Scanners sturen (emit) bijna altijd JPEG uit. Het laden van een JPEG in een TPicture decodeert deze naar een bitmap, waarna PDFium deze bij het opslaan opnieuw codeert (re-encodes). Dat zijn twee lossy round trips die u niet nodig hebt. AddJpegImage sluit de originele gecomprimeerde bytes rechtstreeks vanuit een stream op de pagina in (embeds), wat zowel sneller als visueel schoner is voor een batch met grote volumes
var
Stream: TFileStream;
begin
// ... after AddPage + PageNumber for the current page ...
Stream := TFileStream.Create(FileName, fmOpenRead);
try
// Embeds the JPEG bytes as-is; no decode/re-encode cycle.
Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
finally
Stream.Free;
end;
end;
U berekent X, Y, DrawW en DrawH nog steeds op dezelfde manier, aangezien u de pixelafmetingen nodig hebt om te schalen. Lees deze uit het bestand of middels een snelle parse (header parse), en overhandig de onbewerkte (raw) stream vervolgens aan AddJpegImage. Voor PNG- of TIFF-scans is de AddImage-route de juiste; bewaar de JPEG-snelkoppeling (shortcut) voor het formaat waarop deze daadwerkelijk van toepassing is
Elke pagina labelen
Gearchiveerde scans zijn makkelijker te controleren wanneer elke pagina zijn eigen bronbestandsnaam draagt. AddText tekent een string op een user-space coördinaat, zodat een bijschrift vlak onder de afbeelding staat. Denk aan de omgekeerde Y-as: om een label onder de scan te plaatsen, trekt u af (subtract) van de onderrand van de afbeelding in plaats van dat u erbij optelt
// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
Nog één laatste punt over het opslaan. SaveAs is een functie die een Boolean retourneert. Controleer in productiecode dus het resultaat in plaats van aan te nemen dat het schrijven is geslaagd; een volle schijf of een vergrendeld (locked) uitvoerpad zal anders stilletjes falen. Zodra de loop (lus) is voltooid en het bestand is geschreven, hebt u precies wat het archief nodig had: één geordende PDF per dossier (case file), pagina's op maat geschaald en klaar om in elke willekeurige viewer gelezen te worden
Dezelfde bouwstenen (building blocks) dekken gerelateerde taken. Verander de regel voor paginagrootte (per-page sizing) en u krijgt een fotoboek met één afbeelding per vel; behoud de lus maar lees uit een TIFF-bron met meerdere pagina's (multi-page source) en u hebt een converter voor een faxarchief. Als u het bredere plaatje wilt zien van het programmatisch opbouwen van PDF's, raadpleeg dan PDF-documenten vanaf nul creëren met de PDFium-component; om het resultaat later weer naar het scherm te renderen, zie PDF-pagina's converteren naar JPEG-afbeeldingen met de PDFium-component
De PDFium-component van loslab.com bundelt de API's voor documentcreatie, rendering en tekst die in deze serie worden gebruikt