Odborný článok

Spájanie naskenovaných obrázkov do jedného PDF pomocou PDFium Component v Delphi

Tím pre spracovanie pohľadávok mal k dispozícii tridsať rokov papierových spisov, ktoré prechádzali cez prieťahový skener. Skener ukladal jeden obrázok JPEG na stránku do priečinka s názvami ako 0001.jpg, 0002.jpg a tak ďalej. Archív však v skutočnosti potreboval jeden súbor PDF na každý spis s usporiadanými stránkami, aby si revízor mohol otvoriť jediný dokument namiesto preklikávania stoviek miniatúr obrázkov. Tento posledný krok, teda premena očíslovanej kopy skenov na jeden usporiadaný dokument PDF, je presne témou tohto článku

PDFium Component to zvláda priamo. Okrem vykresľovania a extrakcie textu dokáže tento komponent vytvoriť dokument PDF úplne od nuly: vytvoriť prázdny dokument, pridať prázdnu stránku s ľubovoľnou veľkosťou, umiestniť na ňu obrázok v súradniciach používateľského priestoru a následne ho uložiť. Celý tento proces prebieha na komponente TPdf, takže hromadný prevodník je v podstate len cyklus nad názvami súborov spojený s niekoľkými volaniami

Priebeh konverzie

Pri každom skene musia prebehnúť tri veci. Rozhodnete o veľkosti stránky, umiestnite obrázok na stránku s vynechaním okrajov a prejdete na ďalšiu stránku. PDFium Component poskytuje metódu pre každú z týchto činností: AddPage vytvorí prázdnu stránku so zadanou veľkosťou, AddImage (alebo AddPicture, ak už držíte objekt TPicture) vykreslí bitmapu na aktuálnu stránku a vlastnosť PageNumber určuje, na ktorú stránku budú smerovať nasledujúce volania vykresľovania

Jedným z detailov, ktorý často pletie vývojárov, je súradnicový systém. Používateľský priestor PDF umiestňuje počiatok do ľavého dolného rohu stránky, pričom os Y rastie smerom nahor – čo je presný opak súradníc obrazovky, po ktorých vývojári v Delphi siahajú inštinktívne. Súradnice X, Y, ktoré odovzdávate metóde AddImage, predstavujú ľavý dolný roh obdĺžnika obrázka a Width, Height určujú veľkosť umiestnenia v bodoch, nie veľkosť zdrojového súboru v pixeloch. Ak si to pomiešate, vaše skeny skončia mimo stránky alebo otočené naopak

Vytvorenie dokumentu a pridanie stránky pre každý sken

Začnite s prázdnym dokumentom. Metóda CreateDocument vytvorí nový súbor PDF a ponechá komponent aktívny, takže nie je potrebný žiadny samostatný krok na otvorenie. Odtiaľ prechádzate zoznam naskenovaných súborov a pre každý z nich pridáte stránku, nastavíte ju ako aktuálnu a umiestnite obrázok. Rozmery stránky sú tu nastavené na formát A4 v bodoch (595 × 842 na výšku), čo je štandardná veľkosť listu pre archivovanú korešpondenciu

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;

Každá iterácia vytvorí stránku a okamžite na ňu nastaví vlastnosť PageNumber. Tento druhý riadok je dôležité: metóda AddPage vloží stránku, ale metódy vykresľovania pracujú s tou stránkou, ktorá je aktuálna. Nastavenie PageNumber je to, čo nasmeruje AddImage na stránku, ktorú ste práve vytvorili. Ak to vynecháte, vaše obrázky sa navrstvia na tú stránku, ktorá bola načítaná predtým

V tomto cykle sa skrýva jeden predpoklad: poradie zoznamu Files. Skener pomenuje stránky od 0001.jpg po 0100.jpg, ale výpis adresára ich nemusí vždy vrátiť usporiadané. V momente, keď narazíte na súbor page9.jpg vedľa page10.jpg, bežné triedenie reťazcov zaradí stranu 10 pred stranu 9. Pred spustením cyklu zoznam explicitne usporiadajte a pri skenovaní uprednostňujte názvy doplnené nulami na začiatku, aby lexikálne poradie zodpovedalo poradiu stránok. Poradie stránok je prvá vec, ktorú si revízor všimne, a je to tá najjednoduchšia chyba, ktorej sa dá predísť

Umiestnenie skenu a zachovanie jeho pomeru strán

Sken má zriedka rovnaký tvar ako stránka. Ak ho roztiahnete na celú plochu listu, skreslíte text; ak ho umiestnite v plnej veľkosti v pixeloch, pretečie mimo okrajov. Riešením je zmeniť mierku podľa menšieho z dvoch pomerov (prispôsobenie na šírku alebo prispôsobenie na výšku) a zvyšný priestor vycentrovať. Keďže počiatok súradníc leží vľavo dole, centrovanie znamená rovnomerné rozdelenie zostávajúceho priestoru a jeho pripočítanie k X aj 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;

Tento kód načíta súbor raz, aby zistil jeho rozmery v pixeloch, vypočíta jednotnú mierku a odovzdá cieľový obdĺžnik metóde AddImage. Metóda AddImage prijíma priamo cestu k súboru a smeruje ju cez rovnaký proces spracovania obrázkov ako AddPicture, takže akýkoľvek formát, ktorý grafické jednotky VCL rozpoznajú, funguje bez potreby špeciálnych úprav. Ak už máte obrázok dekódovaný v objekte TPicture (napríklad z panela náhľadu), zavolajte AddPicture(Pic, X, Y, DrawW, DrawH) s rovnakým obdĺžnikom a vynechajte druhé čítanie súboru

Vynechanie dekódovania pri skenoch vo formáte JPEG

Skenery takmer vždy produkujú formát JPEG. Načítanie JPEG do TPicture ho dekóduje na bitmapu a PDFium ho pri ukladaní znova zakóduje, čo predstavuje dva stratové prepočty, ktoré vôbec nepotrebujete. Metóda AddJpegImage vloží pôvodné komprimované bajty priamo do stránky zo streamu, čo je rýchlejšie a vizuálne čistejšie pri hromadnom spracovaní

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;

Súradnice X, Y, DrawW a DrawH stále počítate rovnakým spôsobom, pretože na zmenu mierky potrebujete poznať rozmery v pixeloch. Tieto rozmery prečítajte zo súboru alebo rýchlym rozborom hlavičky a potom odovzdajte surový stream metóde AddJpegImage. Pre skeny vo formáte PNG alebo TIFF je správnou voľbou metóda AddImage; skratku s JPEG si vyhraďte iba pre formát, na ktorý sa skutočne vzťahuje

Označenie každej stránky

Archivované skeny sa jednoduchšie kontrolujú, keď každá stránka nesie názov svojho zdrojového súboru. Metóda AddText vykreslí reťazec na zadaných súradniciach používateľského priestoru, takže popisok sa umiestni priamo pod obrázok. Nezabudnite na obrátenú os Y: ak chcete umiestniť popisok pod sken, odčítavate od dolného okraja obrázka, namiesto toho, aby ste k nemu pripočítavali

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

Posledný bod k ukladaniu. SaveAs is a function that returns a Boolean, so in production code check its result rather than assuming the write succeeded; a full disk or a locked output path fails quietly otherwise. Po dokončení cyklu a zápise súboru máte presne to, čo archív vyžadoval: jeden usporiadaný dokument PDF na spis so stránkami prispôsobenými na šírku, pripravený na čítanie v ľubovoľnom prehliadači

Rovnaké stavebné kamene pokrývajú aj príbuzné úlohy. Zmeňte pravidlo určovania veľkosti stránky a získate fotoknihu s jedným obrázkom na list; ponechajte cyklus, ale čítajte z viacstránkového zdroja TIFF a získate prevodník faxového archívu. Ak sa chcete dozvedieť viac o programovom vytváraní PDF, prečítajte si článok o vytváraní PDF dokumentov úplne od nuly pomocou PDFium Component; ak chcete výsledok neskôr zobraziť na obrazovke, pozrite si článok o prevode PDF stránok na JPEG obrázky pomocou PDFium Component

Komponent PDFium Component od loslab.com spája rozhrania pre vytváranie dokumentov, vykresľovanie a prácu s textom použité v tejto sérii