Een schadeafhandelingsteam had dertig jaar aan papieren dossiers die door een documentenscanner met automatische invoer gingen. De scanner produceerde één JPEG per pagina in een map, genaamd 0001.jpg, 0002.jpg, enzovoort. Wat het archief eigenlijk nodig had, was één PDF per dossier, met de pagina's in de juiste volgorde, zodat een beoordelaar één enkel document kon openen in plaats van door honderd miniatuurafbeeldingen te moeten klikken. Die laatste stap, het omzetten van een genummerde stapel scans in één geordende PDF, is de taak die we hier behandelen.
PDFium VCL handelt dit rechtstreeks af. Naast weergave en tekstextractie kan de component vanaf nul een PDF opbouwen: een leeg document maken, een lege pagina toevoegen in elke gewenste grootte, een afbeelding op die pagina plaatsen in gebruikerscoördinaten en vervolgens opslaan. De hele pijplijn bevindt zich in de component TPdf, dus een batch-converter is simpelweg een lus over bestandsnamen in combinatie met een handvol aanroepen.
De opzet van de conversie
Voor elke scan moeten er drie dingen gebeuren. U bepaalt de paginagrootte, u plaatst de afbeelding op de pagina met een marge en u gaat door naar de volgende pagina. PDFium VCL biedt voor elk een methode: AddPage maakt een lege pagina met een bepaalde grootte, AddImage (of AddPicture als u al een TPicture heeft) tekent de bitmap op de huidige pagina, en PageNumber geeft aan de component door op welke pagina volgende tekenacties moeten worden uitgevoerd.
Het detail waar ontwikkelaars vaak over struikelen, is het coördinatensysteem. De PDF-gebruikersruimte plaatst de oorsprong in de linksonderhoek van de pagina, waarbij de Y-as naar boven toe toeneemt. Dit is het tegenovergestelde van de schermcoördinaten die Delphi-ontwikkelaars uit reflex gebruiken. De X, Y die u doorgeeft aan AddImage is de linksonderhoek van de afbeeldingsrechthoek, en Width, Height zijn de afmetingen voor plaatsing in punten (points), niet de pixelgrootte van het bronbestand. Als u dit omdraait, komen uw scans buiten de pagina of ondersteboven terecht in vergelijking met wat u verwachtte.
Het document maken en een pagina per scan toevoegen
Begin met een leeg document. CreateDocument wijst een nieuw PDF-document toe en activeert de component direct, dus er is geen aparte openingsstap nodig. Vanaf daar loopt u door 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), de standaard papiergrootte 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 aan en stelt onmiddellijk PageNumber hierop in. Die tweede regel is cruciaal: AddPage voegt de pagina toe, maar de tekenmethoden werken op de pagina die op dat moment actief is. Door PageNumber in te stellen, zorgt u ervoor dat AddImage zich richt op de zojuist gemaakte pagina. Slaat u dit over, dan worden uw afbeeldingen gestapeld op de pagina die toevallig als laatste was geladen.
Er schuilt één aanname in die lus: de volgorde van de Files. Een scanner noemt pagina's bijvoorbeeld 0001.jpg tot en met 0100.jpg, maar een enumeratie van de map retourneert ze niet altijd gesorteerd. Zodra u page9.jpg naast page10.jpg heeft staan, zal een eenvoudige tekstsortering pagina 10 vóór pagina 9 plaatsen. Sorteer de lijst expliciet vóór de lus en geef tijdens het scannen de voorkeur aan namen met voorloopnullen, zodat de alfabetische volgorde overeenkomt met de paginavolgorde. Paginavolgorde is het eerste wat een beoordelaar opmerkt, en het is de eenvoudigste fout om te voorkomen.
Een scan plaatsen en de aspectverhouding behouden
Een scan heeft zelden exact dezelfde vorm als de pagina. Als u de afbeelding uitrekt om het hele vel te vullen, vervormt u de tekst; als u deze op de werkelijke pixelgrootte plaatst, loopt deze buiten de paginaranden. De oplossing is om te schalen op basis van de kleinste van de twee verhoudingen (passend in de breedte of passend in de hoogte) en de resterende ruimte te centreren. Omdat de oorsprong linksonder ligt, betekent centreren dat de overgebleven ruimte gelijkmatig wordt verdeeld en wordt opgeteld bij zowel X als Y.
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 eenmaal om de pixelafmetingen te lezen, berekent een uniforme schaal en geeft de plaatsingsrechthoek door aan AddImage. AddImage accepteert rechtstreeks een bestandspad en leidt dit via dezelfde afbeeldingspijplijn als AddPicture, waardoor elk formaat dat door de VCL-grafische eenheden wordt herkend, werkt zonder speciale uitzonderingen. Als u de afbeelding al gedecodeerd in een TPicture heeft (bijvoorbeeld vanuit een voorbeeldscherm), roep dan AddPicture(Pic, X, Y, DrawW, DrawH) aan met dezelfde rechthoek en sla de tweede bestandslezing over.
Het decoderen overslaan voor JPEG-scans
Scanners leveren bijna altijd JPEG-bestanden af. Het laden van een JPEG in een TPicture decodeert deze naar een bitmap, waarna PDFium deze bij het opslaan opnieuw codeert: twee conversies met kwaliteitsverlies die u eenvoudig kunt vermijden. AddJpegImage sluit de originele gecomprimeerde bytes rechtstreeks vanuit een stream in op de pagina, wat zowel sneller is als visueel schoner voor een grote batchverwerking.
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 heeft voor het schalen. Lees deze uit het bestand of via een snelle header-analyse, en geef vervolgens de onbewerkte stream door aan AddJpegImage. Voor PNG- of TIFF-scans is de AddImage-route de juiste keuze; bewaar de JPEG-snelkoppeling uitsluitend voor het formaat waarop deze daadwerkelijk van toepassing is.
Elke pagina labelen
Gearchiveerde scans zijn eenvoudiger te controleren wanneer elke pagina de naam van het bronbestand bevat. AddText tekent een tekstreeks op een gebruikerscoördinaat, zodat een onderschrift net onder de afbeelding wordt geplaatst. Denk aan de omgekeerde Y-as: om een label onder de scan te plaatsen, trekt u af 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 een laatste punt over het opslaan. SaveAs is een functie die een Boolean retourneert. Controleer in productieve code dus het resultaat in plaats van aan te nemen dat het schrijven is geslaagd; een volle schijf of een vergrendeld uitvoerpad faalt anders geruisloos. Zodra de lus is voltooid en het bestand is weggeschreven, heeft u precies wat het archief nodig had: één geordende PDF per dossier, met pagina's die passend zijn geschaald, klaar om te worden geopend in elke PDF-viewer.
Dezelfde bouwstenen zijn geschikt voor vergelijkbare taken. Pas de paginagrootte-regel per pagina aan en u krijgt een fotoboek met één afbeelding per pagina; behoud de lus maar lees uit een TIFF-bron met meerdere pagina's en u heeft een faxarchief-converter. Wilt u een breder beeld van het programmatisch opbouwen van PDF's, zie dan PDF-documenten vanaf nul maken met PDFium VCL; om het resultaat later weer op het scherm weer te geven, zie PDF-pagina's converteren naar JPEG-afbeeldingen met PDFium VCL.
PDFium VCL Component van loslab.com bundelt de API's voor documentcreatie, rendering en tekst die in deze serie worden gebruikt.