PDFium-komponenten skapar textmarkeringsanteckningar, vilket innebär markering, understrykning, genomstrykning och vågig understrykning, via TPdf.CreateAnnotation: du ställer in HasAttachmentPoints := True i TPdfAnnotation-posten och fyller i dess fyrhörning (quadrilateral) AttachmentPoints, och komponenten skriver posten QuadPoints som definieras i ISO 32000-1 §12.5.6.10. Detta är hela API-ytan. Anledningen till att denna artikel existerar är vad som händer under huven, eftersom den råa anropskedjan i PDFium har ett felläge som ger det minst hjälpsamma symptomet i hela verktygslådan: FPDFAnnot_SetAttachmentPoints returnerar falskt på en nyskapad anteckning, varje gång, utan felkod och utan ledtråd. Detta är skapelsesidans följeslagare till vår artikel om att läsa och granska befintliga anteckningar, som går i den andra riktningen genom samma strukturer
Felsökningsscenariot är alltid detsamma. Du skapar en markeringsanteckning, du anropar inställningen för fästpunkter (attachment-points) med index 0, funktionen returnerar falskt, och du börjar tvivla på dina koordinater. Du transponerar punkterna, vänder på Y-axeln, byter ut sidrymden mot enhetsrymden. Inget av det hjälper, eftersom koordinaterna aldrig var problemet. Problemet är indexsemantiken i C-API:et, och när du väl inser det är korrigeringen två rader
Vad QuadPoints betyder i ISO 32000-1
QuadPoints är en array med 8×n tal som beskriver n fyrhörningar, och ISO 32000-1 §12.5.6.10 kräver det på varje textmarkeringsanteckning: varje fyrhörning markerar ett ord eller en grupp av sammanhängande ord som markeringen, understrykningen eller genomstrykningen gäller för. Anteckningens Rect-post finns fortfarande kvar, men för undergrupper av markeringar begränsar den endast området; fyrhörningarna är vad renderaren faktiskt ritar upp. En fyrhörning används snarare än en rektangel eftersom text kan roteras eller skjuvas, så de fyra hörnen sparas som fyra oberoende punkter: x1 y1 x2 y2 x3 y3 x4 y4
Ordningen för dessa fyra punkter är där specifikationen och den installerade basen går skilda vägar. Specifikationstexten beskriver punkterna som att de följer fyrhörningen moturs, men Adobes egen renderare har alltid tolkat dem i ett Z-mönster istället: först den övre kanten från vänster till höger, sedan den nedre kanten från vänster till höger. Eftersom alla författare testade mot Acrobat följer i praktiken varje renderare, inklusive PDFium, Z-mönstret, och filer som följer specifikationens bokstavliga formulering renderas som kollapsade eller förvrängda markeringar i vissa visare. PDFiums struktur FS_QUADPOINTSF kodar exakt denna konvention: (x1,y1) is the top-left corner, (x2,y2) top-right, (x3,y3) bottom-left, (x4,y4) bottom-right, in page coordinates where Y grows upward. Följ den ordningen så är det klart; renderare är överseende med mycket, men en förvrängd fyrhörning är inte en av dem
Varför returnerar FPDFAnnot_SetAttachmentPoints falskt?
FPDFAnnot_SetAttachmentPoints misslyckas på en ny anteckning eftersom dess kontrakt är att ersätta fyrhörningen vid ett givet index, och en nyskapad anteckning har noll fyrhörningar att ersätta. Signaturen tar ett anteckningshandtag, ett quad_index och punkterna; index 0 betyder inte ”den första platsen, skapa den om det behövs”, det betyder ”den befintliga fyrhörningen nummer 0”, och när FPDFAnnot_CountAttachmentPoints rapporterar 0 finns det ingen sådan fyrhörning och anropet returnerar falskt. Funktionen som skapar en plats är FPDFAnnot_AppendAttachmentPoints. Varje anteckning som skapas via FPDFPage_CreateAnnot startar med antalet noll, så skapelsevägen måste anropa Append först, och endast efterföljande uppdateringar får anropa Set
Detta drabbade själva PDFium-komponenten. Fram till v1.79.0 hårdkodade den interna rutinen som delas av CreateAnnotation och SetAnnotation anropet FPDFAnnot_SetAttachmentPoints(Annotation, 0, ...), vilket var korrekt för att uppdatera en befintlig markeringsanteckning och garanterat misslyckades för en ny, vilket visade sig som en EPdfException med meddelandet 'Cannot set attachment points'. Korrigeringen, som levererades i v1.79.1, förgrenar sig utifrån antalet
// Inuti komponentens anteckningsskrivare (v1.79.1+):
// en ny anteckning har inga fyrhörningsplatser än, så Append skapar
// den första; Set ersätter endast en plats som redan finns
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');
Samma mönster gäller om du anropar de exporterade C-funktionerna direkt, vilket komponenten tillåter eftersom alla FPDFAnnot_*-startpunkter exponeras i PDFium.pas. När du har ett FPDF_ANNOTATION-handtag och vill skriva fyrhörningar, fråga FPDFAnnot_CountAttachmentPoints först och dirigera därefter. Om du letar efter ”FPDFAnnot_SetAttachmentPoints returnerar falskt” är den här count-then-append-förgreningen nästan säkert ditt svar
Skapa en markering med TPdf.CreateAnnotation
När komponenten sköter styrningen mellan Append och Set åt dig reduceras skapandet av en markering till att fylla i en post. Exemplet nedan skapar en A4-sida och lägger till en halvtransparent gul markering över ett område på 200×20 punkter; observera att fyrhörningen följer Z-ordningen som beskrivs ovan, och att Rectangle är inställd för att omsluta fyrhörningen, vilket gör att visare som gör hit-test mot Rect fungerar förnuftigt
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 % opacitet
A.HasAttachmentPoints := True;
A.AttachmentPoints[1].X := 50; A.AttachmentPoints[1].Y := 700; // övre vänstra
A.AttachmentPoints[2].X := 250; A.AttachmentPoints[2].Y := 700; // övre högra
A.AttachmentPoints[3].X := 50; A.AttachmentPoints[3].Y := 680; // nedre vänstra
A.AttachmentPoints[4].X := 250; A.AttachmentPoints[4].Y := 680; // nedre högra
A.Rectangle.Left := 50; A.Rectangle.Top := 700;
A.Rectangle.Right := 250; A.Rectangle.Bottom := 680;
A.ContentsText := 'Markerat område';
Pdf.CreateAnnotation(A);
Pdf.SaveAs('highlighted.pdf');
finally
Pdf.Free;
end;
end;
Att byta undergrupp kostar en rad. anUnderline, anStrikeout och anSquiggly tar exakt samma postform, fyrhörningar och allt, eftersom ISO 32000-1 behandlar alla fyra som samma anteckningsfamilj, som endast skiljer sig åt genom hur fyrhörningsområdet dekoreras. Undergrupper som inte är textmarkeringar, som anSquare, anCircle och anText, positionerar sig enbart utifrån Rectangle; låt HasAttachmentPoints förbli falskt för dessa, så körs aldrig fyrhörningssystemet
Varför kompilerar AttachmentPoints[0] i Delphi men misslyckas i FPC?
TQuadrilateralPoint deklareras som array [1..4] of TPdfPoint, en 1-baserad array, och det ställer till det för alla vars fingrar automatiskt väljer nollbaserad indexering. Skriv A.AttachmentPoints[0] och Delphis dcc32 kommer att kompilera det utan klagomål, eftersom intervallkontroll (range checking) är avstängd som standard; vid körning läser eller skriver uttrycket tyst minnet precis före arrayen, vilket i en TPdfAnnotation-post är ett angränsande fält. Din markering får ett trasigt hörn, eller så blir ett angränsande fält korrupt, utan att något undantag utlöses. Free Pascal fångade just denna bugg i våra egna demokällor under Lazarus-porteringen: fpc utför intervallkontroll vid kompilering på konstanta index och avvisade AttachmentPoints[0..3] helt, vilket var hur off-by-one-felet och biblioteksbuggen med Set-versus-Append upptäcktes tillsammans
Två vanor följer av detta. Indexera fyrhörningen 1 till 4, vilket matchar hörnordningen i koden ovan, och bygg din anteckningskod minst en gång med intervallkontroll aktiverad, antingen {$R+} i Delphi eller valfri fpc-build, innan du litar på den. Att en standardmässig dcc32-build godkänns är inte bevis på att indexen är korrekta; det är bara bevis på att inget kraschade på det minne som råkade ligga där
Hämta fyrhörningskoordinater från riktig text
Hårdkodade rektanglar är bra för ett demo, men i produktion följer markeringar faktiska tecken, och koordinaterna bör komma från PDFiums sidtextgeometri snarare än gissningar. Rutinerna som behandlas i vår guide till textextraktion med PDFium Component ger dig avgränsningsrutor (bounding boxes) per tecken i samma sidkoordinatutrymme som fyrhörningarna använder, så att en sökträff konverteras direkt till hörnpunkter: till vänster om det första tecknet, till höger om det sista, topp och botten från radens omfång. Om du genererar texten själv och behöver veta var rader kommer att hamna innan de finns, täcker artikeln om textmätning och radbrytning hur dessa omfång beräknas i förväg
En ärlig begränsning: posten TPdfAnnotation bär på en enda TQuadrilateralPoint, så ett CreateAnnotation-anrop skriver en fyrhörning. Ett val som spänner över tre rader behöver tre fyrhörningar, en per rad, enligt §12.5.6.10, och du har två sätt att nå dit. Det enkla sättet är en anteckning per rad, vilket renderas korrekt överallt och bibehåller API:et på komponentnivå. Det kompakta sättet, en anteckning som bär på tre fyrhörningar, innebär att skapa anteckningen via komponenten och sedan anropa det exporterade FPDFAnnot_AppendAttachmentPoints själv för den andra och tredje fyrhörningen, vilket fungerar just eftersom Append skapar platser snarare än att ersätta dem. Försök inte uppnå flera fyrhörningar genom upprepade anrop till SetAttachmentPoints; varje index som passerar det nuvarande antalet returnerar bara falskt, av samma anledning som index 0 gjorde på den nya anteckningen
Efter skrivning, verifiera i en riktig läsare istället för att lita på returkoder: öppna filen i Acrobat or any PDFium-based viewer and confirm the markup lands on the text, reads at the intended opacity, and survives a save-and-reload round trip. Anteckningstyperna, hanteringen av fyrhörningar och den antalsmedvetna skrivaren som visas här är alla en del av standardkomponenten PDFium Component för Delphi, C++Builder och Lazarus; produktsidan innehåller hela referensen för antecknings-API:et tillsammans med resten av biblioteket