Odborný článok

Porovnanie PDF vedľa seba v Delphi s PDFium Component

Dva dokumenty otvorené naraz, rovnaké číslo strany, každý vo vlastnom rolovateľnom paneli: to je jadro porovnávacieho zobrazovača. PDFium Component to poskytuje cez priamočiary objektový model, v ktorom TPdf vlastní súbor a TPdfView vlastní zobrazenie. Jeden dokument, jedno TPdf, jedno TPdfView. Ak chcete tri panely, máte tri dvojice. Ťažké časti nie sú volania rozhrania; je to aritmetika rozloženia pri zmene veľkosti okna a logika synchronizácie strán, keď sa rozhodujete, ktorý pohľad má nasledovať ktorý

Rozloženie formulára

Formulár VCL drží tri kontajnery TScrollBox vedľa seba, každý s jedným TPdfView vnútri zarovnaným na alClient, aby vyplnil celý box. Medzi boxmi sedia dva komponenty TSplitter, takže používateľ môže šírky stĺpcov upravovať za behu. Panel nástrojov nad panelmi nesie tlačidlá na otvorenie, ovládanie priblíženia a prepínač medzi dvoma a tromi pohľadmi

Režim troch pohľadov je boolean, ktorý si formulár sleduje interne. Keď sa prepne, prepočítate šírky a tretí stĺpec zobrazíte alebo skryjete. Najjednoduchším prístupom je vyčistiť všetky vlastnosti Align, skryť splittery a potom nastaviť absolútne pozície:

Diagram rozloženia formulára porovnávacieho zobrazovača PDF vedľa seba v Delphi postaveného s PDFium Component, ukazujúci panel nástrojov, tri rolovacie boxy s panelmi TPdfView a splittery v režime dvoch a troch pohľadov
Každý panel je rolovací box s jedným TPdfView vnútri a prepínanie medzi dvoma a tromi pohľadmi je len iná sada priradení šírok
procedure TFormMain.UpdateLayout;
var
  TotalWidth: Integer;
begin
  TotalWidth := ClientWidth;

  if ThreeViewMode then
  begin
    ScrollBox3.Visible := True;
    ScrollBox1.Left   := 0;
    ScrollBox1.Width  := TotalWidth div 3;
    ScrollBox2.Left   := ScrollBox1.Width;
    ScrollBox2.Width  := TotalWidth div 3;
    ScrollBox3.Left   := ScrollBox2.Left + ScrollBox2.Width;
    ScrollBox3.Width  := TotalWidth - ScrollBox3.Left;
    // Rovnaké (ClientHeight - výška panela nástrojov) dajte všetkým trom Height
  end
  else
  begin
    ScrollBox3.Visible := False;
    ScrollBox1.Left   := 0;
    ScrollBox1.Width  := TotalWidth div 2;
    ScrollBox2.Left   := ScrollBox1.Width;
    ScrollBox2.Width  := TotalWidth - ScrollBox2.Left;
  end;
end;

Nastavenie Align := alNone na všetkých troch boxoch pred celočíselnou aritmetikou zabráni tomu, aby s vašimi priradeniami zápasil engine obmedzení vo VCL. Ak chcete v režime dvoch pohľadov meniť šírku ťahaním, po umiestnení viditeľnosť splitterov obnovte

Výška každého rolovacieho boxu je klientská oblasť mínus výška panela nástrojov. Keďže panel nástrojov je ukotvený hore cez alTop, ClientHeight - PanelButtons.Height vám dá využiteľný zvislý priestor. Priraďte ju všetkým trom boxom vnútri toho istého volania UpdateLayout, aby nikdy nenastal snímok, v ktorom je jeden box vyšší než ostatné a spôsobí blikanie rozloženia

Otvorenie dokumentu

Každá dvojica panelov potrebuje vlastnú procedúru na otvorenie. Vzor je krátky: deaktivujte komponent, nastavte názov súboru, aktivujte a potom skontrolujte Active; ak zostalo False, vypýtajte si heslo a skúste znovu. Všimnite si, že TPdfView.Active riadi vykresľovanie, ale súbor v skutočnosti otvára TPdf.Active; sú nezávislé. Nastaviť PdfView.Active := True, kým jeho prepojené TPdf ešte aktívne nie je, je neškodné, ale nezobrazí nič

Vývojový diagram otvorenia dokumentu PDF s PDFium Component v Delphi ukazujúci tichú kontrolu Active, jeden opakovaný pokus s heslom a chybový dialóg pre poškodené alebo heslom chránené súbory
Neúspešné načítanie nechá Active na False bez vyvolania výnimky, takže tok ho skontroluje, raz to skúsi s heslom a nakoniec problém ohlási namiesto zobrazenia prázdneho panela
procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
  PdfViewComponent: TPdfView);
var
  Password: string;
begin
  if not OpenDialog.Execute then
    Exit;

  PdfComponent.Active   := False;
  PdfComponent.FileName := OpenDialog.FileName;
  PdfComponent.Password := '';
  PdfComponent.Active   := True;

  // Zlyhania načítania sú tiché: Active zostane False namiesto výnimky.
  if not PdfComponent.Active then
  begin
    // Najskôr ide o súbor chránený heslom; dajte používateľovi jeden pokus.
    if InputQuery('Password', 'Enter document password:', Password) then
    begin
      PdfComponent.Password := Password;
      PdfComponent.Active   := True;
    end;
  end;

  if not PdfComponent.Active then
  begin
    ShowMessage('Could not open ' + OpenDialog.FileName +
      ' (damaged file or wrong password)');
    Exit;
  end;

  PdfViewComponent.PageNumber := 1;
  SetActivePdfView(PdfViewComponent);
end;

Po priradení vždy skontrolujte PdfComponent.Active; poškodený súbor alebo nesprávne heslo spôsobia, že načítanie v predvolenej ceste zlyhá potichu bez vyvolania výnimky. Explicitné nastavenie PdfViewComponent.PageNumber := 1 po úspešnom otvorení zabráni tomu, aby ostalo staré číslo strany z predchádzajúceho dokumentu

Dialóg so správou na konci je zámerný: chcete, aby sa poškodené alebo nepodporované súbory ozvali okamžite a neboli spolknuté ako tichý prázdny panel. Používateľ, ktorý nevidí nič, netuší, či sa súbor načítal a je len prázdny, alebo či ho komponent odmietol. Ohlásenie zlyhania udrží chybu viditeľnú

Sledovanie aktívneho panela

Keď používateľ klikne dovnútra panela, tento panel sa stane aktívnym. Formulár si sleduje súkromné pole FActivePdfView: TPdfView. Vizuálnou spätnou väzbou je zmena farby okraja obklopujúceho TScrollBox: aktívnemu nastavte clHighlight a ostatným clWindow. Napojte to na každú udalosť TPdfView.OnClick aj na procedúru otvorenia, aby fokus nasledoval dokument, ktorý ste práve otvorili

Niektoré operácie sa vzťahujú na všetky viditeľné panely, nie len na aktívny. Túto vetvu riadi boolean FAllViewsMode na formulári. Keď je true, zmeny priblíženia a navigácia po stranách sa rozvetvia do každého panela, ktorý má aktívny dokument:

procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
  if PdfView1.Active then PdfView1.Zoom := NewZoom;
  if PdfView2.Active then PdfView2.Zoom := NewZoom;
  if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;

Synchronizovaná navigácia po stranách

Synchronizovaná navigácia je voliteľná, no užitočná pri postupoch s revíziami dokumentov, kde oba súbory pokrývajú rovnaký rozsah strán. Logika patrí do obsluhy udalosti, ktorá sa spustí po tom, čo používateľ zanavigoval v jednom pohľade. Keď zdrojový pohľad zmení svoje PageNumber, obsluha toto číslo rozšíri do ostatných pohľadov s jednou poistkou: cieľový pohľad musí mať aspoň toľko strán, inak sa preskočí

Vlastnosti PageNumber na TPdfView a na TPdf sú nezávislé. TPdf.PageNumber sleduje, ktorú stranu považuje dokumentový komponent za aktuálnu; TPdfView.PageNumber sleduje, čo je zobrazené na obrazovke. Na účely navigácie chcete vlastnosť pohľadu, nie dokumentu

Zaškrtávacie políčko s popisom typu „Synchronizovať strany“ dáva kontrolu používateľovi. Keď nie je zaškrtnuté, každý panel naviguje nezávisle a obsluha okamžite skončí. Táto nezávislosť je dôležitá pre prípady, keď majú oba dokumenty rôzny počet strán alebo keď chce používateľ nájsť rovnocennú pasáž v preklade, ktorý sa začína na inej strane. Vynútená trvalá synchronizácia by nástroj urobila ťažšie použiteľným než jednoduché usporiadanie dvoch okien na ploche

Jedna vec na pozor: nastavenie PdfView.PageNumber programovo vnútri synchronizačnej obsluhy samo spustí na tomto pohľade udalosť zmeny. Proti nekonečnej rekurzii sa bráňte booleovským príznakom, ktorý nastavíte pred priradením a hneď po ňom zmažete. Príznak je na formulár, nie na pohľad, pretože všetky tri pohľady zdieľajú tú istú obsluhu

Diagram synchronizovanej navigácie po stranách v porovnávacom zobrazovači PDF v Delphi s PDFium Component, so zaškrtávacím políčkom synchronizácie, poistkou počtu strán pre každý cieľový pohľad a príznakom proti rekurzii
Číslo strany putuje zo zdrojového pohľadu do každého ďalšieho pohľadu len vtedy, keď je synchronizácia zapnutá a každý cieľový pohľad danú stranu naozaj obsahuje

Priblíženie na každý panel zvlášť

Každé TPdfView nesie vlastnú vlastnosť Zoom, hodnotu typu Double v percentách, kde Zoom := 100 znamená skutočnú veľkosť (100 %). Jej nastavenie prebije akýkoľvek aktívny FitMode. Pre tlačidlo prispôsobenia šírke na aktívnom paneli prečítajte prispôsobené priblíženie z PdfView.PageWidthZoom[PdfView.PageNumber] a priraďte ho. Pre prispôsobenie strane použite PageZoom[PageNumber]. Obe sú poľové vlastnosti indexované číslom strany od jednotky, takže pred prístupom sa poistite proti nulovému číslu strany

Keď aktuálnu stranu exportujete do obrázka, otočenie prečítajte z pohľadu, ale RenderPage zavolajte na komponente TPdf, nie na pohľade. Bitmapová podoba metódy TPdf.RenderPage berie explicitné rozmery v pixeloch plus hodnotu TRotation a množinu TRenderOptions. Funkčný variant vracia TBitmap vlastnený volajúcim, ktorý po uložení sami uvoľníte:

procedure TFormMain.SaveActiveViewAsImage;
var
  Pdf: TPdf;
  Bmp: TBitmap;
  Jpeg: TJpegImage;
begin
  if not Assigned(FActivePdfView) or not FActivePdfView.Active then
    Exit;

  Pdf := FActivePdfView.Pdf;
  Pdf.PageNumber := FActivePdfView.PageNumber;

  Bmp := Pdf.RenderPage(
    0, 0,
    Round(Pdf.PageWidth * 2),
    Round(Pdf.PageHeight * 2),
    FActivePdfView.Rotation, [], clWhite);
  try
    if SavePictureDialog.Execute then
    begin
      Jpeg := TJpegImage.Create;
      try
        Jpeg.Assign(Bmp);
        Jpeg.CompressionQuality := 90;
        Jpeg.SaveToFile(SavePictureDialog.FileName);
      finally
        Jpeg.Free;
      end;
    end;
  finally
    Bmp.Free;
  end;
end;

Dvojnásobný násobiteľ šírky a výšky dá ostrejší výstup pri dokumentoch s jemným textom. try/finally okolo uvoľnenia bitmapy nie je voliteľné; zrušenie dialógu TSaveDialog stále prejde blokom finally a bitmapu chcete uvoľniť bez ohľadu na to, čo používateľ urobil

Požiadavky na DLL

PDFium Component obaľuje natívnu knižnicu pdfium. 32-bitový hostiteľský proces potrebuje pdfium32.dll, 64-bitový potrebuje pdfium64.dll. Varianty s JavaScriptovým enginom V8 pridávajú príponu v8 a vážia zhruba 23-27 MB oproti 5-6 MB štandardných buildov. Pre porovnávací zobrazovač, ktorý vypína vypĺňanie formulárov (Pdf.FormFill := False), stačí štandardný build bez V8 a distribúcia zostane menšia

Umiestnite DLL do rovnakého adresára ako spustiteľný súbor alebo do ľubovoľného adresára v systémovej premennej PATH. Komponent ju načíta na požiadanie pri aktivácii prvého TPdf, takže chýbajúca DLL sa ozve až v tej chvíli, nie pri štarte aplikácie. Ak dodávate inštalátor, najspoľahlivejším prístupom je skopírovať DLL počas inštalácie do priečinka aplikácie namiesto spoliehania sa na systémový adresár, ktorý môže správca neskôr vyčistiť

Buildy s V8 sú užitočné najmä vtedy, keď potrebujete pracovať s akciami JavaScriptu v PDF, napríklad spúšťať výpočtové polia alebo obsluhy odoslania. Pasívny porovnávací zobrazovač nemá dôvod spúšťať JavaScript; nastavenie Pdf.FormFill := False pred Active := True úplne preskočí prostredie na vypĺňanie formulárov, čo zároveň znamená, že sa engine JS neinicializuje ani pri použití štandardného buildu. To je správne predvolené nastavenie pre zobrazovač len na čítanie bez ohľadu na to, ktorý variant DLL dodávate

Ďalšie podrobnosti o PDFium Component a jeho úplnom rozhraní nájdete na produktovej stránke Delphi PDFium Component