Egy PDF-jegyzet egy oldalhoz csatolt szótár, nem egy rárajzolt jel. Az ISO 32000-1 §12.5 nagyjából két tucat altípust határoz meg, és mindegyik hordoz egy /Subtype-ot, egy oldalkoordinátákban megadott téglalapot, jelzők egy halmazát, és rendszerint egy megjelenési adatfolyamot, amely eldönti, mit fest ki ténylegesen egy nézegető. Az altípusok nem mindegyike ugyanazt jelenti egy dokumentumot átnéző ember számára. Egy Highlight és egy Ink vonás megjegyzés; egy Link navigáció; egy Popup az a kis ablak, amely akkor nyílik meg, amikor rákattintasz egy ragasztott cetlire, saját objektumként tárolva, egy szülő által rámutatva. A válaszok teljes Text-jegyzetek, amelyek egy in-reply-to bejegyzésen keresztül hivatkoznak arra a megjegyzésre, amelyre válaszolnak. Így az oldalszintű jegyzettömb nem az átnéző megjegyzéslistája. Egy lapos zsák, amely megjegyzéseket, az őket összekötő vezetékrendszert, és néhány olyan dolgot tartalmaz, amit egyetlen átnéző sem nevezne megjegyzésnek. Egy panel, amely a tömböt megjegyzéslistaként kezeli, nem fog egyetérteni egyetlen más nézegetővel sem, amit az ügyfél futtat
Egy jegyzet-átnézési munkafolyamat felépítése a PDFium Component-en, a PDFium-alapú VCL/LCL komponensen Delphihez, C++Builderhez és Lazarushoz, azt jelenti, hogy azokra a pontokra összpontosítasz, ahol a nyers tömb és az emberi nézet közötti szakadék bajt okoz: számlálás, indexelés, olyan jelek átszínezése, amelyeket a motor már befagyasztott, törlés szellemek hátrahagyása nélkül, és saját jelek hozzáadása
Miért nem egyezik soha a számod az Acrobat megjegyzéspaneljével
Nyiss meg egy megjegyzésekkel teli szerződést egymás mellett a saját nézegetődben és az Acrobatban, és az összegek ritkán egyeznek. Az Acrobat egy szerkesztett nézetet mutat: a jelöléseket válaszszálakba csoportosítva, a popupokat beolvasztva azokba a jegyzetekbe, amelyekhez tartoznak, a linkeket és az űrlap-widgeteket kihagyva. A nyers tömb mindezt megkülönböztetés nélkül tartalmazza, így egy naiv számlálás egyes szempontból túl magasra, másokból egyszerre túl alacsonyra fut
A popupok felfújják az összeget, mert minden ragasztott cetli egy külön Popup objektummal érkezik, és mindkettő megszámlálása megduplázza a jegyzetet. A válaszok lecsökkentik, ha látható jelekre szűrsz, mivel egy válasz egy Text-jegyzet, amelyből semmi nincs kifestve, amíg valaki ki nem bontja a szálat, és elhagyása elveszíti a beszélgetést. A Hidden és a NoView jelzők leveszik a jegyzetet a képernyőről anélkül, hogy kivennék a tömbből, így egy jelzőkre vak számlálás olyan jeleket is tartalmaz, amelyeket a felhasználó nem lát. A Link-jegyzetek ugyanabban a tömbben ülnek, mint a megjegyzések, és sem a számlálásba, sem a listába nem tartoznak. Döntsd el a számlálási szabályt, mielőtt megírnád a ciklust, és írd le a döntést, mert az „miért mutat a paneled más számot, mint az Acrobat" az első jegy, amit egy átnézési funkció megkap
Mindent egyszer indexelj, aztán soha ne elemezz újra egy oldalt
Egyetlen tervezési szabály vezérli mindazt, ami ezután következik: a szerző, típus vagy oldal szerinti szűrés soha nem elemezheti újra az oldalobjektumokat. Egy 300 oldalas, erősen jelölt dokumentumon az újraelemzés minden legördülő menü-váltásnál olyanná teszi a panelt, amely másodpercekig akadozik egyszerre. A komponens felkínálja az AnnotationCount-ot és az indexelt Annotation[] tulajdonságot, mindkettőt az aktuálisan betöltött oldalra korlátozva, és a TPdfAnnotation rekord, amelyet visszaadnak, azt hordozza, amire egy listanézetnek szüksége van: Subtype, Flags, Color, Rectangle, ContentsText, AuthorText. A helyes lépés az, hogy megnyitáskor egyszer végigsöpröd az összes oldalt, és fenntartod a saját lapos indexedet:
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];
// Csak az átnéző szempontjából releváns altípusokat tartsd meg; jegyezd fel az oldalt
// és index párost, mert minden későbbi szerkesztés ezzel van címezve
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;
Az aláhúzásra érdemes pár a (PageNo, i). Minden későbbi módosítás, legyen az átszínezés vagy törlés, oldalszám plusz jegyzetindex alapján van címezve, és az index törékeny: egy jegyzet eltávolítása újraszámozza mindazt, ami utána következik azon az oldalon. Ezért tervezd meg, hogy bármilyen törlés után újraépíted az érintett oldal bejegyzéseit, ahelyett hogy helyben javítanád az indexszámokat. Az újraépítés egy ezredmásodpercbe kerül. Egy elavult index ezzel szemben a rossz átnéző megjegyzését törli, ami az a fajta hiba, amely aláássa a bizalmat az egész funkcióban
A szálazás megérdemel egy helyet az indexben, még akkor is, ha az első kiadásod csak megszámolja a válaszokat, nem mutatja meg őket. Csoportosítsd az elemeket a szülőhivatkozásuk szerint, amíg az oldal nyitva van, hogy a panel később úgy tudja összecsukni a szálat, ahogy az Acrobat teszi. Ha ezt a csoportosítást lustán, görgetés közben építed újra, azzal meghiúsítod az egyszeri indexelés teljes lényegét, mert olyan oldalakat nyitsz meg újra, amelyek elemzéséért már fizettél. A geometria ugyanazt a fegyelmet igényli. A Rectangle minden rekordban oldaltéri, és az átalakítását nézetkoordinátákká egyetlen közös segédfüggvényben kell elhelyezni, nem szétszórva a kódban. A panelek koordinátahibákat növesztenek, amikor a kijelölés, a hit-teszt és a festés mindegyike a saját zoom- és forgatásmatematikáját találja ki; vezesd mindhármat egyetlen átalakításon keresztül, és egy kiemelés, a listában lévő sora, valamint a kattintási célpontja ugyanahhoz a tintavonalhoz marad rögzítve
Jelölések átszínezése és a megjelenési adatfolyam vétója
Egy kiemelés sárgáról borostyánszínűre változtatása egysorosnak hangzik, és néha az is. A csapda az ISO 32000-1 §12.5.5. Amikor egy jegyzet /AP megjelenési adatfolyamot hordoz, egy szabványkövető nézegető ezt az előre elkészített adatfolyamot festi ki, és a szótár színbejegyzését holt metaadatként kezeli. Az Acrobat lényegében mindenhez ír megjelenési adatfolyamot, amit létrehoz, így az ügyfelektől érkező legtöbb jegyzet már ebben az állapotban van, és a szín, amit magabiztosan beállítottál, soha nem jut el a képernyőre. Az átszínezés egy olvas-módosít-ír művelet az Annotation[] tulajdonságon keresztül, és a komponens őszinte a konfliktussal kapcsolatban: amikor a motor megtagadja, hogy egy szótárszín felülírjon egy beégetett megjelenést, az írás EPdfError-t dob
A := Pdf.Annotation[Item.Index];
A.HasColor := True;
A.Color := $0000B0FF; // borostyán
A.ColorAlpha := 160;
try
Pdf.Annotation[Item.Index] := A;
except
on EPdfError do
begin
// A jegyzet egy előre renderelt /AP adatfolyamot birtokol; önmagában a szótár
// színe nem tudja megváltoztatni, mit festenek ki a nézegetők
Item.AppearanceLocked := True;
StatusBar.SimpleText := 'Color is fixed by the annotation appearance';
end;
end;
Fogd el ezt a kivételt minden alkalommal, és kezeld információként, ne hibaként. Ha kihagyod a védelmet, a paneled vidáman borostyánszínt mutat a saját listájában, miközben az oldal továbbra is sárgát fest; a felhasználó heteken belül úgy jelenti be, hogy „a nézegetőd figyelmen kívül hagyja a szerkesztéseimet", te pedig egy délutánt töltesz azzal, hogy sikertelenül próbálod reprodukálni egy olyan fájlon, amelynek éppenséggel nincs megjelenési adatfolyama. Amint tudod, hogy a megjelenés zárolva van, két becsületes válaszod van: átszínezed a saját kijelölés-overlayedet a jegyzet helyett, hogy az átnéző legalább lássa a kiválasztott kiemelést, vagy megjelölöd a sort megjelenés-zároltként, hogy senki ne várja, hogy a változás megmaradjon
Jegyzetek törlése szellemek hátrahagyása nélkül
A DeleteAnnotation eltávolítja az objektumot az aktuális oldal jegyzetfájából, de érintetlenül hagyja a gyorsítótárazott oldalraszter-t. Ha közvetlenül a hívás után festesz, a törölt kiemelés még mindig a képernyőn van, egy olyan bitképben ülve, amely már nem egyezik a mögötte lévő dokumentummodellel. A megoldás az, hogy az újrarendelést a törlés részének kezeled, nem egy olyan lépésnek, amelyet a hívó elfelejthet:
Pdf.PageNumber := Item.PageNo;
Pdf.DeleteAnnotation(Item.Index); // hiba esetén EPdfError-t dob
Bmp := Pdf.RenderPage(0, 0, ViewWidth, ViewHeight, ro0, [reAnnotations]);
try
PaintPageBitmap(Bmp);
finally
Bmp.Free; // a RenderPage a hívónak adja a bitkép tulajdonjogát
end;
RebuildPageEntries(Item.PageNo); // az Item.Index utáni indexek eltolódtak
Két részletet könnyű elrontani ebben a blokkban. A reAnnotations opciónak jelen kell lennie, különben az új raszter minden megmaradt jegyzetet elejt, és az oldal úgy néz ki, mintha az egész megjegyzéskészletet törölted volna egyetlen jel helyett. És a Bmp.Free nem opcionális: a függvényszerű RenderPage túlterhelés a hívónak adja a bitkép tulajdonjogát, így egy elmaradt felszabadítás minden egyes törlésnél egy teljes oldalas rasztert szivárogtat, amit egy hosszú dokumentumon dolgozó átnéző perceken belül valódi memórianyomássá alakít
Átnézői jelek hozzáadása a saját felhasználói felületedről
A jegyzetek létrehozása a CreateAnnotation-on keresztül történik, amely egy kitöltött TPdfAnnotation rekordot (altípus, téglalap, szín, tartalom, szerző) vesz át, és az aktuális oldalhoz csatolja. Egy ragasztott cetli, anText altípussal, az egyszerű eset: állítsd be a pozíciót, a tartalmat és a szerzőt, és kész vagy. Az Ink-jegyzeteknél akadnak fenn az emberek. A rekord téglalapja csak a rajzot határolja; maguk a vonások pontok tömbjei, amelyeket külön kell csatolni a motor tintavonás-hívásán keresztül, a FPDFAnnot_AddInkStroke-on, amelynek FS_POINTF adatokat adsz, egér- vagy tollbemenetből egyenként rögzített vonásonként. Ha egy Ink-jegyzetet csak egy téglalapból építesz fel, és semmi másból, egy üres firkát kapsz, amely üres helyként renderelődik, ami úgy néz ki, mint egy hiba a motorban, valójában pedig egy félbehagyott jegyzet
Ugyanabban a lélegzetvételben rendezd le a szerzőségi politikát is. Minden jelnek, amelyet a felhasználói felületed létrehoz, egy konzisztens AuthorText-et kell hordoznia, mert az átnéző-szűrő, amit jövő hónapban építesz, csak annyira jó, amennyire a nevek, amelyeket ma a megjegyzésekre bélyegzel. Az üres vagy inkonzisztens szerzőkarakterláncok visszamenőlegesen nem javíthatók minden fájl újbóli megnyitása nélkül
Az átnézés kihozatala a nézegetőből
Az átnézési adatok akkor válnak igazán hasznossá, amikor el tudnak hagyni a nézegetőt, mint egy összefoglaló, amelyet a projektvezető a fájl megnyitása nélkül olvas el, vagy egy CSV, amely egy nyomkövető táblázatot táplál. Exportálj a már felépített indexből, soha ne egy friss elemzésből, és válassz egy stabil módot arra, hogy visszahivatkozz minden egyes jelre. Egy oldalszám a jegyzet téglalapjával párosítva túlél olyan oda-vissza utakat, amelyeket egy tömbindex nem, mert a következő törlés csendben újraszámozza az indexeket, és a CSV-d rossz megjegyzésekre kezd mutatni
Egy megtartásra érdemes sor hordozza az oldalt, az altípust, a szerzőt, a létrehozási időbélyeget, ha a fájl rögzít egyet, a tartalmi szöveget, és egy státuszoszlopot, amelyet te birtokolsz, nem pedig egyet, amit a PDF szolgáltat. Ugyanaz az indexelési átfutás korábban is hasznos, a beérkezéskor, amikor egy dokumentum a csapaton kívülről érkezik, és tudni akarod, mi van benne, mielőtt bárki átnézné. A PDF-beérkezési munkapad-cikk végigvezet ezen a triázson, a form-mező navigáció pedig a tükörképi problémát fedi le: olyan dokumentumok átnézését, amelyeket adatgyűjtésre építettek, nem megjegyzésekre
Egy eset, amit a tömb nem fog megmutatni
Egy hibamód megérdemel egy figyelmeztető jelzést, mert úgy néz ki, mint egy hiba a kódodban, pedig nem az. Egy ügyfél látható kiemeléseket jelent egy egész oldalon, de a paneled semmit sem listáz, és az AnnotationCount nullával tér vissza. A szokásos magyarázat az, hogy a jeleket valahol feljebb belesimították. A belesimítás beégeti a jegyzetmegjelenéseket a hétköznapi oldaltartalomba, így a kiemelések az oldalgrafika részévé válnak, és teljesen megszűnnek jegyzetobjektumként létezni. Nem marad semmi, amit egy jegyzet-API felsorolhatna, átszínezhetne vagy törölhetne. Amikor kifestett jelöléseket látsz nulla számmal, hagyd abba a hiba keresését a felsorolási ciklusodban, és kérdezd meg, hogyan készült a fájl
Az itt használt jegyzetfelület, a felsorolástól és létrehozástól kezdve az átszínezésen és törlésen át a megjelenítést becsületesen tartó renderelési opciókig, a Delphihez, C++Builderhez és Lazarus/FPC-hez készült PDFium Component részeként érkezik