Kad dinamički XFA obrazac u Delphi pregledaču doda ili ukloni stranice, PDFium Component od v3.126.1 prijavljuje novi ukupan broj kroz TPdf.PageCount i TPdf.OnXfaPageCountChanged, jer nativni događaj stranice nosi dodato/uklonjeno delta, a ne ukupan broj. Windows V8 biblioteke u v3.126.1 takođe pomeraju površine pogodaka unosa sa preseljenim poljima, a v3.126.2 ponovo učitava zastarele page handle-ove posle što se layout callback vrati. Prijava baga koja je ovo pokrenula bila je obrazac za naplatu troškova: klikni Add Row dvaput, obrazac naraste na dve stranice, i indikator stranice ponosno piše 1 of 1. Kucaj u polje koje je preseljeno na stranicu 2 i pritisci tastere dospevaju negde nevidljivo. Ništa od toga se nije pokazalo na obrascima fiksnih dužina s kojima svi prvo testiraju, a razlozi zašto vredi da ih znate ako ugrađujete pregledač obrazaca
Šta se dešava kad se dinamički XFA obrazac repaginira?
Dinamički XFA obrazac nema fiksnu listu stranica, pa mu je broj stranica izlaz rasporeda i može se menjati svaki put kad korisnik menja podatke. XFA 3.3 opisuje obrazac kao stablo podobrazaca; ponavljajući podobrazac kontroliše instanceManager, a skripta poput _Row.addInstance() klonira još jedan red. Procesor rasporeda zatim ponovo uliva sadržaj u površine stranica, što može da doda stranicu, oduzme stranicu ili prebaci postojeća polja na drugu stranicu. ISO 32000-1 §12.7.8 definiše samo kako XFA paketi putuju unutar PDF-a; sve što se posle toga dešava pripada XFA engine-u, koji je u PDFium Component-u PDFium-ov sopstveni XFA raspored koji radi u procesu domaćina. Delphi pregledač se dakle bavi dokumentom čiji su broj stranica, veličine stranica i pozicije widgeta svi živo stanje. Tri stvari krenu naopako kad domaćin pretpostavi drugačije:
- Broj stranica koji domaćin kešira za navigaciju, opsege klizanja i brojače stranica zastareva, ili još gore, ažurira se pogrešnim brojem
- Polja koja se presele pokazuju ivicu na novoj poziciji dok editor i površina pogodaka miša ostaju na starim koordinatama
- Pregledač zadržava page handle koji je raspored zamenio, pa klikovi i crtanje idu na stranicu koja više ne postoji u tom obrascu
Održavanje izmena redova kroz čuvanje i ponovno otvaranje je odvojen problem sa svojim pravilima; ovaj tekst ostaje kod onoga što se dešava za vreme rada unutar pregledača
Koji PDFium runtime treba dinamički XFA?
Dinamički XFA u PDFium Component-u traži V8/XFA build nativne biblioteke, biran globalnom promenljivom EnableV8Engine u jedinici PDFium pre nego što se prvi dokument učita. Proces se opredeljuje za jedan DLL prvi put kad bilo koji TPdf učita biblioteku, i običan PDFium build ne može uopšte da pokrene XFA engine. Kad se dokument otvori, TPdf zirisne u fajl za XFA oznake i prebacuje se na V8 build automatski, ali samo ako još nijedna obična biblioteka nije učitana u tom procesu. Kad se opredeljenje već krenulo na pogrešnu stranu, TPdf.OnXfaRuntimeMissing opali jednom da domaćin može reći korisniku da restartuje. Eksplicitno postavljanje flaga pri startu uklanja nagađanje. Struktura callback-a FPDF_FORMFILLINFO koja nosi XFA događaje takođe mora da se poklapa s DLL-om; pozadina je u tekstu o FPDF_FORMFILLINFO verziji 2 i XFA callback ABI-ju, a otkrivanje XFA obrazaca i čitanje njihovih paketa pokriva razlikovanje tipova obrazaca pre nego što otvorite pregledač
uses
PDFium;
procedure TClaimForm.FormCreate(Sender: TObject);
begin
// Odluči pre nego što prvi TPdf učita nativnu biblioteku:
// proces ne može kasnije da pređe 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 prijavio 1 za obrazac od dve stranice?
Pre v3.126.1 PDFium Component je čuvao argument page_count nativnog događaja stranice kao ukupan broj dokumenta, a taj argument je zapravo apsolutna razlika između novog i starog broja stranica. PDFium podiže FFI_PageEvent kad prolaz rasporeda završi s tipom događaja stranica dodata ili uklonjena; interno prvo ažurira svoj sačuvani broj stranica pa onda prosleđuje abs(new - old). Na početnom rasporedu stari broj je nula, pa delta jednaka ukupnom broju, i statički ogledni obrazac od tri stranice prijavljuje tri stranice kao što se očekuje. Baš zato obrasci za test fiksnih dužina nikad nisu otkrili bag. Prvi put kad se dinamički obrazac proširi sa jedne na dve stranice, delta je 1, i omotač je postavio i TPdf.PageCount i parametar NewCount od OnXfaPageCountChanged na 1. Uklanjanje reda iz obrasca od tri stranice dalo je istu vrstu besmislica u drugom smeru
Nagomilavanje delte na prethodnu vrednost nije ni sigurna popravka. Redosled callback-a inicijalizacije i rasporeda znači da omotač ne može uvek da veruje svom ranijem broju kao bazi, pa tekući zbir može da odstupi. Od v3.126.1 callback ignoriše argument kao broj i zove FPDF_GetPageCount nad dokumentom, koji čita ukupan broj iz upravo završenog rasporeda. Zatim briše keširane scene stranica, čuva taj ukupan broj kao XFA premošćenje broja stranica iza TPdf.PageCount-a, i tek onda podiže OnXfaPageCountChanged. Do trenutka kad vaš handler radi, NewCount i FPdf.PageCount se slažu
procedure TClaimForm.PdfXfaPageCountChanged(Sender: TObject; NewCount: Integer);
begin
// v3.126.1+: NewCount je ukupan broj završenog rasporeda, nikad delta.
// Ovo radi unutar PDFium layout callback-a: ažuriraj samo UI stanje domaćina,
// ne zatvaraj dokument i ne učitavaj stranice ponovo odavde
UpdatePageRange(NewCount);
end;
procedure TClaimForm.PdfViewPageChange(Sender: TObject);
begin
// Okida se posle svakog ponovnog učitavanja stranice, uključujući odloženi XFA refresh
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 opali samo za Full XFA obrasce čiji se raspored menja za vreme rada. Statički XFA i AcroForm dokumenti ga nikad ne podižu, pa pregledač koji rukuje oba može da ostavi isti handler dodeljen. Neodateljan handler je takođe bezbedan; premošćenje iza TPdf.PageCount-a primenjuje se svakako, a događaj postoji da domaćin osveži šta god je keširao
Zašto kutija za unos ostaje na staroj stranici kad se polje preseli?
Ivica se pomerila a editor nije jer je nativni XFA notifier poredio pravougaonik sa samim sobom. Kad raspored promeni geometriju već učitanog widgeta, od PDFium-a se očekuje da primeti nov pravougaonik i pozove PerformLayout nad widgetom, koji premešta tekstualni editor i njegovu površinu pogodaka. Provera je uporedila GetWidgetRect() s RecacheWidgetRect(). Obe funkcije vraćaju const referencu na istog člana, a recache prepisuje tog člana na mestu, pa je poređenje uvek videlo dve identične vrednosti i učitani widgeti su preskakali svoj relayout
Simptom je isplivao kad je test promenio visinu podobrasca tako da postojeća polja pređu na sledeću stranicu. Na obe V8 arhitekture ivica polja crtala se na novoj poziciji dok su kucani tekst i površina pogodaka miša ostajali na prethodnoj Y koordinati. Eksplicitan relayout to nije ispravio, ni ponovno učitavanje stranice, jer je widget i dalje verovao da mu je geometrija tekuća. Windows V8 biblioteke isporučene s v3.126.1 kopiraju stari pravougaonik po vrednosti pre recache-a i porede tu kopiju, pa se preseljeni widgeti relayout-uju i uređena vrednost ispadne tačno tamo gde je ivica. Ovo je nativna popravka: putuje s DLL-ovima, pa ažuriranje Pascal jedinica uz zadržavanje starijeg pdfium.v8.dll-a ostavlja pomerene površine pogodaka na mestu. Regresiona provera koja je to gonila prvo uređuje preživeli red u ne-podrazumevanu vrednost, a zatim traži tu vrednost na novoj lokaciji polja, jer bi red obnovljen podrazumevanim vrednostima inače izgledao kao prolaz
Kako TPdfView ponovo učitava stranice ne vadeći handle ispod PDFium-a?
Od v3.126.2 TPdfView odlaže ponovno učitavanje stranice koje sledi XFA promenu rasporeda dok se nativni stek poziva ne odmota. Događaj stranice obično opali dok PDFium još obrađuje unos: korisnik je kliknuo dugme Add Row, klik je pokrenuo skriptu, skripta je promenila broj instanci, i raspored je završio unutar tog istog nativnog poziva. Zatvaranje i ponovno otvaranje page handle-a u tom trenutku oslobodilo bi objekat koji pozivaoc još koristi. Pre v3.126.2 pregledač se samo invalidirao, pa prikazani page handle mogao je da ostane usmeren na stanje pre rasporeda, i ako je korisnik bio na poslednjoj stranici kad je nestala, izabrani broj stranice bio je van opsega
Odloženo osvežavanje radi u par malih koraka, i oni objašnjavaju ponašanje koje vidite s domaćinom:
- Callback događaja stranice označava prikaz kao s odloženim XFA osvežavanjem rasporeda i objavljuje privatnu prozorsku poruku; ponovljeni događaji pre dolaska poruke stapaju se u jedno osvežavanje
- Prikaz bez window handle-a još zadržava flag čekanja i objavljuje poruku iz
CreateWnd-a, dok promena dokumenta, deaktivacija prikaza ili njegovo uništavanje brišu flag - Kad poruka stigne, prikaz briše izbor teksta, highlight pretrage i indeks fokusiranog polja, jer se sva tri odnosila na stari raspored
- Izabrana stranica se stega na novi
PageCount; promenjeni broj stranice ide kroz normalnu zamenu stranice, inače se trenutna stranica ponovo učitava, i režim prilagođavanja se ponovo primenjuje - Ako raspored ne ostavi nijednu stranicu, prikaz istovaruje stari page handle umesto da crta stranicu koja više ne postoji
Isto ograničenje važi i za vaš sopstveni kod. OnXfaPageCountChanged radi unutar tog nativnog layout callback-a, pa ga tretirajte kao obaveštenje: ažurirajte oznake, opsege brojača i stanje trake alata tamo, a sve teže, poput zatvaranja dokumenta ili otvaranja drugog, stavite u red s objavljenom porukom da se izvrši posle što se callback vrati. TPdfView.OnPageChange vam zatim kaže kad je prikaz zaista ponovo učitao stranicu, i čitanje PdfView1.PageNumber-a u tom trenutku daje vam stegnutu vrednost. Prelazak Tab tasterom i FormType provere koje pregledač obrazaca radi pri otvaranju pokriveni su u tekstu o navigaciji po poljima PDF obrazaca s PDFium Component-om
Zašto klik na Full XFA polje podiže "Cannot open text page"?
Full XFA stranice nemaju PDF tekstualnu stranicu, i pre v3.126.2 podrazumevani izbor teksta i detekcija veza u pregledaču pokušali su da je učitaju svakako. S TPdfView.AllowUserTextSelection na podrazumevanom True-u, lebdenje je pitalo tekstualni sloj za znak ispod miša, a klik mišem puštao je automatsku URL proveru nad tekstom stranice. Na Full XFA stranici tekstualna stranica ne može da se otvori, pa običan klik u polje mogao je da se završi izuzetkom Cannot open text page. Od v3.126.2 oba interna puta vraćaju bez rezultata kad je TPdf.FormType ftXfaFull i XFA runtime dostupan, pa podrazumevane postavke rade i unos u polja ostaje dostupan
Isključivanje AllowUserTextSelection-a za Full XFA dokumente je i dalje razumna UI odluka, jer nema teksta stranice za izbor i gestovi vučenja ne treba da pokreću režim izbora. Nije to zamena za nadogradnju, međutim: na ranijim verzijama URL provera pri kliku nije zavisila od tog svojstva, pa je pregledač mogao da pogodi isti izuzetak i sa isključenim izborom
procedure TClaimForm.ConfigureViewerForForm;
begin
// FormType čita otvoreni dokument, pa ovo zovi posle FPdf.Active := True
if FPdf.XFA and (FPdf.FormType = ftXfaFull) and FPdf.XfaRuntimeAvailable then
begin
// Na Full XFA stranicama ne postoji PDF tekstualni sloj; polja ostaju uređiva
PdfView1.AllowUserTextSelection := False;
StatusBar1.SimpleText := Format('Dynamic XFA form, %d page(s)',
[FPdf.PageCount]);
end
else
PdfView1.AllowUserTextSelection := True;
end;
Kucanje je dobilo svoju popravku u v3.126.2. Nativni XFA tekstualni editor ne zamenjuje izbor kad primi znak: FORM_OnChar ubacuje na kursor, a Backspace briše jedan znak, pa je izbor vrednosti i kucanje preko nje davalo stari i novi tekst jedan uz drugi. PDFium Component sada pamti da je klik dospeo na XFA tekstualno polje i usmerava kucane znakove, Backspace i Delete kroz FORM_ReplaceSelection kad god izbor postoji i dokument daje dozvolu popunjavanja ili izmene. Da li se read-only XFA polje sme menjati i dalje odlučuje nativni editor, pa polje označeno read-only u obrazcu zadržava vrednost čak i u dokumentu koji inače dozvoljava popunjavanje. Postavljanje TPdfView.AllowFormEvents na False takođe prekida ovo usmeravanje tastature, što pregledač samo za čitanje drži samo za čitanje
Brzi podsetnik: dinamički XFA u Delphi pregledaču
| Simptom | Uzrok | Ispravljeno u |
|---|---|---|
| Broj stranica pokazuje 1 posle što obrazac naraste na dve stranice | Nativni događaj stranice prosleđuje dodato/uklonjeno delta, a ne ukupan broj | v3.126.1 (omotač) |
| Ivica polja se pomera, kucani tekst i površina pogodaka ostaju iza | Učitani widget preskočio relayout posle samo-poređenja | v3.126.1 (Windows V8 biblioteke) |
| Pregledač crta ili usmerava unos na stanje stranice pre rasporeda | Page handle nije ponovo učitan posle repaginacije | v3.126.2 (odloženo osvežavanje) |
| Klik u polje podiže Cannot open text page | Izbor teksta i URL provera na stranicama bez tekstualnog sloja | v3.126.2 |
| Kucanje preko izabrane vrednosti dodaje umesto da zameni | Nativni XFA editor ubacuje na kursor | v3.126.2 |
- Postavite
EnableV8EnginenaTruepre nego što se bilo koji dokument učita, i rukujteOnXfaRuntimeMissing-om za slučaj da je obična biblioteka učitana prva - Čitajte ukupan broj iz
TPdf.PageCount-a ili parametraNewCountodOnXfaPageCountChanged-a; nikad sami ne sabirajte ni oduzimajte brojeve stranica - Držite
OnXfaPageCountChangedhandler lagan, jer radi unutar nativnog layout callback-a - Sinhronizujte indikator tekuće stranice u
TPdfView.OnPageChange-u, koji opali posle što odloženo ponovno učitavanje stegne broj stranice - Rasporedite v3.126.1 ili novije Windows V8 DLL-ove zajedno s jedinicama; popravka relayout-a widgeta živi u nativnom kodu
- Testirajte s obrascem koji zaista menja broj stranica i premešta uređeno polje preko preloma stranice, jer obrasci fiksnih dužina kriju svaki bag sa ove liste
Dinamički XFA pretvara broj stranica i geometriju polja u žive vrednosti, i pregledač ostaje ispravan samo ako ih uzima iz završenog rasporeda i ponovo učitava stranice u bezbednom trenutku. PDFium Component rukuje obojim unutar TPdf-a i TPdfView-a, pa domaćin samo treba da sluša. Detalji i preuzimanja su na stranici proizvoda PDFium Component for Delphi