Tekninen artikkeli

Vanhentunut teksti muokkauksen jälkeen: PDFiumin FPDF_TEXTPAGE-välimuisti

Kutsut AddText-metodia leimataksesi rivin PDF-sivulle PDFiumPas:lla, sitten kutsut heti FindFirst-metodia vahvistaaksesi, että leima osui, ja haku palautuu tyhjänä. Teksti on sivulla — Acrobat näyttää sen — mutta PDFiumPas:n TPdf-komponentti pitää erillisen välimuistitetun FPDF_TEXTPAGE-rakenteen, jäsennettynä kerran sivun sisältövirrasta, eikä muokkaus päivitä tuota rakennetta takautuvasti itsestään. Kysele sitä ennen kuin se on virkistetty, ja luet sivun täsmälleen sellaisena kuin se näytti ennen muutostasi, ei sen jälkeen

Miksi PDFium palauttaa vanhentunutta tekstiä heti muokkauksen jälkeen?

PDFiumPas kääri Googlen PDFium-renderöintimoottorin Delphille ja C++Builderille, ja sen teksti- ja muokkauskutsut ulottuvat kahteen eri alijärjestelmään tuon moottorin sisällä. FPDF_TEXTPAGE kuuluu lukupuolelle: FPDFText_LoadPage käy sivun sisältövirran läpi kerran ja rakentaa tekstisivun — merkkikoodit, sijainnit, fonttimitat, sanarajat — ja PDFiumPas pitää tuon rakenteen välimuistitettuna niin kauan kuin sivu pysyy ladattuna. Muokkauskutsut kuten FPDFPage_InsertObject tai FPDFPage_GenerateContent toimivat täysin erilaisella esityksellä, sivun olio- ja sisältövirtakaaviolla, eikä PDFium työnnä noita muutoksia jo avoimeen tekstisivuun itsestään. Sen uudelleenrakentaminen jokaisella muokkauksella tekisi erämuokkauksesta kohtuuttoman hitaan, joten suunnittelu vaihtaa tuon kustannuksen säännöksi sen sijaan — kuka tahansa pitää kahvaa hallussaan, sulkee sen sisältöä muuttavan muokkauksen jälkeen, ja seuraava luku rakentaa tuoreen

TPdf:n tekstivälimuistin sisällä: FTextPage, LoadTextPage ja UnloadTextPage

TPdf seuraa välimuistitettua kahvaa yhdessä yksityisessä kentässä, FTextPage, ja kääri sen elinkaaren kahteen metodiin. LoadTextPage tarkistaa, onko FTextPage nil, ja vain siinä tapauksessa kutsuu FPDFText_LoadPage-funktiota nykyistä sivua vasten; jos kahva on jo olemassa, LoadTextPage käyttää sitä uudelleen kysymättä, onko sivu muuttunut sen rakentamisen jälkeen. UnloadTextPage on toinen puolisko: se sulkee natiivin kahvan funktiolla FPDFText_ClosePage, asettaa FTextPage:n takaisin nilliksi, ja pudottaa myös välimuistitetun verkkolinkkilistan ja minkä tahansa kesken olevan hakuistunnon, koska molemmat johdettiin samasta tekstisivusta ja vanhenevat samasta syystä

LoadTextPagen uudelleenkäyttö-tarkistamatta-käytös on juuri se, miksi järjestys on merkityksellinen. Jokainen tekstikysely TPdf:llä — Text, FindFirst, GetWebLinks — ohjautuu ensin LoadTextPage-funktion kautta, joten niin kauan kuin FTextPage yhä pitää muokkausta edeltävää kahvaa, millään noista kutsuista ei ole tapaa tietää, että muutos tapahtui. Sivunavigointi ei koskaan ollut riski tässä: UnloadPage, joka ajetaan sivunvaihdoilla, uudelleenlatauksilla ja asiakirjan sulkemisella, on aina sulkenut tekstisivun sivun itsensä mukana. Avoin kysymys oli aina muokkauksista, jotka sovelletaan sivuun, jolla yhä istut

Mitkä PDFiumPas-metodit virkistävät välimuistin automaattisesti?

TPdf:n omat sivunmuokkausmetodit — AddText, SetText, SetTextPositions, AddPath, RemoveObject ja InsertFormObjectFromXObject — kukin kutsuu UnloadTextPage-funktiota ennen kuin ne kutsuvat UpdatePage-funktiota (PDFiumin FPDFPage_GenerateContent) sarjallistaakseen muutoksen sisältövirtaan. Kutsu mitä tahansa näistä, ja aivan seuraava Text-, FindFirst- tai GetWebLinks-kutsu rakentaa tekstisivun uudelleen sisällöstä sellaisena kuin se nyt on, ilman että puoleltasi vaaditaan mitään ylimääräistä kutsua

var
  Pdf: TPdf;
  Index: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'invoice.pdf';
    Pdf.Active := True;
    Pdf.PageNumber := 1;

    Pdf.AddText('Reviewed by J. Alvarez', 'Helvetica', 10, 72, 40, clBlack, 255, 0);
    // AddText already closed the cached text page, so this FindFirst
    // call rebuilds it fresh before it searches
    Index := Pdf.FindFirst('Reviewed by J. Alvarez');
    if Index >= 0 then
      ShowMessage('Stamp confirmed at character ' + IntToStr(Index));
  finally
    Pdf.Free;
  end;
end;

Malli, joka yhä hajoaa: raa'an TextPage-kahvan välimuistittaminen

TPdf paljastaa elävän kahvan vain luku -ominaisuuden TextPage kautta, harvinaista tapausta varten, jossa sinun on kutsuttava FPDFText_*-funktiota, jota PDFiumPas ei ole kääräissyt. Tuo pako-luukku on myös se yksi paikka, jossa automaattinen mitätöinti ei voi auttaa: heti kun kopioit FPDF_TEXTPAGE-arvon ominaisuudesta paikalliseen muuttujaan, PDFiumPas:lla ei ole tapaa tietää, että pidät sitä yhä hallussasi, eikä tapaa päivittää kopiotasi, kun UnloadTextPage ajetaan jossain muualla koodissasi

var
  Pdf: TPdf;
  RawHandle: FPDF_TEXTPAGE;
  StaleCount: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract.pdf';
    Pdf.Active := True;
    Pdf.PageNumber := 1;

    RawHandle := Pdf.TextPage;    // FPDFText_LoadPage handle, cached in FTextPage
    Pdf.SetText(0, 'Amended Clause 4.2');
    // SetText already closed RawHandle and set Pdf.TextPage back to nil.
    // Calling any FPDFText_* function against the old value now touches a
    // handle PDFium has already freed — undefined behavior, not a bug you
    // can catch with a nil check
    StaleCount := FPDFText_CountChars(RawHandle);
  finally
    Pdf.Free;
  end;
end;

Kahvan käyttäminen sen jälkeen, kun FPDFText_ClosePage on ajettu sille, on määrittelemätöntä käytöstä itse PDFiumissa, ei PDFiumPas-käytäntö, jonka voisit valita ohittaa — se voi palauttaa viimeksi tunnetun datan, palauttaa ei mitään, tai kaataa prosessin, eikä se, mikä näistä tapahtuu tietyssä käännöksessä, ole jotain, mihin sovelluskoodin pitäisi luottaa. Turvallinen sääntö on kapea: lue Pdf.TextPage tuoreena, välittömästi ennen FPDFText_*-kutsua, joka sitä tarvitsee, äläkä koskaan pidä kopiota yli lauseen, joka saattaa muokata sivua

Erä muokkauksesi, sitten kysele kerran

Mikään tästä ei tarkoita, että jokainen AddText- tai RemoveObject-kutsu tarvitsee puolustavan tekstikyselyn heti sen jälkeen tuloksen tarkistamiseksi. Jokainen muokkausmetodi maksaa jo tekstisivun sulkemisen kustannuksen kerran; kysely jokaisen yksittäisen muokkauksen jälkeen silmukan sisällä maksaa tuon kustannuksen uudelleen ilman hyötyä, koska FPDFText_LoadPage käy koko sisältövirran läpi uudelleen joka kerta, kun se ajetaan

var
  Pdf: TPdf;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'watermarked.pdf';
    Pdf.Active := True;
    Pdf.PageNumber := 1;

    // Strip every text object that looks like a draft watermark. Each
    // RemoveObject call already invalidates the cache on its own, so
    // nothing needs refreshing by hand between iterations
    for I := Pdf.ObjectCount - 1 downto 0 do
      if (Pdf.ObjectType[I] = otText) and (Pdf.ObjectBounds[I].Top > 700) then
        Pdf.RemoveObject(I, True);

    // Query once, after the whole batch is done, not once per removal
    if Pdf.FindFirst('DRAFT') < 0 then
      ShowMessage('Watermark cleared');
  finally
    Pdf.Free;
  end;
end;

Sama erittelylogiikka pätee nimenomaan hakutilaan. FindNext ja FindPrevious jatkavat FindFirst-funktion aloittamaa istuntoa, ja tuo istunto puretaan UnloadTextPage-funktiolla kaiken muun mukana, joten FindNext-kutsun kutsuminen uudelleen muokkauksen jälkeen — FindFirst-funktion uudelleenkutsumisen sijaan — nostaa poikkeuksen sen sijaan, että hiljaa jatkaisi hakua sisältöä vastaan, jota ei enää ole olemassa. Kohtele mitä tahansa muokkausta kovana rajana sekä tekstisisällölle että hakupositiolle, ja anna yhden tuoreen FindFirst-kutsun muokkausten toisella puolella poimia haku takaisin

Mihin tämä sopii poiminta- ja annotaatiotyön kanssa

Pelkkä tekstin poiminta — sivun tekstin lukeminen muuttamatta mitään — ei koskaan törmää mihinkään tästä, koska mikään ei mitätöi kahvaa, jota mikään muokkaus ei ole koskettanut. Miten Text, merkkisuorakulmiot ja sanarajat toimivat muokkaamattomalla sivulla, käsitellään artikkelissa rinnakkaisartikkeli tekstin poiminnasta PDFiumPas:lla, joka kattaa tuon maaperän ilman tätä artikkelia, joka lisää tekstisivuvälimuistin elinkaaren päälle

Välimuistin elinkaari on merkityksellisin työnkuluissa, jotka muokkaavat ja sitten välittömästi toimivat tuloksen perusteella: korjauksen leimaaminen ja sen etsiminen, kappaleen redaktointi ja sen katoamisen vahvistaminen, tai lauseen paikantaminen ankkuroidakseen merkintäannotaation heti tekstin lisäämisen jälkeen sen lähelle. Tuo viimeinen tapaus kannattaa merkitä erikseen — quad-point-merkintäannotaatiot sijoitetaan merkkisuorakulmioista, jotka luetaan tekstisivulta, joten ennen muokkausta talteen otetuista koordinaateista rakennettu annotaatio päätyy korostamaan väärää kohtaa heti, kun muokkaus astuu voimaan

TPdf:n muokkaus- ja teksti-API:t ovat osa PDFium-komponenttia Delphille ja C++Builderille, ja tuotesivulla on koko metodiviite tässä käsitellyille muokkaus-, poiminta- ja hakupinnoille