Egy kárrendezési csapatnak (claims-processing team) harminc évnyi papíralapú aktát kellett átfuttatnia egy lapolvasó (sheet-fed) szkenneren. A szkenner oldalanként egy JPEG-et köpött ki egy mappába, olyan nevekkel, mint 0001.jpg, 0002.jpg, és így tovább. Amire az archívumnak valójában szüksége volt, az egy ügyiratonkénti egyetlen PDF, amelyben az oldalak sorrendben vannak, hogy az ellenőr egyetlen dokumentumot nyithasson meg ahelyett, hogy száz képbélyegképen (thumbnails) kattintgatna végig. Ez az utolsó lépés, egy számozott halom szkennelt kép egyetlen, rendezett PDF-fé alakítása a mi feladatunk itt
A PDFium Component közvetlenül kezeli ezt. A renderelésen és a szövegkinyerésen (text extraction) túl a komponens a semmiből is képes PDF-et építeni: hozzon létre egy üres dokumentumot, adjon hozzá egy tetszőlegesen méretezett üres oldalt, dobjon rá egy képet arra az oldalra felhasználói térbeli (user-space) koordinátákkal, majd mentse el. Az egész folyamat a TPdf komponensen él, így egy kötegelt konvertáló (batch converter) mindössze egy fájlneveken végiglépdelő ciklusból és egy maroknyi hívásból áll
A konverzió formája
Minden egyes szkenner-kép esetében három dolognak kell történnie. Ön eldönti az oldalméretet, behelyezi a képet az oldalra (margót hagyva), majd továbblép a következő oldalra. A PDFium Component mindegyikre ad egy metódust: az AddPage létrehoz egy üres oldalt adott méretben, az AddImage (vagy az AddPicture, ha már rendelkezik egy TPicture objektummal) belerajzolja a bittérképet (bitmap) az aktuális oldalba, a PageNumber pedig megmondja a komponensnek, hogy a későbbi rajzolási hívások melyik oldalt célozzák
Az az egyetlen részlet, ami megtréfálja az embereket, az a koordináta-rendszer. A PDF felhasználói tere (user space) az origót az oldal bal alsó sarkába teszi, a Y pedig felfelé növekszik, ellentétben a képernyőkoordinátákkal, amihez a Delphi-fejlesztők reflexből nyúlnak. Az X, Y, amit az AddImage-nek átad, a kép téglalapjának bal alsó sarka, a Width, Height pedig az elhelyezési méret pontokban (points), nem pedig a forrásfájl pixelmérete. Ha ezt elvéti, a beszkennelt képei lelógnak majd az oldalról, vagy fejjel lefelé jelennek meg ahhoz képest, ahová várta őket
A dokumentum létrehozása és oldalanként egy szkennelt kép
Kezdje egy üres dokumentummal. A CreateDocument lefoglal egy friss PDF-et, és a komponenst aktívan hagyja, így nincs külön megnyitási lépés. Innentől kezdve végigmegy a szkennelt fájlok listáján, és mindegyikhez hozzáad egy oldalt, aktuálissá teszi, majd elhelyezi a képet. Az oldalméretek itt pontban mért A4-esek (595 × 842 portré), ami az archivált levelezés szabványos lapmérete
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;
Minden iteráció létrehoz egy oldalt, és azonnal beállítja rá a PageNumber-t. Ennek a második sornak nagy jelentősége van: az AddPage beszúrja az oldalt, de a rajzoló metódusok azon az oldalon hatnak, amelyik éppen aktuális, így a PageNumber beállítása az, ami az AddImage-t az éppen elkészített oldalra irányítja. Hagyja ki, és a képei egymásra fognak rakódni azon az oldalon, amelyik éppen be volt töltve
Egy feltételezés bújik meg ebben a ciklusban: a Files (fájlok) sorrendje. Egy szkenner 0001.jpg-től 0100.jpg-ig nevezi el az oldalakat, de a könyvtár felsorolása nem mindig rendezve adja vissza őket, és abban a pillanatban, hogy a page9.jpg-hez ér a page10.jpg mellett, egy sima sztringrendezés a 10. oldalt a 9. oldal elé teszi. A lista rendezését kifejezetten (explicit módon) végezze el a ciklus előtt, és részesítse előnyben a nullákkal kiegészített neveket a szkenneléskor, hogy a lexikális sorrend megegyezzen az oldal sorrendjével. Az oldalak sorrendje az az egyetlen dolog, amit egy ellenőr azonnal észrevesz, és a megelőzése a legolcsóbb hiba
Egy szkennelt kép elhelyezése és a képarány megtartása
Egy beszkennelt kép ritkán azonos alakú az oldallal. Ha megnyújtja, hogy kitöltse a lapot, a szöveg eltorzul; ha teljes pixelméretben helyezi el, akkor lelóg (overflows). A megoldás az, hogy a szélességhez vagy a magassághoz illeszkedő (width-fit vagy height-fit) két arány közül a kisebbikkel léptékezze, a fennmaradó részt pedig középre igazítsa. Mivel az origó a bal alsó sarokban van, a középre igazítás azt jelenti, hogy a megmaradó helyet egyenletesen elosztjuk, és hozzáadjuk az X-hez és az Y-hoz is
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;
Ez egyszer tölti be a fájlt, hogy leolvassa a pixelméreteit, kiszámít egyetlen egységes méretezést (scale), és átadja az elhelyezési téglalapot az AddImage-nek. Az AddImage közvetlenül fogad egy fájlelérési utat, és ugyanazon a kép-csővezetéken (image pipeline) vezeti át, mint az AddPicture, így bármilyen formátum, amit a VCL grafikus egységei felismernek, különösebb speciális esetek (special-casing) nélkül működik. Ha az előnézeti panelről a képet már dekódolva egy TPicture-ben tárolja, hívja az AddPicture(Pic, X, Y, DrawW, DrawH) függvényt ugyanazzal a téglalappal, és hagyja ki a második fájlolvasást
A dekódolás kihagyása JPEG képeknél
A szkennerek szinte mindig JPEG formátumot adnak ki. A JPEG TPicture-be történő betöltése dekódolja azt egy bittérképpé, majd a PDFium mentéskor újra kódolja, vagyis két veszteséges körutat (lossy round trips) tesz meg, amire önnek nincs szüksége. Az AddJpegImage az eredeti tömörített bájtokat egy folyamból (stream) egyenesen az oldalba ágyazza, ami gyorsabb és vizuálisan is tisztább egy nagy volumenű kötegelt műveletnél
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;
Az X, Y, DrawW és DrawH értékeket továbbra is ugyanúgy kell kiszámítania, mivel a méretezéshez szüksége van a pixelméretekre. Olvassa ki ezeket a fájlból vagy egy gyors fejléc-elemzésből (header parse), majd adja át a nyers folyamot (raw stream) az AddJpegImage-nek. PNG vagy TIFF szkennelések esetén az AddImage a helyes útvonal; a JPEG parancsikont (shortcut) tartsa fenn arra a formátumra, amelyre az valójában vonatkozik
Az egyes oldalak feliratozása
Az archivált szkennelt dokumentumokat könnyebb ellenőrizni, ha minden oldal tartalmazza a forrásfájl nevét. Az AddText egy sztringet rajzol ki a felhasználói térbeli (user-space) koordinátákhoz, így a felirat közvetlenül a kép alá kerül. Ne feledje a fordított (inverted) Y tengelyt: ahhoz, hogy a feliratot a szkennelt kép alá helyezze, a kép alsó éléből kell kivonnia, ahelyett, hogy hozzáadna
// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
Még egy utolsó megjegyzés a mentéssel kapcsolatban. A SaveAs egy olyan függvény, amely logikai (Boolean) értéket ad vissza, így az éles (production) kódban inkább ellenőrizze az eredményét, ahelyett, hogy feltételezné az írás sikerességét; ellenkező esetben a teli lemez vagy a zárolt kimeneti útvonal csendben meghiúsul (fails quietly). Amikor a ciklus véget ér, és a fájl kiírásra kerül, pontosan azt kapja, amire az archívumnak szüksége volt: egy darab sorrendbe állított PDF-et minden egyes ügyirathoz (case file), az oldalak a mérethez igazítva, és bármilyen megjelenítőben olvashatóan
Ugyanezek az építőelemek lefedik a kapcsolódó munkákat is. Cserélje fel az oldalankénti méretezési szabályt, és egy fotókönyvet kap, oldalanként egy képpel; tartsa meg a ciklust, de olvasson egy többoldalas TIFF forrásból, és máris van egy fax-archívum konvertálója (fax-archive converter). Ha átfogóbb képet szeretne kapni a PDF-ek programozott építéséről, lásd: PDF-dokumentumok készítése az alapoktól a PDFium Component segítségével; ahhoz pedig, hogy az eredményt később visszarendelhesse a képernyőre, lásd: PDF oldalak konvertálása JPEG képekké a PDFium Component segítségével
A loslab.com-ról származó PDFium Component magában foglalja az ebben a sorozatban használt dokumentum-létrehozási, renderelési és szöveges API-kat