PDFium Component stvara bilješke za označavanje teksta, što znači isticanje (highlight), podcrtavanje (underline), precrtavanje (strikeout) i valovito podcrtavanje (squiggly), putem metode TPdf.CreateAnnotation: postavite HasAttachmentPoints := True na zapis TPdfAnnotation i popunite njegov četverokut AttachmentPoints, a komponenta zapisuje unos QuadPoints definiran u ISO 32000-1 §12.5.6.10. To je cijela površina API-ja. Razlog zašto ovaj članak postoji je ono što se događa ispod toga, jer sirovi PDFium lanac poziva ima način kvara koji proizvodi najmanje koristan simptom u cijelom alatu: FPDFAnnot_SetAttachmentPoints vraća false na svježe stvorenoj bilješci, svaki put, bez koda pogreške i bez ikakvog nagovještaja. Ovo je prateći članak za stranu stvaranja našem članku o čitanju i pregledavanju postojećih bilješki, koji prolazi u drugom smjeru kroz iste strukture
Scena uklanjanja pogrešaka uvijek je ista. Stvorite bilješku isticanja, pozovete postavljač spojnih točaka (attachment-points setter) s indeksom 0, funkcija vrati false, i počnete sumnjati u svoje koordinate. Transponirate točke, okrenete os Y, zamijenite prostor stranice za prostor uređaja. Ništa od toga ne pomaže jer koordinate nikada nisu bile problem. Problem je u indeksnoj semantici C API-ja, a kada je jednom uočite, ispravak je u dva retka
Što QuadPoints znače u ISO 32000-1
QuadPoints je niz od 8×n brojeva koji opisuju n četverokuta, a ISO 32000-1 §12.5.6.10 zahtijeva ga na svakoj bilješci za označavanje teksta: svaki četverokut označava riječ ili skupinu susjednih riječi na koje se isticanje, podcrtavanje ili precrtavanje odnosi. Unos Rect za bilješku i dalje postoji, ali za podtipove označavanja on samo omeđuje regiju; četverokuti (quads) su ono što renderer zapravo iscrtava. Četverokut se koristi umjesto pravokutnika jer tekst može biti rotiran ili nagnut, pa se četiri kuta spremaju kao četiri neovisne točke: x1 y1 x2 y2 x3 y3 x4 y4
Redoslijed tih četiriju točaka je mjesto gdje se specifikacija i instalirana baza korisnika razilaze. Tekst specifikacije opisuje točke kao praćenje četverokuta u smjeru suprotnom od kazaljke na satu, ali Adobeov vlastiti renderer uvijek ih je tumačio u Z uzorku: prvo gornji rub slijeva nadesno, a zatim donji rub slijeva nadesno. Budući da su svi autori testirali u odnosu na Acrobat, praktički svaki renderer, uključujući i PDFium, prati Z uzorak, a datoteke koje prate doslovni tekst specifikacije iscrtavaju se kao urušena ili uvrnuta isticanja u nekim preglednicima. PDFiumova struktura FS_QUADPOINTSF kodira upravo tu konvenciju: (x1,y1) je gornji lijevi kut, (x2,y2) gornji desni, (x3,y3) donji lijevi, (x4,y4) donji desni, u koordinatama stranice gdje Y raste prema gore. Pratite taj redoslijed i gotovi ste; rendereri su popustljivi prema mnogim stvarima, ali zbrkani četverokut nije jedna od njih
Zašto FPDFAnnot_SetAttachmentPoints vraća false?
FPDFAnnot_SetAttachmentPoints ne uspijeva na novoj bilješci jer je njegov ugovor zamjena četverokuta na zadanom indeksu, a svježe stvorena bilješka ima nula četverokuta za zamjenu. Potpis funkcije prima ručku bilješke, quad_index i točke; indeks 0 ne znači "prvi utor, stvori ga ako je potrebno", nego znači "postojeći četverokut broj 0", a kada FPDFAnnot_CountAttachmentPoints javi 0, takav četverokut ne postoji i poziv vraća false. Funkcija koja stvara utor je FPDFAnnot_AppendAttachmentPoints. Svaka bilješka stvorena putem FPDFPage_CreateAnnot započinje s brojem nula, pa staza stvaranja mora najprije pozvati Append, a tek naknadna ažuriranja mogu pozvati Set
To je pogodilo i samu komponente PDFium Component. Do verzije v1.79.0 interna rutina koju dijele CreateAnnotation i SetAnnotation imala je čvrsto kodirano FPDFAnnot_SetAttachmentPoints(Annotation, 0, ...), što je bilo točno za ažuriranje postojeće bilješke označavanja i zajamčeno neuspješno za novu, očitujući se kao EPdfException s porukom 'Cannot set attachment points'. Ispravak, isporučen u verziji v1.79.1, grana se na temelju broja
// Inside the component's annotation writer (v1.79.1+):
// a new annotation has no quad slots yet, so Append creates
// the first one; Set only replaces a slot that already exists
if FPDFAnnot_CountAttachmentPoints(Annotation) = 0 then
Check(FPDFAnnot_AppendAttachmentPoints(Annotation, QuadPoints) <> 0,
'Cannot set attachment points')
else
Check(FPDFAnnot_SetAttachmentPoints(Annotation, 0, QuadPoints) <> 0,
'Cannot set attachment points');
Isti uzorak vrijedi i ako izravno pozivate izvezene C funkcije, što vam komponenta dopušta jer su sve ulazne točke FPDFAnnot_* izložene u datoteci PDFium.pas. Kad god držite ručku FPDF_ANNOTATION i želite pisati četverokute, najprije pitajte FPDFAnnot_CountAttachmentPoints i usmjerite poziv u skladu s tim. Ako tražite zašto "FPDFAnnot_SetAttachmentPoints vraća false", ovo grananje s brojenjem i appendanjem je gotovo sigurno vaš odgovor
Stvaranje isticanja pomoću TPdf.CreateAnnotation
Budući da komponenta umjesto vas obavlja usmjeravanje Append-naspram-Set, stvaranje isticanja svodi se na popunjavanje zapisa. Primjer u nastavku stvara A4 stranicu i postavlja poluprozirno žuto isticanje preko regije od 200×20 točaka; imajte na umu da četverokut prati gore opisani Z redoslijed i da je Rectangle postavljen tako da obuhvati četverokut, što održava razumno ponašanje u preglednicima koji testiraju pogotke u odnosu na Rect
var
Pdf: TPdf;
A: TPdfAnnotation;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument;
Pdf.AddPage(0, 595, 842);
FillChar(A, SizeOf(A), 0);
A.Subtype := anHighlight;
A.HasColor := True;
A.Color := clYellow;
A.ColorAlpha := $80; // 50% opacity
A.HasAttachmentPoints := True;
A.AttachmentPoints[1].X := 50; A.AttachmentPoints[1].Y := 700; // top-left
A.AttachmentPoints[2].X := 250; A.AttachmentPoints[2].Y := 700; // top-right
A.AttachmentPoints[3].X := 50; A.AttachmentPoints[3].Y := 680; // bottom-left
A.AttachmentPoints[4].X := 250; A.AttachmentPoints[4].Y := 680; // bottom-right
A.Rectangle.Left := 50; A.Rectangle.Top := 700;
A.Rectangle.Right := 250; A.Rectangle.Bottom := 680;
A.ContentsText := 'Highlighted region';
Pdf.CreateAnnotation(A);
Pdf.SaveAs('highlighted.pdf');
finally
Pdf.Free;
end;
end;
Promjena podtipova košta jedan redak. anUnderline, anStrikeout i anSquiggly uzimaju identičan oblik zapisa, četverokute i sve ostalo, jer ISO 32000-1 tretira sva tri kao istu obitelj bilješki koja se razlikuje samo po načinu ukrašavanja regije četverokuta. Podtipovi koji nisu označavanje teksta, kao što su anSquare, anCircle i anText, pozicioniraju se isključivo na temelju Rectangle; za njih ostavite HasAttachmentPoints na False i mehanizam za četverokute se nikada neće pokrenuti
Zašto se AttachmentPoints[0] kompajlira u Delphiju, ali ne uspijeva u FPC-u?
TQuadrilateralPoint je deklariran kao array [1..4] of TPdfPoint, što je 1-bazirani niz, i to zbunjuje svakoga čiji prsti automatski kreću prema nultom indeksiranju. Napišite A.AttachmentPoints[0] i Delphijev dcc32 će to kompajlirati bez pritužbi, karena je provjera raspona (range checking) prema zadanim postavkama isključena; u vrijeme izvršavanja (runtime) izraz tiho čita ili piše u memoriju neposredno prije niza, što je u zapisu TPdfAnnotation susjedno polje. Vaše isticanje dobiva jedan pogrešan kut ili se susjedno polje ošteti, a ništa ne javlja pogrešku. Free Pascal je uhvatio upravo ovu pogrešku u našim vlastitim demo izvorima tijekom porta za Lazarus: fpc izvodi provjeru raspona u vrijeme kompajliranja na konstantnim indeksima i odbio je AttachmentPoints[0..3] izravno, što je ujedno otkrilo i problem s indeksom i pogrešku knjižnice Set-naspram-Append
Slijede dvije navike. Indeksirajte četverokut od 1 do 4, podudarajući se s redoslijedom kutova u gornjem kodu, i izgradite svoj kod za bilješke barem jednom s uključenom provjerom raspona, bilo pomoću {$R+} u Delphiju ili u bilo kojoj fpc izvedbi, prije nego što mu povjerujete. Prolazak zadane dcc32 kompilacije nije dokaz da su indeksi točni; to je samo dokaz da se ništa nije srušilo na memoriji koja se slučajno tamo zatekla
Dohvaćanje koordinata četverokuta iz stvarnog teksta
Rutine pokrivene u našem vodiču za ekstrakciju teksta s PDFium komponentom daju vam okvire (bounding boxes) po znaku u istom koordinatnom prostoru stranice koji koriste četverokuti, tako da se pogodak pretrage izravno pretvara u kutne točke: lijevo od prvog znaka, desno od zadnjeg, gornja i donja strana iz raspona retka. Ako sami generirate tekst i trebate znati gdje će reci pasti prije nego što postoje, članak o mjerenju teksta i prijelomu riječi pokriva izračun tih raspona unaprijed
Jedna poštena granica: zapis TPdfAnnotation nosi jedan TQuadrilateralPoint, pa jedan poziv CreateAnnotation zapisuje jedan četverokut. Odabir koji se proteže kroz tri retka treba tri četverokuta, po jedan za svaki redak, prema §12.5.6.10, a imate dva načina da to postignete. Jednostavan način je jedna bilješka po retku, što se svugdje ispravno prikazuje i zadržava API na razini komponente. Kompaktan način, jedna bilješka koja nosi tri četverokuta, znači stvaranje bilješke kroz komponentu, a zatim samostalno pozivanje izvezene funkcije FPDFAnnot_AppendAttachmentPoints za drugi i treći četverokut, što radi upravo zato što Append stvara utore umjesto da ih zamjenjuje. Ne pokušavajte postići višestruki četverokut kroz ponovljene pozive SetAttachmentPoints; svaki indeks iznad trenutnog broja jednostavno vraća false, iz istog razloga iz kojeg je indeks 0 to učinio na svježe stvorenoj bilješci
Nakon pisanja, provjerite u stvarnom pregledniku umjesto da vjerujete povratnim kodovima: otvorite datoteku u Acrobatu ili bilo kojem pregledniku temeljenom na PDFium-u i potvrdite da označavanje sjeda na tekst, čita se u željenoj prozirnosti i preživljava krug spremanja i ponovnog učitavanja. Vrste bilješki, rukovanje četverokutima i pisac svjestan broja prikazani ovdje dio su standardne komponente PDFium Component za Delphi, C++Builder i Lazarus; stranica proizvoda nosi kompletnu referencu API-ja za bilješke uz ostatak knjižnice