Egy bekezdés köré húzott téglalapnak felülvizsgálat közben nem kell a PDF-en belüli jelöléssé válnia. A HotPDF THPDFViewerModel-je felteszi az AddHighlightRegion-t, egy metódust, amely minden kiemelést memóriabeli rekordként tart, nem pedig a betöltött dokumentum módosításaként, így egy felülvizsgáló akár tucatnyi oldalt is megjelölhet, miközben a lemezen lévő fájl bájtról bájtra ugyanaz marad, ami volt. Nagyíts 6400%-ra, forgasd el az oldalt 90 fokkal, válts Szélességhez igazításból Oldalhoz igazításra, és ugyanaz a téglalap még mindig ugyanazon a bekezdésen landol, mert a koordináta-matematika a jelölés rajzolásának pillanatában érvényes tényleges renderelési geometrián megy keresztül
A PDF-megjelenítő köré épített felülvizsgálati eszközök folyamatosan ebbe a problémába ütköznek. Egy korrektúrázási képernyő, egy minőségbiztosítási átvizsgálás generált számlákon, egy belső jóváhagyási munkafolyamat: mindegyiknek lehetővé kell tennie, hogy valaki egy oldal egy területére felhívja a figyelmet anélkül, hogy minden piszkozati jelölés a fájl állandó módosításává válna, és anélkül, hogy egy teljes annotáció-alrendszerhez kellene nyúlni, csak azért, hogy megjelenjen egy színes doboz, miközben valaki még dönt arról, hogy a jelölés odaillik-e. A HotPDF erre egy dedikált kiemelési réteggel válaszol, amely teljes egészében a Delphiben egyéni PDF-megjelenítő MVC-architektúrával történő építéséről szóló cikkben leírt felosztás Model oldalán ül, és ez az oka annak is, hogy ugyanaz a kiemelési lista vezérelhető egy egységtesztből is, ablak-handle nélkül
Mit tárol valójában a HotPDF AddHighlightRegion-je?
Az AddHighlightRegion pontosan három dolgot tárol jelölésenként: egy nulla-alapú oldalindexet, egy THPDFRectangle-t PDF felhasználói térbeli koordinátákban, és egy TColor-t, mindezt egy THPDFViewerHighlight rekordba csomagolva a THPDFViewerModel-en belül. A Viewer.HighlightRegion(PageIndex, PageRect, clYellow), vagy az ezzel egyenértékű Model.AddHighlightRegion hívása hozzáfűz egyet ezekből a rekordokból egy privát tömbhöz, és visszaadja annak indexét, és ez az index az egyetlen handle, amit a hívó visszakap: nincs külön objektum, nincs referenciaszámlált interfész, nincs mit felszabadítani. A cikkben leírt minden más képesség, a jelölés rajzolása, áttérképezése egy nagyítási változás után, törlése, erre az egyetlen kis rekordra épül
Minden téglalap normalizálódik és levágásra kerül, mielőtt elfogadásra kerülne. Az AddHighlightRegion felcseréli a bal és jobb élt, ha egy felülvizsgáló jobbról balra húz, felcseréli a felsőt és alsót egy felfelé irányuló húzás esetén, majd az eredményt a GetLoadedPageBox-on keresztül lekért oldal MediaBoxához vágja. Egy téglalap, amely nulla szélességgel, nulla magassággal, vagy teljesen az oldalon kívül végzi, egyenesen elutasításra kerül: a metódus -1-et ad vissza, és semmi nem kerül hozzáadásra a listához. Ez a visszatérési érték nem díszítés: egy külső felülvizsgálati fájlból újraépített kiemeléscsomag, vagy elavult koordinátákból egy oldal cseréje után, csendben elveszíthet bejegyzéseket, ha a hívó nem ellenőrzi azt
Hogyan marad igazodva egy kiemelés nagyítás vagy forgatás után?
Egy kiemelés azért marad igazodva, mert a HotPDF PDF-oldaltérben tárolja azt, és minden egyes újrarajzoláskor visszavetíti képernyőtérbe, ahelyett hogy egy képernyő-téglalapot tárolna, amely elavulttá válna abban a pillanatban, amikor a nagyítási szint megváltozik. A THPDFViewerModel.PagePointToView és inverze, a ViewPointToPage, két szakaszban végzi ezt a vetítést: először az oldal saját /Rotate bejegyzését, majd a Megjelenítő független ViewRotation-jét, amely soha nem íródik vissza a PDF-be, és csak azt befolyásolja, amit a Megjelenítő mutat. A transzformáció visszavonása egérfelengedéskor ugyanezt a két szakaszt futtatja fordított sorrendben, és ez teszi lehetővé, hogy egy nagy nagyításnál, egy 270 fokkal elforgatott oldalon rajzolt kiemelés pontosan a megfelelő helyen landoljon, miután a felülvizsgáló visszaállítja a nézetet Oldalhoz igazításra
A vetítéshez használt DPI ugyanannyira számít, mint a forgatás. A HotPDF Megjelenítője rögzíti a jelenleg a képernyőn lévő bitkép pontos DPI-jét az FRenderedDPI-ben, közvetlenül minden renderelés után, és az ImageMouseUp ugyanezt az értéket adja át a ViewPointToPage-nek, így egy egérkoordináta mindig azzal a felbontással kerül átalakításra, amellyel ténylegesen rajzolták, nem pedig az aktuális nagyítási tulajdonságból újraszámított felbontással. A CreatePageSnapshot és rokonai a DPI-t 12 és 2400 közötti tartományra korlátozzák, de az interaktív renderelési útvonalon nincs ilyen plafon: a szabványos nagyítási létra 6400%-nál tetőzik, ami jóval 2400 DPI fölé számítódik az alapértelmezett 96 DPI-s alapvonalnál, így egy pillanatkép-stílusú korlát újrafelhasználása a koordináta-leképezéshez több pixellel eltolna minden kiemelést a nagyítási tartomány tetején. Két kisebb alapértelmezés kerekíti ki az interakciót: egy két pixelnél rövidebb húzás bármelyik tengelyen kattintásként kezelődik, és nem hoz létre kiemelést, és a kiemelés nem kezdődhet el, amíg legalább egy oldal ténylegesen le nem renderelődött, mivel az FRenderedDPI nullával indul
Az interaktív kiemelés bekötése egy felülvizsgálati képernyőbe
Az interaktív kiemelés bekapcsolása egy három tulajdonságos feladat magán a THPDFViewer vezérlőn: állítsd az InteractionMode-ot vimHighlight-ra az alapértelmezett vimBrowse helyett, válassz egy HighlightColor-t, amelynek alapértelmezése clYellow, és kezeld az OnMarqueeSelect-et, hogy megtudd, mit rajzolt éppen a felülvizsgáló. Minden más, az egér elfogása, a pontozott kijelölő téglalap rajzolása, miközben a felülvizsgáló húz, a felengedési pont visszaalakítása oldaltérré, az AddHighlightRegion hívása, a vezérlőn belül történik, mielőtt ez az esemény kiváltódna
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;
Az OnMarqueeSelect csak olyan húzásra váltódik ki, amely ténylegesen kiemelést eredményezett: egy húzásnak túl kicsi kattintás azonnal törli a kijelölési fedvényt, és egy húzás, amely teljesen az oldalon kívül landol, eléri az AddHighlightRegion-t, de ott ugyanúgy elutasításra kerül, ahogyan egy programozott hívás esetén is történne, így az esemény mindkét esetben néma marad. Egy megvalósítási részlet, amit érdemes tudni, ha a kiemelés valaha is leáll a vezérlő szélein: az egérfogás magáé a THPDFViewer-é, egy TScrollBox leszármazottjáé, nem a belső TImage-é, amely az oldal bitképét mutatja, és ez teszi lehetővé, hogy egy felülvizsgáló a renderelt oldal szélén túlra húzzon, és mégis tiszta felengedést kapjon
Kiemelések hozzáadása, eltávolítása és visszaolvasása kódból
A kiemeléseknek egyáltalán nem kell egérhúzásból származniuk. A Viewer.HighlightRegion(PageIndex, PageRect, Color), amely ugyanabba a Model.AddHighlightRegion-ba tölt bele, amelyet az interaktív húzás belsőleg meghív, kifejezetten azért nyilvános, hogy egy felülvizsgálati képernyő újraépíthesse a kiemeléseket olyan adatokból, amelyek már megvannak: egy adatbázisból betöltött megjegyzésekből, egy szövegkeresés eredményeiből, vagy egy korábbi munkamenetből visszaállított jelölésekből. Mivel a koordináták egyszerű PDF felhasználói térbeli számok, ez az útvonal semmiben nem függ attól, hogy egy oldal előbb renderelődött-e, ellentétben az interaktív húzással, amelynek szüksége van rá, hogy az FRenderedDPI már valódi értéket tartalmazzon
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;
Egyetlen kiemelés eltávolítása az a pont, ahol a tömb-alapú tárolás átüt. A RemoveHighlightRegion töröl egy rekordot, és minden későbbi rekordot egy pozícióval lejjebb tol, hogy bezárja a rést, ami azt jelenti, hogy bármely korábban rögzített index, akár egy OnMarqueeSelect eseményből, akár egy korábbi felsorolásból, már nem megbízható, amint a listában előtte lévő valami eltávolításra kerül. Az OnHighlightChange minden hozzáadáskor, eltávolításkor és ClearHighlightRegions híváskor kiváltódik, de nem hordoz információt arról, hogy mi változott, így a biztonságos minta az, hogy jelzésként kezeljük, hogy újraépítsük bármely listát, amelyet egy felülvizsgálati panel mutat a HighlightCount és a TryGetHighlightRegion alapján, ahelyett hogy egy gyorsítótárazott indexet foltoznánk a helyén
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;
Mikor kellene, hogy egy jelölés helyette valódi Highlight annotációvá váljon?
Egy kiemelési területnek abban a pillanatban kellene valódi annotációvá válnia, amikor túl kell élnie azon az egyetlen THPDFViewer példányon kívül. A HotPDF felteszi az AddHighlightAnnotation-t is egy új oldalhoz, és az AddLoadedHighlightAnnotation-t egy már betöltött dokumentumhoz, és a szinte azonos név ellenére ez egy teljesen más mechanizmus: mindkettő egy valódi ISO 32000-1 §12.5.6.10 szerinti szövegjelölő annotációt ír, PDF /Subtype /Highlight, az oldal /Annots tömbjébe, /QuadPoints-szal jelölve a pontos glyph-futamot, és minden szabványkövető PDF-megjelenítő megjeleníti azt, amint a fájl mentésre kerül, nemcsak maga a HotPDF. Ugyanez a mechanizmus-határ dönti el, hogy egy jelölés végigmegy-e az XFDF-en: egy AddLoadedHighlightAnnotation-nal létrehozott annotáció egy normál PDF-objektum, amelyet az ExportLoadedAnnotationsToXFDF felszed, és átad az Acrobatnak vagy egy másik felülvizsgáló eszköznek ISO 19444-1 jelölésként, ezt a PDF-annotációk Delphiben történő importálásáról és exportálásáról XFDF-ként szóló cikk tárgyalja, míg egy, az AddHighlightRegion-on keresztül hozzáadott terület láthatatlan ennek az exportnak, mert soha nem íródott bele az objektumgráfba: csak addig létezik, amíg az őt létrehozó THPDFViewerModel. Egy oldalon elérhető jelölő és geometriai annotációtípusok teljes családját, és hogy egy téglalap hogyan helyez el mindegyiket, a PDF-annotációkról szóló cikk Delphiben a HotPDF-fel tárgyalja, és a gyakorlati szabály egyszerű: tartsd eldobhatónak a jelölést, amíg egy dokumentumot még megvitatnak, és véglegesítsd annotációvá, amint egy döntés végleges
Hol ér véget a kiemelési réteg
A kiemelési réteg a maga részéről meg sem próbál átlátszó szövegkiemelő tollnak látszani: a RefreshDocument minden területet kétpixeles körvonalas téglalapként rajzol meg a saját színében a gyorsítótárazott oldal-bitkép tetején, ugyanúgy, ahogyan a keresési találatokat is rajzolja, ahelyett hogy egy színes kitöltést kevernie az alatta lévő szöveg fölé, így a klasszikus sárga-mosásos kinézetet az alkalmazáskódban kell megrajzolni, vagy egy előléptetett annotáció saját megjelenési folyamára (appearance stream) kell hagyni. Egy képesség, amit érdemes újrahasznosítani, amint egy terület létezik, a CreateCurrentPageRegionSnapshot, amely ugyanazt a THPDFRectangle-t veszi át, amit egy kiemelés már hordoz, és csak azt a területet rendereli egy bitképbe, hasznos ahhoz, hogy egy kis előnézeti képet csatoljunk egy felülvizsgálati megjegyzéshez a teljes oldal exportálása nélkül. Egy felülvizsgálati buildnek nem kell előre választania a két mechanizmus között: állítsd be alapértelmezettként minden új jelölést eldobható THPDFViewerHighlight területként, amíg egy megjegyzésszál nyitva marad, és csak akkor hívd az AddLoadedHighlightAnnotation-t, amikor egy felülvizsgáló lezárja azt, ami érintetlenül tartja a betöltött PDF-et az oda-vissza folyamat alatt, amely a legtöbb változást eredményezi. Az itt leírt megjelenítő vezérlő a Delphihez és C++Builderhez készült szabványos HotPDF komponens része, a fent hivatkozott annotáció- és form-API-k mellett