Wywołujesz AddText, aby wbić linię na stronę PDF za pomocą PDFiumPas, a następnie natychmiast wywołujesz FindFirst, aby potwierdzić, że pieczątka wylądowała, a wyszukiwanie wraca puste. Tekst jest na stronie — Acrobat go pokazuje — ale komponent TPdf w PDFiumPas przechowuje osobną, buforowaną strukturę FPDF_TEXTPAGE, sparsowaną raz ze strumienia zawartości strony, a edycja nie aktualizuje tej struktury wstecznie sama z siebie. Odpytaj ją, zanim została odświeżona, a odczytasz stronę dokładnie tak, jak wyglądała przed twoją zmianą, nie po niej
Dlaczego PDFium zwraca nieaktualny tekst zaraz po edycji?
PDFiumPas opakowuje silnik renderowania PDFium od Google dla Delphi i C++Buildera, a jego wywołania tekstowe i edycyjne sięgają do dwóch różnych podsystemów wewnątrz tego silnika. FPDF_TEXTPAGE należy do strony odczytu: FPDFText_LoadPage przechodzi raz przez strumień zawartości strony i buduje stronę tekstową — kody znaków, pozycje, metryki czcionek, granice słów — a PDFiumPas przechowuje tę strukturę w buforze tak długo, jak strona pozostaje wczytana. Wywołania edycyjne, takie jak FPDFPage_InsertObject czy FPDFPage_GenerateContent, operują na zupełnie innej reprezentacji, na grafie obiektów i strumienia zawartości strony, a PDFium nie wpycha tych zmian do już otwartej strony tekstowej samodzielnie. Odbudowywanie jej przy każdej edycji uczyniłoby edycję wsadową niedopuszczalnie wolną, więc projekt wymienia ten koszt na regułę: kto trzyma uchwyt, zamyka go po edycji zmieniającej zawartość, a kolejny odczyt buduje świeży
Wnętrze pamięci podręcznej tekstu w TPdf: FTextPage, LoadTextPage i UnloadTextPage
TPdf śledzi buforowany uchwyt w jednym prywatnym polu, FTextPage, i opakowuje jego cykl życia w dwie metody. LoadTextPage sprawdza, czy FTextPage jest nil, i tylko w tym przypadku wywołuje FPDFText_LoadPage na bieżącej stronie; jeśli uchwyt już istnieje, LoadTextPage ponownie go wykorzystuje, nie pytając, czy strona zmieniła się od czasu jego zbudowania. UnloadTextPage to druga połowa: zamyka natywny uchwyt przez FPDFText_ClosePage, ustawia FTextPage z powrotem na nil, a także porzuca buforowaną listę linków webowych i wszelką trwającą sesję wyszukiwania, ponieważ obie zostały wyprowadzone z tej samej strony tekstowej i stają się nieaktualne z tego samego powodu
Zachowanie ponownego wykorzystania bez sprawdzania w LoadTextPage to dokładnie dlatego, dlaczego kolejność ma znaczenie. Każde zapytanie tekstowe na TPdf — Text, FindFirst, GetWebLinks — przechodzi najpierw przez LoadTextPage, więc dopóki FTextPage wciąż trzyma uchwyt sprzed edycji, żadne z tych wywołań nie ma sposobu, by wiedzieć, że zmiana się wydarzyła. Nawigacja po stronach nigdy nie była tu ryzykiem: UnloadPage, uruchamiany przy przełączaniu stron, przeładowaniach i zamknięciu dokumentu, zawsze zamykał stronę tekstową razem z samą stroną. Otwartym pytaniem zawsze były edycje zastosowane do strony, na której wciąż siedzisz
Które metody PDFiumPas automatycznie odświeżają pamięć podręczną?
Własne metody edycji strony w TPdf — AddText, SetText, SetTextPositions, AddPath, RemoveObject i InsertFormObjectFromXObject — każda wywołuje UnloadTextPage, zanim wywoła UpdatePage (funkcję PDFium FPDFPage_GenerateContent), aby zserializować zmianę do strumienia zawartości. Wywołaj którąkolwiek z nich, a sam następny Text, FindFirst czy GetWebLinks odbuduje stronę tekstową z zawartości w jej obecnym stanie, bez żadnego dodatkowego wywołania z twojej strony
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;
Wzorzec, który wciąż się psuje: buforowanie surowego uchwytu TextPage
TPdf udostępnia żywy uchwyt przez tylko-do-odczytu właściwość TextPage, na rzadki przypadek, gdy musisz wywołać funkcję FPDFText_*, której PDFiumPas nie opakował. Ta furtka ucieczkowa to też jedyne miejsce, w którym automatyczna inwalidacja nie może pomóc: gdy tylko skopiujesz wartość FPDF_TEXTPAGE z właściwości do zmiennej lokalnej, PDFiumPas nie ma sposobu, by wiedzieć, że wciąż ją trzymasz, ani sposobu, by zaktualizować twoją kopię, gdy UnloadTextPage uruchomi się gdzie indziej w twoim kodzie
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;
Użycie uchwytu po tym, jak uruchomiono na nim FPDFText_ClosePage, to niezdefiniowane zachowanie w samym PDFium, nie konwencja PDFiumPas, którą można wybrać zignorować — może zwrócić ostatnio znane dane, może zwrócić nic, albo zawiesić proces, i to, co z tego się zdarzy w danym buildzie, nie jest czymś, na czym kod aplikacji powinien polegać. Bezpieczna reguła jest wąska: odczytaj Pdf.TextPage na świeżo, bezpośrednio przed wywołaniem FPDFText_*, które go potrzebuje, i nigdy nie trzymaj kopii przez instrukcję, która mogłaby edytować stronę
Grupuj edycje wsadowo, potem odpytaj raz
Nic z tego nie oznacza, że każde wywołanie AddText czy RemoveObject potrzebuje obronnego zapytania tekstowego zaraz po sobie, aby sprawdzić wynik. Każda metoda edycji już płaci koszt jednorazowego zamknięcia strony tekstowej; odpytywanie po każdej pojedynczej edycji wewnątrz pętli płaci ten koszt ponownie bez żadnej korzyści, ponieważ FPDFText_LoadPage za każdym uruchomieniem przechodzi na nowo przez cały strumień zawartości
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;
Ta sama logika grupowania odnosi się konkretnie do stanu wyszukiwania. FindNext i FindPrevious kontynuują sesję rozpoczętą przez FindFirst, a ta sesja zostaje zdemontowana przez UnloadTextPage razem ze wszystkim innym, więc wywołanie FindNext ponownie po edycji — zamiast ponownego wywołania FindFirst — zgłasza wyjątek, zamiast po cichu wznawiać wyszukiwanie względem zawartości, która już nie istnieje. Traktuj każdą edycję jako twardą granicę zarówno dla zawartości tekstowej, jak i pozycji wyszukiwania, i pozwól, aby jedno świeże FindFirst po drugiej stronie twoich edycji podjęło wyszukiwanie na nowo
Gdzie to pasuje do pracy z ekstrakcją i adnotacjami
Zwykłe wyodrębnianie tekstu — czytanie tekstu strony bez zmieniania czegokolwiek — nigdy nie natrafia na nic z tego, ponieważ nic nie unieważnia uchwytu, którego żadna edycja nie dotknęła. To, jak Text, prostokąty znaków i granice słów działają na niezmodyfikowanej stronie, omawia towarzyszący artykuł o wyodrębnianiu tekstu za pomocą PDFiumPas, bez cyklu życia pamięci podręcznej strony tekstowej, który dodaje ten artykuł
Cykl życia pamięci podręcznej ma największe znaczenie w przepływach pracy, które edytują, a następnie natychmiast działają na wyniku: wbijanie poprawki i wyszukiwanie jej, redagowanie akapitu i potwierdzanie, że zniknął, lub lokalizowanie frazy, aby zakotwiczyć adnotację znacznikową zaraz po wstawieniu obok niej tekstu. Ten ostatni przypadek warto oznaczyć osobno — adnotacje znacznikowe z punktami czworokąta są pozycjonowane na podstawie prostokątów znaków odczytanych ze strony tekstowej, więc adnotacja zbudowana ze współrzędnych przechwyconych przed edycją kończy podświetlaniem niewłaściwego miejsca, gdy tylko edycja wyląduje
API edycji i tekstu TPdf jest częścią komponentu PDFium dla Delphi i C++Buildera, a strona produktu zawiera pełne odniesienie do metod dla powierzchni edycji, ekstrakcji i wyszukiwania opisanych tutaj