Odborný článok

Tvorba PDF prehliadača v Delphi pomocou PDFium Component

Prehliadač PDF v Delphi sa opiera o dva komponenty a ich vzájomné prepojenie. TPdf spravuje samotný dokument: otvára súbor, dešifruje ho a poskytuje informácie o počte strán a metadátach. TPdfView je vizuálny ovládací prvok, ktorý vykresľuje stránky na obrazovke a zabezpečuje posúvanie, približovanie a sledovanie stránky, ktorú si používateľ práve prezerá. PDFium Component využíva rovnaké vykresľovacie jadro, aké sa dodáva v prehliadači Chrome, takže výsledné glyfy, vyhladzovanie a farby na plátne presne zodpovedajú tomu, čo vaši používatelia už poznajú. Úloha nespočíva vo vykresľovaní. Spočíva v prepojení objektu dokumentu so zobrazením, v bezpečnom načítaní bez pádov pri poškodenom alebo heslom chránenom súbore a v poskytnutí niekoľkých ovládacích prvkov, ktoré z prehliadača robia hotový produkt: otáčanie stránok, zmena priblíženia a prispôsobenie stránky oknu

Tento návod vás prevedie touto zostavou v poradí, v akom ju skutočne vytvárate. Všetko tu vykresľuje jednu stránku naraz, čo vyžaduje väčšina procesov spracovania dokumentov. Ak potrebujete stránky zoradené pod sebou v jednom súvislom stĺpci, ide o iné rozhodnutie o rozložení, ktorému sa venuje samostatný článok a nie je predmetom tohto návodu

Prepojenie TPdf a TPdfView

Umiestnite komponenty TPdf a TPdfView na formulár a potom povedzte zobrazeniu, ktorý dokument má zobraziť. Toto jediné priradenie predstavuje celé prepojenie medzi nevizuálnym dokumentom a ovládacím prvkom, ktorý ho vykresľuje

Architektúra prehliadača PDF Delphi, kde TPdf vlastní dokument, TPdfView ho maľuje a jedno priradenie vlastnosti ich spojí na vrchu DLL PDFium
TPdf vlastní dokument, zatiaľ čo TPdfView ho maľuje, a jediné priradenie spojí oboje cez zdieľaný PDFium engine
procedure TFormMain.FormCreate(Sender: TObject);
begin
  // Pdf a PdfView boli umiestnené v návrhovom režime.
  PdfView.Pdf := Pdf;                 // zobrazenie vykreslí čokoľvek, čo tento dokument obsahuje
  PdfView.FitMode := pfmFitWidth;     // spustiť používateľa s rozumným priblížením
end;

Pred spustením kódu musí byť na počítači prítomná natívna knižnica PDFium. PDFium Component volá súbor pdfium32.dll alebo pdfium64.dll v závislosti od vašej cieľovej platformy a dokument sa jednoducho odmietne otvoriť, ak DLL súbor nemožno nájsť. Distribuujte zodpovedajúci DLL súbor vedľa vášho spustiteľného súboru alebo ho umiestnite tam, kde ho nájde systémový zavádzač. Verzie s povoleným V8 existujú iba pre súbory PDF, ktoré obsahujú JavaScript, ktorý chcete spustiť, čo bežný prehliadač nepotrebuje. Preto siahnite po štandardnej verzii DLL, pokiaľ nemáte konkrétny dôvod urobiť inak

Načítanie dokumentu bez dôvery voči vstupu

Inštinkt velí zabaliť načítanie do bloku try/except a vyvolanú výnimku považovať za zlyhanie. Tento inštinkt je tu však nesprávny a jeho uplatnenie vedie k prehliadaču, ktorý vyzerá v poriadku, kým mu niekto nepredloží poškodený súbor. Nastavenie Active := True nevyvolá výnimku pri zlyhaní načítania. PDFium Component zachytí internú chybu a ponechá vlastnosť Active na hodnote False, takže jediným spoľahlivým spôsobom, ako zistiť, či sa dokument otvoril, je prečítať túto vlastnosť po jej nastavení

Rozhodovací tok načítania pre prehliadač PDFium Delphi, kde nastavenie Active nikdy nevyvolá, tiché false znamená zlé heslo alebo poškodený súbor a nasleduje jedno opakovanie hesla
Aktivácia nikdy nevyhodí výnimku pri zlyhaní, takže prehliadač prečíta Active späť a na tichú false odpovie jediným opakovaním hesla
procedure TFormMain.OpenDocument(const FileName: string);
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;                 // nikdy nevyvolá výnimku; zlyhanie ponechá Active = False
  if not Pdf.Active then
  begin
    ShowMessage('Could not open ' + FileName);
    Exit;
  end;
  PdfView.PageNumber := 1;            // zobrazenie sleduje svoju vlastnú aktuálnu stránku
  UpdatePageLabel;
end;

Dve veci si zaslúžia pozornosť. Prvou je, že PageNumber existuje na oboch objektoch a oba sú nezávislé. Pdf.PageNumber je predstava dokumentu o aktuálnej stránke; PdfView.PageNumber je stránka, ktorú ovládací prvok skutočne zobrazuje, a je to tá, ktorú nastavujete na presun používateľa v rámci súboru. Nastavenie jednej vlastnosti nepresunie druhú, preto prehliadač vždy riadi vlastnosť zobrazenia. Druhou vecou je indexovanie od 1: stránky prebiehajú od 1 do Pdf.PageCount, nie od 0, čo môže prekvapiť každého, kto je zvyknutý na polia indexované od nuly

Spracovanie šifrovaného súboru

Šifrované dokumenty sa začleňujú do rovnakej cesty načítania. Ak je heslo na otvorenie nastavené pred aktiváciou, dokument sa pri otvorení dešifruje; ak chýba alebo je nesprávne, vlastnosť Active zostane na hodnote False rovnako ako pri poškodenom súbore. Riešením je preto požiadať o heslo a zopakovať aktiváciu

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;       // musí byť nastavené pred 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;

Keďže zlyhanie je tiché pri nesprávnom hesle aj pri poškodenom súbore, nemôžete ich od seba odlíšiť iba na záklaste vlastnosti Active. V praxi je to pre prehliadač prijateľné: používateľ buď zadá správne heslo, alebo zistí, že súbor sa nedá otvoriť, pričom správa je v oboch prípadoch rovnaká

Stránkovanie v dokumente

S otvoreným dokumentom je navigácia aritmetikou nad vlastnosťou PdfView.PageNumber ohraničenou hodnotou Pdf.PageCount. Jedinou reálnou prácou je obmedzenie rozsahu, aby tlačidlá nikdy neposunuli stránku mimo povolený rozsah a tlačidlá pre prechod na začiatok a koniec zostali na koncoch súboru zakázané

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;

// štyri navigačné tlačidlá sa zredukujú na jedno volanie každé
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;

Textové pole pre „prechod na stránku N“ je rovnaké volanie GoToPage napájané z analyzovaného celého čísla, pričom ohraničenie pokrýva prípad, keď používateľ zadá číslo 9999 v desaťstránkovom súbore. Ponechajte UpdatePageLabel ako jediné miesto, ktoré zapisuje text napríklad „Stránka 3 z 12“, aby údaj na paneli nikdy nešiel mimo synchrón s tým, čo zobrazuje zobrazenie

Lupa: explicitné percentá a režimy prispôsobenia

Priblíženie v komponentu TPdfView prichádza v dvoch variantoch, ktoré sa navzájom ovplyvňujú, a pochopenie tejto interakcie určuje rozdiel medzi ovládacím prvkom priblíženia, ktorý funguje správne, a tým, ktorý s používateľom bojuje. Priamou cestou je vlastnosť Zoom vyjadrujúca percentuálny podiel, kde 100 znamená skutočnú veľkosť. Druhou cestou je FitMode, ktorý hovorí zobrazeniu, aby vypočítalo priblíženie za vás a prepočítavalo ho pri zmene veľkosti okna

Interakcia Zoom a FitMode v prehliadači PDFium Delphi, kde priradenie presného Zoom vymaže FitMode na pfmNone a výber fit režimu podá priblíženie späť pohľadu
Priradenie presného zoom vyčistí fit režim a výber fit režimu vráti výpočet zoom späť pohľadu
// pevné priblíženia
PdfView.Zoom := 100;     // skutočná veľkosť
PdfView.Zoom := 50;      // polovica
PdfView.Zoom := 200;     // dvojnásobok

// nechať zobrazenie prispôsobiť stránku oknu a udržiavať to pri zmene veľkosti
PdfView.FitMode := pfmFitWidth;   // šírka stránky vyplní ovládací prvok
PdfView.FitMode := pfmFitPage;    // celá stránka viditeľná
PdfView.FitMode := pfmActualSize; // 1:1 s bodmi dokumentu

Tu je časť, ktorá často pletie vývojárov. Priame priradenie hodnoty do Zoom vynuluje FitMode na pfmNone. Je to správne správanie, nie chyba: v momente, keď používateľ zvolí presných 150 %, zobrazenie už nemôže zároveň dodržiavať „prispôsobenie na šírku“, pretože tieto dve požiadavky sú v konflikte. Dôsledkom pre vaše používateľské rozhranie je, že tlačidlo priblíženia a tlačidlo prispôsobenia stránky predstavujú vzájomne sa vylučujúce stavy a na paneli nástrojov by mal byť viditeľný aktívny režim. Keď používateľ klikne na prispôsobenie stránky, nastavte FitMode; keď klikne na číselné priblíženie, nastavte Zoom a nechajte ho, aby režim prispôsobenia sám vyčistil

Ak by ste radšej vypočítali hodnotu prispôsobenia sami, napríklad na naplnenie posuvníka priblíženia aktuálnym percentom prispôsobenia, pomocné metódy pre jednotlivé stránky vám poskytnú tieto čísla bez zmeny režimu. Metódy PageWidthZoom[N], PageZoom[N] a ActualSizeZoom[N] vracajú percento, ktoré by prispôsobilo stránku N na šírku, na výšku alebo ju vykreslilo v skutočnej veľkosti

// inicializovať zobrazenie priblíženia hodnotou prispôsobenia šírke aktuálnej stránky
var
  FitPercent: Double;
begin
  FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
  ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;

Čo hotový prehliadač skutočne potrebuje

Pôvodný názov prácu trochu nadhodnocuje. Vyššie uvedený prehliadač má len niekoľko desiatok riadkov a už robí to, čo spracovanie dokumentov vyžaduje: otvorí súbor, prežije poškodený súbor, zobrazí stránku, presúva sa medzi stránkami a mení zväčšenie ručne alebo prispôsobením. PDFium robí tie zložité časti potichu. Vložené písma sa správne vykreslia, anotácie a polia formulárov sa vykreslia tam, kam ich dokument umiestnil, a stránka, ktorú vidíte, sa zhoduje s tou, ktorú by videl používateľ prehliadača Chrome, pretože obe vykresľuje rovnaké jadro

Z tohto základu sú ďalšie doplnky skôr prírastkové než štrukturálne. Výber textu a vyhľadávanie čítajú z rovnakej textovej vrstvy, ktorú PDFium už stavia; metadáta ako Pdf.Title and Pdf.Author sú dostupné cez jednoduché čítanie vlastností; rotácia a odtiene sivej sú možnosti vykresľovania, ktoré odovzdávate pri kreslení stránky na bitmapu. Žiadna z týchto vecí nemení základ, ktorý tu máte – a tým je objekt dokumentu, zobrazenie a tok načítania a navigácie, ktorý ich spája. Urobte tento základ správne a zvyšok je už len dekorácia

Komponenty TPdf a TPdfView použité v tomto návode sú súčasťou knižnice PDFium Component pre Delphi a C++Builder, ktorá obsahuje kompletnú referenčnú implementáciu prehliadača na svojej produktovej stránke