Obdélník nakreslený kolem odstavce během revize nemusí skončit jako trvalá značka uvnitř PDF. THPDFViewerModel z HotPDF vystavuje AddHighlightRegion, metodu, která drží každé zvýraznění jako záznam v paměti, ne jako změnu načteného dokumentu, takže recenzent může označit desítky stránek, zatímco soubor na disku zůstává bajt po bajtu tím, čím byl. Přibližte na 6400 %, otočte stránku o 90 stupňů, přepněte z Přizpůsobit šířce na Přizpůsobit stránce a stejný obdélník pořád skončí na stejném odstavci, protože souřadnicová matematika prochází přes skutečnou geometrii vykreslení v okamžiku, kdy byla značka nakreslena
Nástroje pro revizi postavené kolem prohlížeče PDF na tento problém narážejí neustále. Obrazovka pro připomínkování, kontrola kvality na generovaných fakturách, interní schvalovací proces — všechny potřebují umožnit někomu upozornit na oblast stránky, aniž by se každá pracovní značka proměnila v trvalou změnu souboru, a bez sahání po plnohodnotném subsystému anotací jen proto, aby se zobrazil barevný rámeček, zatímco se ještě rozhoduje, zda značka do dokumentu patří. HotPDF na to odpovídá vyhrazenou vrstvou zvýraznění, která sedí zcela na straně Modelu z rozdělení popsaného v stavbě vlastního prohlížeče PDF s architekturou MVC v Delphi, a to je také důvod, proč lze stejný seznam zvýraznění řídit z jednotkového testu, aniž by bylo v dohledu jediné okno
Co si AddHighlightRegion v HotPDF vlastně ukládá?
AddHighlightRegion ukládá pro každou značku přesně tři věci: index stránky počítaný od nuly, THPDFRectangle v souřadnicích uživatelského prostoru PDF a TColor, vše zabalené jako záznam THPDFViewerHighlight uvnitř THPDFViewerModel. Volání Viewer.HighlightRegion(PageIndex, PageRect, clYellow), nebo ekvivalentní Model.AddHighlightRegion, připojí jeden takový záznam do soukromého pole a vrátí jeho index, a tento index je jediný „handle", který volající dostane zpět: neexistuje žádný samostatný objekt, žádné rozhraní s počítáním referencí, nic k uvolnění. Všechny ostatní schopnosti v tomto článku — kreslení značky, její přemapování po změně zoomu, mazání — jsou postaveny nad tímto jedním malým záznamem
Každý obdélník se před přijetím normalizuje a ořízne. AddHighlightRegion prohodí levý a pravý okraj, pokud recenzent táhne zprava doleva, prohodí horní a dolní okraj pro tažení nahoru, a pak výsledek ořízne podle MediaBoxu stránky získaného přes GetLoadedPageBox. Obdélník, který skončí s nulovou šířkou, nulovou výškou nebo zcela mimo stránku, se rovnou odmítne: metoda vrátí -1 a do seznamu se nic nepřidá. Tato návratová hodnota není jen dekorace: dávka zvýraznění znovu sestavená z externího souboru revize, nebo ze zastaralých souřadnic poté, co byla stránka nahrazena, může tiše ztratit záznamy, pokud ji volající nekontroluje
Jak zůstává zvýraznění zarovnané po zoomu nebo otočení?
Zvýraznění zůstává zarovnané proto, že si je HotPDF ukládá v prostoru stránky PDF a při každém překreslení jej promítá znovu do prostoru obrazovky, místo aby si ukládal obdélník obrazovky, který by zastaral v okamžiku změny úrovně zoomu. THPDFViewerModel.PagePointToView a jeho inverze ViewPointToPage provádějí tuto projekci ve dvou fázích: nejdřív vlastní záznam stránky /Rotate, pak nezávislé ViewRotation prohlížeče, které se nikdy nezapisuje zpátky do PDF a ovlivňuje jen to, co prohlížeč zobrazuje. Zrušení transformace při puštění myši projde stejnými dvěma fázemi v opačném pořadí, a právě to umožňuje, aby zvýraznění nakreslené při vysokém zoomu na stránce otočené o 270 stupňů skončilo přesně na správném místě poté, co recenzent vrátí pohled zpět na Přizpůsobit stránce
DPI použité pro tuto projekci je stejně důležité jako otočení. Prohlížeč HotPDF zachytí přesné DPI bitmapy, která je právě na obrazovce, do FRenderedDPI hned po každém vykreslení, a ImageMouseUp předává tutéž hodnotu do ViewPointToPage, takže se souřadnice myši vždy převádí pomocí rozlišení, ve kterém byla skutečně nakreslena, ne rozlišení přepočítaného z aktuální vlastnosti zoomu. CreatePageSnapshot a jeho příbuzné metody omezují DPI na rozsah 12 až 2400, ale interaktivní cesta vykreslování žádný takový strop nenese: standardní žebřík zoomu vrcholí na 6400 %, což se při výchozím základu 96 DPI přepočítá na výrazně přes 2400 DPI, takže znovupoužití limitu ve stylu snímku pro mapování souřadnic by na horním konci rozsahu zoomu posunulo každé zvýraznění o několik pixelů. Dvě menší výchozí hodnoty interakci doplňují: tažení kratší než dva pixely na kterékoli ose se bere jako klik a nevytvoří žádné zvýraznění a zvýrazňování nemůže začít, dokud se skutečně nevykreslila alespoň jedna stránka, protože FRenderedDPI začíná na nule
Zapojení interaktivního zvýrazňování do obrazovky revize
Zapnutí interaktivního zvýrazňování je úkolem na tři vlastnosti přímo na ovládacím prvku THPDFViewer: nastavte InteractionMode na vimHighlight místo výchozího vimBrowse, vyberte HighlightColor, jehož výchozí hodnota je clYellow, a obsluhujte OnMarqueeSelect, abyste zjistili, co recenzent právě nakreslil. Všechno ostatní — zachycení myši, kreslení tečkovaného výběrového obdélníku během tažení, převod bodu puštění zpět do prostoru stránky, volání AddHighlightRegion — se odehraje uvnitř ovládacího prvku dřív, než se tato událost vyvolá
type
TReviewForm = class(TForm)
Viewer: THPDFViewer;
ReviewLog: TMemo;
procedure FormCreate(Sender: TObject);
private
procedure ViewerMarqueeSelect(Sender: TObject; Shift: TShiftState;
PageIndex: Integer; const PageRect: THPDFRectangle;
HighlightIndex: Integer);
end;
// PdfDoc is a THotPDF already loaded elsewhere on the form
procedure TReviewForm.FormCreate(Sender: TObject);
begin
Viewer.PDFDocument := PdfDoc;
Viewer.InteractionMode := vimHighlight;
Viewer.HighlightColor := clLime;
Viewer.OnMarqueeSelect := ViewerMarqueeSelect;
end;
procedure TReviewForm.ViewerMarqueeSelect(Sender: TObject; Shift: TShiftState;
PageIndex: Integer; const PageRect: THPDFRectangle; HighlightIndex: Integer);
begin
ReviewLog.Lines.Add(Format('page %d, mark #%d at (%.1f, %.1f)-(%.1f, %.1f)',
[PageIndex + 1, HighlightIndex, PageRect.Left, PageRect.Bottom,
PageRect.Right, PageRect.Top]));
end;
OnMarqueeSelect se vyvolá jen pro tažení, které skutečně vyprodukovalo zvýraznění: klik příliš malý na to, aby se počítal jako tažení, okamžitě vyčistí výběrový překryv, a tažení, které skončí zcela mimo stránku, se sice dostane do AddHighlightRegion, ale je tam odmítnuto stejným způsobem jako u programového volání, takže událost v obou případech mlčí. Jeden detail implementace, který stojí za znalost, pokud se zdá, že zvýrazňování na okrajích ovládacího prvku přestává reagovat: zachycení myši patří samotnému THPDFViewer, potomkovi TScrollBox, ne vnitřnímu TImage, který zobrazuje bitmapu stránky — právě to umožňuje recenzentovi táhnout za okraj vykreslené stránky a přesto dostat čisté puštění
Přidávání, odebírání a zpětné čtení zvýraznění z kódu
Zvýraznění vůbec nemusí pocházet z tažení myší. Viewer.HighlightRegion(PageIndex, PageRect, Color), které interně vede do stejného Model.AddHighlightRegion, jaké volá interaktivní tažení, je veřejné právě proto, aby obrazovka revize mohla znovu sestavit zvýraznění z dat, která už má: komentáře načtené z databáze, výsledky textového hledání nebo značky obnovené z předchozí relace. Protože souřadnice jsou obyčejná čísla v uživatelském prostoru PDF, nic na této cestě nezávisí na tom, že by se stránka musela nejdřív vykreslit, na rozdíl od interaktivního tažení, které potřebuje, aby FRenderedDPI už drželo skutečnou hodnotu
var
I: Integer;
Item: TPriorComment; // your own record: PageIndex + PageRect
NewIndex: Integer;
begin
for I := 0 to PriorComments.Count - 1 do
begin
Item := TPriorComment(PriorComments[I]);
NewIndex := Viewer.HighlightRegion(Item.PageIndex, Item.PageRect, clAqua);
if NewIndex < 0 then
LogWarning('comment %d fell outside the page and was dropped', [I]);
end;
end;
Odebírání jednoho zvýraznění je místo, kde se projeví úložiště podložené polem. RemoveHighlightRegion smaže jeden záznam a posune každý pozdější záznam o jednu pozici dolů, aby se mezera uzavřela, což znamená, že jakýkoli index zachycený dřív — z události OnMarqueeSelect nebo z předchozího výčtu — už není důvěryhodný, jakmile se odebere cokoli před ním v seznamu. OnHighlightChange se vyvolá při každém přidání, odebrání i volání ClearHighlightRegions, ale nenese žádnou informaci o tom, co se změnilo, takže bezpečný vzor je brát ji jako signál k tomu, aby se jakýkoli seznam zobrazovaný v panelu revize znovu sestavil z HighlightCount a TryGetHighlightRegion, místo aby se opravoval cachovaný index na místě
procedure TReviewForm.ViewerHighlightChange(Sender: TObject);
var
I: Integer;
Mark: THPDFViewerHighlight;
begin
MarkList.Items.Clear;
for I := 0 to Viewer.Model.HighlightCount - 1 do
if Viewer.Model.TryGetHighlightRegion(I, Mark) then
MarkList.Items.AddObject(Format('page %d', [Mark.PageIndex + 1]),
TObject(I));
end;
Kdy by se měla značka místo toho stát skutečnou anotací Highlight?
Oblast zvýraznění by se měla stát skutečnou anotací v okamžiku, kdy potřebuje přežít mimo tuto jednu instanci THPDFViewer. HotPDF také vystavuje AddHighlightAnnotation pro novou stránku a AddLoadedHighlightAnnotation pro už načtený dokument, a navzdory téměř identickému názvu jde o zcela odlišný mechanismus: obě metody zapíšou do pole stránky /Annots skutečnou textovou značkovací anotaci podle ISO 32000-1 §12.5.6.10, PDF /Subtype /Highlight, s /QuadPoints označujícími přesný běh glyfů, a jakmile se soubor uloží, vykreslí ji jakýkoli standardu odpovídající prohlížeč PDF, ne jen vlastní prohlížeč HotPDF. Stejná hranice mechanismu rozhoduje o tom, zda se značka přenese přes XFDF: anotace vytvořená přes AddLoadedHighlightAnnotation je normální objekt PDF, který ExportLoadedAnnotationsToXFDF zachytí a předá Acrobatu nebo jinému revizímu nástroji jako značkování ISO 19444-1, popsané v článku import a export anotací PDF jako XFDF v Delphi, zatímco oblast přidaná přes AddHighlightRegion je pro tento export neviditelná, protože nikdy nebyla zapsána do grafu objektů: existuje jen tak dlouho, jak dlouho existuje THPDFViewerModel, který ji vytvořil. Celou rodinu značkovacích a geometrických typů anotací dostupných na stránce a to, jak každou z nich obdélník umístí, popisuje článek o anotacích PDF v Delphi s HotPDF, a praktické pravidlo je jednoduché: dokud se o dokumentu ještě diskutuje, nechte značku jednorázovou, a jakmile je rozhodnutí konečné, převeďte ji na anotaci
Kde vrstva zvýraznění končí
Vrstva zvýraznění se ze své podstaty nesnaží vypadat jako průsvitné zvýrazňovací pero: RefreshDocument nakreslí každou oblast jako dvoupixelový obrysový obdélník ve vlastní barvě navrch cachované bitmapy stránky, stejně jako kreslí zásahy hledání, místo aby přes text pod ní míchal barevnou výplň, takže klasický vzhled žlutého přelivu je třeba domalovat v kódu aplikace nebo jej odložit na vlastní vzhledový proud povýšené anotace. Jedna schopnost, kterou se vyplatí znovu použít, jakmile oblast existuje, je CreateCurrentPageRegionSnapshot, která bere stejný THPDFRectangle, jaký už zvýraznění nese, a vykreslí jen tuto oblast do bitmapy, což se hodí pro připojení malého náhledového obrázku k připomínce revize bez exportu celé stránky. Sestavení pro revizi si nemusí mezi oběma mechanismy vybrat předem: nastavte výchozí chování tak, aby každá nová značka byla jednorázovou oblastí THPDFViewerHighlight, dokud vlákno komentářů zůstává otevřené, a volejte AddLoadedHighlightAnnotation až ve chvíli, kdy ji recenzent vyřeší, což udrží načtené PDF nedotčené během tam-a-zpět, které způsobuje nejvíc změn. Ovládací prvek prohlížeče popsaný zde je součástí standardní komponenty HotPDF pro Delphi a C++Builder, spolu se zbytkem API pro anotace a formuláře uvedeným výše