PDFium Component čuva uređene XFA vrednosti obrasca tačno, kroz čuvanje i ponovno otvaranje, kad pokreće Windows V8 runtime pdfium.v8.dll isporučen u v3.125.2 ili kasniji. Stariji runtime-i dodavali su novu liniju vrednostima polja, sekli emoji na nepovezan BMP znak, tiho preskakali čuvanja XFA-a jednog toka i mogli su da progutaju pali konačni upis. Jedan simptom ponovnog otvaranja uopšte nije manjk biblioteke: dinamički obrazac čiji korenski podobrazac nema restoreState="auto" gradi svoj raspored iz šablona
Prijave bagova za ovo su sve izgledale isto. Kupac popuni XFA obrazac u Delphi pregledaču, sačuva, otvori ponovo, i nešto je mrvicu ofrljeno. Prazna kutija za napomene sada drži jedan prazan red, a posle drugog čuvanja drži dva. Ime ukucano s emoji-jem vraća se s privatno-upotrebnim glifom. Niko ne dobija grešku, što ove bagove čini skupim: odstupanje ispliva nedeljama kasnije u tuđem izvozu
Šta krene naopako kad se XFA obrazac sačuva i otvori ponovo?
Četiri odvojena manjka u nativnom XFA putu čuvanja izazvala su odstupanje vrednosti, i svaki se krio iza uspešno izgledajućeg čuvanja. Dva su došla iz serializacije, jedan iz rasporeda skladištenja jednog toka, i jedan iz samog PDF writer-a. Tabela preslikava svaki simptom na uzrok i na izdanje u kojem je PDFium Component ispravio
| Simptom posle ponovnog otvaranja | Uzrok | Ispravljeno u |
|---|---|---|
| Prazno polje drži novu liniju; vrednosti dobijaju po jednu za svako čuvanje | Oba XFA writera ubacivala su rasporedne nove redove posle početnih oznaka | v3.125.2, pdfium.v8.dll |
| U+1F642 se vraća kao U+F642, ili emoji nestane iz paketa obrasca | 16-bitno skraćivanje wchar_t-a u dekodovanju; filtriranje surrogata u serializatoru obrasca | v3.125.2, pdfium.v8.dll |
| Izmene u XFA dokumentu jednog toka jednostavno nestanu | Nativno čuvanje odbilo je raspored toka, ali povratna vrednost je ignorisana | v3.125.2; komentari i instrukcije obrade čuvaju se od v3.126.0 |
| Skraćen fajl iako je čuvanje prijavilo uspeh | Konačni baferisani upis pao je posle što je writer već vratio uspeh | v3.125.2 V8 runtime; v3.125.3 obični pdfium.dll |
| Dinamički obrazac od tri stranice otvara se kao dve | Korenski podobrazac ne traži restoreState="auto" | Pisanje obrasca, nije manjk biblioteke |
Raniji tekstovi su zaključili da se XFA izmene polja uopšte ne mogu očuvati s PDFium-om, što je bilo tačno za runtime-e tog vremena. Noviji V8 runtime čuva XFA vrednosti nativno, pa izmena načinjena u živom obrazcu stiže do sačuvanog datasets paketa bez operacija po paketima na vašoj strani
Koji PDFium runtime čuva XFA vrednosti?
XFA vernost čuvanja zavisi od nativnog DLL-a, ne od Delphi omotača, pa je prva provera koji je runtime vaš proces zaista učitao. PDFium Component isporučuje dva Windows builda po arhitekturi: obični pdfium.dll, buildovan bez V8-a i XFA-e, i pdfium.v8.dll, koji nosi JavaScript engine i XFA form runtime. Samo pdfium.v8.dll može da pokrene XFA obrazac, pa svaka XFA popravka opisana ovde živi tamo, počevši od ponovo buildovanih Win32 i Win64 V8 biblioteka u v3.125.2
Popravka konačnog upisa je generički PDF writer kod, pa znači i za obične dokumente. v3.125.3 ponovo je buildovala obične pdfium.dll biblioteke da nose istu ispravku. Deljeni izvorni kod nije dokaz deljenog ponašanja: dok se binarni fajl ne ponovo builduje, stari DLL zadržava stari bag
Druga zamka sedela je u učitaču. Pre v3.125.2 postavljanje EnableV8Engine-a na True nateralo je vezivanje da uzme podrazumevano ime pdfium.v8.dll i ignoriše punu putanju u LibraryName. Aplikacija usmerena na sveže raspoređen runtime mogla je da nastavi učitavati stariju kopiju iz drugog foldera. Od v3.125.2 LibraryName koji sadrži direktorijum bira baš taj fajl u oba režima engine-a, a nedostajuća putanja pada umesto da se vrati na drugu uključenu biblioteku
uses
System.SysUtils, PDFium;
procedure SelectXfaRuntime;
begin
// Direktorijum u LibraryName-u prikova tačno ovaj fajl (v3.125.2 i kasnije);
// ako fajl nedostaje, učitavanje podiže umesto da padne na rezervu
{$IFDEF WIN64}
PDFium.LibraryName := ExtractFilePath(ParamStr(0)) + 'DLLs\Win64\pdfium.v8.dll';
{$ELSE}
PDFium.LibraryName := ExtractFilePath(ParamStr(0)) + 'DLLs\Win32\pdfium.v8.dll';
{$ENDIF}
PDFium.EnableV8Engine := True;
PDFium.LoadLibrary; // pali pri startu, ne pri prvom čuvanju
end;
Posle otvaranja dokumenta, TPdf.XFA vam kaže da fajl sadrži XFA-u, a TPdf.XfaRuntimeAvailable da učitani DLL zaista ume da je izvrši. Ako treba i da razlikujete statične od dinamičkih obrazaca, TPdf.FormType vraća ftXfaFull ili ftXfaForeground; tekst o otkrivanju XFA obrazaca i izvlačenju XFA paketa u Delphiju tu sondu pokriva detaljno
Zašto sačuvana XFA polja dobijaju dodatne nove redove?
Sačuvana XFA polja dobijala su nove redove jer su oba nativna XFA writera, opšti pisac XML elemenata i serializator paketa obrasca, lepo-štampali svoj izlaz s novim redom posle početnih oznaka. U većini XML-a taj razmak je kozmetika. U XFA podacima nije: kad se datasets paket ponovo parsira, tekst između <Comments> i </Comments> je vrednost polja, s novim redom uključenim. Prazno polje se zato otvaralo s jednim LF-om, i svaki dalji ciklus čuvanje-otvaranje mogao je dodati još jedan
Očigledna popravka, odsecanje vrednosti pri učitavanju, bila bi pogrešna. Korisnici kucaju vodeće razmake, prateće razmake i namerno višelinijski tekst u XFA polja, i blok adrese ili kôd fiksne širine moraju preživeti bajt po bajt. Popravka v3.125.2 zato uklanja samo razmak koji je sam serializer sintetizovao oko oznaka. Korisničke vrednosti, postojeći tekstualni čvorovi i CDATA sekcije prolaze nedirnuti, pa " indented" ostaje uvučen i namerno prazno polje ostaje prazno
Zašto se emoji vraća kao drugi znak?
Emoji se vraćao pogrešan jer je Windows wchar_t širok 16 bitova, i dva dekoderska puta čuvala su punu Unicode skalarnu vrednost u jednom wchar_t-u. UTF-8 dekoder toka i parser numeričkih znakovnih referenci poput 🙂 obojica su to radili. U+1F642, lice blago nasmejano, ne staje u 16 bitova, pa su viši bitovi otpali i pojavio se U+F642: code point u Privatnoj upotrebljenoj zoni koji većina fontova renderuje kao kutiju ili ništa
Serializator obrasca imao je suprotan problem. Filtrirao je znakove po jedan wchar_t odjednom, video dve surrogate kodne jedinice koje su nevažeće usamljene, i bacio obe, pa je emoji nestao iz paketa obrasca sasvim. U v3.125.2 dekoder troši svaku skalarnu vrednost u celosti i emituje pravilni par surrogata. Kad ostane samo jedan izlazni slot, zadržava niži surrogate na čekanju i ne javlja kraj toka dok je ta jedinica još u baferu. UTF-8 sekvenca podeljena preko blokova čitanja prenosi se na sledeće čitanje umesto da se baci. Izvoznik obrasca sada drži važeće parove surrogata zajedno, i numeričke znakovne reference daju prave parove takođe
Latin-1 probni podaci nikad ne pokazuju ništa od ovoga, pa svaki XFA round-trip test treba najmanje jedan znak iz dopunskog plana
XFA jednog toka i neuspesi čuvanja koje je nitko video
XFA dokument jednog toka izgubio je izmene jer je nativni pomoćnik čuvanja odbio taj raspored skladištenja, a njegov pozivaoc ignorisao neuspeh. ISO 32000-1 §12.7.8 dozvoljava da /XFA unos rečnika interaktivne forme bude ili niz imena paketa i tokova ili jedan tok koji drži ceo XDP dokument. Nizovi paketa su uobičajen slučaj, ali pojedinačni tokovi su sasvim legalni, i PDF čuvanje se završilo kao da se ništa nije dogodilo dok su podaci obrasca ostali na starim vrednostima
Od v3.125.2 V8 runtime rukuje podržanim podskupom jednog toka. Prvo izvozi oba živa paketa, datasets i form, u pripremnu zonu i validira ih, i tek onda zamenjuje poklapajuće pakete u originalnom XDP-u. Ostali paketi i deklaracije korenskog namespace-a se čuvaju. Ako priprema padne, istrajni XFA tok se nikad ne dira i dokument zadržava svoju oznaku izmene
XML komentari i instrukcije obrade tražile su dodatnu pažnju jer ih interni XML DOM odbacuje. U v3.125.2 njihovo prisustvo učinilo je da čuvanje padne ravno umesto da sadržaj tiho izgubi. v3.126.0 ih čuva: pre parsiranja, svaki komentar ili instrukcija obrade zamenjuje se markerom građenim iz prefiksa koji se ne pojavljuje nigde u originalnom tekstu. Posle što se živi paketi zamene, svaki marker mora da se pojavi tačno jednom pre nego što se prvobitni token vrati i tok zapiše. Tokeni van zamenjenih paketa zato čuvaju svoj tekst i red, uključujući tokene u prologu, šablonu i ostalim paketima
Neki ulazi se i dalje odbijaju namerno, i svako odbijanje je eksplicitan neuspeh čuvanja:
- Komentari ili instrukcije obrade unutar živih
datasetsiliformpaketa, pošto se njihove prvobitne pozicije ne mogu preslikati u sveže izvezeni sadržaj - DTD deklaracije i XMLDSig potpisi, jer preređivanje XDP-a ne može da zadrži XML potpis valjanim
- Nevaljano UTF-8 ili UTF-16 kodovanje, nepotpune oznake, nevaljane znakovne reference, nepoznati entiteti i deformisane instrukcije obrade, koji se odbijaju umesto da se tiho poprave
Izlaz jednog toka je UTF-8 i čuva XML model sadržaja, ne prvobitni raspored bajtova ni deklaraciju kodovanja
Poslednji manjk sedeo je ispod XFA-e. Nativni pisac fajla baferuje izlaz u blokovima od 32 KB i ispirao je konačni delimični blok tek u svom destruktoru, posle što je pisac dokumenta već prijavio uspeh. Disk-pun ili I/O greška na tom poslednjem bloku bila je nevidljiva pozivaocu. Od v3.125.2 u V8 runtime-u i v3.125.3 u običnom runtime-u, to konačno ispiranje je deo rezultata čuvanja, i XFA oznaka izmene briše se tek posle pravog uspeha. Na Delphi strani, TPdf.SaveAs(const FileName: string; Option: TSaveOption = saNone; PdfVersion: TPdfVersion = pvUnknown): Boolean piše u privremeni fajl pored cilja i premešta ga na mesto tek kad čuvanje vrati True, pa palo čuvanje ostavlja prethodni fajl netaknut
Zašto se dinamički XFA obrazac otvara s manje stranica?
Dinamički XFA obrazac se otvara s manje stranica kad njegov korenski podobrazac ne deklariše restoreState="auto", i to je odluka u pisanju obrasca, a ne manjk PDFium Component-a. U XFA 3.3, restoreState na korenskom podobrascu podrazumeva manual. Pod manual-om, XFA procesor vraća samo ograničeno stanje iz sačuvanog paketa obrasca i ostalo prepušta skriptama autora. Sačuvane vrednosti polja i brojevi instanci ponavljajućih podobrazaca se i dalje vraćaju, ali geometrijska svojstva postavljena za vreme rada ne
Slučaj koji je ovo otkrio bio je obrazac od tri stranice čija je skripta uvećala podobrazac na h="450pt". Sačuvani paket obrasca držao je novu visinu, vrednosti i brojeve instanci. Pri ponovnom otvaranju, međutim, raspored je ponovo građen iz visina šablona i obrazac se prelio na dve stranice. Runtime je bio u pravu: šablon nikad nije tražio automatsku obnovu. Deklarisanje na korenskom podobrascu ispravlja ponovno otvaranje:
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
<subform name="form1" layout="tb" restoreState="auto">
<pageSet>
<pageArea name="Page1">
<contentArea x="0.25in" y="0.25in" w="8in" h="10.5in"/>
<medium stock="letter"/>
</pageArea>
</pageSet>
<subform name="Details" layout="tb" w="7.5in">
<!-- polja; skripte mogu da menjaju h ili dodaju instance za vreme rada -->
</subform>
</subform>
</template>
Ako ne posedujete šablon, ne krpite oko njega u pregledaču: obrazac koji se oslanja na manual režim očekuje da mu sopstvene skripte grade stanje. Živa repaginacija dok korisnik kuca je odvojena tema, pokrivena u tekstu o kako PDFium Component prati dinamičke XFA brojeve stranica i preseljena polja
Kako proverite XFA čuvanje u Delphiju?
Jedina pouzdana provera XFA čuvanja jeste ponovno otvaranje sačuvanog fajla u svežoj TPdf instanci i čitanje sačuvanih podataka nazad. TPdf.GetXfaDatasets vraća datasets paket kakav je sačuvan u dokumentu, ne živi XFA model podataka, pa ga zovite pre čuvanja i videćete stare vrednosti. Posle ponovnog otvaranja pokazuje tačno ono što je zapisano. Dokument jednog toka nema posebno imenovane pakete: PDFium izveštava ceo XDP kao jedan paket s praznim imenom, pa GetXfaPacketByName('datasets') i GetXfaDatasets ne vraćaju ništa, a rezerva čita ceo tok kroz GetXfaFormPackets
uses
System.SysUtils, PDFium, FPdfXfa;
function ReadSavedXfaData(const FileName: string): string;
var
Pdf: TPdf;
Packets: TXfaPacketList;
Bytes: TBytes;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
Bytes := Pdf.GetXfaDatasets; // raspored niza paketa
if Length(Bytes) = 0 then
begin
Packets := Pdf.GetXfaFormPackets; // jedan tok: jedan neimenovan paket
if Length(Packets) = 1 then
begin
SetLength(Bytes, Length(Packets[0].Content));
if Length(Bytes) > 0 then
Move(Packets[0].Content[0], Bytes[0], Length(Bytes));
end;
end;
Result := TEncoding.UTF8.GetString(Bytes); // sačuvani XDP izlaz je UTF-8
finally
Pdf.Free;
end;
end;
Rutina čuvanja zatim predaje zakašnjelu izmenu, proverava rezultat SaveAs-a i poredi ponovo otvorenu vrednost. TPdf.ClearFormFieldFocus ubija fokus obrasca, što je trenutak kad PDFium predaje bafer izmena fokusiranog polja. TPdf.SetFocusedFormFieldText(const Value: WString): Boolean popunjava fokusirano polje programski, ali se oslanja na fokus koji omotač prati kroz FocusFormField, koji prelazi preko widget anotacija. Dinamička XFA stranica obično ih nema, pa tamo tekst obično stiže kroz unos tastaturom u TPdfView-u, i funkcija vraća False kad nema praćenog polja s fokusom
function XmlText(const S: string): string;
begin
Result := StringReplace(S, '&', '&', [rfReplaceAll]);
Result := StringReplace(Result, '<', '<', [rfReplaceAll]);
end;
procedure SaveXfaAndVerify(Pdf: TPdf; const FileName, FieldTag,
Expected: string);
var
Saved: string;
begin
// Opciono popunjavanje skriptom; False znači da nema praćenog polja s fokusom
if (Pdf.FocusedFormFieldIndex >= 0) and
not Pdf.SetFocusedFormFieldText(Expected) then
raise EPdfError.Create('Could not write the focused field');
Pdf.ClearFormFieldFocus; // predaje bafer izmena
if not Pdf.SaveAs(FileName) then // uključuje konačno ispiranje (v3.125.2+)
raise EPdfError.CreateFmt('Saving %s failed', [FileName]);
Saved := ReadSavedXfaData(FileName);
if Pos('<' + FieldTag + '>' + XmlText(Expected) + '</' + FieldTag + '>',
Saved) = 0 then
raise EPdfError.CreateFmt('%s did not survive the round trip', [FieldTag]);
end;
Tretirajte test podstringa kao smoke test. Prazan element može se serializovati kao <Tag/>, atributi mogu iskočiti na elementima podataka, i bežanje iza & i < je odluka serializatora. Za produkcijske provere, učitajte ponovo otvoreni XML pravim XML parserom i uporedite tekstualni čvor vezanog elementa podataka. Pokrenite i proveru dvaput zaredom, jer je manjak novog reda pokazao puni oblik tek na drugoj generaciji
Brzi podsetnik: lista provere XFA vernosti čuvanja
- Rasporedite
pdfium.v8.dlliz v3.125.2 ili kasniju za XFA obrasce, i v3.125.3 ili kasniju za običnipdfium.dll, da ispravka konačnog upisa bude u oba - Usmerite
LibraryNamena punu putanju i postaviteEnableV8Enginena True; nedostajuća putanja pada umesto da učita drugu kopiju - Potvrdite
TPdf.XFAiTPdf.XfaRuntimeAvailableposle otvaranja dokumenta - Zovite
ClearFormFieldFocuspreSaveAs-a da se fokusirano polje preda - Nikad ne ignorišite Boolean rezultat
SaveAs-a; False rezultat ostavlja prethodni fajl na mestu - Proverite ponovnim otvaranjem u novom
TPdf-u i čitanjemGetXfaDatasets, s rezervom naGetXfaFormPacketsza XFA jednog toka - Testirajte s praznim vrednostima, vodećim razmacima, višelinijskim tekstom,
&i znakom iz dopunskog plana, kroz dve generacije čuvanja - Očekujte eksplicitne neuspehe čuvanja za DTD-ove, XMLDSig i komentare unutar živih paketa XFA jednog toka
- Ako dinamički obrazac izgubi geometriju vremena rada pri ponovnom otvaranju, proverite korenski podobrazac za
restoreState="auto"pre nego što posumnjate na biblioteku
Za strukturu callback-a koju XFA runtime očekuje od aplikacije domaćina, pogledajte FPDF_FORMFILLINFO verziju 2 i XFA ABI u Delphiju. V8 runtime, Delphi i C++Builder omotač i kontrola pregledača svi su deo PDFium Component-a za Delphi i C++Builder, koji uključuje oba Windows runtime-a za Win32 i Win64