Teknisk artikkel

Kombinere skannede bilder til én PDF med PDFium-komponent i Delphi

Et team for skadebehandling hadde tretti år med papirfiler som gikk gjennom en arkmater-skanner. Skanneren spyttet ut ett JPEG-bilde per side i en mappe, navngitt 0001.jpg, 0002.jpg, og så videre. Det arkivet egentlig trengte var én PDF per saksmappe, med sidene i rekkefølge, slik at en anmelder kunne åpne ett enkelt dokument i stedet for å klikke gjennom hundre miniatyrbilder. Det siste trinnet, å gjøre en nummerert bunke skanninger om til en enkelt ordnet PDF, er jobben her

PDFium-komponenten håndterer det direkte. Utover gjengivelse og tekstutvinning, kan komponenten bygge en PDF fra bunnen av: opprette et tomt dokument, legge til en tom side i valgfri størrelse, slippe et bilde på den siden i bruker-romskoordinater, og deretter lagre. Hele rørledningen (pipeline) lever på TPdf-komponenten, så en batch-konverterer er en løkke over filnavn pluss en håndfull kall

Formen på konverteringen

Tre ting må skje for hver skanning. Du bestemmer sidestørrelsen, du plasserer bildet inni siden med en marg, og du går videre til neste side. PDFium-komponenten gir deg én metode for hver: AddPage oppretter en tom side ved en gitt størrelse, AddImage (eller AddPicture hvis du allerede har en TPicture) tegner bitkartet inn i den gjeldende siden, og PageNumber forteller komponenten hvilken side etterfølgende tegnekall retter seg mot

Den ene detaljen som feller folk er koordinatsystemet. PDF-brukerrom plasserer opprinnelsen nederst i venstre hjørne av siden, med Y som øker oppover, det motsatte av skjermkoordinatene Delphi-utviklere strekker seg etter på refleks. X, Y-en du sender til AddImage er nederste venstre hjørne av bilderektangelet, og Width, Height er plasseringsstørrelsen i punkter, ikke pikselstørrelsen til kildefilen. Gjør det baklengs, og skanningene dine lander utenfor siden eller opp-ned i forhold til der du forventet dem

Opprette dokumentet og en side per skanning

Start med et tomt dokument. CreateDocument tildeler en fersk PDF og lar komponenten være aktiv, så det er ikke noe eget åpningstrinn. Derfra går du gjennom listen over skannede filer, og for hver enkelt legger du til en side, gjør den gjeldende og plasserer bildet. Sidedimensjonene her er A4 i punkter (595 × 842 portrett), standard arksstørrelse for arkivert korrespondanse

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;

Hver iterasjon oppretter en side og setter umiddelbart PageNumber til den. Den andre linjen betyr noe: AddPage setter inn siden, men tegnemetodene virker på den siden som er gjeldende, så å sette PageNumber er det som retter AddImage mot siden du nettopp lagde. Hopper du over det, vil bildene dine stables på uansett hvilken side som tilfeldigvis ble lastet inn før

En antagelse skjuler seg i den løkken: rekkefølgen til Files. En skanner navngir sider 0001.jpg gjennom 0100.jpg, men en katalogopplisting (directory enumeration) returnerer dem ikke alltid sortert, og i det øyeblikket du treffer page9.jpg ved siden av page10.jpg, setter en ren strengsortering side 10 før side 9. Sorter listen eksplisitt før løkken, og foretrekk null-utfylte navn ved skanning slik at leksikal rekkefølge samsvarer med siderekkefølge. Sidesekvens er det ene en anmelder legger merke til med en gang, og det er den billigste feilen å forhindre

Plassering av en skanning og å beholde sideforholdet

En skanning har sjelden samme form som siden. Hvis du strekker den til å fylle arket, forvrenger du teksten; hvis du plasserer den i full pikselstørrelse, flyter den over. Løsningen er å skalere etter det minste av de to forholdene, breddetilpasning eller høydetilpasning, og sentrere det som er til overs. Fordi opprinnelsen sitter nederst til venstre, betyr sentrering å dele restplassen jevnt og legge den til både X og 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;

Dette laster filen én gang for å lese pikseldimensjonene dens, beregner en enkelt uniform skala, og sender plasseringsrektangelet til AddImage. AddImage aksepterer en filsti direkte og ruter den gjennom den samme bildepipelinen som AddPicture, så ethvert format VCL-grafikkenhetene gjenkjenner, fungerer uten spesialbehandling. Hvis du allerede har bildet dekodet i en TPicture fra en forhåndsvisningsrute, kaller du AddPicture(Pic, X, Y, DrawW, DrawH) med det samme rektangelet og hopper over den andre fillesingen

Hoppe over dekodingen for JPEG-skanninger

Skannere sender nesten alltid ut JPEG. Å laste inn en JPEG i en TPicture dekoder den til et bitkart, og deretter re-koder PDFium den ved lagring, to tapstunge rundturer du ikke trenger. AddJpegImage bygger de originale komprimerte bytene rett inn i siden fra en strøm, som er både raskere og visuelt renere for et stort antall

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 beregner fortsatt X, Y, DrawW og DrawH på samme måte, siden du trenger pikseldimensjonene for å skalere. Les de fra filen eller en rask parsing av headeren, og lever den rå strømmen til AddJpegImage. For PNG- eller TIFF-skanninger er AddImage-banen den rette; reserver JPEG-snarveien for formatet den faktisk gjelder for

Merke hver side

Arkiverte skanninger er enklere å revidere når hver side bærer sitt kildefilnavn. AddText tegner en streng ved en brukerrom-koordinat, så en bildetekst sitter rett under bildet. Husk den inverterte Y-aksen: for å sette en etikett under skanningen, subtraherer du fra bildets bunnkant i stedet for å legge til det

// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
  X, Y - 14, clGray);

Ett siste poeng om lagring. SaveAs er en funksjon som returnerer en boolsk verdi, så i produksjonskode sjekker du resultatet i stedet for å anta at skrivingen lyktes; en full disk eller en låst utdata-sti feiler stille ellers. Når løkken er ferdig og filen er skrevet, har du akkurat det arkivet trengte: én ordnet PDF per saksmappe, sider skalert for å passe, klar til å leses i et hvilket som helst visningsprogram

De samme byggeklossene dekker relaterte jobber. Bytt regelen for dimensjonering per side og du får en fotobok med ett bilde per ark; behold løkken men les fra en TIFF-flersidig kilde og du har en faksarkivkonverterer. Hvis du vil ha et bredere bilde av det å bygge PDF-er programmatisk, se opprette PDF-dokumenter fra bunnen av med PDFium-komponent; for å gjengi resultatet tilbake til skjermen senere, se konvertering av PDF-sider til JPEG-bilder med PDFium-komponent

PDFium-komponenten fra loslab.com samler funksjonene for dokumentoppretting, gjengivelse og tekst som brukes gjennom hele denne serien