Kad dinamička XFA forma u Delphi pregledniku dodaje ili uklanja stranice, PDFium Component od v3.126.1 novu ukupnost izvještava kroz TPdf.PageCount i TPdf.OnXfaPageCountChanged, jer nativni događaj stranice nosi dodano/uklonjeno delta, a ne ukupnost. Windows V8 biblioteke u v3.126.1 također pomiču input hit područja s preseljenim poljima, a v3.126.2 ponovno učitava zastarjele handlove stranica nakon što se layout povratni poziv vrati. Bug izvještaj koji je ovo pokrenuo bio je forma za troškovne prijave: kliknite Add Row dvaput, forma naraste na dvije stranice, a pokazatelj stranice ponosno piše 1 of 1. Ukucajte u polje koje se preselilo na stranicu 2 i tipke slete negdje nevidljivo. Ništa od toga nije se pokazalo na uzorcima formi fiksne duljine s kojima svatko prvo testira, a razlozi su vrijedni poznavanja ako ugrađujete preglednik formi
Što se dogodi kad se dinamička XFA forma repaginira?
Dinamička XFA forma nema fiksni popis stranica, pa je njezin broj stranica izlaz layouta i može se mijenjati svaki put kad korisnik uređuje podatke. XFA 3.3 opisuje formu kao stablo subformi; ponavljajuću subformu kontrolira instanceManager, a skripta poput _Row.addInstance() klonira još jedan red. Layout procesor zatim ponovno utječe sadržaj u područja stranica, što može dodati stranicu, oduzeti stranicu ili gurnuti postojeća polja na drugu stranicu. ISO 32000-1 §12.7.8 definira samo kako XFA paketi putuju unutar PDF-a; sve što se poslije toga dogodi pripada XFA motoru, koji je u PDFium Componentu PDFiumov vlastiti XFA layout koji radi u host procesu. Delphi preglednik se zato suočava s dokumentom čiji su broj stranica, veličine stranica i pozicije widgeta svi živo stanje. Tri stvari krenu krivo kad host pretpostavi drugačije:
- Broj stranica koji host predmemorira za navigaciju, raspone skrolanja i spinere stranica zastarijeva, ili gore, ažurira se s pogrešnim brojem
- Polja koja se preseljavaju pokazuju rub u novoj poziciji dok editor i mišljevo hit područje ostaju na starim koordinatama
- Preglednik drži handle stranice koji je layout zamijenio, pa klikovi i crtanje idu na stranicu koja više ne postoji u toj formi
Očuvanje izmjena redova kroz spremanje i ponovno otvaranje zaseban je problem sa svojim pravilima; ovaj članak ostaje kod onoga što se događa u vrijeme izvođenja unutar preglednika
Koji PDFium runtime treba dinamički XFA?
Dinamički XFA u PDFium Componentu zahtijeva V8/XFA build nativne biblioteke, odabran globalnom varijablom EnableV8Engine u jedinici PDFium prije učitavanja prvog dokumenta. Proces se obveže na jednu DLL prvi put kad bilo koji TPdf učita biblioteku, a obični PDFium build ne može pokrenuti XFA motor uopće. Kad se dokument otvori, TPdf zagleda u datoteku za XFA markerima i automatski prebaci na V8 build, ali samo ako još nijedna obična biblioteka nije učitana u tom procesu. Kad se obveza već uputila krivim smjerom, TPdf.OnXfaRuntimeMissing pali jednom da host može reći korisniku da ponovno pokrene aplikaciju. Izričito postavljanje zastavice pri pokretanju uklanja nagađanje. Struktura povratnih poziva FPDF_FORMFILLINFO koja nosi XFA događaje također mora odgovarati DLL-u; pozadina je u FPDF_FORMFILLINFO verziji 2 i XFA callback ABI-ju, a otkrivanje XFA formi i čitanje njihovih paketa pokriva razlikovanje tipova formi prije nego otvorite preglednik
uses
PDFium;
procedure TClaimForm.FormCreate(Sender: TObject);
begin
// Odlučite prije nego prvi TPdf učita nativnu biblioteku:
// proces kasnije ne može prijeći s pdfium.dll na pdfium.v8.dll
EnableV8Engine := True;
FPdf := TPdf.Create(nil);
FPdf.OnXfaRuntimeMissing := PdfXfaRuntimeMissing;
FPdf.OnXfaPageCountChanged := PdfXfaPageCountChanged;
FPdf.FileName := 'C:\Forms\expense-claim.pdf';
FPdf.Active := True;
PdfView1.Pdf := FPdf;
PdfView1.OnPageChange := PdfViewPageChange;
PdfView1.Active := True;
UpdatePageRange(FPdf.PageCount);
end;
procedure TClaimForm.PdfXfaRuntimeMissing(Sender: TObject);
begin
StatusBar1.SimpleText :=
'This XFA form needs the V8 runtime; restart the application to enable it';
end;
Zašto je PageCount javio 1 za formu od dvije stranice?
Prije v3.126.1 PDFium Component spremio je argument page_count nativnog događaja stranice kao ukupnost dokumenta, a taj je argument zapravo apsolutna razlika između novog i starog broja stranica. PDFium podiže FFI_PageEvent nakon što layout prolaz završi s tipom događaja stranica dodana ili stranica uklonjena; interno prvo ažurira svoj spremljeni broj stranica, a zatim prosljeđuje abs(new - old). Na početnom layoutu stari je broj nula, pa je delta jednaka ukupnosti, i tustranični statični uzorak javlja tri stranice kao što se očekuje. Upravo zato forme za testiranje fiksne duljine nikad nisu otkrile bug. Prvi put kad se dinamička forma proširi s jedne na dvije stranice, delta je 1, i wrapper je postavio i TPdf.PageCount i parametar NewCount od OnXfaPageCountChanged na 1. Uklanjanje reda iz forme od tri stranice proizvelo je istu vrstu besmisla u drugom smjeru
Akumuliranje delte na prethodnu vrijednost nije ni siguran popravak. Redoslijed inicijalizacije i layout povratnih poziva znači da wrapper svojem ranijem broju ne može uvijek vjerovati kao polaznoj točki, pa tekuća suma može odstupati. Od v3.126.1 povratni poziv argument ignorira kao broj i zove FPDF_GetPageCount na dokumentu, koji čita ukupnost iz layouta koji je upravo dovršen. Zatim očisti predmemorirane scene stranica, tu ukupnost spremi kao XFA premošćenje broja stranica iza TPdf.PageCount, i tek onda podiže OnXfaPageCountChanged. Do trenutka kad vaš handler radi, NewCount i FPdf.PageCount slažu se
procedure TClaimForm.PdfXfaPageCountChanged(Sender: TObject; NewCount: Integer);
begin
// v3.126.1+: NewCount je ukupnost dovršenog layouta, nikad delta.
// Ovo radi unutar PDFium layout povratnog poziva: ažurirajte samo stanje host UI-ja,
// ne zatvarajte dokument niti ponovno učitavajte stranice odavde
UpdatePageRange(NewCount);
end;
procedure TClaimForm.PdfViewPageChange(Sender: TObject);
begin
// Pali nakon svakog ponovnog učitavanja stranice, uključujući odgođeno XFA osvježavanje
PageSpin.Value := PdfView1.PageNumber;
end;
procedure TClaimForm.UpdatePageRange(Count: Integer);
begin
PageSpin.MinValue := 1;
PageSpin.MaxValue := Count;
PageLabel.Caption := Format('of %d', [Count]);
end;
Događaj pali samo za Full XFA forme čiji se layout mijenja u vrijeme izvođenja. Static XFA i AcroForm dokumenti nikad ga ne podižu, pa preglednik koji rukuje s oba može ostaviti isti handler dodijeljen. Ostaviti ga nedodijeljen također je sigurno; premošćenje iza TPdf.PageCount primjenjuje se svakako, a događaj postoji da host može osvježiti sve što je predmemorirao
Zašto input okvir ostaje na staroj stranici kad se polje preseli?
Rub se pomaknuo, a editor nije jer je nativni XFA notifikator uspoređivao pravokutnik sa samim sobom. Kad layout promijeni geometriju već učitanog widgeta, PDFium je trebao uočiti novi pravokutnik i zvati PerformLayout na widgetu, koji preusmjeruje tekstovni editor i njegovo hit područje. Provjera je uspoređivala GetWidgetRect() s RecacheWidgetRect(). Obje funkcije vraćaju const referencu na istog člana, a recache taj član prepisuje na mjestu, pa je usporedba uvijek vidjela dvije identične vrijednosti i učitani widgeti preskakali su svoj relayout
Simptom se pokazao kad je test promijenio visinu subforme tako da su postojeća polja prešla na sljedeću stranicu. Na obje V8 arhitekture rub polja crtao se na novoj poziciji dok su ukućani tekst i mišljevo hit područje ostajali na prethodnoj Y koordinati. Izričit relayout to nije popravio, niti ponovno učitavanje stranice, jer je widget i dalje vjerovao da mu je geometrija aktualna. Windows V8 biblioteke isporučene s v3.126.1 kopiraju stari pravokutnik po vrijednosti prije recachea i uspoređuju tu kopiju, pa se preseljeni widgeti relayoutaju i uređena vrijednost pojavljuje točno tamo gdje je rub. Ovo je nativni popravak: putuje s DLL-ovima, pa ažuriranje Pascal jedinica uz zadržavanje starijeg pdfium.v8.dll ostavlja pogrešno smještena hit područja na mjestu. Regresijska provjera koja ga je potjerala prvo uređuje preživjeli red u nestandardnu vrijednost, a zatim traži tu vrijednost na novoj lokaciji polja, jer bi red ponovno izgrađen standardnim vrijednostima inače izgledao kao prolaz
Kako TPdfView ponovno učitava stranice ne izmičući handle ispod PDFiuma?
Od v3.126.2 TPdfView odgađa ponovno učitavanje stranice koje slijedi nakon XFA layout promjene dok se nativni stog poziva ne odmota. Događaj stranice obično pali dok PDFium još obrađuje unos: korisnik je kliknuo gumb Add Row, klik je pokrenuo skriptu, skripta je promijenila broj instanci, i layout je završio unutar istog nativnog poziva. Zatvaranje i ponovno otvaranje handlea stranice u tom bi trenutku oslobodilo objekt koji pozivatelj još koristi. Prije v3.126.2 preglednik se samo invalidirao, pa prikazani handle stranice mogao je i dalje pokazivati na stanje prije layouta, i ako je korisnik bio na zadnjoj stranici kad je nestala, odabrani broj stranice bio je izvan raspona
Odgođeno osvježavanje radi u nekoliko malih koraka, i oni objašnjavaju ponašanje koje vidite s hosta:
- Callback događaja stranice označava prikaz kao onaj s XFA layout osvježavanjem na čekanju i objavljuje privatnu poruku prozora; ponovljeni događaji prije nego poruka stigne stapaju se u jedno osvježavanje
- Prikaz koji još nema handle prozora zadržava zastavicu na čekanju i objavljuje poruku iz
CreateWnd, dok promjena dokumenata, deaktivacija prikaza ili njegovo uništenje brišu zastavicu - Kad poruka stigne, prikaz briše odabir teksta, isticanje pretrage i indeks fokusiranog polja, jer se sva tri odnosila na stari layout
- Odabrana stranica stisne se na novi
PageCount; promijenjeni broj stranice prolazi kroz normalnu zamjenu stranice, inače se trenutna stranica ponovno učitava, i način pristajanja primjenjuje se ponovno - Ako layout uopće ne ostavi stranica, prikaz istovaruje stari handle stranice umjesto da crta stranicu koja više ne postoji
Isto ograničenje vrijedi i za vaš vlastiti kod. OnXfaPageCountChanged radi unutar tog nativnog layout povratnog poziva, pa ga tretirajte kao obavijest: ažurirajte natpise, raspone spinera i stanje alatne trake tamo, a sve teže, poput zatvaranja dokumenta ili otvaranja drugog, stavite u red s objavljenom porukom da se izvrši nakon što se callback vrati. TPdfView.OnPageChange vam tada govori kada je prikaz stvarno ponovno učitao stranicu, i čitanje PdfView1.PageNumber u tom trenutku daje vam stisnutu vrijednost. Prijelaz Tab tipkom i FormType provjere koje preglednik formi pokreće pri otvaranju pokriva PDF navigacija poljima formi s PDFium Componentom
Zašto klik na Full XFA polje podiže "Cannot open text page"?
Full XFA stranice nemaju PDF tekstovnu stranicu, a prije v3.126.2 zadani odabir teksta i detekcija poveznica u pregledniku pokušavali su je svejedno učitati. S TPdfView.AllowUserTextSelection na zadanom True, lebdenje je od tekstovnog sloja tražilo znak pod mišem, a klik mouse-up pokretao je automatsku URL probu nad tekstom stranice. Na Full XFA stranici tekstovnu stranicu nije moguće otvoriti, pa je običan klik u polje mogao završiti iznimkom Cannot open text page. Od v3.126.2 oba unutarnja puta vraćaju bez rezultata kad je TPdf.FormType ftXfaFull i XFA runtime dostupan, pa zadane postavke rade i unos u polja ostaje dostupan
Isključivanje AllowUserTextSelection za Full XFA dokumente i dalje je razuman UI izbor, jer nema teksta stranice za odabrati i povlačni pokreti ne bi trebali pokretati način odabira. Nije to, međutim, zamjena za nadogradnju: na ranijim verzijama URL proba na klik nije ovisila o tom svojstvu, pa je preglednik mogao udariti istu iznimku i s isključenim odabirom
procedure TClaimForm.ConfigureViewerForForm;
begin
// FormType čita otvoreni dokument, pa ovo zovite nakon FPdf.Active := True
if FPdf.XFA and (FPdf.FormType = ftXfaFull) and FPdf.XfaRuntimeAvailable then
begin
// PDF tekstovni sloj ne postoji na Full XFA stranicama; polja ostaju editabilna
PdfView1.AllowUserTextSelection := False;
StatusBar1.SimpleText := Format('Dynamic XFA form, %d page(s)',
[FPdf.PageCount]);
end
else
PdfView1.AllowUserTextSelection := True;
end;
Tipkanje trebalo je vlastiti popravak u v3.126.2. Nativni XFA tekstovni editor ne mijenja odabir kad primi znak: FORM_OnChar umeće na caret, a Backspace briše jedan znak, pa je odabir vrijednosti i tipkanje preko nje proizvodilo stari i novi tekst jedno uz drugo. PDFium Component sada pamti da je klik sletio na XFA tekstovno polje i usmjerava ukućane znakove, Backspace i Delete kroz FORM_ReplaceSelection kad god odabir postoji i dokument dopušta fill-forms ili modify dozvolu. Hoće li se promijeniti read-only XFA polje i dalje odlučuje nativni editor, pa polje označeno read-only u formi zadržava vrijednost čak i u dokumentu koji inače dopušta popunjavanje. Postavljanje TPdfView.AllowFormEvents na False također zaustavlja ovo tipkovničko usmjeravanje, što read-only preglednik drži read-only
Brza referenca: dinamički XFA u Delphi pregledniku
| Simptom | Uzrok | Popravljeno u |
|---|---|---|
| Broj stranica pokazuje 1 nakon što forma naraste na dvije stranice | Nativni događaj stranice prosljeđuje dodano/uklonjeno delta, ne ukupnost | v3.126.1 (wrapper) |
| Rub polja se pomakne, ukućani tekst i hit područje ostaju | Učitani widget preskočio je relayout nakon samousporedbe | v3.126.1 (Windows V8 biblioteke) |
| Preglednik crta ili usmjerava unos na stanje stranice prije layouta | Handle stranice nije ponovno učitan nakon repaginacije | v3.126.2 (odgođeno osvježavanje) |
| Klik u polje podiže Cannot open text page | Odabir teksta i URL proba na stranicama bez tekstovnog sloja | v3.126.2 |
| Tipkanje preko odabrane vrijednosti nadodaje umjesto da zamijeni | Nativni XFA editor umeće na caret | v3.126.2 |
- Postavite
EnableV8EnginenaTrueprije nego bilo koji dokument učita, i rukujteOnXfaRuntimeMissingza slučaj da je obična biblioteka učitana prva - Ukupnost čitajte iz
TPdf.PageCountili parametraNewCountodOnXfaPageCountChanged; nikad sami ne zbrajajte ni oduzimajte brojeve stranica - Držite handler
OnXfaPageCountChangedlaganim, jer radi unutar nativnog layout povratnog poziva - Sinkronizirajte pokazatelj trenutne stranice u
TPdfView.OnPageChange, koji pali nakon što odgođeno ponovno učitavanje stisne broj stranice - Isporučujte Windows V8 DLL-ove v3.126.1 ili novije zajedno s jedinicama; popravak relayouta widgetova živi u nativnom kodu
- Testirajte s formom koja stvarno mijenja broj stranica i preseljava uređeno polje preko prijeloma stranice, jer uzorci fiksne duljine kriju svaki bug s ovog popisa
Dinamički XFA pretvara broj stranica i geometriju polja u žive vrijednosti, a preglednik ostaje ispravan samo kad ih uzima iz dovršenog layouta i ponovno učitava stranice u sigurnom trenutku. PDFium Component rukuje obojim unutar TPdf i TPdfView, pa host samo treba slušati. Detalji i preuzimanja na PDFium Component for Delphi stranici proizvoda