Tekninen artikkeli

Tekstin merkintähuomautukset PDFium QuadPointsilla Delphissä

PDFium-komponentti luo tekstin merkintähuomautuksia — eli korostuksia, alleviivauksia, yliviivauksia ja aaltoviivoja — TPdf.CreateAnnotation-metodin kautta: asetat arvon HasAttachmentPoints := True TPdfAnnotation-tietueeseen ja täytät sen AttachmentPoints-nelikulmion, ja komponentti kirjoittaa standardin ISO 32000-1 §12.5.6.10 määrittelemän QuadPoints-tietueen. Tämä kattaa koko API:n. Syy tämän artikkelin olemassaoloon on se, mitä tapahtuu pinnan alla, sillä PDFiumin raa'alla kutsuketjulla on vikatila, joka tuottaa työkalupakin vähiten hyödyllisen oireen: FPDFAnnot_SetAttachmentPoints palauttaa arvon false juuri luodulle huomautukselle joka ikinen kerta ilman virhekoodia tai vihjettä. Tämä on luontipuolen vastine artikkelillemme olemassa olevien huomautusten lukemisesta ja tarkastelusta, joka kulkee samaa rakennetta vastakkaiseen suuntaan

Vianetsintätilanne on aina samanlainen. Luot korostushuomautuksen, kutsut attachment-points-asettajaa indeksillä 0, funktio palauttaa arvon false, ja alat epäliä koordinaattejasi. Käännät pisteitä, vaihdat Y-akselin suuntaa, vaihdat sivutilan ja laitotilan välillä. Mikään tästä ei auta, koska koordinaatit eivät koskaan olleet ongelma. Ongelma on C-API:n indeksisemantiikka, ja kun sen ymmärtää, korjaus vaatii vain kaksi riviä

Mitä QuadPoints tarkoittaa standardissa ISO 32000-1

QuadPoints on 8×n-pituinen lukutaulukko, joka kuvaa n kappaletta nelikulmioita. Standardi ISO 32000-1 §12.5.6.10 vaatii tämän jokaisessa tekstin merkintähuomautuksessa: kukin nelikulmio merkitsee sanaa tai peräkkäisten sanojen ryhmää, johon korostus, alleviivaus tai yliviivaus kohdistuu. Huomautuksen Rect-tietue on edelleen olemassa, mutta merkintätyypeillä se vain rajaa alueen; nelikulmiot (quads) ovat ne, jotka piirtäjä todellisuudessa maalaa. Nelikulmiota käytetään suorakulmion sijaan siksi, että teksti voi olla kierrettyä tai vinoutunutta, joten neljä kulmaa tallennetaan neljänä itsenäisenä pisteenä: x1 y1 x2 y2 x3 y3 x4 y4

Nämä neljä pistettä ovat kohta, jossa määrittely ja käytännön toteutukset eroavat toisistaan. Määrittelyteksti kuvaa pisteet siten, että ne kiertävät nelikulmion vastapäivään, mutta Adoben oma piirtomoottori on aina tulkinnut ne Z-kuviona: ensin yläreuna vasemmalta oikealle, sitten alareuna vasemmalta oikealle. Koska kaikki kehittäjät testasivat toteutuksiaan Acrobatia vasten, käytännössä kaikki piirtomoottorit — mukaan lukien PDFium — noudattavat Z-kuviota, ja määrittelyn sanatarkkaa ohjetta noudattavat tiedostot piirtyvät joissakin katseluohjelmissa luhistuneina tai vääristyneinä korostuksina. PDFiumin FS_QUADPOINTSF-rakenne koodaa juuri tämän käytännön: (x1,y1) on vasen yläkulma, (x2,y2) oikea yläkulma, (x3,y3) vasen alakulma, (x4,y4) oikea alakulma sivukoordinaatistossa, jossa Y kasvaa ylöspäin. Noudata tätä järjestystä, niin kaikki toimii; piirtomoottorit sallivat monia asioita, mutta sekava nelikulmio ei ole yksi niistä

Miksi FPDFAnnot_SetAttachmentPoints palauttaa arvon false?

FPDFAnnot_SetAttachmentPoints epäonnistuu uudessa huomautuksessa, koska sen tehtävä on korvata annetussa indeksissä oleva nelikulmio, ja vastikään luodussa huomautuksessa on nolla nelikulmiota korvattavaksi. Kutsun allekirjoitus ottaa huomautuskahvan, quad_index-indeksin ja pisteet. Indeksi 0 ei tarkoita "ensimmäistä paikkaa luoden sen tarvittaessa", se tarkoittaa "olemassa olevaa nelikulmiota numero 0". Kun FPDFAnnot_CountAttachmentPoints raportoi arvoksi 0, tällaista nelikulmiota ei ole ja kutsu palauttaa arvon false. Paikan luova funktio on FPDFAnnot_AppendAttachmentPoints. Jokainen FPDFPage_CreateAnnot-metodilla luotu huomautus alkaa nollamäärällä, joten luontipolun on kutsuttava ensin Append-metodia, ja vain myöhemmät päivitykset voivat kutsua Set-metodia

Tämä osui myös itse PDFium-komponenttiin. Versioon v1.79.0 saakka CreateAnnotation- ja SetAnnotation-metodien jakama sisäinen rutiini käytti kovakoodattua kutsua FPDFAnnot_SetAttachmentPoints(Annotation, 0, ...), mikä oli oikein olemassa olevan merkintähuomautuksen päivittämisessä mutta epäonnistui varmasti uudella huomautuksella, mikä näkyi EPdfException-poikkeuksena viestillä 'Cannot set attachment points'. Korjaus, joka julkaistiin versiossa v1.79.1, haarautuu määrän perusteella

// Komponentin huomautusten kirjoittajan sisällä (v1.79.1+):
// uudella huomautuksella ei ole vielä nelikulmiopaikkoja, joten Append luo
// ensimmäisen; Set vain korvaa jo olemassa olevan paikan
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');

Sama kaava pätee, jos kutsut vietyjä C-funktioita suoraan, minkä komponentti sallii, koska kaikki FPDFAnnot_*-liityntäpisteet on tuotu esiin PDFium.pas-tiedostossa. Aina kun hallussasi on FPDF_ANNOTATION-kahva ja haluat kirjoittaa nelikulmioita, kysy ensin FPDFAnnot_CountAttachmentPoints-arvoa ja reititä sen mukaan. Jos etsit vastausta kysymykseen "FPDFAnnot_SetAttachmentPoints returns false", tämä laske-ja-liitä-haara on lähes varmasti vastaus ongelmaasi

Korostuksen luominen TPdf.CreateAnnotation-metodilla

Kun komponentti hoitaa Append- ja Set-valinnan puolestasi, korostuksen luominen pelkistyy tietueen täyttämiseksi. Alla oleva esimerkki luo A4-sivun ja asettaa puoliläpinäkyvän keltaisen korostuksen 200×20 pisteen alueelle. Huomaa, että nelikulmio noudattaa yllä kuvattua Z-järjestystä ja että Rectangle on asetettu sulkemaan nelikulmio sisäänsä, jotta katseluohjelmien tekemät Rect-tarkistukset toimivat järkevästi

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 % peittävyys
    A.HasAttachmentPoints := True;
    A.AttachmentPoints[1].X := 50;  A.AttachmentPoints[1].Y := 700; // vasen yläkulma
    A.AttachmentPoints[2].X := 250; A.AttachmentPoints[2].Y := 700; // oikea yläkulma
    A.AttachmentPoints[3].X := 50;  A.AttachmentPoints[3].Y := 680; // vasen alakulma
    A.AttachmentPoints[4].X := 250; A.AttachmentPoints[4].Y := 680; // oikea alakulma
    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;

Miksi AttachmentPoints[0] kääntyy Delphissä mutta epäonnistuu FPC-kääntäjällä?

TQuadrilateralPoint on määritelty muodossa array [1..4] of TPdfPoint (1-pohjainen taulukko), ja tämä hämmentää kaikkia, joiden sormet ovat tottuneet nollapohjaiseen indeksointiin. Jos kirjoitat A.AttachmentPoints[0], Delphin dcc32 kääntää sen mukisematta, koska alueen tarkistus (range checking) on oletuksena pois päältä. Suoritusaikana lauseke lukee tai kirjoittaa hiljaa muistia juuri ennen taulukkoa, mikä TPdfAnnotation-tietueessa on viereinen kenttä. Korostuksesi saa yhden roskakulman tai naapurikenttä korruptoituu, eikä mikään laukaise virhettä. Free Pascal löysi tämän nimenomaisen virheen omista demo-lähdekoodeistamme Lazarus-porttauksen aikana: fpc suorittaa käännösaikaisen aluetarkistuksen vakioindekseille ja hylkäsi AttachmentPoints[0..3]-ilmaisun kokonaan, mikä paljasti yhdellä ohitetun indeksin ja Set-versus-Append-kirjastovirheen yhdessä

Tästä seuraa kaksi tapaa. Indeksoi nelikulmio 1:stä 4:ään yllä olevan koodin kulmajärjestyksen mukaisesti, ja käännä huomautuskoodisi vähintään kerran alueentarkistus päällä (joko {$R+} Delphissä tai mikä tahansa fpc-käännös) ennen kuin luotat siihen. Oletusarvoisen dcc32-käännöksen läpimeno ei ole todiste siitä, että indeksit ovat oikein — se on vain todiste siitä, ettei mikään kaatunut sillä muistialueella, joka sattui siinä kohdassa olemaan

Nelikulmion koordinaattien saaminen todellisesta tekstistä

Kovakoodatut suorakulmiot sopivat esittelyyn, mutta tuotantotason korostukset seuraavat todellisia merkkejä. Koordinaattien tulisi tulla PDFiumin tekstisivun geometriasta arvailun sijaan. Rutiinit, joita käsitellään artikkelissamme tekstin uuttamisesta PDFium-komponentilla, antavat merkkikohtaiset rajauslaatikot samassa sivun koordinaattitilassa kuin nelikulmiot. Siten hakutulos muuttuu suoraan kulmapisteiksi: ensimmäisen merkin vasen reuna, viimeisen oikea, ylä- ja alareuna rivin ulottuvuuksista. Jos luot tekstiä itse ja sinun on tiedettävä, mihin rivit asettuvat ennen niiden luomista, tekstin mittausta ja rivitystä käsittelevä artikkeli kattaa näiden ulottuvuuksien laskemisen etukäteen

Yksi rehellinen rajoitus: TPdfAnnotation-tietue kantaa yhtä TQuadrilateralPoint-tietuetta, joten yksi CreateAnnotation-kutsu kirjoittaa yhden nelikulmion. Kolmelle riville ulottuva valinta tarvitsee kolme nelikulmiota (yksi per rivi pykälän §12.5.6.10 mukaisesti), ja sinulla on kaksi tapaa saavuttaa tämä. Yksinkertainen tapa on luoda yksi huomautus per rivi, mikä piirtyy oikein kaikkialla ja säilyttää komponenttitason API:n. Tiivis tapa, eli yksi huomautus kolmella nelikulmiolla, tarkoittaa huomautuksen luomista komponentin kautta ja viemäsi FPDFAnnot_AppendAttachmentPoints-funktion kutsumista itse toiselle ja kolmannelle nelikulmiolle. Tämä toimii juuri siksi, että Append luo paikkoja korvaamisen sijaan. Älä yritä saavuttaa monen nelikulmion tilaa toistuvilla SetAttachmentPoints-kutsuilla — jokainen nykyisen määrän ylittävä indeksi palauttaa vain arvon false samasta syystä kuin indeksi 0 teki uudessa huomautuksessa

Kirjoittamisen jälkeen varmista tulos todellisella katseluohjelmalla pelkkien paluukoodien luottamisen sijaan: avaa tiedosto Acrobatissa tai missä tahansa PDFium-pohjaisessa katseluohjelmassa ja varmista, että merkintä osuu tekstiin, näkyy halutulla peittävyydellä ja säilyy tallennuksen ja uudelleenlatauksen edestakaisessa matkassa. Tässä esitetyt huomautustyypit, nelikulmioiden käsittely ja määrätietoinen kirjoittaja ovat kaikki osa standardia PDFium Component -kirjastoa Delphille, C++Builderille ja Lazarukselle. Tuotesivu sisältää täydellisen huomautus-API-viitteen muun kirjaston ohella