Peržiūros metu aplink pastraipą nupieštas stačiakampis nebūtinai turi tapti žyma PDF faile. HotPDF THPDFViewerModel suteikia AddHighlightRegion metodą, kuris kiekvieną paryškinimą laiko atmintyje esančiu įrašu, o ne įkelto dokumento pakeitimu, todėl tikrintojas gali pažymėti dešimtis puslapių, kol diske esantis failas išlieka bitas į bitą toks pats. Priartinkite iki 6400 %, pasukite puslapį 90 laipsnių, pereikite nuo Fit Width prie Fit Page, ir tas pats stačiakampis vis tiek pataikys į tą pačią pastraipą, nes koordinačių matematika žymės piešimo metu remiasi tikrąja atvaizdavimo geometrija
PDF peržiūros programa pagrįsti peržiūros įrankiai su šia problema susiduria nuolat. Redagavimo ekranui, sugeneruotų sąskaitų faktūrų QA patikrai ar vidiniam patvirtinimo procesui reikia leisti atkreipti dėmesį į puslapio sritį, kad kiekviena juodraštinė žyma netaptų nuolatiniu failo pakeitimu ir nereikėtų visos anotacijų posistemės vien tam, kad rodant spalvotą langelį būtų galima nuspręsti, ar žyma turi likti. HotPDF tai išsprendžia skirtu paryškinimų sluoksniu, esančiu tik Model pusėje MVC atskyrimo, aprašyto straipsnyje pasirinktinės PDF peržiūros programos su MVC architektūra kūrimas Delphi, todėl tą patį paryškinimų sąrašą galima valdyti ir iš vienetinio testo be jokios lango rankenos
Ką iš tikrųjų saugo HotPDF AddHighlightRegion
AddHighlightRegion kiekvienai žymai saugo lygiai tris dalykus: nulinį puslapio indeksą, THPDFRectangle PDF naudotojo erdvės koordinatėse ir TColor, visa tai supakuota į THPDFViewerHighlight įrašą, esantį THPDFViewerModel. Iškvietus Viewer.HighlightRegion(PageIndex, PageRect, clYellow) arba lygiavertį Model.AddHighlightRegion, vienas iš šių įrašų pridedamas prie privataus masyvo ir grąžinamas jo indeksas, o šis indeksas yra vienintelė rankena, kurią gauna iškvietėjas: nėra atskiro objekto, nuorodų skaičiavimo sąsajos ir nieko, ką reikėtų atlaisvinti. Visos kitos šiame straipsnyje aprašytos galimybės — žymės piešimas, jos perskaičiavimas pakeitus mastelį ir ištrynimas — sukurtos ant šio mažo įrašo
Kiekvienas stačiakampis prieš priimant normalizuojamas ir apkerpamas. AddHighlightRegion sukeičia kairįjį ir dešinįjį kraštus, jei tikrintojas tempia iš dešinės į kairę, sukeičia viršų ir apačią tempiant aukštyn, tada apkerpa rezultatą pagal puslapio MediaBox, gautą per GetLoadedPageBox. Stačiakampis, kurio plotis arba aukštis tampa nulinis arba kuris yra visiškai už puslapio ribų, iš karto atmetamas: metodas grąžina -1, o į sąrašą nieko nepridedama. Ši grąžinama reikšmė nėra dekoratyvi: išoriniame peržiūros faile arba pasenusiose koordinatėse po puslapio pakeitimo atkurtų paryškinimų paketas gali tyliai prarasti įrašus, jei iškvietėjas jos nepatikrins
Kaip paryškinimas išlieka lygiuotas pakeitus mastelį ar pasukus
Paryškinimas išlieka lygiuotas, nes HotPDF saugo jį PDF puslapio erdvėje ir per kiekvieną perpiešimą iš naujo projektuoja į ekrano erdvę, o ne saugo ekrano stačiakampį, kuris pasentų vos pasikeitus mastelio lygiui. THPDFViewerModel.PagePointToView ir atvirkštinis ViewPointToPage šį projektavimą atlieka dviem etapais: pirmiausia taikomas paties puslapio /Rotate įrašas, tada nepriklausomas Viewer ViewRotation, kuris niekada neįrašomas atgal į PDF ir veikia tik tai, ką rodo Viewer. Transformacijos atšaukimas paleidus pelę atlieka tuos pačius du etapus atvirkštine tvarka, todėl aukštu masteliu 270 laipsnių pasuktame puslapyje nupieštas paryškinimas tiksliai atsiduria reikiamoje vietoje, kai tikrintojas grąžina rodinį į Fit Page
Šiam projektavimui naudojama DPI reikšmė yra tokia pat svarbi kaip ir pasukimas. HotPDF Viewer iškart po kiekvieno atvaizdavimo užfiksuoja tikslų ekrane rodomos taškinės grafikos DPI į FRenderedDPI, o ImageMouseUp perduoda tą pačią reikšmę į ViewPointToPage, todėl pelės koordinatė visada konvertuojama naudojant raišką, kuria ji iš tikrųjų buvo nupiešta, o ne raišką, perskaičiuotą pagal dabartinę mastelio savybę. CreatePageSnapshot ir susiję metodai apriboja DPI nuo 12 iki 2400, tačiau interaktyvus atvaizdavimo kelias tokios viršutinės ribos neturi: standartinė mastelio pakopa siekia 6400 %, o pagal numatytąją 96 DPI bazę tai yra gerokai daugiau nei 2400 DPI, todėl momentinės kopijos ribos naudojimas koordinatėms nustatyti aukščiausiame mastelyje kiekvieną paryškinimą pastumtų keliais pikseliais. Sąveiką užbaigia dvi mažesnės numatytosios reikšmės: trumpesnis nei dviejų pikselių tempimas bet kuria ašimi laikomas spustelėjimu ir nesukuria paryškinimo, o paryškinti negalima, kol bent vienas puslapis iš tikrųjų neatvaizduotas, nes FRenderedDPI pradedamas nuo nulio
Interaktyvaus paryškinimo prijungimas prie peržiūros ekrano
Interaktyviam paryškinimui įjungti pačiame THPDFViewer valdiklyje reikia nustatyti tris savybes: InteractionMode pakeisti iš numatytojo vimBrowse į vimHighlight, pasirinkti HighlightColor, kurios numatytoji reikšmė yra clYellow, ir apdoroti OnMarqueeSelect, kad sužinotumėte, ką tikrintojas nupiešė. Visa kita — pelės fiksavimas, punktyrinio pasirinkimo stačiakampio piešimas tempiant, atleidimo taško konvertavimas atgal į puslapio erdvę ir AddHighlightRegion iškvietimas — vyksta valdiklio viduje prieš suveikiant šiam įvykiui
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 suveikia tik tada, kai tempiant iš tikrųjų sukuriamas paryškinimas: per mažas spustelėjimas iš karto išvalo pasirinkimo perdangą, o tempimas, visiškai atsidūręs už puslapio ribų, pasiekia AddHighlightRegion, tačiau ten atmetamas taip pat, kaip programinis iškvietimas, todėl įvykis abiem atvejais lieka tylus. Verta žinoti vieną įgyvendinimo detalę, jei paryškinimas ties valdiklio kraštais kada nors nustotų reaguoti: pelę fiksuoja pats THPDFViewer, TScrollBox palikuonis, o ne vidinis TImage, rodantis puslapio taškinę grafiką, todėl tikrintojas gali tempti už atvaizduoto puslapio krašto ir vis tiek tvarkingai atleisti pelę
Paryškinimų pridėjimas, šalinimas ir perskaitymas iš kodo
Paryškinimai nebūtinai turi atsirasti tempiant pele. Viewer.HighlightRegion(PageIndex, PageRect, Color), kuris nukreipia į tą patį Model.AddHighlightRegion, kurį viduje iškviečia interaktyvus tempimas, yra viešas būtent tam, kad peržiūros ekranas galėtų atkurti jau turimus duomenis: iš duomenų bazės įkeltus komentarus, teksto paieškos rezultatus arba iš ankstesnio seanso atkurtas žymas. Kadangi koordinatės yra paprasti PDF naudotojo erdvės skaičiai, šiam keliui nereikia, kad puslapis būtų iš anksto atvaizduotas, kitaip nei interaktyviam tempimui, kuriam FRenderedDPI turi jau turėti tikrą reikšmę
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;
Vieno paryškinimo šalinimas atskleidžia masyvu pagrįstą saugyklą. RemoveHighlightRegion ištrina vieną įrašą ir perkelia visus vėlesnius įrašus viena pozicija žemyn, kad uždarytų tarpą, todėl anksčiau užfiksuotas indeksas iš OnMarqueeSelect įvykio arba ankstesnio išvardijimo nebėra patikimas, kai pašalinamas prieš jį sąraše buvęs elementas. OnHighlightChange suveikia kiekvieną kartą pridedant ar šalinant žymą ir iškvietus ClearHighlightRegions, tačiau nepateikia informacijos apie tai, kas pasikeitė, todėl saugu jį laikyti signalu iš naujo sukurti peržiūros skydelyje rodomą sąrašą naudojant HighlightCount ir TryGetHighlightRegion, o ne vietoje taisyti išsaugotą indeksą
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 žyma turėtų tapti tikra Highlight anotacija
Paryškinimo sritis turėtų tapti tikra anotacija tą akimirką, kai jai reikia išlikti už to vieno THPDFViewer egzemplioriaus ribų. HotPDF taip pat suteikia AddHighlightAnnotation naujam puslapiui ir AddLoadedHighlightAnnotation jau įkeltam dokumentui, tačiau nepaisant beveik vienodo pavadinimo tai yra visiškai kitoks mechanizmas: abu metodai įrašo tikrą ISO 32000-1 §12.5.6.10 teksto žymėjimo anotaciją, PDF /Subtype /Highlight, į puslapio /Annots masyvą, o /QuadPoints pažymi tikslią simbolių seką, todėl bet kurią atitinkanti PDF peržiūros programa ją atvaizduoja išsaugojus failą, ne tik HotPDF. Ta pati mechanizmo riba lemia, ar žyma pereina per XFDF: anotacija, sukurta naudojant AddLoadedHighlightAnnotation, yra įprastas PDF objektas, kurį ExportLoadedAnnotationsToXFDF aptinka ir perduoda Acrobat ar kitam peržiūros įrankiui kaip ISO 19444-1 žymėjimą, aprašytą straipsnyje PDF anotacijų importavimas ir eksportavimas kaip XFDF Delphi, o per AddHighlightRegion pridėta sritis šiam eksportui nematoma, nes ji niekada nebuvo įrašyta į objektų grafiką ir egzistuoja tik tol, kol egzistuoja ją sukūręs THPDFViewerModel. Visas puslapyje galimų žymėjimo ir geometrinių anotacijų tipų rinkinys bei tai, kaip stačiakampis nustato kiekvieną iš jų, aprašytas straipsnyje apie PDF anotacijas Delphi su HotPDF, o praktinė taisyklė paprasta: kol dokumentas dar svarstomas, žymą laikykite laikina, o sprendimui tapus galutiniam paverskite ją anotacija
Kur baigiasi paryškinimų sluoksnis
Pats paryškinimų sluoksnis nesistengia atrodyti kaip permatomas žymeklis: RefreshDocument kiekvieną sritį ant spartinančiojoje atmintyje esančios puslapio taškinės grafikos nupiešia kaip dviejų pikselių kontūro stačiakampį jos spalva, taip pat kaip piešia paieškos rezultatus, todėl klasikinį geltono plovimo vaizdą reikia piešti programos kode arba palikti pačios paaukštintos anotacijos išvaizdos srautui. Viena naudinga galimybė, kai sritis jau egzistuoja, yra CreateCurrentPageRegionSnapshot, kuri ima tą patį THPDFRectangle, kurį turi paryškinimas, ir atvaizduoja tik tą sritį į taškinę grafiką; tai naudinga norint prie peržiūros komentaro pridėti mažą peržiūros vaizdą neeksportuojant viso puslapio. Peržiūros kūriniui nereikia iš anksto rinktis vieno iš šių mechanizmų: kiekvieną naują žymą laikykite laikina THPDFViewerHighlight sritimi, kol atvira komentarų gija, ir tik tikrintojui ją išsprendus iškvieskite AddLoadedHighlightAnnotation, kad įkeltas PDF liktų nepakeistas per daugiausia pakeitimų sukeliančias diskusijas. Čia aprašytas peržiūros valdiklis yra standartinio HotPDF Component, skirto Delphi ir C++Builder, dalis kartu su kitomis pirmiau nurodytomis anotacijų ir formų API