Odborný článok

Porovnávanie PDF dokumentov vedľa seba v Delphi s PDFium Component

Dva dokumenty otvorené súčasne, rovnaké číslo stránky, každý vo vlastnom rolovateľnom paneli: to je základ porovnávacieho prehliadača. PDFium Component to poskytuje prostredníctvom priameho objektového modelu, kde TPdf vlastní súbor a TPdfView riadi zobrazenie. Jeden dokument, jeden TPdf, jeden TPdfView. Ak chcete tri panely, máte tri páry. Tými náročnejšími časťami nie sú volania API, ale matematika rozloženia pri zmene veľkosti okna a logika synchronizácie stránok, keď určujete, ktoré zobrazenie má nasledovať ktoré

Rozloženie formulára

Formulár VCL obsahuje tri kontajnery TScrollBox vedľa seba, z ktorých každý má vo vnútri komponent TPdfView zarovnaný na alClient, takže vypĺňa celý box. Dva komponenty TSplitter sú umiestnené medzi boxmi, aby používateľ mohol počas prevádzky upravovať šírku stĺpcov. Panel nástrojov nad panelmi obsahuje tlačidlá na otvorenie, ovládacie prvky priblíženia a prepínač medzi zobrazením dvoch alebo troch panelov

Režim troch zobrazení je hodnota typu Boolean, ktorú formulár sleduje interne. Pri jej prepnutí prepočítate šírky a zobrazíte alebo skryjete tretí stĺpec. Najjednoduchším prístupom je zrušiť všetky vlastnosti Align, skryť splittery a potom nastaviť absolútne pozície:

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;
    // Apply the same (ClientHeight - toolbar height) to all three Height values
  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 pre všetky tri boxy pred celočíselnou matematikou zabráni tomu, aby mechanizmus obmedzení VCL bojoval s vašimi priradeniami. Po umiestnení obnovte viditeľnosť splitterov, ak chcete povoliť zmenu veľkosti ťahaním v režime dvoch zobrazení

Výška každého scroll boxu je výška klientskej oblasti mínus výška panela nástrojov. Keďže panel nástrojov je ukotvený navrchu pomocou alTop, ClientHeight - PanelButtons.Height vám poskytne využiteľný vertikálny priestor. Priraďte túto hodnotu všetkým trom boxom v rámci rovnakého volania UpdateLayout, aby nikdy nenastal moment, kedy je jeden box vyšší ako ostatné, čo by spôsobilo blikanie rozloženia

Otvorenie dokumentu

Každý pár panelov potrebuje vlastnú procedúru otvorenia. Vzor je stručný: deaktivujte komponent, nastavte názov súboru, pokúste sa o aktiváciu a zachyťte EPdfError, ak súbor vyžaduje heslo. Všimnite si, že TPdfView.Active riadi vykresľovanie, ale TPdf.Active v skutočnosti otvára súbor – tieto vlastnosti sú nezávislé. Nastavenie PdfView.Active := True, keď jeho prepojený TPdf ešte nie je aktívny, je neškodné, ale nič nezobrazí

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 := '';

  try
    PdfComponent.Active := True;
  except
    on E: EPdfError do
    begin
      if InputQuery('Password', 'Enter document password:', Password) then
      begin
        PdfComponent.Password := Password;
        PdfComponent.Active   := True;
      end
      else
        raise;
    end;
  end;

  if PdfComponent.Active then
  begin
    PdfViewComponent.PageNumber := 1;
    SetActivePdfView(PdfViewComponent);
  end;
end;

Po priradení vždy skontrolujte PdfComponent.Active; poškodený súbor alebo nesprávne heslo spôsobí, že načítanie v predvolenej ceste zlyhá potichu bez vyvolania výnimky. Explicitné nastavenie PdfViewComponent.PageNumber := 1 po úspešnom otvorení zabráni zobrazeniu neaktuálneho čísla stránky z predchádzajúceho dokumentu

Kód na spracovanie hesla uvedený vyššie vyvolá výnimku pri akejkoľvek inej chybe než pri známej správe o hesle. Je to zámerné: chcete, aby sa poškodené alebo nepodporované súbory prejavili okamžite a neboli potichu prehltnuté ako prázdny panel. Používateľ, ktorý nič nevidí, netuší, či sa súbor načítal a je jednoducho prázdny, alebo či ho komponent odmietol. Vyvolanie výnimky udržiava chybu viditeľnú

Sledovanie aktívneho panela

Keď používateľ klikne do vnútra panela, tento panel sa stane aktívnym. Formulár sleduje súkromné pole FActivePdfView: TPdfView. Vizuálna odozva je realizovaná zmenou farby okraja prislúchajúceho TScrollBox: pre aktívny nastavte farbu na clHighlight a pre ostatné na clWindow. Prepojte to s udalosťou TPdfView.OnClick každého zobrazenia a s procedúrou otvorenia, aby sa zameranie presunulo na dokument, ktorý ste práve otvorili

Niektoré operácie sa vzťahujú na všetky viditeľné panely, nielen na ten aktívny. Prepínač typu Boolean FAllViewsMode na formulári riadi toto vetvenie. Keď je nastavený na hodnotu True, zmeny priblíženia a navigácia po stránkach sa rozšíria na každý panel, 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 stránok

Synchronizovaná navigácia je voliteľná, ale užitočná pri revízii dokumentov, kde oba súbory pokrývajú rovnaký rozsah stránok. Logika patrí do obsluhy udalosti, ktorá sa spustí po tom, čo používateľ zmení zobrazenie. Keď zdrojové zobrazenie zmení svoje PageNumber, obsluha rozšíri toto číslo do ostatných zobrazení pod jednou podmienkou: cieľové zobrazenie musí mať aspoň toľko stránok, inak sa krok preskočí

Vlastnosti PageNumber pre TPdfView a TPdf sú nezávislé. TPdf.PageNumber sleduje, ktorú stránku považuje komponent dokumentu za aktuálnu; TPdfView.PageNumber sleduje, čo sa zobrazuje na obrazovke. Pre účely navigácie potrebujete vlastnosť zobrazenia (view), nie dokumentu

Začiarkavacie políčko označené napríklad „Synchronizovať stránky“ dáva kontrolu používateľovi. Keď je nezačiarknuté, každý panel sa pohybuje nezávisle a obsluha okamžite skončí. Táto nezávislosť je dôležitá pre prípady, kedy majú dva dokumenty odlišný počet strán, alebo keď chce používateľ nájsť ekvivalentnú pasáž v preklade, ktorý začína na inej stránke. Trvalé vynútenie synchronizácie by robilo nástroj menej praktickým ako jednoduché usporiadanie dvoch okien na ploche

Jedna vec, na ktorú si treba dať pozor: programové nastavenie PdfView.PageNumber vo vnútri obsluhy synchronizácie samo osebe spustí udalosť zmeny na danom zobrazení. Zabráňte nekonečnej rekurzii pomocou príznaku Boolean, ktorý nastavíte pred priradením a vymažete ihneď po ňom. Tento príznak je na úrovni formulára, nie zobrazenia, keďže všetky tri zobrazenia zdieľajú rovnakú obsluhu

Priblíženie na panel

Každý TPdfView má vlastnú vlastnosť Zoom, typ Double v percentách, kde 1.0 znamená 100%. Jej nastavenie prepíše akýkoľvek aktívny FitMode. Pre tlačidlo „prispôsobiť šírke“ na aktívnom paneli prečítajte zoom prispôsobenia z PdfView.PageWidthZoom[PdfView.PageNumber] a priraďte ho. Pre prispôsobenie na celú stránku použite PageZoom[PageNumber]. Obe sú indexované vlastnosti poľa začínajúce od 1, preto pred prístupom k nim skontrolujte, či číslo stránky nie je nula

Keď exportujete aktuálnu stránku do obrázka, prečítajte otočenie zo zobrazenia, ale zavolajte RenderPage na komponente TPdf, nie na zobrazení. Bitová mapa metódy TPdf.RenderPage preberá explicitné rozmery v pixeloch, hodnotu TRotation a sadu možností TRenderOptions. Variant funkcie 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;

Násobiteľ 2x pre šírku a výšku poskytuje ostrejší výstup pre dokumenty s jemným textom. Konštrukcia try/finally okolo uvoľnenia bitovej mapy nie je voliteľná; zrušenie dialógu TSaveDialog stále zasiahne blok finally a chcete, aby sa bitová mapa uvoľnila bez ohľadu na to, čo používateľ urobil

Požiadavky na DLL

PDFium Component obaluje natívnu knižnicu pdfium. 32-bitový hostiteľský proces vyžaduje pdfium32.dll, 64-bitový vyžaduje pdfium64.dll. Varianty s JavaScript engine V8 pridávajú príponu v8 s veľkosťou približne 23 – 27 MB v porovnaní s 5 – 6 MB štandardných zostáv. Pre porovnávací prehliadač, ktorý zakazuje vypĺňanie formulárov (Pdf.FormFill := False), postačuje štandardná zostava bez V8 a distribúcia zostáva menšia

Umiestnite DLL do rovnakého adresára ako spustiteľný súbor alebo do akéhokoľvek adresára v systémovej ceste PATH. Komponent ju načíta na požiadanie, keď sa aktivuje prvý TPdf, takže chýbajúca DLL sa prejaví v tomto bode a nie pri spustení aplikácie. Ak dodávate inštalátor, najspoľahlivejším prístupom je skopírovať DLL do priečinka aplikácie počas inštalácie, namiesto spoliehania sa na systémový adresár, ktorý môže správca neskôr vyčistiť

Zostavy s V8 sú užitočné predovšetkým vtedy, keď potrebujete pracovať s akciami JavaScriptu v PDF, napríklad na spúšťanie výpočtových polí alebo odosielacích skriptov. Pasívny porovnávací prehliadač nemá dôvod spúšťať JavaScript; nastavenie Pdf.FormFill := False pred Active := True úplne preskočí prostredie vypĺňania formulárov, čo tiež znamená, že sa neinicializuje žiadny engine JS, aj keď sa použije štandardná zostava. To je správna predvolená možnosť pre prehliadač určený len na čítanie bez ohlladu na to, ktorú verziu DLL dodávate

Ďalšie podrobnosti o komponente PDFium Component a jeho kompletnom API nájdete na produktovej stránke Delphi PDFium Component