Zavoláte AddText, aby ste na stránku PDF pomocou PDFiumPas vytlačili riadok, potom okamžite zavoláte FindFirst, aby ste potvrdili, že pečiatka dorazila, a vyhľadávanie sa vráti prázdne. Text je na stránke — Acrobat ho zobrazí — no komponent TPdf v PDFiumPas drží samostatnú vyrovnávaciu pamäť štruktúry FPDF_TEXTPAGE, naparsovanú raz z obsahového prúdu stránky, a úprava túto štruktúru sama osebe spätne neaktualizuje. Dopytujte ju ešte pred jej obnovením a prečítate stránku presne tak, ako vyzerala pred vašou zmenou, nie po nej
Prečo PDFium hneď po úprave vráti zastaraný text?
PDFiumPas obaľuje vykresľovací engine PDFium od Googlu pre Delphi a C++Builder, a jeho volania pre text a úpravu siahajú do dvoch odlišných subsystémov vnútri tohto enginu. FPDF_TEXTPAGE patrí čítacej strane: FPDFText_LoadPage raz prejde obsahový prúd stránky a postaví textovú stránku — kódy znakov, pozície, metriky písma, hranice slov — a PDFiumPas túto štruktúru drží vo vyrovnávacej pamäti počas celej doby, kým stránka zostáva načítaná. Volania úpravy ako FPDFPage_InsertObject alebo FPDFPage_GenerateContent pracujú na úplne inej reprezentácii, na grafe objektov a obsahového prúdu stránky, a PDFium tieto zmeny sám od seba netlačí do už otvorenej textovej stránky. Znovupostavenie pri každej úprave by urobilo dávkovú úpravu neprijateľne pomalou, takže návrh túto cenu vymieňa za pravidlo namiesto toho — ktokoľvek drží handle, ho po úprave meniacej obsah zatvorí, a ďalšie čítanie postaví čerstvý
Vnútri textovej vyrovnávacej pamäte TPdf: FTextPage, LoadTextPage, a UnloadTextPage
TPdf sleduje uložený handle v jedinom súkromnom poli, FTextPage, a obaľuje jeho životný cyklus do dvoch metód. LoadTextPage skontroluje, či je FTextPage nil, a iba v tom prípade zavolá FPDFText_LoadPage voči aktuálnej stránke; ak už handle existuje, LoadTextPage ho znova použije bez toho, aby sa pýtal, či sa stránka od jeho postavenia zmenila. UnloadTextPage je druhá polovica: zatvorí natívny handle cez FPDFText_ClosePage, nastaví FTextPage späť na nil, a tiež zahodí uložený zoznam webových odkazov a akúkoľvek prebiehajúcu reláciu vyhľadávania, keďže oba boli odvodené z tej istej textovej stránky a zastarávajú z toho istého dôvodu
Správanie znovupoužitia-bez-kontroly v LoadTextPage je presne dôvod, prečo záleží na poradí. Každý textový dopyt na TPdf — Text, FindFirst, GetWebLinks — prechádza najprv cez LoadTextPage, takže pokým FTextPage stále drží handle spred úpravy, žiadne z týchto volaní nemá žiadny spôsob, ako vedieť, že sa zmena stala. Navigácia stránok tu nikdy nebola rizikom: UnloadPage, ktorá beží pri prepínaní stránok, opätovnom načítaní, a zatvorení dokumentu, vždy zatvorila textovú stránku spolu so samotnou stránkou. Otvorená otázka bola vždy o úpravách aplikovaných na stránku, na ktorej stále sedíte
Ktoré metódy PDFiumPas obnovujú vyrovnávaciu pamäť automaticky?
Vlastné metódy úpravy stránky TPdf — AddText, SetText, SetTextPositions, AddPath, RemoveObject, a InsertFormObjectFromXObject — každá zavolá UnloadTextPage ešte pred tým, než zavolá UpdatePage (FPDFPage_GenerateContent z PDFium), aby zmenu serializovala do obsahového prúdu. Zavolajte ktorékoľvek z nich a hneď ďalšie volanie Text, FindFirst, alebo GetWebLinks znova postaví textovú stránku z obsahu tak, ako práve stojí, bez akéhokoľvek extra volania z vašej strany
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;
Vzor, ktorý sa stále láme: ukladanie surového handlu TextPage
TPdf sprístupňuje živý handle cez vlastnosť TextPage iba na čítanie, pre vzácny prípad, keď potrebujete zavolať funkciu FPDFText_*, ktorú PDFiumPas neobalil. Táto únikový poklop je aj jediné miesto, kde automatická invalidácia nedokáže pomôcť: len čo skopírujete hodnotu FPDF_TEXTPAGE z vlastnosti do lokálnej premennej, PDFiumPas nemá žiadny spôsob, ako vedieť, že ju stále držíte, a žiadny spôsob, ako aktualizovať vašu kópiu, keď niekde inde vo vašom kóde beží UnloadTextPage
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;
Použitie handlu po tom, čo naň už bežal FPDFText_ClosePage, je nedefinované správanie priamo v samotnom PDFium, nie konvencia PDFiumPas, ktorú by ste sa mohli rozhodnúť ignorovať — môže vrátiť naposledy známe dáta, nevrátiť nič, alebo padnúť s procesom, a ktorá z týchto možností nastane na danom builde, nie je niečo, na čo by mal kód aplikácie spoliehať. Bezpečné pravidlo je úzke: čítajte Pdf.TextPage čerstvo, tesne pred volaním FPDFText_*, ktoré ho potrebuje, a nikdy nedržte kópiu naprieč príkazom, ktorý by mohol stránku upraviť
Dávkujte svoje úpravy, potom dopytujte raz
Nič z toho neznamená, že každé volanie AddText alebo RemoveObject potrebuje hneď po sebe obranný textový dopyt na kontrolu výsledku. Každá metóda úpravy už platí cenu za jedno zatvorenie textovej stránky; dopytovanie po každej jednotlivej úprave vnútri slučky platí túto cenu znova bez akéhokoľvek prínosu, keďže FPDFText_LoadPage pri každom behu znova prechádza celý obsahový prúd
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;
Rovnaká logika dávkovania platí konkrétne aj pre stav vyhľadávania. FindNext a FindPrevious pokračujú v relácii spustenej pomocou FindFirst, a táto relácia sa zruší spolu so všetkým ostatným cez UnloadTextPage, takže opätovné volanie FindNext po úprave — namiesto opätovného volania FindFirst — vyvolá výnimku namiesto toho, aby ticho pokračovalo vo vyhľadávaní voči obsahu, ktorý už neexistuje. Berte akúkoľvek úpravu ako tvrdú hranicu ako pre obsah textu, tak pre pozíciu vyhľadávania, a nechajte jeden čerstvý FindFirst na druhej strane vašich úprav vyhľadávanie znova zdvihnúť
Kde to zapadá do práce s extrakciou a anotáciami
Obyčajná extrakcia textu — čítanie textu stránky bez akejkoľvek zmeny — sa s ničím z tohto nikdy nestretne, pretože nič neinvaliduje handle, ktorého sa žiadna úprava nedotkla. Ako fungujú Text, obdĺžniky znakov, a hranice slov na nezmenenej stránke, pokrýva sprievodný článok o extrakcii textu s PDFiumPas bez životného cyklu vyrovnávacej pamäte textovej stránky, ktorý tento článok pridáva navrch
Životný cyklus vyrovnávacej pamäte je najdôležitejší v pracovných postupoch, ktoré upravia a potom okamžite konajú na výsledku: napečiatkovanie opravy a jej vyhľadanie, redakcia odseku a potvrdenie, že je preč, alebo nájdenie frázy na ukotvenie anotácie značkovania hneď po vložení textu vedľa nej. Práve ten posledný prípad sa oplatí označiť samostatne — anotácie značkovania s bodmi štvoruholníka sú polohované z obdĺžnikov znakov prečítaných z textovej stránky, takže anotácia postavená zo súradníc zachytených pred úpravou skončí zvýrazňujúc nesprávne miesto vo chvíli, keď úprava dorazí
API pre úpravu a text v TPdf sú súčasťou komponentu PDFium pre Delphi a C++Builder, a stránka produktu nesie plnú referenciu metód pre plochy úpravy, extrakcie a vyhľadávania opísané tu