Pravokutnik nacrtan oko odlomka tijekom pregleda ne mora postati oznaka unutar PDF-a. HotPDF-ov THPDFViewerModel izlaže AddHighlightRegion, metodu koja svako isticanje čuva kao zapis u memoriji umjesto kao izmjenu učitanog dokumenta, pa pregledavatelj može označiti desetke stranica dok datoteka na disku ostaje bajt po bajt ista. Zumirajte na 6400%, zakrenite stranicu za 90 stupnjeva, prijeđite s Fit Width na Fit Page i isti će pravokutnik i dalje biti na istom odlomku jer se izračun koordinata u trenutku crtanja oznake provodi kroz stvarnu geometriju iscrtavanja
Alati za pregled izgrađeni oko PDF preglednika stalno nailaze na ovaj problem. Zaslon za redline pregled, kontrola kvalitete generiranih računa, interni tijek odobravanja: svima je potrebno omogućiti da netko istakne područje stranice bez pretvaranja svake privremene oznake u trajnu izmjenu datoteke i bez uvođenja punog podsustava anotacija samo radi prikaza obojenog okvira dok se još odlučuje treba li oznaka ostati. HotPDF to rješava posebnim slojem isticanja koji se nalazi u cijelosti na strani Model podjele opisane u članku izrada prilagođenog PDF preglednika s MVC arhitekturom u Delphiju, zbog čega se istim popisom isticanja može upravljati i iz testa jedinice bez ikakvog prozorskog handlea
Što zapravo pohranjuje HotPDF-ov AddHighlightRegion?
AddHighlightRegion za svaku oznaku pohranjuje točno tri stvari: indeks stranice počevši od nule, THPDFRectangle u koordinatama korisničkog prostora PDF-a i TColor, sve zapakirano kao zapis THPDFViewerHighlight unutar THPDFViewerModel. Poziv Viewer.HighlightRegion(PageIndex, PageRect, clYellow) ili istovjetni Model.AddHighlightRegion dodaje jedan takav zapis u privatno polje i vraća njegov indeks, a to je jedini handle koji pozivatelj dobiva: nema zasebnog objekta, sučelja s brojanjem referenci ni objekta koji treba osloboditi. Svaka druga mogućnost u ovom članku, crtanje oznake, ponovno mapiranje nakon promjene zumiranja i njezino brisanje, izgrađena je na tom jednom malom zapisu
Svaki se pravokutnik normalizira i izrezuje prije prihvaćanja. AddHighlightRegion zamjenjuje lijevi i desni rub ako pregledavatelj povlači zdesna nalijevo, zamjenjuje gornji i donji rub pri povlačenju prema gore, a zatim rezultat izrezuje prema MediaBoxu stranice dohvaćenom kroz GetLoadedPageBox. Pravokutnik koji završi sa širinom nula, visinom nula ili se u cijelosti nalazi izvan stranice odmah se odbija: metoda vraća -1 i ništa se ne dodaje na popis. Ta povratna vrijednost nije ukrasna: skup isticanja obnovljen iz vanjske datoteke pregleda ili iz zastarjelih koordinata nakon zamjene stranice može tiho izgubiti stavke ako je pozivatelj ne provjeri
Kako isticanje ostaje poravnato nakon zumiranja ili rotacije?
Isticanje ostaje poravnato zato što ga HotPDF pohranjuje u prostoru stranice PDF-a i pri svakom ponovnom iscrtavanju ponovno projicira u prostor zaslona, umjesto da pohrani pravokutnik zaslona koji bi zastario čim se promijeni razina zumiranja. THPDFViewerModel.PagePointToView i njegova inverzna metoda ViewPointToPage tu projekciju provode u dva koraka: najprije kroz vlastiti unos stranice /Rotate, a zatim kroz neovisni ViewRotation preglednika, koji se nikad ne zapisuje natrag u PDF i utječe samo na ono što preglednik prikazuje. Poništavanje transformacije pri otpuštanju miša provodi ista dva koraka obrnutim redoslijedom, pa se isticanje nacrtano pri velikom zumiranju na stranici zakrenutoj za 270 stupnjeva nalazi na točno pravom mjestu nakon što pregledavatelj vrati prikaz na Fit Page
DPI korišten za tu projekciju jednako je važan kao i rotacija. HotPDF-ov preglednik bilježi točan DPI bitmape koja je trenutačno na zaslonu u FRenderedDPI odmah nakon svakog iscrtavanja, a ImageMouseUp tu istu vrijednost prosljeđuje u ViewPointToPage kako bi se koordinata miša uvijek pretvarala pomoću razlučivosti kojom je stvarno nacrtana, a ne razlučivosti ponovno izračunate iz trenutačnog svojstva zumiranja. CreatePageSnapshot i srodne metode ograničavaju DPI na raspon od 12 do 2400, ali interaktivni put iscrtavanja nema takvu gornju granicu: standardna ljestvica zumiranja doseže 6400%, što pri zadanoj osnovici od 96 DPI daje znatno više od 2400 DPI, pa bi ponovna upotreba ograničenja u stilu snimke za mapiranje koordinata pomaknula svako isticanje za nekoliko piksela na vrhu raspona zumiranja. Dvije manje zadane postavke zaokružuju interakciju: povlačenje kraće od dva piksela na bilo kojoj osi smatra se klikom i ne stvara isticanje, a isticanje ne može započeti dok se ne iscrta barem jedna stranica jer FRenderedDPI počinje od nule
Uključivanje interaktivnog isticanja u zaslon za pregled
Uključivanje interaktivnog isticanja posao je s tri svojstva na samoj kontroli THPDFViewer: postavite InteractionMode na vimHighlight umjesto zadane vrijednosti vimBrowse, odaberite HighlightColor, koji je zadano clYellow, i obradite OnMarqueeSelect da biste saznali što je pregledavatelj upravo nacrtao. Sve ostalo, hvatanje miša, crtanje isprekidanog pravokutnika odabira tijekom povlačenja, pretvaranje točke otpuštanja natrag u prostor stranice i pozivanje AddHighlightRegion, odvija se unutar kontrole prije pokretanja tog događaja
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 pokreće se samo za povlačenje koje je doista proizvelo isticanje: premalen klik za povlačenje odmah uklanja sloj odabira, a povlačenje koje u cijelosti završi izvan stranice dolazi do AddHighlightRegion, gdje se odbija na isti način kao programski poziv, pa događaj u oba slučaja ostaje tih. Jedan detalj vrijedi zapamtiti ako se čini da isticanje na rubovima kontrole prestaje reagirati: hvatanje miša pripada samom THPDFViewer, potomku klase TScrollBox, a ne unutarnjem TImage koji prikazuje bitmapu stranice, što pregledavatelju omogućuje povlačenje izvan ruba iscrtane stranice uz uredno otpuštanje
Dodavanje, uklanjanje i ponovno čitanje isticanja iz koda
Isticanja uopće ne moraju nastati povlačenjem miša. Viewer.HighlightRegion(PageIndex, PageRect, Color), koji vodi do istog Model.AddHighlightRegion što ga interaktivno povlačenje interno poziva, javno je izložen upravo zato da zaslon za pregled može obnoviti isticanja iz podataka koje već ima: komentara učitanih iz baze, rezultata tekstnog pretraživanja ili oznaka vraćenih iz prethodne sesije. Budući da su koordinate obični brojevi korisničkog prostora PDF-a, ništa u tom putu ne ovisi o tome je li stranica prethodno iscrtana, za razliku od interaktivnog povlačenja kojemu je potrebno da FRenderedDPI već sadrži stvarnu vrijednost
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;
Uklanjanje pojedinačnog isticanja pokazuje da je pohrana utemeljena na polju. RemoveHighlightRegion briše jedan zapis i pomiče svaki kasniji zapis za jedno mjesto prema dolje kako bi zatvorio prazninu, što znači da indeks zabilježen ranije, iz događaja OnMarqueeSelect ili prethodnog popisivanja, više nije pouzdan čim se ukloni nešto što je bilo ispred njega u popisu. OnHighlightChange pokreće se pri svakom dodavanju, uklanjanju i pozivu ClearHighlightRegions, ali ne nosi podatak o tome što se promijenilo, pa je siguran obrazac tretirati ga kao signal za ponovnu izgradnju popisa koji prikazuje ploča za pregled pomoću HighlightCount i TryGetHighlightRegion, umjesto krpanja predmemoriranog indeksa na mjestu
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;
Kada bi oznaka umjesto toga trebala postati stvarna anotacija Highlight?
Područje isticanja treba postati stvarna anotacija čim mora preživjeti izvan te jedne instance THPDFViewer. HotPDF također izlaže AddHighlightAnnotation za novu stranicu i AddLoadedHighlightAnnotation za već učitani dokument, a unatoč gotovo identičnom nazivu to je potpuno drukčiji mehanizam: oba upisuju stvarnu tekstualnu anotaciju za označavanje prema ISO 32000-1 §12.5.6.10, PDF /Subtype /Highlight, u polje /Annots stranice, s /QuadPoints koji označavaju točan niz glifova, a svaki sukladni PDF preglednik prikazuje je nakon spremanja datoteke, ne samo HotPDF-ov. Ista granica mehanizma određuje prolazi li oznaka kroz XFDF: anotacija izrađena pomoću AddLoadedHighlightAnnotation normalan je PDF objekt koji ExportLoadedAnnotationsToXFDF preuzima i predaje Acrobatu ili drugom alatu za pregled kao oznaku prema ISO 19444-1, što je opisano u članku uvoz i izvoz PDF anotacija kao XFDF u Delphiju, dok je područje dodano kroz AddHighlightRegion nevidljivo tom izvozu jer uopće nije zapisano u grafu objekata: postoji samo dok postoji THPDFViewerModel koji ga je stvorio. Cijela obitelj vrsta oznaka i geometrijskih anotacija dostupnih na stranici te način na koji pravokutnik postavlja svaku od njih obrađeni su u članku o PDF anotacijama u Delphiju s HotPDF-om, a praktično pravilo je jednostavno: oznaku držite privremenom dok se o dokumentu još raspravlja, a pretvorite je u anotaciju kada je odluka konačna
Granice sloja isticanja
Sloj isticanja sa svoje strane ne pokušava izgledati kao prozirni flomaster: RefreshDocument svako područje crta kao pravokutnik obrisa debljine dva piksela u vlastitoj boji iznad predmemorirane bitmape stranice, na isti način na koji crta rezultate pretraživanja, pa klasični izgled žutog premaza treba nacrtati u kodu aplikacije ili ga prepustiti vlastitom toku izgleda promaknute anotacije. Jedna mogućnost koju vrijedi ponovno iskoristiti nakon što područje postoji jest CreateCurrentPageRegionSnapshot, koji uzima isti THPDFRectangle što ga isticanje već nosi i iscrtava samo to područje u bitmapu, što je korisno za dodavanje male pretpregledne slike komentaru pregleda bez izvoza cijele stranice. Pregledna verzija ne mora unaprijed birati između ta dva mehanizma: svaku novu oznaku zadano stvarajte kao privremeno područje THPDFViewerHighlight dok je nit komentara otvorena i pozovite AddLoadedHighlightAnnotation tek kada je pregledavatelj riješi, čime učitani PDF ostaje netaknut tijekom rasprave koja stvara najviše promjena. Ova kontrola preglednika dio je standardne HotPDF komponente za Delphi i C++Builder, zajedno s ostalim API-jima za anotacije i obrasce navedenima iznad