Tým zpracovávající pojistné události měl třicet let papírových spisů, které procházely skenerem s podavačem listů. Skener vyplivl jeden JPEG na stránku do složky, pojmenovaný 0001.jpg, 0002.jpg a tak dále. Co ale archiv ve skutečnosti potřeboval, byl jeden PDF dokument na spisový případ, se stránkami popořadě, aby revizor mohl otevřít jediný dokument namísto proklikávání se přes sto miniatur obrázků. Ten poslední krok – přeměnit očíslovanou hromadu skenů na jedno uspořádané PDF – je právě tímto úkolem
Komponenta PDFium to zvládne přímo. Kromě vykreslování a extrakce textu dokáže komponenta postavit PDF od nuly: vytvořit prázdný dokument, přidat čistou stránku libovolné velikosti, umístit na tuto stránku obrázek v souřadnicích uživatelského prostoru a poté uložit. Celé workflow žije na komponentě TPdf, takže dávkový konvertor představuje cyklus přes názvy souborů plus několik volání
Tvar konverze
Pro každý sken se musí stát tři věci. Rozhodnete o velikosti stránky, umístíte obrázek dovnitř stránky s ponecháním okraje a přejdete na další stránku. Komponenta PDFium vám na každou z nich dává jednu metodu: AddPage vytvoří prázdnou stránku dané velikosti, AddImage (nebo AddPicture, pokud už držíte instanci TPicture) nakreslí bitmapu na aktuální stránku, a PageNumber řekne komponentě, na kterou stránku míří následná volání kreslení
Jediný detail, který lidi zaskočí, je souřadnicový systém. Uživatelský prostor (user space) PDF umisťuje počátek do levého dolního rohu stránky s tím, že osa Y roste směrem nahoru, což je přesný opak souřadnic obrazovky, po kterých vývojáři v Delphi sahají ze zvyku. Souřadnice X, Y, které předáváte funkci AddImage, je levý dolní roh obdélníku obrázku, a Width, Height jsou rozměry umístění v bodech (points), nikoli v pixelech zdrojového souboru. Pokud si to spletete, vaše skeny přistanou mimo stránku nebo budou vzhůru nohama vůči tomu, kde jste je očekávali
Vytvoření dokumentu a stránky pro každý sken
Začněte s prázdným dokumentem. CreateDocument alokuje nové PDF a ponechá komponentu aktivní, takže neexistuje žádný samostatný krok pro otevření. Odtud procházíte seznam naskenovaných souborů a pro každý z nich přidáte stránku, uděláte z ní aktuální a umístíte obrázek. Rozměry stránky jsou zde A4 v bodech (595 × 842 na výšku), standardní velikost listu pro archivovanou korespondenci
procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
PageW = 595.0; // šířka A4 v bodech
PageH = 842.0; // výška A4 v bodech
Margin = 36.0; // půlpalcový okraj kolem každého skenu
var
I: Integer;
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // nové, prázdné, již aktivní
for I := 0 to Files.Count - 1 do
begin
Pdf.AddPage(I + 1, PageW, PageH); // index stránky založený na 1
Pdf.PageNumber := I + 1; // učiní novou stránku aktuální
PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
end;
Pdf.SaveAs(OutputPath);
finally
Pdf.Free;
end;
end;
Každá iterace vytvoří stránku a okamžitě na ni nastaví PageNumber. Tento druhý řádek je důležitý: AddPage vloží stránku, ale kreslící metody působí na tu stránku, která je zrovna aktuální, takže právě nastavení PageNumber je to, co nasměruje AddImage na stránku, kterou jste zrovna vytvořili. Pokud to přeskočíte, vaše obrázky se naskládají na tu stránku, která byla náhodou načtena předtím
Jeden předpoklad se skrývá v této smyčce: řazení prvků v seznamu Files. Skener pojmenovává stránky 0001.jpg až 0100.jpg, ale procházení adresáře je nemusí vždy vrátit seřazené, a v momentě kdy narazíte na page9.jpg vedle page10.jpg, obyčejné lexikální řazení řetězců postaví stranu 10 před stranu 9. Explicitně seznam seřaďte před smyčkou, a preferujte názvy doplněné nulami už při samotném skenování, takže lexikální pořadí odpovídá pořadí stránek. Sled stránek je to první, čeho si revizor okamžitě všimne, a je to ta nejlacinější chyba, které se dá zabránit
Umístění skenu se zachováním jeho poměru stran
Sken má zřídkakdy stejný tvar jako stránka. Pokud jej roztáhnete, abyste zaplnili list, zkreslíte text; pokud jej umístíte v plné velikosti pixelů, přeteče. Opravou je provést škálování menším z těchto dvou poměrů, přizpůsobením šířky (width-fit) nebo výšky (height-fit), a to co zbyde, vycentrovat. Protože počátek sedí v levém dolním rohu, centrování znamená rozdělit zbývající místo rovnoměrně a přidat jej jak k X, tak i k 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 atd. přes VCL grafické jednotky
AvailW := PageW - 2 * Margin;
AvailH := PageH - 2 * Margin;
// Přizpůsobit okrajům bez zkreslení skenu.
Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
DrawW := Pic.Width * Scale;
DrawH := Pic.Height * Scale;
// Vycentrování: zbylý prostor se rozdělí napůl. Y se měří od spodku stránky.
X := (PageW - DrawW) / 2;
Y := (PageH - DrawH) / 2;
Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
finally
Pic.Free;
end;
end;
Toto načte soubor jednou pro přečtení jeho rozměrů v pixelech, vypočítá jediné stejnoměrné měřítko a předá obdélník s umístěním funkci AddImage. AddImage přijímá cestu k souboru přímo a směruje ji přes stejný obrazový tok jako AddPicture, takže jakýkoli formát, který grafické VCL jednotky rozpoznají, bude fungovat bez výjimek. Pokud už máte obrázek dekódovaný v instanci TPicture z nějakého panelu náhledu, zavolejte AddPicture(Pic, X, Y, DrawW, DrawH) se stejným obdélníkem a přeskočte to druhé načítání souboru
Přeskočení dekódování u JPEG skenů
Skenery téměř vždy chrlí formát JPEG. Načtení JPEGu do objektu TPicture jej dekóduje na bitmapu a poté jej PDFium při ukládání znovu kóduje, což jsou dva ztrátové kroky sem a tam, které nepotřebujete. AddJpegImage vloží původní komprimované bajty ze streamu rovnou do stránky, což je u velkoobjemové dávky rychlejší i vizuálně čistší
var
Stream: TFileStream;
begin
// ... po AddPage + PageNumber pro aktuální stránku ...
Stream := TFileStream.Create(FileName, fmOpenRead);
try
// Vloží bajty JPEG přesně tak, jak jsou; žádný cyklus dekódování/překódování.
Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
finally
Stream.Free;
end;
end;
Pořád vypočítáte X, Y, DrawW a DrawH stejným způsobem, protože rozměry v pixelech pro účely měřítka potřebujete. Ty si přečtete ze souboru nebo rychlým rozborem hlavičky a pak funkci AddJpegImage podstrčíte přímo surový datový proud. U skenů v PNG nebo TIFF je správnou cestou AddImage; ponechte zkratku pro JPEG tomu formátu, na který se ve skutečnosti vztahuje
Štítkování každé stránky
Archivované skeny se snadněji kontrolují, když každá strana nese název svého zdrojového souboru. AddText vykreslí řetězec na souřadnici v uživatelském prostoru, takže popisek sedí těsně pod obrázkem. Pamatujte na otočenou osu Y: chcete-li umístit štítek pod sken, od spodní hrany obrázku odečítáte, místo abyste k ní přičítali
// Titulek pod skenem: Y se směrem ke spodnímu okraji stránky zmenšuje.
Pdf.AddText('Soubor: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
A nakonec ještě jedna poznámka k ukládání. SaveAs je funkce vracející Boolean, takže v produkčním kódu její výsledek kontrolujte místo toho, abyste předpokládali, že zápis uspěl; plný disk nebo zamčená výstupní cesta jinak tiše selže. Jakmile se smyčka dokončí a soubor je zapsán, máte přesně to, co archiv požadoval: jedno seřazené PDF na jeden spisový případ, stránky ve velikosti uzpůsobené pro přizpůsobení, připraveno ke čtení v libovolném prohlížeči
Stejné stavební bloky pokrývají příbuzné úkoly. Změňte pravidlo pro velikost jednotlivé stránky a máte fotoknihu s jedním obrázkem na každý list; zachovejte smyčku ale čtěte z vícestránkového TIFF zdroje, a máte konvertor faxového archivu. Pokud byste chtěli mít širší pohled na to, jak se dají tvořit PDF z kódu, podívejte se na článek vytváření PDF dokumentů od nuly s komponentou PDFium; abyste vyrendrovali výsledek zpátky na obrazovku, přečtěte si článek převádění stránek PDF na obrázky JPEG pomocí komponenty PDFium
Komponenta PDFium Component z loslab.com obsahuje v balení API rozhraní pro tvorbu dokumentů, renderování i práci s textem, tak, jak je použito v celém tomto seriálu