Ett skaderegleringsteam hade trettio år av pappersakter som gick igenom en arkmatad skanner. Skannern spottade ut en JPEG per sida i en mapp, namngivna 0001.jpg, 0002.jpg och så vidare. Vad arkivet egentligen behövde var en PDF per ärendeakt, med sidorna i ordning, så en granskare (reviewer) kunde öppna ett enda dokument istället för att klicka sig igenom hundra bildminiatyrer (thumbnails). Det sista steget, att förvandla en numrerad hög med skanningar till en enda ordnad PDF, är jobbet här
PDFium Component hanterar det direkt. Utöver rendering och textextraktion kan komponenten bygga en PDF från grunden: skapa ett tomt dokument, lägga till en tom sida med valfri storlek, släppa en bild på den sidan i användarrymdens koordinater, och sedan spara. Hela flödet lever på TPdf-komponenten, så en satskonverterare (batch converter) är en loop över filnamn plus en handfull anrop
Formen på konverteringen
Tre saker måste hända för varje skanning. Du bestämmer sidstorleken, du placerar bilden inuti sidan och lämnar en marginal, och du går vidare till nästa sida. PDFium Component ger dig en metod för varje: AddPage skapar en tom sida i en given storlek, AddImage (eller AddPicture om du redan har en TPicture) ritar bitmappen på den aktuella sidan, och PageNumber talar om för komponenten vilken sida som efterföljande ritanrop siktar på
Den detalj som får folk att snubbla är koordinatsystemet. PDF:ens användarrymd lägger origo (origin) i det nedre vänstra hörnet av sidan, med Y ökande uppåt, raka motsatsen till de skärmkoordinater Delphi-utvecklare sträcker sig efter av ren reflex. Det X, Y du skickar till AddImage är den nedre vänstra hörnan av bildrektangeln, och Width, Height är placeringsstorleken i punkter, inte källfilens pixelstorlek. Får du det baklänges landar dina skanningar utanför sidan eller upp och ner i förhållande till var du förväntade dig dem
Att skapa dokumentet och en sida per skanning
Börja med ett tomt dokument. CreateDocument allokerar en ny PDF och lämnar komponenten aktiv, så det finns inget separat öppet steg. Därifrån går du igenom listan över skannade filer, och för varje fil lägger du till en sida, gör den aktuell och placerar bilden. Siddimensionerna här är A4 i punkter (595 × 842 stående), den standardiserade arkstorleken för arkiverad korrespondens
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;
Varje iteration skapar en sida och ställer omedelbart in PageNumber på den. Den andra raden spelar roll: AddPage lägger in sidan men ritmetoderna agerar på den sida som är aktuell, så att ställa in PageNumber är det som riktar AddImage mot den sida du just skapade. Hoppa över det och dina bilder staplas på den sida som råkade vara laddad före
Ett antagande döljer sig i den loopen: ordningen på Files. En skanner döper sidor 0001.jpg till 0100.jpg, men en kataloguppräkning returnerar dem inte alltid sorterade, och i samma ögonblick du stöter på page9.jpg bredvid page10.jpg lägger en vanlig strängsortering sida 10 före sida 9. Sortera listan explicit före loopen, och föredra nollutfyllda (zero-padded) namn vid skanningstillfället så att den lexikaliska ordningen matchar sidordningen. Sidsekvensen är den enda sak en granskare märker omedelbart, och det är det billigaste misstaget att förhindra
Att placera en skanning och behålla dess bildförhållande
En skanning har sällan samma form som sidan. Om du sträcker (stretch) ut den för att fylla arket förvränger du texten; om du placerar den i full pixelstorlek flödar den över. Lösningen är att skala med det minsta av de två förhållandena (ratios), breddpassning eller höjdpassning, och centrera det som blir över. Eftersom origo sitter längst ner till vänster innebär centrering att dela det överblivna utrymmet jämnt och lägga till det på både X och 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;
Detta laddar filen en gång för att läsa dess pixeldimensioner, beräknar en enda enhetlig skala och skickar placeringsrektangeln till AddImage. AddImage accepterar en filsökväg direkt och dirigerar den genom samma bildpipeline som AddPicture, så alla format som VCL:s grafikenheter känner igen fungerar utan särbehandling (special-casing). Om du redan har bilden avkodad i en TPicture från ett förhandsgranskningsfönster, anropar du AddPicture(Pic, X, Y, DrawW, DrawH) med samma rektangel och hoppar över den andra filläsningen
Att hoppa över avkodningen för JPEG-skanningar
Skannrar avger nästan alltid JPEG. Att ladda en JPEG in i en TPicture avkodar den till en bitmapp, och sedan kodar PDFium om den vid sparning, två destruktiva (lossy) rundresor (round trips) du inte behöver. AddJpegImage bäddar in de ursprungliga komprimerade byten rakt in i sidan från en ström (stream), vilket är både snabbare och visuellt renare för en satskörning med stor volym
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;
Du beräknar fortfarande X, Y, DrawW och DrawH på samma sätt, eftersom du behöver pixeldimensionerna för att skala. Läs dem från filen eller en snabb huvudtolkning (header parse), och lämna sedan den råa strömmen till AddJpegImage. För PNG- eller TIFF-skanningar är AddImage-vägen den rätta; reservera JPEG-genvägen för det format som den faktiskt gäller
Att märka varje sida
Arkiverade skanningar är lättare att granska när varje sida bär sitt källfilnamn. AddText ritar en sträng vid en användarrymdskoordinat, så en bildtext (caption) sitter precis under bilden. Kom ihåg den inverterade Y-axeln: för att sätta en etikett (label) under skanningen, drar du ifrån från bildens underkant snarare än att lägga till på den
// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
En sista punkt om att spara. SaveAs är en funktion som returnerar en Boolean, så i produktionskod kontrollerar du dess resultat snarare än att anta att skrivningen lyckades; en full disk eller en låst utmatningssökväg misslyckas tyst annars. När loopen avslutas och filen är skriven, har du exakt vad arkivet behövde: en ordnad PDF per ärendeakt, sidor skalade för att passa, redo att läsas i valfri visare
Samma byggklossar täcker relaterade jobb. Byt (swap) ut regeln för storleksändring per sida så får du en fotobok med en bild per ark; behåll loopen men läs från en flersidig TIFF-källa så har du en fax-arkivkonverterare. Om du vill ha en bredare bild av att bygga PDF:er programmatiskt, se att skapa PDF-dokument från grunden med PDFium Component; för att rendera resultatet tillbaka till skärmen senare, se att konvertera PDF-sidor till JPEG-bilder med PDFium Component
PDFium Component från loslab.com buntar (bundles) ihop de dokument-skapar-, renderings- och text-API:er som används genomgående i denna serie