Tehnični članak

Označevanje besedila s točkami PDFium QuadPoints v Delphiju

Komponenta PDFium ustvarja označitve besedila (torej poudarjanje, podčrtavanje, prečrtavanje in valovito podčrtavanje) prek metode TPdf.CreateAnnotation: v zapisu TPdfAnnotation nastavite HasAttachmentPoints := True in izpolnite njegovo štirikotno polje AttachmentPoints, komponenta pa zapiše vnos QuadPoints, kot ga definira standard ISO 32000-1 §12.5.6.10. To je celotno področje API-ja. Razlog za obstoj tega članka pa se skriva pod njim, saj ima surovo klicanje verige PDFium način neuspeha, ki ustvari najmanj koristen simptom: funkcija FPDFAnnot_SetAttachmentPoints vsakič vrne false na sveže ustvarjeni označitvi, brez kode napake ali kakršnega koli namiga. Ta članek je spremljevalec na strani ustvarjanja za naš članek o branju in pregledovanju obstoječih označb, ki opisuje nasprotno smer skozi iste strukture

Scenarij razhroščevanja je vedno enak. Ustvarite označitev s poudarjanjem, pokličete nastavitveno funkcijo pritrdilnih točk z indeksom 0, funkcija vrne false, vi pa začnete ugibati svoje koordinate. Transponirate točke, obrnete os Y, zamenjate prostor strani za prostor naprave. Nič od tega ne pomaga, ker koordinate nikoli niso bile težava. Težava je v semantiki indeksov v API-ju C, ko jo enkrat prepoznate, pa je popravek dolg le dve vrstici

Kaj pomenijo QuadPoints v standardu ISO 32000-1

QuadPoints je polje 8×n števil, ki opisuje n štirikotnikov, standard ISO 32000-1 §12.5.6.10 pa ga zahteva pri vsaki označitvi besedila: vsak štirikotnik označuje besedo ali skupino sosednjih besed, na katere se nanaša poudarjanje, podčrtavanje ali prečrtavanje. Vnos Rect označitve še vedno obstaja, vendar za podvrste označevanja določa le meje območja; štirikotniki (quads) so tisto, kar izrisovalnik dejansko naslika. Štirikotnik je izbran namesto pravokotnika zato, ker je besedilo lahko zasukano ali poševno, tako da so štirje vogali shranjeni kot štiri neodvisne točke: x1 y1 x2 y2 x3 y3 x4 y4

Vrstni red teh štirih točk je tisto, kjer se specifikacija in uveljavljene rešitve razhajajo. Besedilo specifikacije opisuje točke tako, da štirikotnik poteka v nasprotni smeri urinega kazalca, vendar je Adobov lasten izrisovalnik te točke vedno interpretiral v vzorcu črke Z: najprej zgornji rob od leve proti desni, nato spodnji rob od leve proti desni. Ker so vsi avtorji testirali v programu Acrobat, skoraj vsi izrisovalniki (vključno s PDFium) sledijo vzorcu Z, datoteke, ki dobesedno sledijo specifikaciji, pa se v nekaterih pregledovalnikih izrišejo kot zrušena ali zvita poudarjanja. PDFium-ova struktura FS_QUADPOINTSF kodira natanko to konvencijo: (x1,y1) je zgornji levi vogal, (x2,y2) zgornji desni, (x3,y3) spodnji levi in (x4,y4) spodnji desni vogal, v koordinatah strani, kjer Y raste navzgor. Sledite temu vrstnemu redu; izrisovalniki so prizanesljivi pri mnogih stvareh, toda zmešan štirikotnik ni ena izmed njih

Zakaj FPDFAnnot_SetAttachmentPoints vrne false?

Funkcija FPDFAnnot_SetAttachmentPoints na novi označitvi ne uspe, ker je njena pogodba ta, da zamenja štirikotnik na določenem indeksu, sveže ustvarjena označitev pa nima štirikotnikov, ki bi jih lahko zamenjala. Podpis funkcije sprejme ročico označitve, indeks quad_index in točke; indeks 0 ne pomeni "prvega mesta in njegovega ustvarjanja po potrebi", ampak pomeni "obstoječi štirikotnik številka 0", ko pa FPDFAnnot_CountAttachmentPoints sporoči 0, takega štirikotnika ni in klic vrne false. Funkcija, ki dejansko ustvari mesto, je FPDFAnnot_AppendAttachmentPoints. Vsaka označitev, ustvarjena prek FPDFPage_CreateAnnot, se začne s številom nič, zato mora pot ustvarjanja najprej poklicati Append, šele kasnejše posodobitve pa lahko pokličejo Set

To je doletelo tudi samo komponento PDFium. Do različice v1.79.0 je interna rutina, ki si jo delita CreateAnnotation in SetAnnotation, trdo kodirala klic FPDFAnnot_SetAttachmentPoints(Annotation, 0, ...), kar je bilo pravilno za posodabljanje obstoječe označitve besedila in zagotovljen neuspeh za novo označitev, kar se je odrazilo kot izjema EPdfException s sporočilom 'Cannot set attachment points'. Popravek, poslan v različici v1.79.1, se veje glede na število točk

// 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');

Enak vzorec velja, če neposredno kličete izvožene funkcije C, kar vam komponenta omogoča, saj so vse vstopne točke FPDFAnnot_* izpostavljene v enoti PDFium.pas. Kadarkoli držite ročico FPDF_ANNOTATION in želite zapisati štirikotnike, najprej povprašajte funkcijo FPDFAnnot_CountAttachmentPoints in ustrezno usmerite klic. Če iščete razlog, zakaj "FPDFAnnot_SetAttachmentPoints vrne false", je ta veja s preverjanjem števila in nato dodajanjem (append) skoraj zagotovo vaš odgovor

Ustvarjanje poudarjanja s TPdf.CreateAnnotation

Ker komponenta namesto vas izvaja usmerjanje med Append in Set, se ustvarjanje poudarjanja zvede na izpolnjevanje zapisa. Spodnji primer ustvari stran A4 in spusti polprosojno rumeno poudarjanje čez območje velikosti 200×20 točk; upoštevajte, da štirikotnik sledi zgoraj opisanemu vrstnemu redu Z in da je lastnost Rectangle nastavljena tako, da obdaja štirikotnik, kar poskrbi, da se pregledovalniki, ki izvajajo preizkus zadetka (hit-test) glede na Rect, obnašajo smiselno

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;

Sprememba podvrst stane eno vrstico. Označitve anUnderline, anStrikeout in anSquiggly sprejmejo enako obliko zapisa, vključno s štirikotniki, saj standard ISO 32000-1 obravnava vse štiri kot isto družino označb, ki se razlikujejo le po tem, kako je štirikotno območje okrašeno. Podvrste, ki niso označitve besedila, kot so anSquare, anCircle in anText, se pozicionirajo le na podlagi Rectangle; za te pustite HasAttachmentPoints na False in mehanizem štirikotnikov se nikoli ne bo zagnal

Zakaj se AttachmentPoints[0] prevede v Delphiju, a spodleti v FPC?

Tip TQuadrilateralPoint je deklariran kot array [1..4] of TPdfPoint, torej polje z začetnim indeksom 1, to pa zavede vsakogar, čigar prsti privzeto izberejo indeksiranje z začetkom pri nič. Zapišite A.AttachmentPoints[0] in Delphijev prevajalnik dcc32 bo kodo prevedel brez pritožb, saj je preverjanje obsega privzeto izklopljeno; ob izvajanju bo izraz tiho prebral ali zapisal pomnilnik tik pred poljem, kar je v zapisu TPdfAnnotation sosednje polje. Vaše poudarjanje dobi en smetiščni vogal, ali pa se sosednje polje poškoduje, pri tem pa se nič ne sproži. Prevajalnik Free Pascal je ujel točno to napako v naših lastnih demo virih med prenosom na Lazarus: fpc izvaja preverjanje obsega ob prevajanju na konstantnih indeksih in je klic AttachmentPoints[0..3] takoj zavrnil, s čimer sta bili skupaj odkriti napaka z indeksom in napaka knjižnice Set-nasproti-Append

Sledita dve navadi. Indeksirajte štirikotnik od 1 do 4, kar ustreza vrstnemu redu vogalov v zgornji kodi, in pred zaupanjem kodi vsaj enkrat zgradite svojo kodo za označevanje z omogočenim preverjanjem obsega, bodisi z direktivo {$R+} v Delphiju ali s katero koli gradnjo fpc. Uspešen prevod s privzetim dcc32 ni dokaz, da so indeksi pravilni; je le dokaz, da se nič ni zrušilo na pomnilniku, ki je bil naključno tam

Pridobivanje koordinat štirikotnika iz realnega besedila

Trdo kodirani pravokotniki so primerni za predstavitev, toda produkcijska poudarjanja sledijo dejanskim znakom (glyphs), koordinate pa morajo izvirati iz geometrije besedilne strani PDFium in ne iz ugibanja. Rutine, opisane v našem vodniku za ekstrakcijo besedila s komponento PDFium, vam dajejo omejitvene okvire za vsak znak v istem koordinatnem prostoru strani, kot ga uporabljajo štirikotniki, tako da se najdeni rezultat iskanja pretvori neposredno v vogalne točke: levo od prvega znaka, desno od zadnjega, zgoraj in spodaj iz obsega vrstice. Če besedilo ustvarjate sami in morate vedeti, kje bodo vrstice padle, preden obstajajo, članek o merjenju besedila in prelomu vrstic opisuje izračun teh obsegov vnaprej

Ena poštena meja: zapis TPdfAnnotation nosi eno točko TQuadrilateralPoint, zato en klic CreateAnnotation zapiše en štirikotnik. Izbira, ki obsega tri vrstice, potrebuje tri štirikotnike (enega na vrstico, v skladu s §12.5.6.10), do tja pa vodita dve poti. Preprosta pot je ena označitev na vrstico, kar se povsod pravilno izriše in ohranja API na ravni komponente. Kompaktna pot, kjer ena označitev nosi tri štirikotnike, pomeni ustvarjanje označitve prek komponente in nato samostojno klicanje izvožene funkcije FPDFAnnot_AppendAttachmentPoints za drugi in tretji štirikotnik, kar deluje natanko zato, ker Append ustvarja mesta in jih ne zamenjuje. Ne poskušajte doseči več štirikotnikov prek večkratnih klicov SetAttachmentPoints; vsak indeks čez trenutno število preprosto vrne false, iz istega razloga kot je to storil indeks 0 na novi označitvi

Po zapisovanju preverite rezultat v realnem pregledovalniku in ne zaupajte le povratnim kodam: odprite datoteko v programu Acrobat ali katerem koli pregledovalniku na osnovi PDFium ter potrdite, da označitev pristane na besedilu, se izriše z načrtovano prosojnostjo in preživi krog shranjevanja in ponovnega nalaganja. Vrste označitev, upravljanje štirikotnikov in pisec, ki upošteva število točk, opisani tukaj, so del standardne komponente PDFium Component za Delphi, C++Builder in Lazarus; stran izdelka prinaša celoten sklic na API za označevanje skupaj z ostalim delom knjižnice