O adnotare PDF este un dicționar atașat unei pagini, nu o marcă desenată pe ea. ISO 32000-1 §12.5 definește aproximativ două duzini de subtipuri, iar fiecare poartă un /Subtype, un dreptunghi în coordonate de pagină, un set de indicatori și de obicei un flux de aspect care decide ce pictează efectiv un viewer. Subtipurile nu înseamnă toate același lucru pentru o persoană care revizuiește un document. Un Highlight și o trasare Ink sunt comentarii; un Link este navigare; un Popup este fereastra mică ce se deschide când faceți clic pe o notă adezivă, stocată ca propriul ei obiect și indicată de un părinte. Răspunsurile sunt adnotări Text complete care referențiază comentariul la care răspund printr-o intrare in-reply-to. Așadar, array-ul de adnotări la nivel de pagină nu este lista de comentarii a recenzentului. Este o pungă plată care conține comentarii, instalația care le conectează și câteva lucruri pe care niciun recenzent nu le-ar numi vreodată comentariu. Un panou care tratează array-ul drept lista de comentarii va fi în dezacord cu orice alt viewer pe care îl rulează clientul
Construirea unui flux de revizuire a adnotărilor pe PDFium Component, componenta VCL/LCL bazată pe PDFium pentru Delphi, C++Builder și Lazarus, înseamnă concentrarea pe punctele unde acel decalaj dintre array-ul brut și vederea umană provoacă probleme: numărarea, indexarea, recolorarea mărcilor pe care motorul le-a înghețat deja, ștergerea fără a lăsa fantome și adăugarea propriilor dvs. mărci
De ce numărul dvs. nu se potrivește niciodată cu panoul de comentarii al Acrobat
Deschideți un contract marcat în viewer-ul dvs. și în Acrobat, unul lângă altul, iar totalurile rareori coincid. Acrobat arată o vedere curatoriată: marcaje grupate în fire de răspuns, popup-uri pliate în notele cărora le aparțin, link-uri și widget-uri de formular lăsate afară. Array-ul brut le ține pe toate nediferențiate, așa că un număr naiv este prea mare într-un fel și prea mic în altul, în același timp
Popup-urile umflă totalul, pentru că fiecare notă adezivă vine cu un obiect Popup separat, iar numărarea ambelor dublează nota. Răspunsurile îl dezumflă dacă filtrați după mărcile vizibile, întrucât un răspuns este o adnotare Text fără nimic pictat până când cineva extinde firul, iar renunțarea la el pierde discuția. Indicatorii Hidden și NoView scot o adnotare de pe ecran fără să o scoată din array, așa că un număr orb la indicatori include mărci pe care utilizatorul nu le poate vedea. Adnotările Link stau în același array ca și comentariile și nu aparțin nici numărului, nici listei. Decideți regula de numărare înainte de a scrie bucla, și notați decizia, pentru că „de ce panoul dvs. arată un număr diferit de Acrobat” este primul tichet pe care îl câștigă o funcționalitate de revizuire
Indexați totul o dată, apoi nu mai reanalizați niciodată o pagină
O regulă de proiectare guvernează tot ce urmează: filtrarea după autor, tip sau pagină nu trebuie niciodată să reanalizeze obiectele paginii. Pe un document de 300 de pagini cu marcaj intens, reanalizarea la fiecare schimbare de dropdown transformă panoul în ceva care se blochează câteva secunde odată. Componenta expune AnnotationCount și proprietatea indexată Annotation[], ambele limitate la pagina încărcată în prezent, iar înregistrarea TPdfAnnotation pe care o returnează poartă ce are nevoie o vedere de listă: Subtype, Flags, Color, Rectangle, ContentsText, AuthorText. Mișcarea corectă este să parcurgeți fiecare pagină o dată la momentul deschiderii și să țineți propriul dvs. index plat:
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];
// Păstrați doar subtipurile relevante pentru recenzent; înregistrați pagina și
// perechea de index, pentru că toate editările ulterioare sunt adresate prin ea
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;
Perechea care merită subliniată este (PageNo, i). Fiecare mutație ulterioară, fie o recolorare, fie o ștergere, este adresată prin numărul paginii plus indexul adnotării, iar indexul este fragil: eliminarea unei adnotări renumerotează tot ce urmează după ea pe acea pagină. Așa că planificați să reconstruiți intrările paginii afectate după orice ștergere, în loc să corectați numerele de index pe loc. Reconstrucția costă o milisecundă. Un index învechit, în schimb, șterge comentariul recenzentului greșit, ceea ce este genul de bug care erodează încrederea în întreaga funcționalitate
Firele de discuție merită un loc în index, chiar dacă prima dvs. versiune doar numără răspunsurile, în loc să le arate. Grupați elementele după referința lor de părinte cât timp aveți pagina deschisă, astfel încât panoul să poată plia mai târziu un fir așa cum face Acrobat. Reconstruirea acelei grupări leneș, în timpul derulării, învinge întregul scop al indexării o singură dată, pentru că redeschide pagini pentru care ați plătit deja costul analizei. Geometria vrea aceeași disciplină. Rectangle din fiecare înregistrare este în spațiul paginii, iar convertirea lui în coordonate de vedere aparține unui singur helper partajat, nu împrăștiată prin cod. Panourile dezvoltă bug-uri de coordonate atunci când selecția, testarea de hit și pictarea inventează fiecare propria matematică de zoom și rotație; rutați toate cele trei printr-o singură conversie, iar o evidențiere, rândul ei din listă și ținta ei de clic rămân fixate pe aceeași cerneală
Recolorarea marcajului și vetoul fluxului de aspect
Schimbarea unei evidențieri din galben în chihlimbariu sună ca o linie de cod, și uneori chiar este. Capcana este ISO 32000-1 §12.5.5. Când o adnotare poartă un flux de aspect /AP, un viewer conform pictează acel flux pre-construit și tratează intrarea de culoare din dicționar ca metadate moarte. Acrobat scrie fluxuri de aspect pentru practic tot ce creează, așa că majoritatea adnotărilor sosite de la clienți sunt deja în această stare, iar culoarea pe care ați setat-o atât de încrezător nu ajunge niciodată pe ecran. Recolorarea este un citește-modifică-scrie prin proprietatea Annotation[], iar componenta este onestă în privința conflictului: când motorul refuză să lase o culoare din dicționar să suprascrie un aspect deja copt, scrierea ridică EPdfError
A := Pdf.Annotation[Item.Index];
A.HasColor := True;
A.Color := $0000B0FF; // chihlimbariu
A.ColorAlpha := 160;
try
Pdf.Annotation[Item.Index] := A;
except
on EPdfError do
begin
// Adnotarea deține un flux /AP pre-randat; culoarea din
// dicționar, singură, nu poate schimba ce pictează viewer-ele
Item.AppearanceLocked := True;
StatusBar.SimpleText := 'Color is fixed by the annotation appearance';
end;
end;
Prindeți acea excepție de fiecare dată și tratați-o ca informație, nu ca eșec. Săriți garda și panoul dvs. arată vesel chihlimbariu în propria listă, în timp ce pagina continuă să picteze galben; utilizatorul îl raportează săptămâni mai târziu ca „viewer-ul dvs. îmi ignoră editările”, iar dvs. vă petreceți o după-amiază eșuând să reproduceți asta pe un fișier care se întâmplă să nu aibă niciun flux de aspect. Odată ce știți că aspectul este blocat, aveți două răspunsuri oneste: recolorați propriul dvs. overlay de selecție, în loc de adnotare, astfel încât recenzentul să vadă măcar evidențierea pe care a ales-o, sau marcați rândul ca aspect-blocat, astfel încât nimeni să nu se aștepte ca schimbarea să rămână
Ștergerea adnotărilor fără a lăsa fantome
DeleteAnnotation elimină obiectul din arborele de adnotări al paginii curente, dar lasă neatins rasterul de pagină din cache. Pictați imediat după apel și evidențierea ștearsă este încă pe ecran, stând într-un bitmap care nu se mai potrivește cu modelul de document din spatele lui. Soluția este să tratați re-randarea ca parte a ștergerii, nu ca un pas pe care apelantul l-ar putea uita:
Pdf.PageNumber := Item.PageNo;
Pdf.DeleteAnnotation(Item.Index); // ridică EPdfError la eșec
Bmp := Pdf.RenderPage(0, 0, ViewWidth, ViewHeight, ro0, [reAnnotations]);
try
PaintPageBitmap(Bmp);
finally
Bmp.Free; // RenderPage predă apelantului proprietatea bitmap-ului
end;
RebuildPageEntries(Item.PageNo); // indicii de după Item.Index s-au deplasat
Două detalii din acel bloc sunt ușor de greșit. Opțiunea reAnnotations trebuie să fie prezentă, altfel noul raster elimină fiecare adnotare rămasă, iar pagina arată de parcă ați fi șters întregul set de comentarii, în loc de o singură marcă. Iar Bmp.Free nu este opțional: supraîncărcarea de tip funcție a lui RenderPage predă apelantului proprietatea bitmap-ului, așa că o eliberare lipsă scurge un raster de pagină întreagă la fiecare ștergere, ceea ce un recenzent care lucrează printr-un document lung va transforma în presiune reală de memorie în câteva minute
Adăugarea mărcilor de recenzent din propriul dvs. UI
Crearea adnotărilor trece prin CreateAnnotation, care primește o înregistrare TPdfAnnotation completată (subtip, dreptunghi, culoare, conținut, autor) și o atașează la pagina curentă. O notă adezivă, subtipul anText, este cazul ușor: setați poziția, conținutul și autorul și ați terminat. Adnotările Ink sunt locul unde oamenii se prind. Dreptunghiul înregistrării doar delimitează desenul; trasările în sine sunt array-uri de puncte care trebuie atașate separat prin apelul de trasare-cerneală al motorului, FPDFAnnot_AddInkStroke alimentat cu date FS_POINTF, capturate de la intrarea mouse-ului sau a peniței, o trasare odată. Construiți o adnotare Ink doar dintr-un dreptunghi și nimic altceva, și obțineți un mâzgălit gol care se randează ca spațiu gol, ceea ce arată ca un bug în motor și este de fapt o adnotare pe jumătate terminată
Tranșați politica de autorat în aceeași suflare. Fiecare marcă pe care o creează UI-ul dvs. ar trebui să poarte un AuthorText consistent, pentru că filtrul de recenzenți pe care îl construiți luna viitoare este doar la fel de bun ca numele pe care le ștampilați pe comentarii astăzi. Șirurile de autor goale sau inconsistente nu pot fi reparate retroactiv fără să redeschideți fiecare fișier
Scoaterea recenziei din viewer
Datele de recenzie își justifică locul odată ce pot părăsi viewer-ul, ca un sumar pe care șeful de proiect îl citește fără să deschidă fișierul, sau ca un CSV care alimentează o foaie de urmărire. Exportați din indexul pe care l-ați construit deja, niciodată dintr-o analiză proaspătă, și alegeți un mod stabil de a vă referi înapoi la fiecare marcă. Un număr de pagină asociat cu dreptunghiul adnotării supraviețuiește unor drumuri dus-întors pe care un index de array nu le supraviețuiește, pentru că următoarea ștergere renumerotează tacit indicii, iar CSV-ul dvs. începe să indice spre comentariile greșite
Un rând care merită păstrat poartă pagina, subtipul, autorul, marcajul de timp al creării atunci când fișierul înregistrează unul, textul de conținut și o coloană de status pe care o dețineți dvs., nu una pe care o furnizează PDF-ul. Aceeași trecere de indexare este utilă mai devreme, la recepție, când un document sosește din afara echipei și vreți să știți ce conține înainte ca cineva să îl revizuiască. Articolul despre banca de lucru pentru recepția PDF parcurge acel triaj, iar navigarea câmpurilor de formular acoperă problema în oglindă: revizuirea documentelor construite pentru a colecta date, nu comentarii
Un caz pe care array-ul nu vi-l va arăta
Un mod de eșec merită semnalat pentru că arată ca un defect în codul dvs. și nu este. Un client raportează evidențieri vizibile peste tot pe o pagină, dar panoul dvs. nu listează nimic, iar AnnotationCount revine zero. Explicația obișnuită este că mărcile au fost aplatizate undeva în amonte. Aplatizarea coace aspectele adnotărilor în conținutul obișnuit al paginii, așa că evidențierile devin parte din grafica paginii și încetează complet să mai existe ca obiecte de adnotare. Nu mai rămâne nimic pentru ca un API de adnotări să enumere, recoloreze sau șteargă. Când vedeți marcaj pictat cu un număr zero, nu mai căutați bug-ul în bucla dvs. de enumerare și întrebați cum a fost produs fișierul
Suprafața de adnotare folosită aici, de la enumerare și creare, prin recolorare, ștergere și opțiunile de randare care țin afișarea onestă, este livrată cu PDFium Component pentru Delphi, C++Builder și Lazarus/FPC