Dva dokumenty otevřené najednou, stejné číslo stránky, každý ve vlastním rolovacím (scrollable) panelu: to je jádro porovnávacího prohlížeče. Komponenta PDFium toho dosahuje prostřednictvím přímočarého objektového modelu, kde TPdf vlastní soubor a TPdfView vlastní zobrazení. Jeden dokument, jeden TPdf, jedno TPdfView. Chcete tři panely, máte tři páry. Nejtěžšími částmi nejsou volání API; jsou to aritmetické výpočty rozložení, když se mění velikost okna, a logika synchronizace stránek, když se rozhodujete, který pohled má sledovat který
Rozvržení formuláře (Form Layout)
Formulář VCL obsahuje tři kontejnery TScrollBox vedle sebe, každý s vnitřním TPdfView zarovnaným na alClient, takže vyplňuje celý box. Mezi boxy sedí dvě komponenty TSplitter, takže uživatel může za běhu (runtime) upravovat šířku sloupců. Nástrojová lišta (toolbar) nad panely nese tlačítka pro otevření, ovládací prvky zoomu a přepínač mezi zobrazením dvou / tří pohledů
Režim tří pohledů (three-view) je boolean hodnota, kterou formulář interně sleduje. Když se přepne, přepočítáte šířky a zobrazíte nebo skryjete třetí sloupec. Nejjednodušším přístupem je vymazat všechny vlastnosti Align, skrýt splittery a poté nastavit absolutní pozice:
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;
Nastavení Align := alNone u všech tří boxů před celočíselnou aritmetikou zabrání tomu, aby nástroj pro omezení VCL (constraint engine) bojoval s vašimi přiřazeními. Pokud chcete mít v režimu dvou pohledů možnost měnit velikost tažením (drag-to-resize), obnovte viditelnost splitterů po umístění
Výška každého scroll boxu je klientská oblast zmenšená o výšku panelu nástrojů. Protože je panel nástrojů ukotven nahoře s alTop, ClientHeight - PanelButtons.Height vám poskytne použitelný vertikální prostor. Přiřaďte tuto hodnotu všem třem boxům uvnitř stejného volání UpdateLayout, aby nikdy nedošlo k vykreslení (frame), kdy je jeden box vyšší než ostatní, což by způsobilo bliknutí (flicker) rozvržení
Otevření dokumentu
Každý pár panelů potřebuje svou vlastní proceduru otevření. Vzor je krátký: deaktivovat komponentu, nastavit název souboru, aktivovat a poté zkontrolovat Active; pokud zůstalo False, vyžádejte si heslo a akci opakujte. Pamatujte, že TPdfView.Active je to, co řídí vykreslování, ale TPdf.Active je to, co skutečně otevírá soubor; jsou na sobě nezávislé. Nastavení PdfView.Active := True v době, kdy jeho propojený TPdf ještě není aktivní, je neškodné, ale nic se 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 := '';
PdfComponent.Active := True;
// Load failures are silent: Active stays False instead of raising.
if not PdfComponent.Active then
begin
// Most likely a password-protected file; give the user one retry.
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 přiřazení vždy zkontrolujte PdfComponent.Active; poškozený soubor nebo chybné heslo způsobí tiché selhání načítání bez vyvolání výjimky ve výchozí cestě (default path). Výslovné nastavení PdfViewComponent.PageNumber := 1 po úspěšném otevření zabrání zachování zastaralého čísla stránky z předchozího dokumentu
Dialog zprávy na konci je úmyslný: chcete, aby poškozené nebo nepodporované soubory okamžitě vypluly na povrch spíše než aby byly polknuty jako tichý prázdný panel. Uživatel, který nic nevidí, nemá tušení, zda se soubor načetl a je prostě prázdný, nebo zda jej komponenta odmítla. Nahlášení selhání udržuje chybu viditelnou
Sledování aktivního panelu
Když uživatel klikne uvnitř panelu, tento panel se stane aktivním. Formulář sleduje privátní pole FActivePdfView: TPdfView. Vizuální odezvou (feedback) je změna barvy okraje obsahujícího TScrollBox: pro aktivní box jej nastavte na clHighlight a pro ostatní na clWindow. Připojte toto na událost TPdfView.OnClick u každého z nich a do otevírací procedury, aby fokus (focus) sledoval dokument, který jste právě otevřeli
Některé operace se vztahují na všechny viditelné panely místo jen na ten aktivní. Boolovská proměnná FAllViewsMode na formuláři řídí tuto větev. Když je pravdivá (true), změny zoomu a navigace stránek se rozprostřou (fan out) na každý panel, který má aktivní 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á navigace stránek
Synchronizovaná navigace je volitelná, ale užitečná pro pracovní postupy (workflows) revize dokumentů, kde oba soubory pokrývají stejný rozsah stránek. Logika patří do obslužné rutiny události (event handler), která se spustí poté, co uživatel naviguje jeden pohled. Když zdrojový pohled (source view) změní své PageNumber, handler přenese (propagates) toto číslo do ostatních pohledů za dodržení jedné ochrany (guard): cílový pohled musí mít alespoň tolik stránek, jinak jej přeskočí (skip)
Proměnná PageNumber v TPdfView a v TPdf jsou na sobě nezávislé. TPdf.PageNumber sleduje, kterou stránku dokumentová komponenta považuje za aktuální; TPdfView.PageNumber sleduje to, co je zobrazeno na obrazovce. Pro účely navigace chcete vlastnost (property) pohledu, nikoli vlastnost dokumentu
Zaškrtávací políčko (checkbox) s popiskem např. „Sync pages“ dává uživateli kontrolu. Pokud není zaškrtnuté, každý panel se naviguje nezávisle a obslužná rutina (handler) se okamžitě ukončí. Tato nezávislost je důležitá pro případy užití, kdy mají dva dokumenty odlišný počet stránek, nebo kde uživatel potřebuje najít ekvivalentní pasáž v překladu, který začíná na jiné stránce. Vynucení neustálé synchronizace by učinilo nástroj obtížněji použitelným, než je jednoduché rozvržení do dvou oken na ploše
Jedna věc, na kterou si dejte pozor: programové nastavení PdfView.PageNumber v rámci obslužné rutiny (sync handler) samo vyvolá událost změny u tohoto pohledu. Chraňte se před nekonečnou rekurzí pomocí boolovského příznaku (flag), který nastavíte před přiřazením a bezprostředně poté jej vyčistíte. Příznak je pro celý formulář, nikoliv pro jednotlivý pohled (per-view), protože všechny tři pohledy sdílejí stejnou obslužnou rutinu
Přiblížení na panel (Zoom Per Panel)
Každý TPdfView nese svou vlastní vlastnost Zoom, typ Double v procentech, kde Zoom := 100 znamená skutečnou velikost (100 %). Jeho nastavení překryje (overrides) jakýkoli aktivní FitMode. Pro tlačítko přizpůsobit šířce (fit-to-width) na aktivním panelu si přečtěte zoom přizpůsobení z PdfView.PageWidthZoom[PdfView.PageNumber] a přiřaďte jej. Pro přizpůsobení stránce (fit-to-page) použijte PageZoom[PageNumber]. Obojí jsou vlastnosti polí (array properties) indexované číslem stránky začínajícím od 1, proto se před přístupem k nim ujistěte (guard), že číslo stránky není nula
Když exportujete aktuální stránku do obrázku, přečtěte rotaci z pohledu, ale RenderPage volejte nad komponentou TPdf, nikoliv nad pohledem. Bitmapová podoba TPdf.RenderPage přebírá explicitní pixelové rozměry společně s hodnotou TRotation a sadou vlastností TRenderOptions. Varianta funkce vrací TBitmap ve vlastnictví volajícího (caller-owned), který musíte sami po uložení uvolnit (free):
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ásobič 2x pro šířku a výšku poskytuje ostřejší výstup (sharper output) u dokumentů s drobným textem. try/finally kolem uvolnění bitmapy není volitelný; zrušení v TSaveDialog přesto narazí do bloku finally a vy chcete bitmapu uvolnit bez ohledu na to, co uživatel udělal
Požadavky na DLL
Komponenta PDFium obaluje nativní knihovnu pdfium. 32bitový hostitelský proces (host process) vyžaduje pdfium32.dll; 64bitový hostitel potřebuje pdfium64.dll. Varianty s JavaScriptovým enginem V8 mají příponu v8 a váží zhruba 23-27 MB na rozdíl od 5-6 MB v případě standardních sestavení (builds). Pro porovnávací prohlížeč, který zakazuje vyplňování formulářů (Pdf.FormFill := False), plně postačuje standardní sestavení bez V8 a udržuje distribuci mnohem menší
Umístěte DLL do stejného adresáře se spustitelným souborem, případně do kteréhokoli adresáře v systémové PATH. Komponenta ji načítá na vyžádání (on demand) v době aktivace prvního TPdf, takže v tomto okamžiku vypluje na povrch chybějící DLL mnohem pravděpodobněji než při startu samotné aplikace. Poskytujete-li instalátor, nejspolehlivějším přístupem je v průběhu instalace nakopírovat DLL do složky dané aplikace a příliš nespoléhat na systémový adresář, který by správce mohl později vyčistit
Sestavení s V8 se primárně hodí pro interakci s JavaScriptovými (JS) akcemi formátu PDF, a to např. u kalkulačních (výpočtových) polí (calculation fields) nebo u submit handlerů. Pasivní porovnávací prohlížeč (passive comparison viewer) nemá žádný důvod spouštět JavaScript; nastavení Pdf.FormFill := False před Active := True naprosto přeskočí prostředí obstarávající vyplňování formulářů (form-fill environment), což znamená, že nebude inicializován ani žádný JS engine, i když by bylo dodáváno standardní sestavení. Je to správné (defaultní) výchozí chování u prohlížečů, jež slouží pouze ke čtení (read-only), a to bez ohledu na dodávanou variantu knihovny (DLL variant)
Další podrobnosti o komponentě PDFium a jejím kompletním API najdete na produktové stránce Delphi PDFium Component