Exit code: 0 Wall time: 12.4 seconds Output: Zastarelo besedilo po urejanju: predpomnilnik PDFium FPDF_TEXTPAGE

Tehnični članak

Zastarelo besedilo po urejanju: predpomnilnik PDFium FPDF_TEXTPAGE

S AddText dodate vrstico na stran PDF z uporabo PDFiumPas, nato pa takoj pokličete FindFirst, da preverite, ali je bil žig dodan, vendar iskanje ne vrne ničesar. Besedilo je na strani — Acrobat ga prikaže — toda komponenta TPdf v PDFiumPas hrani ločeno predpomnjeno strukturo FPDF_TEXTPAGE, enkrat razčlenjeno iz vsebinskega toka strani, urejanje pa te strukture samo po sebi ne posodobi za nazaj. Če jo poizvedete, preden je osvežena, preberete stran natanko tako, kot je bila videti pred spremembo, ne po njej

Zakaj PDFium takoj po urejanju vrne zastarelo besedilo

PDFiumPas ovije Googlov pogon za izris PDFium za Delphi in C++Builder, njegovi klici za besedilo in urejanje pa dosežejo dva različna podsistema v tem pogonu. FPDF_TEXTPAGE pripada bralni strani: FPDFText_LoadPage enkrat prehodi vsebinski tok strani in zgradi besedilno stran — kode znakov, položaje, metrike pisav, meje besed — PDFiumPas pa to strukturo predpomni, dokler je stran naložena. Klici za urejanje, kot sta FPDFPage_InsertObject in FPDFPage_GenerateContent, delujejo na povsem drugačni predstavitvi, grafu predmetov strani in vsebinskega toka, PDFium pa teh sprememb sam ne potisne v že odprto besedilno stran. Ponovna gradnja po vsakem urejanju bi paketno urejanje naredila nesprejemljivo počasno, zato zasnova ta strošek zamenja za pravilo — kdor ima ročico, jo po spremembi vsebine zapre, naslednje branje pa zgradi svežo

V besedilnem predpomnilniku TPdf: FTextPage, LoadTextPage in UnloadTextPage

TPdf predpomnjeno ročico spremlja v enem zasebnem polju, FTextPage, njen življenjski cikel pa ovije v dve metodi. LoadTextPage preveri, ali je FTextPage nil, in samo v tem primeru pokliče FPDFText_LoadPage za trenutno stran; če ročica že obstaja, jo LoadTextPage znova uporabi, ne da bi preverila, ali se je stran od njene izdelave spremenila. UnloadTextPage je druga polovica: izvorno ročico zapre z FPDFText_ClosePage, FTextPage znova nastavi na nil ter odstrani tudi predpomnjen seznam spletnih povezav in morebitno tekočo sejo iskanja, saj oba izhajata iz iste besedilne strani in iz istega razloga zastarata

Prav vedenje metode LoadTextPage, ki ročico znova uporabi brez preverjanja, pojasni, zakaj je zaporedje pomembno. Vsaka poizvedba besedila v TPdfText, FindFirst, GetWebLinks — najprej poteka skozi LoadTextPage, zato dokler FTextPage še vsebuje ročico izpred urejanja, noben od teh klicev ne more vedeti, da se je zgodila sprememba. Krmarjenje po straneh tu nikoli ni bilo tveganje: UnloadPage, ki se izvede pri menjavi strani, ponovnem nalaganju in zapiranju dokumenta, je vedno zaprl besedilno stran skupaj s samo stranjo. Odprto vprašanje so bila vedno urejanja strani, na kateri se še vedno nahajate

Katere metode PDFiumPas samodejno osvežijo predpomnilnik

Lastne metode TPdf za urejanje strani — AddText, SetText, SetTextPositions, AddPath, RemoveObject in InsertFormObjectFromXObject — vsaka pokliče UnloadTextPage, preden pokliče UpdatePage (FPDFPage_GenerateContent v PDFium), da spremembo serializira v vsebinski tok. Pokličite katero koli od njih in naslednji klic Text, FindFirst ali GetWebLinks bo besedilno stran znova zgradil iz trenutno obstoječe vsebine, brez dodatnega klica z vaše strani

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;

Vzorec, ki se še vedno zlomi: predpomnjenje surove ročice TextPage

TPdf živo ročico razkrije prek lastnosti TextPage, ki je samo za branje, za redek primer, ko morate poklicati funkcijo FPDFText_*, ki je PDFiumPas ne ovija. Ta izhod v sili je tudi edino mesto, kjer samodejna razveljavitev ne more pomagati: ko vrednost FPDF_TEXTPAGE iz lastnosti kopirate v lokalno spremenljivko, PDFiumPas ne more vedeti, da jo še vedno držite, niti ne more posodobiti vaše kopije, ko se UnloadTextPage izvede drugje v vaši kodi

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;

Uporaba ročice po tem, ko se je nad njo izvedel FPDFText_ClosePage, je nedoločeno vedenje v samem PDFium, ne konvencija PDFiumPas, ki bi jo lahko prezrli — lahko vrne zadnje znane podatke, ne vrne ničesar ali sesuje proces, na to, kaj se bo zgodilo v določeni gradnji, pa se programska koda ne sme zanašati. Varno pravilo je ozko: tik pred klicem FPDFText_*, ki jo potrebuje, na novo preberite Pdf.TextPage in nikoli ne hranite kopije čez stavek, ki bi lahko uredil stran

Paketno izvedite urejanja, nato poizvedite enkrat

To ne pomeni, da vsak klic AddText ali RemoveObject potrebuje takojšnjo obrambno poizvedbo besedila za preverjanje rezultata. Vsaka metoda za urejanje že plača strošek enkratnega zaprtja besedilne strani; poizvedba po vsakem posameznem urejanju v zanki ta strošek plača znova brez koristi, saj FPDFText_LoadPage ob vsakem izvajanju znova prehodi celoten vsebinski tok

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;

Enaka paketna logika velja posebej za stanje iskanja. FindNext in FindPrevious nadaljujeta sejo, začeto z FindFirst, UnloadTextPage pa jo skupaj z vsem drugim prekine, zato ponovni klic FindNext po urejanju — namesto ponovnega klica FindFirst — sproži izjemo, namesto da bi tiho nadaljeval iskanje po vsebini, ki ne obstaja več. Vsako urejanje obravnavajte kot strogo mejo za vsebino besedila in položaj iskanja, nato pa naj en svež FindFirst na drugi strani vaših urejanj znova začne iskanje

Kako se to umešča v pridobivanje besedila in delo z opombami

Pridobivanje navadnega besedila — branje besedila strani brez kakršne koli spremembe — se temu nikoli ne izpostavi, saj nič ne razveljavi ročice, ki se je ni dotaknilo nobeno urejanje. Za delovanje Text, pravokotnikov znakov in meja besed na nespremenjeni strani poskrbi spremljevalni članek o pridobivanju besedila s PDFiumPas, ne da bi dodal življenjski cikel predpomnilnika besedilne strani, ki ga obravnava ta članek

Življenjski cikel predpomnilnika je najpomembnejši pri potekih dela, ki stran uredijo in nato takoj ukrepajo na rezultatu: dodajo popravek in ga poiščejo, prekrijejo odstavek in preverijo, ali je izginil, ali poiščejo besedno zvezo, na katero pripnejo opombo, takoj po vstavljanju besedila v njeni bližini. Zadnji primer je vredno posebej izpostaviti — opombe z označbami quad-point so postavljene iz pravokotnikov znakov, prebranih z besedilne strani, zato opomba, zgrajena iz koordinat, zajetih pred urejanjem, po izvedeni spremembi označi napačno mesto

API-ji za urejanje in besedilo v TPdf so del komponente PDFium Component za Delphi in C++Builder, stran izdelka pa vsebuje celoten referenčni pregled metod za urejanje, pridobivanje in iskanje, ki jih obravnava ta članek