Egy PDF-megjelenítő Delphiben két komponensre és a köztük lévő kapcsolatokra (wiring) vezethető vissza. A TPdf birtokolja a dokumentumot: megnyitja a fájlt, visszafejti (decrypts), és válaszol az oldalszámmal és a metaadatokkal kapcsolatos kérdésekre. A TPdfView az a vizuális vezérlő (visual control), amely az oldalakat a képernyőre festi, és kezeli a görgetést, a nagyítást (zoom), valamint azt az oldalt, amelyet a felhasználó éppen néz. A PDFium Component ugyanazt a renderelő motort csomagolja be, amelyet a Chrome-ban is szállítanak, így a vásznon (canvas) kapott glyphek, élsimítások (anti-aliasing) és színek megegyeznek azzal, amit a felhasználók már látnak a böngészőjükben. A munka nem a renderelésben van. Abban áll, hogy a dokumentum objektumot összekapcsolja a nézettel, sérült vagy jelszóval védett fájl esetén összeomlás nélkül töltse be, és megadja a felhasználónak azt a maroknyi vezérlőt, amelytől egy megjelenítő befejezettnek érződik: lapozás, a nagyítás megváltoztatása, és az oldal ablakhoz igazítása
Ez a cikk abban a sorrendben vezeti végig ezen az összeállításon, ahogyan ténylegesen megépíti azt. Itt minden egyszerre csak egy oldalt renderel, amit a legtöbb dokumentum-munkafolyamat is igényel. Ha az oldalakat egyetlen folyamatosan görgethető oszlopban egymásra rakva szeretné látni, az egy eltérő elrendezési döntés, és nem ez az itt bemutatott út
A TPdf összekötése a TPdfView-val
Dobjon be egy TPdf-et és egy TPdfView-t a formra, majd mondja meg a nézetnek, hogy melyik dokumentumot jelenítse meg. Ez az egyetlen értékadás jelenti a teljes kapcsolatot a nem-vizuális dokumentum és az azt kirajzoló vezérlő között
procedure TFormMain.FormCreate(Sender: TObject);
begin
// Pdf and PdfView were dropped at design time.
PdfView.Pdf := Pdf; // the view paints whatever this document holds
PdfView.FitMode := pfmFitWidth; // start the user at a sensible zoom
end;
Mielőtt ebből bármi is lefutna, a PDFium natív könyvtárának (native library) rajta kell lennie a gépen. A PDFium Component a célplatformtól függően a pdfium32.dll vagy a pdfium64.dll fájlt hívja meg, és a dokumentum egyszerűen nem hajlandó megnyílni, ha a DLL nem található. Szállítsa a megfelelő DLL-t a végrehajtható fájlja mellett, vagy helyezze oda, ahol a rendszerbetöltő (system loader) megtalálja. A V8-kompatibilis buildek csak azokhoz a PDF-ekhez léteznek, amelyek végrehajtandó JavaScriptet tartalmaznak, egy egyszerű megjelenítő viszont nem tesz ilyet, ezért válassza a standard DLL-t, hacsak nincs konkrét oka arra, hogy ne így tegyen
Egy dokumentum betöltése a bemenet megbízhatóságának feltételezése nélkül
Az ösztönös reakció az, hogy a betöltést egy try/except blokkba csomagoljuk, és a kivételt hibaként kezeljük. Ez az ösztön itt téves, és ha elrontjuk, akkor egy olyan megjelenítőt kapunk, amely jól néz ki, amíg valaki egy sérült fájlt nem ad neki. Az Active := True beállítása nem dob kivételt (raise) betöltési hiba esetén. A PDFium Component elkapja a belső hibát, és az Active tulajdonságot False értéken hagyja, így az egyetlen őszinte módja annak, hogy megtudjuk, megnyílt-e a dokumentum, ha visszaolvassuk a tulajdonságot, miután beállítottuk azt
procedure TFormMain.OpenDocument(const FileName: string);
begin
Pdf.FileName := FileName;
Pdf.Active := True; // never raises; failure leaves Active = False
if not Pdf.Active then
begin
ShowMessage('Could not open ' + FileName);
Exit;
end;
PdfView.PageNumber := 1; // the view tracks its own current page
UpdatePageLabel;
end;
Két dolog érdemel figyelmet. Az első, hogy a PageNumber mindkét objektumon létezik, és a kettő független egymástól. A Pdf.PageNumber a dokumentum aktuális oldalra vonatkozó fogalma; a PdfView.PageNumber pedig az az oldal, amelyet a vezérlő ténylegesen megjelenít, és ezt állítja be, hogy végigvezesse a felhasználót a fájlon. Az egyik beállítása nem mozgatja a másikat, így egy megjelenítő mindig a nézet (view) tulajdonságát vezérli. A második dolog az 1-alapú indexelés: az oldalak 1-től a Pdf.PageCount-ig tartanak, nem pedig 0-tól, ami megtréfálhatja mindazokat, akik hozzászoktak a nulla-alapú tömbökhöz
Titkosított fájl kezelése
A titkosított dokumentumok ugyanebbe a betöltési útvonalba illeszkednek be. Ha a megnyitási jelszó az aktiválás előtt be van állítva, a dokumentum megnyitás közben visszafejtésre kerül; ha rossz vagy hiányzik, az Active False marad pontosan úgy, mint egy korrupt fájl esetében. A helyreállítás (recovery) tehát abból áll, hogy bekérjük a jelszót, és újra megpróbáljuk az aktiválást
procedure TFormMain.OpenWithPassword(const FileName: string);
var
Password: string;
begin
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
begin
if InputQuery('Password required', 'Password:', Password) then
begin
Pdf.Password := Password; // must be set before Active := True
Pdf.Active := True;
end;
if not Pdf.Active then
begin
ShowMessage('Unable to open the document.');
Exit;
end;
end;
PdfView.PageNumber := 1;
end;
Mivel a hiba csendes (silent) mind a rossz jelszó, mind a sérült fájl esetében, a kettőt nem tudja megkülönböztetni pusztán az Active tulajdonság alapján. A gyakorlatban ez elfogadható egy megjelenítő esetében: a felhasználó vagy megadja a helyes jelszót, vagy megtudja, hogy a fájl nem fog megnyílni, és az üzenet mindkét esetben ugyanazt mondja
Lapozás a dokumentumban
A megnyitott dokumentum navigációja aritmetikai művelet a PdfView.PageNumber-ön, amelyet a Pdf.PageCount határol. Az egyetlen igazi munka a korlátozás (clamping), így a gombok soha nem tolják az oldalt a tartományon kívülre, és az első és utolsó gombok letiltva maradnak a fájl végein
procedure TFormMain.GoToPage(NewPage: Integer);
begin
if not Pdf.Active then
Exit;
if NewPage < 1 then
NewPage := 1
else if NewPage > Pdf.PageCount then
NewPage := Pdf.PageCount;
PdfView.PageNumber := NewPage;
UpdatePageLabel;
end;
// the four navigation buttons reduce to one call each
procedure TFormMain.FirstClick(Sender: TObject); begin GoToPage(1); end;
procedure TFormMain.PrevClick(Sender: TObject); begin GoToPage(PdfView.PageNumber - 1); end;
procedure TFormMain.NextClick(Sender: TObject); begin GoToPage(PdfView.PageNumber + 1); end;
procedure TFormMain.LastClick(Sender: TObject); begin GoToPage(Pdf.PageCount); end;
Egy "Ugrás az N. oldalra" szövegmező ugyanaz a GoToPage hívás, egy elemzett (parsed) egész számból táplálva, a korlátozás (clamp) pedig lefedi azt az esetet, amikor a felhasználó 9999-et gépel be egy tízoldalas fájlba. Tartsa meg az UpdatePageLabel-t, mint azt az egyetlen helyet, amely kiírja a "3 / 12. oldal" szöveget, így a kijelzett érték soha nem csúszik szét (out of sync) azzal, amit a nézet mutat
Nagyítás: explicit százalékok és igazítási módok
A TPdfView-n a nagyítás (zoom) két változatban érkezik, amelyek kölcsönhatásba lépnek egymással, és ezen kölcsönhatás megértése jelenti a különbséget egy jól viselkedő nagyításvezérlő és egy olyan között, amelyik harcol a felhasználóval. A közvetlen út a Zoom tulajdonság, egy százalék, ahol a 100 az eredeti méretet jelenti. A másik út a FitMode, amely arra utasítja a nézetet, hogy számítsa ki önnek a nagyítást, és folyamatosan számolja újra azt, ahogy az ablak mérete változik
// fixed magnifications
PdfView.Zoom := 100; // actual size
PdfView.Zoom := 50; // half
PdfView.Zoom := 200; // double
// let the view size the page to the window, and keep it sized on resize
PdfView.FitMode := pfmFitWidth; // page width fills the control
PdfView.FitMode := pfmFitPage; // whole page visible
PdfView.FitMode := pfmActualSize; // 1:1 with the document's points
Itt van az a rész, amiben az emberek elbotlanak. A Zoom közvetlen hozzárendelése visszaállítja a FitMode-ot pfmNone-ra. Ez a helyes viselkedés, nem pedig hiba: abban a pillanatban, amikor a felhasználó egy pontos 150%-ot választ, a nézet már nem tarthatja tiszteletben a "szélességhez igazítást" ("fit to width") is, mert a két kérés ütközik. A felhasználói felület (UI) szempontjából ennek az a következménye, hogy a nagyítás gomb és az oldalhoz igazítás (fit-to-page) gomb kölcsönösen kizárják egymást, az eszköztárnak pedig láthatóvá kell tennie az aktív módot. Amikor a felhasználó az oldalhoz igazításra kattint, állítsa be a FitMode-ot; amikor egy numerikus nagyításra kattint, állítsa be a Zoom-ot, és hagyja, hogy magától törölje az igazítási módot (fit mode)
Ha inkább ön szeretné kiszámítani az igazítási értéket (fit value), talán azért, hogy egy nagyítási csúszkát beállítson az aktuális igazítási százalékkal, az oldalankénti (per-page) segédek megadják önnek a számokat a mód megváltoztatása nélkül. A PageWidthZoom[N], a PageZoom[N] és az ActualSizeZoom[N] azt a százalékot adják vissza, amely az N. oldalt a szélességhez igazítaná, egészben jelenítené meg, vagy tényleges (actual) méretben renderelné
// seed a zoom readout from the fit-to-width value of the current page
var
FitPercent: Double;
begin
FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;
Mire van valójában szüksége egy kész megjelenítőnek
A fenti megjelenítő néhány tucat sornyi, és már elvégzi azt a munkát, amire egy dokumentum-munkafolyamatnak szüksége van: megnyit egy fájlt, túlél egy hibásat, megjelenít egy oldalt, mozog az oldalak között, és kézzel vagy igazítással megváltoztatja a nagyítást. A PDFium csendben elvégzi a nehéz részeket. A beágyazott betűtípusok feloldódnak, a jegyzetek és az űrlapmezők ott jelennek meg, ahová a dokumentum helyezi őket, és az oldal, amit ön lát, megegyezik azzal, amit egy Chrome-felhasználó is látna, mivel ugyanaz a motor (engine) rajzolja mindkettőt
Ebből az alapból a kiegészítések (additions) inkább növekményesek, mint strukturálisak. A szövegkijelölés és a keresés ugyanabból a szövegrétegből olvas, amit a PDFium már amúgy is felépít; a metaadatok, például a Pdf.Title és a Pdf.Author egyetlen tulajdonság-olvasásra vannak; a forgatás és a szürkeárnyalat renderelési opciók, amiket átad, amikor az oldalt egy bittérképre (bitmap) rajzolja. Ezek egyike sem változtatja meg az itt meglévő gerincet (spine), ami a dokumentum objektum, a nézet és az ezeket összekötő "betöltés majd navigálás" (load-then-navigate) folyamat. Építse fel ezt a gerincet jól, és a többi már csak díszítés
A mindenütt használt TPdf és TPdfView komponensek a Delphihez és C++Builderhez készült PDFium Component részei, amelynek termékoldala a teljes megjelenítő (viewer) referenciát tartalmazza