PDF anotacija je rječnik (dictionary) pridružen stranici, a ne oznaka nacrtana na njoj. ISO 32000-1 §12.5 definira otprilike dva tuceta podtipova, a svaki od njih sadrži /Subtype, pravokutnik u koordinatama stranice, skup zastavica i obično strujanje izgleda (appearance stream) koje određuje što preglednik zapravo iscrtava. Podtipovi ne znače svi istu stvar osobi koja pregledava dokument. Isticanje (Highlight) i potez tintom (Ink) su komentari; poveznica (Link) je navigacija; skočni prozor (Popup) je mali prozor koji se otvara kada kliknete na ljepljivu bilješku (sticky note), pohranjen kao zaseban objekt na koji upućuje roditeljski objekt. Odgovori su potpune tekstualne anotacije (Text) koje upućuju na komentar na koji odgovaraju putem unosa in-reply-to. Stoga niz anotacija na razini stranice nije recenzentov popis komentara. To je ravna vreća koja sadrži komentare, veze koje ih povezuju i nekoliko stvari koje nijedan recenzent uopće ne bi nazvao komentarom. Ploča koja taj niz tretira kao popis komentara neće se podudarati s bilo kojim drugim preglednikom koji korisnik pokrene.
Izrada tijeka rada za pregled anotacija na PDFium komponenti (PDFium Component), VCL/LCL komponenti temeljenoj na PDFiumu za Delphi, C++Builder i Lazarus, znači usredotočiti se na točke u kojima taj jaz između sirovog niza i ljudskog prikaza uzrokuje probleme: brojanje, indeksiranje, promjena boje oznaka koje je pokretač već zamrznuo, brisanje bez preostalih tragova i dodavanje vlastitih oznaka.
Zašto se vaš broj nikada ne podudara s Acrobatovim oknom za komentare
Otvorite označeni ugovor u svom pregledniku i u Acrobatu jedan pored drugog i ukupni iznosi će se rijetko podudarati. Acrobat prikazuje pročišćeni prikaz: oznake grupirane u niti odgovora, skočni prozori sklopljeni u bilješke kojima pripadaju, a poveznice i widgeti obrazaca su izostavljeni. Sirovi niz sadrži sve to bez razlikovanja, pa naivno brojanje istovremeno daje prevelik broj u some aspektima i premali u drugima.
Skočni prozori (Popups) povećavaju ukupan broj jer svaka ljepljiva bilješka dolazi sa zasebnim objektom Popup, pa brojanje obaju objekata udvostručuje bilješku. Odgovori ga smanjuju ako filtrirate prema vidljivim oznakama, budući da je odgovor tekstualna anotacija na kojoj se ništa ne iscrtava dok netko ne proširi nit, a njegovo odbacivanje gubi raspravu. Zastavice Hidden i NoView uklanjaju anotaciju sa zaslona bez uklanjanja iz niza, tako da brojanje koje zanemaruje zastavice uključuje oznake koje korisnik ne može vidjeti. Anotacije poveznica (Link) nalaze se u istom nizu kao i komentari i ne pripadaju ni broju ni popisu. Odlučite o pravilu brojanja prije nego što napišete petlju i zapišite tu odluku, jer je pitanje "zašto vaša ploča prikazuje drugačiji broj od Acrobata" prva prijava kvara koju značajka pregleda dobiva.
Indeksirajte sve jednom, a zatim nikada više ne analizirajte stranicu
Jedno pravilo dizajna pokreće sve što slijedi: filtriranje prema autoru, vrsti ili stranici nikada ne smije ponovno analizirati objekte stranice. Na dokumentu od 300 stranica s mnogo oznaka, ponovno analiziranje pri svakoj promjeni padajućeg izbornika pretvara ploču u nešto što zastajkuje po nekoliko sekundi. Komponenta izlaže AnnotationCount i indeksirano svojstvo Annotation[], oba ograničena na trenutno učitanu stranicu, a zapis TPdfAnnotation koji vraćaju nosi ono što je potrebno prikazu popisa: Subtype, Flags, Color, Rectangle, ContentsText, AuthorText. Ispravan potez je proći kroz svaku stranicu jednom pri otvaranju i voditi vlastiti ravni 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;
Par koji vrijedi istaknuti je (PageNo, i). Svaka kasnija izmjena, bilo promjena boje ili brisanje, adresira se brojem stranice i indeksom anotacije, a indeks je osjetljiv: uklanjanje anotacije ponovno numerira sve nakon nje na toj stranici. Stoga planirajte ponovnu izgradnju unosa pogođene stranice nakon bilo kojeg brisanja, umjesto krpanja indeksnih brojeva na licu mjesta. Ponovna izgradnja traje milisekundu. Nasuprot tome, zastarjeli indeks briše komentar pogrešnog recenzenta, što je vrsta pogreške koja narušava povjerenje u cijelu značajku.
Povezivanje u niti (threading) zaslužuje mjesto u indeksu čak i ako vaše prvo izdanje samo broji odgovore umjesto da ih prikazuje. Grupirajte stavke prema njihovoj roditeljskoj referenci dok vam je stranica otvorena, kako bi ploča kasnije mogla sklopiti nit na način na koji to radi Acrobat. Odgođena rekonstrukcija tog grupiranja tijekom pomicanja poništava cijelu svrhu jednokratnog indeksiranja jer ponovno otvara stranice koje ste već analizirali. Geometrija zahtijeva istu disciplinu. Rectangle u svakom zapisu je u prostoru stranice, a pretvaranje u koordinate prikaza pripada jednom zajedničkom pomoćnom programu, a ne razbacano kroz kod. Ploče dobivaju pogreške u koordinatama kada odabir, testiranje pogotka (hit-testing) i crtanje izmišljaju vlastitu matematiku zumiranja i rotacije; usmjerite sve tri stavke kroz jednu pretvorbu i isticanje, njegov redak na popisu i cilj klika ostaju vezani uz istu tintu.
Promjena boje oznaka i veto strujanja izgleda
Promjena isticanja iz žute u boju jantara zvuči kao jedna linija koda, a ponekad to i jest. Problem je u ISO 32000-1 §12.5.5. Kada anotacija sadrži /AP strujanje izgleda (appearance stream), kompatibilni preglednik iscrtava to unaprijed izgrađeno strujanje i tretira unos boje u rječniku kao neaktivne metapodatke. Acrobat stvara strujanja izgleda za gotovo sve što kreira, pa je većina anotacija koje dolaze od korisnika već u tom stanju, a boja koju ste tako pouzdano postavili nikada ne stiže na zaslon. Promjena boje je operacija čitanja-izmjene-pisanja (read-modify-write) kroz svojstvo Annotation[], a komponenta je iskrena u vezi s tim sukobom: kada pokretač odbije dopustiti da boja iz rječnika nadjača ugrađeni izgled, zapis podiže iznimku 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;
Ulovite tu iznimku svaki put i tretirajte je kao informaciju, a ne kao neuspjeh. Preskočite zaštitu i vaša će ploča veselo prikazivati boju jantara na vlastitom popisu dok se stranica i dalje iscrtava u žutoj boji; korisnik to tjednima kasnije prijavi kao "vaš preglednik zanemaruje moje izmjene", a vi provedete poslijepodne bezuspješno pokušavajući to reproducirati na datoteci koja slučajno nema strujanje izgleda. Kada saznate da je izgled zaključan, imate dva poštena odgovora: promijenite boju vlastitog sloja za odabir umjesto anotacije, tako da recenzent barem vidi isticanje koje je odabrao, ili označite redak kao zaključan izgledom (appearance-locked) kako nitko ne bi očekivao da će se promjena primijeniti.
Brisanje anotacija bez preostalih tragova
DeleteAnnotation uklanja objekt iz stabla anotacija trenutne stranice, ali ostavlja predmemorirani raster stranice nepromijenjenim. Crtajte odmah nakon poziva i izbrisano isticanje je još uvijek na zaslonu, nalazeći se u bitmapi koja više ne odgovara modelu dokumenta iza nje. Rješenje je tretirati ponovno iscrtavanje kao dio brisanja, a ne kao korak koji bi pozivatelj mogao zaboraviti:
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
Dva detalja u tom bloku lako je pogriješiti. Opcija reAnnotations mora biti prisutna, inače novi raster odbacuje svaku preostalu anotaciju i stranica izgleda kao da ste obrisali cijeli skup komentara umjesto jedne oznake. Osim toga, Bmp.Free nije neobavezan: preopterećena funkcija RenderPage predaje vlasništvo nad bitmapom pozivatelju, pa propuštanje oslobađanja uzrokuje curenje rastera cijele stranice pri svakom pojedinačnom brisanju, što će recenzent koji radi na dugačkom dokumentu pretvoriti u stvarni pritisak na memoriju u roku od nekoliko minuta.
Dodavanje recenzentskih oznaka iz vlastitog korisničkog sučelja
Stvaranje anotacija ide kroz CreateAnnotation, koji prima popunjeni zapis TPdfAnnotation (podtip, pravokutnik, boja, sadržaj, autor) i pridružuje ga trenutnoj stranici. Ljepljiva bilješka, podtip anText, jednostavan je slučaj: postavite položaj, sadržaj i autora i gotovi ste. Anotacije tintom (Ink) su mjesto gdje se ljudi najčešće zbune. Pravokutnik zapisa samo ograničava crtež; sami potezi su nizovi točaka koje se moraju zasebno pridružiti putem poziva za potez tintom pokretača, FPDFAnnot_AddInkStroke s podacima FS_POINTF, snimljenim iz unosa mišem ili olovkom, potez po potez. Uz to, izgradite anotaciju tintom iz pravokutnika i ničeg drugog i dobit ćete praznu škrabotinu koja se iscrtava kao prazan prostor, što izgleda kao pogreška u pokretaču, a zapravo je dopola dovršena anotacija.
Istovremeno riješite i politiku autorstva. Svaka oznaka koju stvori vaše korisničko sučelje trebala bi nositi dosljedan AuthorText, jer je filtar recenzenta koji ćete izraditi sljedeći mjesec dobar samo onoliko koliko su dobra imena kojima danas označavate komentare. Prazni ili nedosljedni nizovi autora ne mogu se popraviti retroaktivno bez ponovnog otvaranja svake datoteke.
Izvoz pregleda iz preglednika
Podaci o pregledu postaju korisni kada mogu napustiti preglednik, bilo kao sažetak koji voditelj projekta čita bez otvaranja datoteke ili kao CSV koji hrani tablicu za praćenje. Izvozite iz indeksa koji ste već izgradili, nikada iz nove analize, i odaberite stabilan način upućivanja na svaku oznaku. Broj stranice uparen s pravokutnikom anotacije preživljava promjene koje indeks niza ne preživljava, jer sljedeće brisanje tiho ponovno numerira indekse i vaš CSV počinje upućivati na pogrešne komentare.
Redak koji vrijedi zadržati sadrži stranicu, podtip, autora, vremensku oznaku stvaranja kada je datoteka bilježi, tekst sadržaja i stupac statusa koji vi posjedujete, a ne onaj koji pruža PDF. Isti prolaz indeksiranja koristan je i ranije, tijekom unosa, kada dokument stigne izvan tima i želite znati što se u njemu nalazi prije nego što ga itko pregleda. Članak o radnom stolu za unos PDF-a vodi vas kroz tu trijažu, a navigacija kroz polja obrasca pokriva zrcalni problem: pregled dokumenata izgrađenih za prikupljanje podataka umjesto komentara.
Jedan slučaj koji vam niz neće prikazati
Jedan način neuspjeha zaslužuje pozornost jer izgleda kao pogreška u vašem kodu, a zapravo nije. Korisnik prijavi vidljiva isticanja po cijeloj stranici, ali vaša ploča ne prikazuje ništa, a AnnotationCount vraća nulu. Uobičajeno objašnjenje je da su oznake spljoštene (flattened) negdje ranije u tijeku rada. Spljoštavanje ugrađuje izglede anotacija u običan sadržaj stranice, pa isticanja postaju dio grafike stranice i potpuno prestaju postojati kao objekti anotacija. Više nema ničega što bi API za anotacije mogao nabrojati, prebojati ili izbrisati. Kada vidite nacrtane oznake s nultim brojem, prestanite tražiti pogrešku u svojoj petlji nabrajanja i pitajte kako je datoteka stvorena.
Sučelje anotacija koje se ovdje koristi, od nabrajanja i stvaranja do promjene boje, brisanja i opcija iscrtavanja koje održavaju prikaz točnim, dolazi s PDFium komponentom za Delphi, C++Builder i Lazarus/FPC.