Apelați AddText pentru a ștampila o linie pe o pagină PDF cu PDFiumPas, apoi apelați imediat FindFirst pentru a confirma că ștampila a aterizat, iar căutarea revine goală. Textul este pe pagină — Acrobat îl arată — dar componenta TPdf a PDFiumPas păstrează o structură FPDF_TEXTPAGE separată în cache, analizată o dată din fluxul de conținut al paginii, iar o editare nu actualizează retroactiv acea structură de la sine. Interogați-o înainte de a fi fost reîmprospătată, și citiți pagina exact așa cum arăta înainte de schimbarea dvs., nu după
De ce returnează PDFium text perimat imediat după o editare?
PDFiumPas învelește motorul de randare PDFium al Google pentru Delphi și C++Builder, iar apelurile sale de text și editare ajung la două subsisteme diferite în interiorul acelui motor. FPDF_TEXTPAGE aparține de partea de citire: FPDFText_LoadPage parcurge fluxul de conținut al paginii o dată și construiește pagina de text — coduri de caracter, poziții, metrici de font, limite de cuvinte — iar PDFiumPas păstrează acea structură în cache atâta timp cât pagina rămâne încărcată. Apelurile de editare precum FPDFPage_InsertObject sau FPDFPage_GenerateContent operează pe o reprezentare complet diferită, graful de obiecte și flux de conținut al paginii, iar PDFium nu împinge acele schimbări într-o pagină de text deja deschisă de la sine. Reconstruirea ei la fiecare editare ar face editarea în lot inacceptabil de lentă, așa că designul schimbă acel cost pentru o regulă în schimb — oricine deține handle-ul îl închide după o editare care schimbă conținutul, iar următoarea citire construiește unul proaspăt
În interiorul cache-ului de text al TPdf: FTextPage, LoadTextPage și UnloadTextPage
TPdf urmărește handle-ul din cache într-un singur câmp privat, FTextPage, și învelește ciclul său de viață în două metode. LoadTextPage verifică dacă FTextPage este nil și, doar în acel caz, apelează FPDFText_LoadPage față de pagina curentă; dacă un handle există deja, LoadTextPage îl refolosește fără a întreba dacă pagina s-a schimbat de când a fost construit. UnloadTextPage este cealaltă jumătate: închide handle-ul nativ cu FPDFText_ClosePage, setează FTextPage înapoi la nil, și elimină de asemenea lista de link-uri web din cache și orice sesiune de căutare în curs, întrucât ambele au fost derivate din aceeași pagină de text și devin perimate din același motiv
Comportamentul de refolosire-fără-verificare al LoadTextPage este exact motivul pentru care secvențierea contează. Fiecare interogare de text pe TPdf — Text, FindFirst, GetWebLinks — se canalizează prin LoadTextPage mai întâi, așa că atâta timp cât FTextPage încă ține handle-ul dinainte de editare, niciunul din acele apeluri nu are nicio modalitate de a ști că s-a întâmplat o schimbare. Navigarea de pagină nu a fost niciodată riscul aici: UnloadPage, care rulează la schimbări de pagină, reîncărcări și închiderea documentului, a închis întotdeauna pagina de text odată cu pagina însăși. Întrebarea deschisă a fost întotdeauna despre editările aplicate pe pagina pe care încă stați
Care metode PDFiumPas reîmprospătează cache-ul automat?
Propriile metode de editare de pagină ale TPdf — AddText, SetText, SetTextPositions, AddPath, RemoveObject și InsertFormObjectFromXObject — fiecare apelează UnloadTextPage înainte de a apela UpdatePage (FPDFPage_GenerateContent al PDFium) pentru a serializa schimbarea în fluxul de conținut. Apelați oricare din acestea, iar chiar următorul apel Text, FindFirst, sau GetWebLinks reconstruiește pagina de text din conținutul așa cum stă acum, fără niciun apel suplimentar necesar din partea dvs
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;
Tiparul care încă se rupe: punerea în cache a handle-ului brut TextPage
TPdf expune handle-ul viu printr-o proprietate TextPage doar-citire, pentru cazul rar în care trebuie să apelați o funcție FPDFText_* pe care PDFiumPas nu a învelit-o. Acea trapă de evacuare este de asemenea singurul loc unde invalidarea automată nu poate ajuta: odată ce copiați valoarea FPDF_TEXTPAGE din proprietate într-o variabilă locală, PDFiumPas nu are nicio modalitate de a ști că încă o dețineți, și nicio modalitate de a vă actualiza copia atunci când UnloadTextPage rulează altundeva în codul dvs
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;
Folosirea unui handle după ce FPDFText_ClosePage a rulat pe el este comportament nedefinit în PDFium însuși, nu o convenție PDFiumPas pe care puteți alege să o ignorați — poate returna ultimele date cunoscute, poate returna nimic, sau poate prăbuși procesul, iar care din acestea se întâmplă pe un anumit build nu este ceva de care codul aplicației ar trebui să depindă. Regula sigură este restrânsă: citiți Pdf.TextPage proaspăt, imediat înainte de apelul FPDFText_* care are nevoie de el, și nu păstrați niciodată o copie peste o instrucțiune care ar putea edita pagina
Grupați-vă editările, apoi interogați o singură dată
Nimic din toate acestea nu înseamnă că fiecare apel AddText sau RemoveObject are nevoie de o interogare defensivă de text imediat după el pentru a verifica rezultatul. Fiecare metodă de editare deja plătește costul închiderii paginii de text o dată; interogarea după fiecare editare individuală într-o buclă plătește acel cost din nou fără niciun beneficiu, întrucât FPDFText_LoadPage parcurge din nou întregul flux de conținut de fiecare dată când rulează
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;
Aceeași logică de grupare se aplică specific stării de căutare. FindNext și FindPrevious continuă o sesiune începută de FindFirst, iar acea sesiune este demontată de UnloadTextPage odată cu tot restul, așa că apelarea din nou a FindNext după o editare — în loc să apelați din nou FindFirst — ridică o excepție, în loc să reia silențios o căutare pe conținut care nu mai există. Tratați orice editare ca o limită dură atât pentru conținutul de text, cât și pentru poziția de căutare, și lăsați un FindFirst proaspăt de partea cealaltă a editărilor dvs. să reia căutarea
Unde se potrivește asta cu munca de extracție și adnotare
Extracția de text simplă — citirea textului unei pagini fără a schimba nimic — nu se lovește niciodată de nimic din toate acestea, pentru că nimic nu invalidează un handle pe care nicio editare nu l-a atins. Pentru cum funcționează Text, dreptunghiurile de caractere și limitele de cuvinte pe o pagină nemodificată, articolul complementar despre extragerea de text cu PDFiumPas acoperă acel teren fără ciclul de viață al cache-ului de pagină de text pe care îl adaugă acest articol deasupra
Ciclul de viață al cache-ului contează cel mai mult în fluxuri de lucru care editează și apoi acționează imediat pe rezultat: ștampilarea unei corecții și căutarea ei, redactarea unui paragraf și confirmarea că a dispărut, sau localizarea unei fraze pentru a ancora o adnotare de marcaj chiar după inserarea de text lângă ea. Acel ultim caz merită semnalat separat — adnotările de marcaj cu puncte quad sunt poziționate din dreptunghiuri de caractere citite de pe pagina de text, așa că o adnotare construită din coordonate capturate înainte de o editare ajunge să evidențieze locul greșit odată ce editarea aterizează
API-urile de editare și text ale TPdf fac parte din componenta PDFium pentru Delphi și C++Builder, iar pagina de produs conține referința completă de metode pentru suprafețele de editare, extracție și căutare acoperite aici