AddText pakeičia PDF puslapį, tačiau iškart po to iškviestas FindFirst gali nematyti pakeitimo ir grąžinti tuščią rezultatą. Tekstas puslapyje yra — Acrobat jį rodo — tačiau PDFiumPas TPdf komponentas laiko atskirą, iš puslapio turinio srauto vieną kartą sukurtą podėliuotą FPDF_TEXTPAGE struktūrą, kurios redagavimas savaime atgaline data neatnaujina. Užklauskite jos prieš atnaujinimą, ir perskaitysite puslapį tiksliai tokį, koks jis buvo prieš pakeitimą, o ne po jo
Kodėl PDFium iškart po redagavimo grąžina pasenusį tekstą
PDFiumPas apgaubia „Google“ PDFium atvaizdavimo variklį, skirtą Delphi ir C++Builder, o jo teksto ir redagavimo iškvietimai pasiekia dvi skirtingas šio variklio posistemes. FPDF_TEXTPAGE priklauso skaitymo pusei: FPDFText_LoadPage vieną kartą perbėga puslapio turinio srautą ir sukuria teksto puslapį — simbolių kodus, pozicijas, šrifto metriką, žodžių ribas — o PDFiumPas šią struktūrą laiko podėlyje tol, kol puslapis lieka įkeltas. Redagavimo iškvietimai, tokie kaip FPDFPage_InsertObject ar FPDFPage_GenerateContent, veikia visai kitokį atvaizdavimą — puslapio objektų ir turinio srauto grafiką, — ir PDFium savaime šių pakeitimų nepersiunčia į jau atidarytą teksto puslapį. Perkurti jį po kiekvieno redagavimo padarytų paketinį redagavimą nepriimtinai lėtą, todėl vietoje šios kainos pasirinkta taisyklė — rankeną turintis kodas ją uždaro po turinį keičiančio redagavimo, o kitas skaitymas sukuria naują
TPdf teksto podėlyje: FTextPage, LoadTextPage ir UnloadTextPage
TPdf podėliuotą rankeną seka viename privačiame lauke FTextPage, o jo gyvavimo ciklą apgaubia dviem metodais. LoadTextPage patikrina, ar FTextPage yra nil, ir tik tokiu atveju iškviečia FPDFText_LoadPage dabartiniam puslapiui; jei rankena jau yra, LoadTextPage ją tiesiog pakartotinai naudoja, neklausdama, ar puslapis nuo jos sukūrimo nepasikeitė. UnloadTextPage yra antroji pusė: ji uždaro natyvią rankeną per FPDFText_ClosePage, atstato FTextPage atgal į nil, taip pat panaikina podėliuotą saitynų sąrašą bei bet kokį vykstantį paieškos seansą, nes abu buvo gauti iš to paties teksto puslapio ir pasensta dėl tos pačios priežasties
Būtent dėl šio LoadTextPage pakartotinio naudojimo be tikrinimo elgesio ir svarbi iškvietimų seka. Kiekviena TPdf teksto užklausa — Text, FindFirst, GetWebLinks — pirmiausia praeina per LoadTextPage, todėl kol FTextPage vis dar laiko rankeną iš prieš redagavimą buvusios būsenos, nė vienas iš šių iškvietimų neturi jokio būdo sužinoti, kad įvyko pakeitimas. Puslapio navigacija čia niekada nebuvo rizika: UnloadPage, kuris paleidžiamas perjungiant puslapius, iš naujo įkeliant ar uždarant dokumentą, teksto puslapį kartu su pačiu puslapiu uždarydavo visada. Atviras klausimas visada buvo tik apie redagavimus, atliktus tame pačiame puslapyje, kuriame vis dar esate
Kurie PDFiumPas metodai podėlį atnaujina automatiškai
TPdf nuosavi puslapio redagavimo metodai — AddText, SetText, SetTextPositions, AddPath, RemoveObject ir InsertFormObjectFromXObject — kiekvienas iškviečia UnloadTextPage prieš iškviesdami UpdatePage (PDFium FPDFPage_GenerateContent), kad pakeitimas būtų įrašytas į turinio srautą. Iškvietus bet kurį iš jų, kitas Text, FindFirst ar GetWebLinks iškvietimas teksto puslapį atkuria iš dabartinio turinio, ir jums nereikia jokio papildomo veiksmo
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 jau uždarė podėlyje esantį teksto puslapį, todėl šis FindFirst
// iškvietimas jį atkuria iš naujo prieš pradėdamas paiešką
Index := Pdf.FindFirst('Reviewed by J. Alvarez');
if Index >= 0 then
ShowMessage('Stamp confirmed at character ' + IntToStr(Index));
finally
Pdf.Free;
end;
end;
Šablonas, kuris vis dar sugenda: podėliuojama neapdorota TextPage rankena
TPdf gyvą rankeną atveria per tik skaitymui skirtą savybę TextPage — retam atvejui, kai reikia iškviesti FPDFText_* funkciją, kurios PDFiumPas dar neapgaubė. Ši išeitis yra ir vienintelė vieta, kur automatinis podėlio panaikinimas nebepadeda: kai FPDF_TEXTPAGE reikšmę iš savybės nukopijuojate į lokalų kintamąjį, PDFiumPas nebeturi jokio būdo žinoti, kad ją vis dar laikote, nei kaip atnaujinti jūsų kopiją, kai UnloadTextPage paleidžiamas kitoje jūsų kodo vietoje
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 rankena, podėliuota FTextPage
Pdf.SetText(0, 'Amended Clause 4.2');
// SetText jau uždarė RawHandle ir nustatė Pdf.TextPage atgal į nil.
// Bet kokios FPDFText_* funkcijos iškvietimas prieš seną reikšmę dabar
// liečia rankeną, kurią PDFium jau atlaisvino — neapibrėžtas elgesys,
// ne klaida, kurią galima pagauti nil patikra
StaleCount := FPDFText_CountChars(RawHandle);
finally
Pdf.Free;
end;
end;
Rankenos naudojimas po to, kai jai buvo pritaikytas FPDFText_ClosePage, pačiame PDFium yra neapibrėžtas elgesys, o ne PDFiumPas susitarimas, kurio galima nepaisyti — ji gali grąžinti paskutinius žinomus duomenis, negrąžinti nieko arba nulaužti procesą, ir kuris iš šių variantų įvyks konkrečioje versijoje, programos kodas neturėtų numatyti iš anksto. Saugi taisyklė paprasta: Pdf.TextPage skaitykite iš naujo tiesiai prieš FPDFText_* iškvietimą, kuriam jos reikia, ir niekada nelaikykite kopijos per sakinį, galintį redaguoti puslapį
Grupuokite redagavimus, tada atlikite vieną užklausą
Tai nereiškia, kad po kiekvieno AddText ar RemoveObject iškvietimo reikia papildomos apsauginės teksto užklausos rezultatui patikrinti. Kiekvienas redagavimo metodas jau vieną kartą sumoka teksto puslapio uždarymo kainą; užklausiant po kiekvieno pavienio redagavimo cikle ta kaina sumokama dar kartą be jokios naudos, nes FPDFText_LoadPage kaskart iš naujo perbėga visą turinio srautą
var
Pdf: TPdf;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'watermarked.pdf';
Pdf.Active := True;
Pdf.PageNumber := 1;
// Pašalinkite kiekvieną teksto objektą, kuris atrodo kaip juodraščio
// vandens ženklas. Kiekvienas RemoveObject iškvietimas jau pats savaime
// panaikina podėlį, todėl tarp iteracijų nieko rankiniu būdu atnaujinti nereikia
for I := Pdf.ObjectCount - 1 downto 0 do
if (Pdf.ObjectType[I] = otText) and (Pdf.ObjectBounds[I].Top > 700) then
Pdf.RemoveObject(I, True);
// Užklauskite vieną kartą, kai visas paketas baigtas, o ne po kiekvieno pašalinimo
if Pdf.FindFirst('DRAFT') < 0 then
ShowMessage('Watermark cleared');
finally
Pdf.Free;
end;
end;
Ta pati paketinio apdorojimo logika galioja ir paieškos būsenai. FindNext ir FindPrevious tęsia FindFirst pradėtą seansą, o tą seansą kartu su viskuo kitu nutraukia UnloadTextPage, todėl iškvietus FindNext po redagavimo — vietoje pakartotinio FindFirst iškvietimo — gaunama išimtis, o ne tyliai tęsiama paieška turinyje, kurio jau nebėra. Laikykite bet kokį redagavimą griežta riba ir teksto turiniui, ir paieškos pozicijai, ir tegul vienas naujas FindFirst po redagavimų vėl pradeda paiešką
Kaip tai susiję su išskleidimu ir anotacijomis
Paprastas teksto išskleidimas — puslapio teksto skaitymas nieko nekeičiant — su šia problema niekada nesusiduria, nes joks veiksmas nepanaikina rankenos, kurios nepalietė joks redagavimas. Apie tai, kaip Text, simbolių stačiakampiai ir žodžių ribos veikia nepakeistame puslapyje, papasakoja susijęs straipsnis apie teksto išskleidimą su PDFiumPas, neliesdamas šiame straipsnyje pridedamo teksto puslapio podėlio gyvavimo ciklo
Podėlio gyvavimo ciklas svarbiausias darbo eigose, kai redaguojama ir iškart veikiama pagal rezultatą: uždedant taisymo žymę ir jos ieškant, ištrinant pastraipą ir patvirtinant, kad jos nebeliko, arba surandant frazę, prie kurios pritvirtinama žymėjimo anotacija, iškart įterpus tekstą šalia jos. Šis paskutinis atvejis vertas atskiro paminėjimo — keturių taškų žymėjimo anotacijos pozicionuojamos pagal simbolių stačiakampius, nuskaitytus iš teksto puslapio, todėl anotacija, sukurta iš koordinačių, užfiksuotų prieš redagavimą, po redagavimo galiausiai paryškina ne tą vietą
TPdf redagavimo ir teksto API priklauso PDFium Component Delphi ir C++Builder aplinkoms, o produkto puslapyje pateikiama pilna metodų nuoroda apie čia aprašytą redagavimo, išskleidimo ir paieškos funkcionalumą