PDF anotacija yra prie puslapio prisegtas žodynas, o ne ant jo nupieštas ženklas. ISO 32000-1 §12.5 apibrėžia maždaug du tuzinus potipių, ir kiekvienas iš jų turi /Subtype, stačiakampį puslapio koordinatėmis, vėliavėlių (flags) rinkinį ir dažniausiai išvaizdos srautą (appearance stream), kuris nustato, ką peržiūros programa iš tikrųjų piešia. Šie potipiai dokumentą peržiūrinčiam asmeniui nereiškia to paties. Paryškinimas (Highlight) ir rašalo brūkšnys (Ink) yra komentarai; nuoroda (Link) yra navigacija; iškylantysis langas (Popup) yra nedidelis langas, kuris atsidaro spustelėjus lipnų lapelį, saugomas kaip atskiras objektas ir nurodomas tėvinio objekto. Atsakymai yra pilnos teksto (Text) anotacijos, kurios per įrašą „in-reply-to“ nurodo komentarą, į kurį atsakoma. Todėl puslapio lygio anotacijų masyvas nėra peržiūros dalyvio komentarų sąrašas. Tai yra plokščias rinkinys, kuriame yra komentarai, juos jungiančios jungtys ir keletas dalykų, kurių joks peržiūros dalyvis nepavadintų komentaru. Skydelis, kuris šį masyvą traktuoja kaip komentarų sąrašą, nesutaps su jokia kita peržiūros programa, kurią naudoja klientas.
Anotacijų peržiūros darbo eigos kūrimas naudojant „PDFium Component“ (PDFium pagrįstą VCL/LCL komponentą, skirtą Delphi, C++Builder ir Lazarus) reiškia susitelkimą į tuos punktus, kur šis atotrūkis tarp neapdoroto masyvo ir žmogaus matomo vaizdo sukelia problemų: skaičiavimą, indeksavimą, spalvų keitimą žymėjimams, kuriuos variklis jau užfiksavo, trynimą nepaliekant likučių (ghosts) ir savo pačių žymių pridėjimą.
Kodėl jūsų skaičius niekada nesutampa su „Acrobat“ komentarų skydeliu
Atidarykite sužymėtą sutartį savo peržiūros programoje ir „Acrobat“ šalia viena kitos, ir bendri skaičiai retai sutaps. „Acrobat“ rodo atrinktą vaizdą: žymėjimai sugrupuoti į atsakymų gijas, iškylantieji langai suskleisti į pastabas, kurioms jie prikai. Neapdorotas masyvas saugo visa tai be diferencijavimo, todėl paprastas skaičiavimas vienais atžvilgiais bus per didelis, o kitais – per mažas.
Iškylantieji langai padidina bendrą skaičių, nes kiekvienas lipnus lapelis pateikiamas su atskiru Popup objektu, o skaičiuojant abu, pastaba padvigubėja. Atsakymai sumažina skaičių, jei filtruojate pagal matomas žymes, nes atsakymas yra teksto (Text) anotacija, kurioje nieko nepiešiama, kol kas nors neišplečia gijos, o jos atsisakius prarandama diskusija. „Hidden“ ir „NoView“ vėliavėlės pašalina anotaciją iš ekrano, bet nepašalina jos iš masyvo, todėl neatsižvelgiant į vėliavėles skaičiuojamos ir tos žymės, kurių vartotojas negali matyti. Nuorodų (Link) anotacijos yra tame pačiame masyve kaip ir komentarai, ir jos neturėtų patekti nei į skaičių, nei į sąrašą. Priimkite sprendimą dėl skaičiavimo taisyklės prieš rašydami ciklą ir jį užfiksuokite, nes klausimas „kodėl jūsų skydelis rodo kitokį skaičių nei Acrobat“ yra pirmoji užklausa, kurią gausite įdiegę peržiūros funkciją.
Indeksuokite viską vieną kartą, tada niekada iš naujo neanalizuokite puslapio
Viena projektavimo taisyklė lemia visa kita: filtravimas pagal autorių, tipą ar puslapį niekada neturi iš naujo analizuoti puslapio objektų. 300 puslapių dokumente su gausiu žymėjimu, pakartotinis analizavimas keičiant kiekvieną išskleidžiamojo sąrašo reikšmę paverčia skydelį stringančiu įrankiu. Komponentas suteikia prieigą prie AnnotationCount ir indeksuotos savybės Annotation[], kurių abiejų galiojimo sritis yra šiuo metu įkeltas puslapis, o jų grąžinamas TPdfAnnotation įrašas turi viską, ko reikia sąrašo rodiniui: Subtype, Flags, Color, Rectangle, ContentsText, AuthorText. Teisingas žingsnis yra vieną kartą atidarant dokumentą nuskaityti kiekvieną puslapį ir išsaugoti savo plokščią indeksą:
procedure TReviewPanel.BuildIndex;
var
PageNo, i: Integer;
A: TPdfAnnotation;
begin
FItems.Clear;
for PageNo := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := PageNo;
for i := 0 to Pdf.AnnotationCount - 1 do
begin
A := Pdf.Annotation[i];
// Keep reviewer-relevant subtypes only; record the page and
// index pair because all later edits are addressed by it
if A.Subtype in [anText, anHighlight, anInk] then
FItems.Add(TReviewItem.Create(PageNo, i,
A.AuthorText, A.ContentsText, A.Rectangle, A.Color));
end;
end;
end;
Pora, kurią verta pabrėžti, yra (PageNo, i). Kiekvienas vėlesnis pakeitimas, nesvarbu, ar tai būtų spalvos keitimas, ar trynimas, yra adresuojamas pagal puslapio numerį ir anotacijos indeksą, o indeksas yra trapus: pašalinus anotaciją, visi po jos tame puslapyje esantys elementai pernumeruojami. Todėl planuokite atkurti paveikto puslapio įrašus po bet kokio pašalinimo, užuot taisę indeksų numerius vietoje. Atkūrimas trunka milisekundę. Priešingai, pasenęs indeksas ištrina netinkamo peržiūros dalyvio komentarą, o tokio tipo klaida sugriauna pasitikėjimą visa funkcija.
Gijos (threads) nusipelno vietos indekse, net jei jūsų pirmoji versija tik skaičiuoja atsakymus, o ne juos rodo. Sugrupuokite elementus pagal jų tėvinę nuorodą, kol puslapis yra atidarytas, kad skydelis vėliau galėtų suskleisti giją taip, kaip tai daro „Acrobat“. Šio grupavimo atkūrimas eigoje slenkant puslapį prieštarauja visai vienkartinio indeksavimo idėjai, nes iš naujo atidaromi puslapiai, kuriuos jau išanalizavote. Geometrijai reikalinga tokia pati tvarka. Kiekvieno įrašo Rectangle yra puslapio erdvėje, o jo konvertavimas į rodinio koordinates turėtų būti atliekamas vienoje bendroje pagalbinėje funkcijoje, o ne išmėtytas visame kode. Skydeliuose atsiranda koordinačių klaidų, kai pasirinkimas, paspaudimo tikrinimas (hit-testing) ir piešimas naudoja atskirus mastelio keitimo bei pasukimo skaičiavimus; nukreipkite visus tris per vieną konvertavimą, ir paryškinimas, jo eilutė sąraše bei jo spustelėjimo taikinys liks susieti su tuo pačiu objektu.
Žymėjimo spalvos keitimas ir išvaizdos srauto veto
Paryškinimo pakeitimas iš geltono į gintarinį skamba kaip viena kodo eilutė, ir kartais taip ir yra. Tačiau kliūtis yra ISO 32000-1 §12.5.5. Kai anotacija turi /AP išvaizdos srautą (appearance stream), standartus atitinkanti peržiūros programa piešia šį iš anksto sugeneruotą srautą, o spalvos įrašą žodyne traktuoja kaip nenaudojamus metaduomenis. „Acrobat“ rašo išvaizdos srautus beveik viskam, ką sukuria, todėl dauguma iš klientų gaunamų anotacijų jau yra tokios būsenos, ir spalva, kurią taip užtikrintai nustatėte, niekada nepasiekia ekrano. Spalvos keitimas yra skaitymo-keitimo-rašymo procesas naudojant savybę Annotation[], ir komponentas atvirai praneša apie konfliktą: kai variklis atsisako leisti žodyno spalvai pakeisti integruotą išvaizdą, rašymas sukelia EPdfError.
A := Pdf.Annotation[Item.Index];
A.HasColor := True;
A.Color := $0000B0FF; // amber
A.ColorAlpha := 160;
try
Pdf.Annotation[Item.Index] := A;
except
on EPdfError do
begin
// The annotation owns a pre-rendered /AP stream; the dictionary
// color alone cannot change what viewers paint
Item.AppearanceLocked := True;
StatusBar.SimpleText := 'Color is fixed by the annotation appearance';
end;
end;
Gaudykite šią išimtį kiekvieną kartą ir vertinkite ją kaip informaciją, o ne kaip klaidą. Praleiskite šią apsaugą ir jūsų skydelis džiaugsmingai rodys gintarinę spalvą savo sąraše, o puslapis toliau bus piešiamas geltonai; vartotojas po kelių savaičių pateiks skundą, kad „jūsų peržiūros programa ignoruoja mano pakeitimus“, o jūs praleisite popietę bandydami tai atkurti su failu, kuris atsitiktinai neturi išvaizdos srauto. Kai žinote, kad išvaizda yra užrakinta, turite du tinkamus sprendimus: pakeiskite savo pasirinkimo sluoksnio (overlay) spalvą, o ne pačios anotacijos, kad peržiūros dalyvis bent jau matytų pasirinktą spalvą, arba pažymėkite eilutę kaip užrakintos išvaizdos, kad niekas nesitikėtų, jog pakeitimas išliks.
Anotacijų trynimas nepaliekant likučių (vaiduoklių)
DeleteAnnotation pašalina objektą iš dabartinio puslapio anotacijų medžio, tačiau palieka talpykloje esantį puslapio rastrinį vaizdą nepakeistą. Nupieškite vaizdą iškart po iškvietimo ir ištrintas paryškinimas vis tiek bus ekrane, esantis taškiniame paveikslėlyje (bitmap), kuris nebeatitinka už jo esančio dokumento modelio. Sprendimas yra pakartotinį atvaizdavimą traktuoti kaip trynimo dalį, o ne kaip žingsnį, kurį iškvietėjas gali pamiršti:
Pdf.PageNumber := Item.PageNo;
Pdf.DeleteAnnotation(Item.Index); // raises EPdfError on failure
Bmp := Pdf.RenderPage(0, 0, ViewWidth, ViewHeight, ro0, [reAnnotations]);
try
PaintPageBitmap(Bmp);
finally
Bmp.Free; // RenderPage hands bitmap ownership to the caller
end;
RebuildPageEntries(Item.PageNo); // indices after Item.Index shifted
Dvi detales šiame bloke lengva supainioti. Turi būti nurodyta parinktis reAnnotations, kitaip naujame rastro vaizde bus pašalintos visos likusios anotacijos ir puslapis atrodys taip, tarsi ištrynėte visus komentarus, o ne vieną žymę. Be to, Bmp.Free yra privalomas: funkcinio stiliaus RenderPage perkrova perduoda taškinio paveikslėlio nuosavybės teisę iškvietėjui, todėl pamiršus jį atlaisvinti, nutekės viso puslapio rastras su kiekvienu trynimu. Peržiūros dalyvis, dirbantis su ilgu dokumentu, dėl to per kelias minutes pajus realų atminties trūkumą.
Peržiūros žymių pridėjimas iš savo vartotojo sąsajos
Anotacijų kūrimas atliekamas per CreateAnnotation, kuri priima užpildytą TPdfAnnotation įrašą (potipis, stačiakampis, spalva, turinys, autorius) ir prideda jį prie dabartinio puslapio. Lipnus lapelis, kurio potipis yra anText, yra paprastas atvejis: nustatykite poziciją, turinį bei autorių ir baigta. Brėžimo (Ink) anotacijos yra ta vieta, kur programuotojai susiduria su sunkumais. Įrašo stačiakampis tik apriboja piešinį; patys brūkšniai yra taškų masyvai, kuriuos reikia pridėti atskirai per variklio rašalo brūkšnio iškvietimą FPDFAnnot_AddInkStroke, pateikiant FS_POINTF duomenis, užfiksuotus iš pelės arba rašiklio įvesties po vieną brūkšnį. Jei sukursite brėžimo anotaciją tik iš stačiakampio, gausite tuščią brėžinį, kuris bus rodomas kaip tuščia vieta – tai atrodo kaip variklio klaida, bet iš tikrųjų tai tik pusiau baigta anotacija.
Tuo pačiu metu nustatykite autorystės politiką. Kiekviena žymė, kurią sukuria jūsų vartotojo sąsaja, turėtų turėti nuoseklų AuthorText, nes peržiūros dalyvių filtras, kurį kursite kitą mėnesį, bus tik toks geras, kokius vardus šiandien įrašysite komentaruose. Tuščių arba nenuoseklių autorių eilučių negalima pataisyti atgaline data neatidarius kiekvieno failo.
Peržiūros duomenų eksportavimas iš peržiūros programos
Peržiūros duomenys tampa naudingi tada, kai gali būti eksportuojami iš peržiūros programos – kaip santrauka, kurią projekto vadovas skaito neatidarydamas failo, arba kaip CSV failas, kuris užpildo sekimo lentelę. Eksportuokite iš jau sukurto indekso, o ne iš naujo analizavimo, ir pasirinkite stabilų būdą nuorodai į kiekvieną žymę. Puslapio numeris, susietas su anotacijos stačiakampiu, išlieka teisingas net po pasikeitimų, ko negalima pasakyti apie masyvo indeksą, nes kitas ištrynimas tyliai pernumeruoja indeksus ir jūsų CSV pradeda rodyti neteisingus komentarus.
Eilutė, kurią verta saugoti, apima puslapį, potipį, autorių, sukūrimo laiko žymą (jei failas ją turi), turinio tekstą ir būsenos stulpelį, kurį valdote jūs, o ne PDF failas. Tas pats indeksavimo žingsnis yra naudingas ir anksčiau, priėmimo (intake) metu, kai dokumentas gaunamas iš už komandos ribų ir norite sužinoti, kas jame yra, prieš kam nors pradedant jį peržiūrėti. Straipsnyje apie PDF priėmimo peržiūros darbo aplinką aprašomas šis procesas, o formos laukų navigacija nagrinėja veidrodinę problemą: dokumentų, skirtų rinkti duomenis, o ne komentarus, peržiūrą.
Vienas atvejis, kurio masyvas jums neparodys
Vienas sutrikimo scenarijus reikalauja dėmesio, nes jis atrodo kaip jūsų kodo defektas, nors taip nėra. Klientas praneša apie matomus paryškinimus visame puslapyje, tačiau jūsų skydelyje nieko nerodoma, o AnnotationCount grąžina nulį. Įprastas paaiškinimas yra tas, kad žymės buvo suplokštintos (flattened) kur nors anksčiau. Suplokštinimas integruoja anotacijų išvaizdą į įprastą puslapio turinį, todėl paryškinimai tampa puslapio grafikos dalimi ir visiškai nustoja egzistuoti kaip anotacijų objektai. Anotacijų API nebeturi ko išvardyti, keisti spalvą ar trinti. Kai matote nupieštą žymėjimą su nuliniu skaičiumi, nustokite ieškoti klaidos savo ciklo kode ir pasidomėkite, kaip šis failas buvo sukurtas.
Čia naudojama anotacijų sąsaja – nuo išvardijimo ir kūrimo iki spalvų keitimo, trynimo ir atvaizdavimo parinkčių, užtikrinantį tikslų rodymą – yra pateikiama su „PDFium Component“, skirtu Delphi, C++Builder ir Lazarus/FPC.