U roept AddText aan om met PDFiumPas een regel op een PDF-pagina te stempelen, en roept vervolgens meteen FindFirst aan om te bevestigen dat de stempel is beland, en de zoekopdracht komt leeg terug. De tekst staat op de pagina — Acrobat toont deze — maar de TPdf-component van PDFiumPas houdt een aparte gecachete FPDF_TEXTPAGE-structuur bij, één keer geparst uit de inhoudsstroom van de pagina, en een bewerking werkt die structuur niet met terugwerkende kracht vanzelf bij. Vraag deze op voordat ze is vernieuwd en u leest de pagina precies zoals deze eruitzag vóór uw wijziging, niet erna
Waarom geeft PDFium verouderde tekst terug direct na een bewerking?
PDFiumPas wikkelt de render-engine PDFium van Google in voor Delphi en C++Builder, en de tekst- en bewerkingsaanroepen ervan bereiken twee verschillende subsystemen binnen die engine. FPDF_TEXTPAGE hoort bij de leeskant: FPDFText_LoadPage doorloopt de inhoudsstroom van de pagina één keer en bouwt de tekstpagina op — tekencodes, posities, lettertypemetrieken, woordgrenzen — en PDFiumPas houdt die structuur gecachet zolang de pagina geladen blijft. Bewerkingsaanroepen zoals FPDFPage_InsertObject of FPDFPage_GenerateContent werken op een compleet andere representatie, de object- en inhoudsstroomgraaf van de pagina, en PDFium duwt die wijzigingen niet uit zichzelf door naar een al geopende tekstpagina. Deze bij elke bewerking opnieuw opbouwen zou batchbewerking onaanvaardbaar traag maken, dus het ontwerp ruilt die kost in voor een regel in plaats daarvan — wie de handle ook vasthoudt, sluit deze na een inhoudswijzigende bewerking, en de volgende lezing bouwt een verse op
Binnen in de tekstcache van TPdf: FTextPage, LoadTextPage, en UnloadTextPage
TPdf houdt de gecachete handle bij in één private veld, FTextPage, en wikkelt de levenscyclus ervan in twee methoden. LoadTextPage controleert of FTextPage nil is en roept, alleen in dat geval, FPDFText_LoadPage aan tegen de huidige pagina; als er al een handle bestaat, hergebruikt LoadTextPage deze zonder te vragen of de pagina is veranderd sinds deze is opgebouwd. UnloadTextPage is de andere helft: het sluit de native handle met FPDFText_ClosePage, zet FTextPage terug op nil, en laat ook de gecachete weblink-lijst en elke lopende zoeksessie vallen, aangezien beide zijn afgeleid van dezelfde tekstpagina en om dezelfde reden verouderen
Het hergebruik-zonder-controle-gedrag van LoadTextPage is precies waarom de volgorde ertoe doet. Elke tekstopvraging op TPdf — Text, FindFirst, GetWebLinks — loopt eerst via LoadTextPage, dus zolang FTextPage nog de handle van vóór de bewerking vasthoudt, heeft geen van die aanroepen enige manier om te weten dat er een wijziging heeft plaatsgevonden. Paginanavigatie is hier nooit het risico geweest: UnloadPage, dat draait bij paginawissels, herladingen en het sluiten van het document, heeft de tekstpagina altijd samen met de pagina zelf gesloten. De open vraag ging altijd over bewerkingen toegepast op de pagina waarop u nog steeds zit
Welke PDFiumPas-methoden vernieuwen de cache automatisch?
De eigen paginabewerkingsmethoden van TPdf — AddText, SetText, SetTextPositions, AddPath, RemoveObject, en InsertFormObjectFromXObject — roepen elk UnloadTextPage aan voordat ze UpdatePage aanroepen (PDFium's FPDFPage_GenerateContent) om de wijziging te serialiseren naar de inhoudsstroom. Roep een van deze aan en de eerstvolgende aanroep van Text, FindFirst, of GetWebLinks bouwt de tekstpagina opnieuw op vanuit de inhoud zoals deze er nu voor staat, zonder dat u een extra aanroep hoeft te doen
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;
Het patroon dat nog steeds breekt: de ruwe TextPage-handle cachen
TPdf stelt de levende handle bloot via een alleen-lezen eigenschap TextPage, voor het zeldzame geval dat u een FPDFText_*-functie moet aanroepen die PDFiumPas niet heeft omwikkeld. Die ontsnappingsroute is ook de ene plek waar de automatische ongeldigverklaring niet kan helpen: zodra u de FPDF_TEXTPAGE-waarde uit de eigenschap kopieert naar een lokale variabele, heeft PDFiumPas geen manier om te weten dat u deze nog steeds vasthoudt, en geen manier om uw kopie bij te werken wanneer UnloadTextPage ergens anders in uw code draait
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;
Een handle gebruiken nadat FPDFText_ClosePage erop heeft gedraaid, is ongedefinieerd gedrag in PDFium zelf, geen PDFiumPas-conventie die u kunt negeren — het kan de laatst bekende data teruggeven, niets teruggeven, of het proces laten crashen, en welke van die uitkomsten zich voordoet bij een bepaalde build is niet iets waarop toepassingscode zou moeten vertrouwen. De veilige regel is nauw: lees Pdf.TextPage vers, direct voordat de FPDFText_*-aanroep die deze nodig heeft, en houd nooit een kopie vast over een statement heen dat de pagina zou kunnen bewerken
Bewerkingen bundelen, dan één keer opvragen
Niets hiervan betekent dat elke aanroep van AddText of RemoveObject direct erna een defensieve tekstopvraging nodig heeft om het resultaat te controleren. Elke bewerkingsmethode betaalt al de kosten van het één keer sluiten van de tekstpagina; na elke afzonderlijke bewerking binnen een lus opvragen, betaalt die kosten opnieuw zonder enig voordeel, aangezien FPDFText_LoadPage elke keer dat het draait de hele inhoudsstroom opnieuw doorloopt
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;
Dezelfde bundelingslogica is specifiek van toepassing op zoektoestand. FindNext en FindPrevious zetten een sessie voort die door FindFirst is gestart, en die sessie wordt samen met al het andere afgebroken door UnloadTextPage, dus FindNext opnieuw aanroepen na een bewerking — in plaats van FindFirst opnieuw aan te roepen — werpt een uitzondering op in plaats van stilletjes een zoekopdracht te hervatten tegen inhoud die niet meer bestaat. Behandel elke bewerking als een harde grens voor zowel tekstinhoud als zoekpositie, en laat één verse FindFirst aan de andere kant van uw bewerkingen de zoekopdracht weer oppakken
Waar dit past bij extractie- en annotatiewerk
Gewone tekstextractie — de tekst van een pagina lezen zonder iets te veranderen — komt hier nooit tegenaan, omdat niets een handle ongeldig maakt die geen enkele bewerking heeft aangeraakt. Voor hoe Text, tekenrechthoeken, en woordgrenzen werken op een ongewijzigde pagina behandelt het begeleidende artikel over het extraheren van tekst met PDFiumPas dat terrein zonder de levenscyclus van de tekstpaginacache die dit artikel er bovenop toevoegt
De levenscyclus van de cache doet er het meest toe in workflows die bewerken en dan onmiddellijk op het resultaat reageren: een correctie stempelen en ernaar zoeken, een paragraaf redigeren en bevestigen dat deze weg is, of een zin lokaliseren om een markup-annotatie te verankeren direct na het invoegen van tekst ernaast. Dat laatste geval is het waard om apart te vermelden — quad-point-markup-annotaties worden gepositioneerd vanuit tekenrechthoeken die van de tekstpagina worden gelezen, dus een annotatie opgebouwd uit coördinaten die zijn vastgelegd vóór een bewerking, markeert uiteindelijk de verkeerde plek zodra de bewerking is beland
De bewerkings- en tekst-API's van TPdf maken deel uit van de PDFium Component voor Delphi en C++Builder, en de productpagina bevat de volledige methodenreferentie voor de hier behandelde bewerkings-, extractie- en zoekoppervlakken