PDFium Component uređene vrijednosti XFA forme sprema točno, kroz spremanje i ponovno otvaranje, kad pokreće Windows V8 runtime pdfium.v8.dll isporučen u v3.125.2 ili novijem. Stariji runtimei dodavali su line feedove vrijednostima polja, rezali emoji na nepovezani BMP znak, tiho preskakali single-stream XFA spremanja i mogli su progutati palo konačno pisanje. Jedan simptom pri ponovnom otvaranju uopće nije defekt biblioteke: dinamička forma čiji korijenski subform nema restoreState="auto" gradi svoj layout iz predloška
Bug izvještaji za ovo svi su izgledali isto. Kupac ispuni XFA prijavni obrazac u Delphi pregledniku, spremi, ponovno otvori, i nešto je malo otklonjeno. Prazni okvir za komentare sada drži prazan red, i nakon drugog spremanja drži dva. Ime upisano s emojiom vraća se s private-use glifom. Nitko ne dobije grešku, i upravo to ove bugove čini skupima: odstupanje ispliva tjednima kasnije u tuđem izvozu
Što krene krivo kad se XFA forma spremi i ponovno otvori?
Četiri odvojena defekta u nativnom XFA putu spremanja uzrokovala su odstupanje vrijednosti, i svaki se skrivao iza spremanja koje je izgledalo uspješno. Dva su došla iz serijalizacije, jedno iz single-stream rasporeda pohrane, a jedno iz samog PDF pisača. Tablica svaki simptom mapira u njegov uzrok i u izdanje u kojem ga je PDFium Component popravio
| Simptom nakon ponovnog otvaranja | Uzrok | Popravljeno u |
|---|---|---|
| Prazno polje drži line feed; vrijednostima raste novi red po spremanju | Oba XFA pisača ubacivala su layout nove redove iza početnih oznaka | v3.125.2, pdfium.v8.dll |
| U+1F642 vraća se kao U+F642, ili emoji nestaje iz form paketa | 16-bitno wchar_t skraćivanje u dekodiranju; filtriranje surrogata u form serijalizatoru | v3.125.2, pdfium.v8.dll |
| Izmjene u single-stream XFA dokumentu jednostavno nestanu | Nativno spremanje odbilo je raspored toka, ali povratna vrijednost ignorirana je | v3.125.2; komentari i instrukcije obrade čuvaju se od v3.126.0 |
| Odrezana datoteka iako je spremanje javilo uspjeh | Konačno međuspremljeno pisanje palo je nakon što je pisač već vratio uspjeh | v3.125.2 V8 runtime; v3.125.3 obični pdfium.dll |
| Dinamička forma od tri stranice otvara se kao dvije | Korijenski subform ne traži restoreState="auto" | Pisanje forme, ne defekt biblioteke |
Raniji članci zaključili su da se XFA izmjene polja uopće ne mogu očuvati s PDFiumom, što je za runtimee onog vremena bilo točno. Noviji V8 runtime XFA vrijednosti sprema nativno, pa izmjena napravljena u živoj formi stiže u spremljeni datasets paket bez paketne kirurgije na vašoj strani
Koji PDFium runtime sprema XFA vrijednosti?
Vjernost XFA spremanja ovisi o nativnom DLL-u, ne o Delphi omotaču, pa je prva provjera koji je runtime vaš proces stvarno učitao. PDFium Component isporučuje dva Windows builda po arhitekturi: obični pdfium.dll, građen bez V8 i XFA-e, i pdfium.v8.dll, koji nosi JavaScript motor i XFA form runtime. Samo pdfium.v8.dll može pokrenuti XFA formu, pa svaki XFA popravak opisan ovdje živi tamo, počevši s ponovno građenim Win32 i Win64 V8 bibliotekama u v3.125.2
Popravak konačnog pisanja generički je PDF pisačev kod, pa je bitan i za obične dokumente. v3.125.3 ponovno je sagradio obične pdfium.dll biblioteke da nose isti popravak. Zajednički izvorni kod nije dokaz zajedničkog ponašanja: dok se binarna datoteka ne sagradi ponovno, stari DLL zadržava stari bug
Druga zamka sjedila je u učitaču. Prije v3.125.2 postavljanje EnableV8Engine na True tjeralo je vezu da odabere zadano ime pdfium.v8.dll i ignorira punu putanju u LibraryName. Aplikacija usmjerena na svježe uveden runtime mogla je nastaviti učitavati stariju kopiju iz druge mape. Od v3.125.2 LibraryName koji sadrži direktorij bira upravo tu datoteku u bilo kojem modu motora, a nedostajuća putanja pada umjesto da padne natrag na drugu priloženu biblioteku
uses
System.SysUtils, PDFium;
procedure SelectXfaRuntime;
begin
// Direktorij u LibraryName pripada ovoj točnoj datoteci (v3.125.2 i novije);
// ako datoteka nedostaje, učitavanje podiže iznimku umjesto da padne natrag
{$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; // padaj pri pokretanju, ne pri prvom spremanju
end;
Nakon otvaranja dokumenta, TPdf.XFA govori vam da datoteka sadrži XFA-u, a TPdf.XfaRuntimeAvailable da učitani DLL stvarno može izvršiti. Ako trebate razlikovati i statičke od dinamičkih formi, TPdf.FormType vraća ftXfaFull ili ftXfaForeground; članak o otkrivanju XFA formi i izvlačenju XFA paketa u Delphiju detaljno pokriva to sondiranje
Zašto spremljena XFA polja dobivaju dodatne line feedove?
Spremljena XFA polja dobila je line feedove jer su oba nativna XFA pisača, generički pisac XML elemenata i serijalizator form paketa, pisali izlaz s novim redom iza početnih oznaka. U većini XML-a taj je razmak kozmetički. U XFA podacima nije: kad se datasets paket ponovno parsira, tekst između <Comments> i </Comments> jest vrijednost polja, novi red uključen. Prazno se polje zato otvaralo držeći jedan LF, i svaki daljnji ciklus spremanja i otvaranja mogao je dodati još jedan
Očigledan popravak, obrezivanje vrijednosti pri učitavanju, bio bi kriv. Korisnici u XFA polja upisuju vodeće razmake, prateće razmake i namjerni višeredni tekst, i blok adrese ili kod fiksne širine mora preživjeti bajt po bajt. Popravak iz v3.125.2 zato uklanja samo whitespace koji je serijalizator sam sintetizirao oko oznaka. Korisničke vrijednosti, postojeći tekstovni čvorovi i CDATA sekcije prolaze netaknuti, pa " indented" ostaje uvučeno, a namjerno prazno polje ostaje prazno
Zašto se emoji vraća kao drugi znak?
Emoji se vraćao krivo jer je Windows wchar_t širok 16 bita, i dva dekodirajuća puta spremala su punu Unicode skalarnu vrijednost u jedan wchar_t. UTF-8 dekoder tokova i parser numeričkih referenci znakova poput 🙂 obojica su to radili. U+1F642, blago nasmijano lice, ne stane u 16 bita, pa su visoki bitovi otpali i umjesto njega pojavio se U+F642: kodna točka u Private Use Area koju većina fontova renderira kao kvadratić ili ništa
Form serijalizator imao je suprotni problem. Filtrirao je znakove po jedan wchar_t, vidio dvije surrogate kodne jedinice koje su zasebno neispravne, i bacio ih obje, pa je emoji nestao iz form paketa u potpunosti. U v3.125.2 dekoder svaku skalarnu vrijednost konzumira u potpunosti i ispisuje pravi surrogate par. Kad ostane samo jedan izlazni slot, donji surrogate drži na čekanju i ne javlja kraj toka dok je ta jedinica još u međuspremniku. UTF-8 sekvenca razdijeljena preko blokova čitanja prenosi se u sljedeće čitanje umjesto da se odbaci. Form izvoznik sada drži valjane surrogate parove zajedno, i numeričke reference znakova proizvode točne parove
Latin-1 testni podaci nikad ne pokazuju ništa od ovoga, pa svaki XFA test povratnog ciklusa treba najmanje jedan znak iz dopunske ravnine
Single-stream XFA i neuspjesi spremanja koje nitko nije vidio
Single-stream XFA dokument gubio je izmjene jer je nativni helper spremanja odbio taj raspored pohrane, a njegov pozivatelj ignorirao je neuspjeh. ISO 32000-1 §12.7.8 dopušta da unos /XFA rječnika interaktivne forme bude ili polje imena paketa i tokova ili jedan tok koji drži cijeli XDP dokument. Polja paketa uobičajeni su slučaj, ali pojedinačni tokovi savršeno su legalni, i PDF spremanje završilo je kao da se ništa nije dogodilo dok su podaci forme ostali na starim vrijednostima
Od v3.125.2 V8 runtime rukuje podržanim single-stream podskupom. Prvo izvozi oba živa paketa, datasets i form, u staging područje i validira ih, i tek onda zamjenjuje odgovarajuće pakete u izvornom XDP-u. Ostali paketi i deklaracije korijenskog namespaca čuvaju se. Ako staging padne, perzistentni XFA tok nikad se ne dira i dokument zadržava svoju oznaku izmjene
XML komentari i instrukcije obrade trebali su dodatnu njegu jer ih unutarnji XML DOM odbacuje. U v3.125.2 njihova je prisutnost natjerala spremanje da padne odmah, umjesto da sadržaj tiho izgubi. v3.126.0 čuva ih: prije parsiranja svaki komentar ili instrukcija obrade zamjenjuje se markerom sagrađenim iz prefiksa koji se nigdje ne pojavljuje u izvornom tekstu. Nakon što se živi paketi zamijene, svaki se marker mora pojaviti točno jednom prije nego se izvorni token obnovi i tok zapiše. Tokeni izvan zamijenjenih paketa zato zadržavaju svoj tekst i redoslijed, uključujući tokene u prologu, predlošku i ostalim paketima
Neki se unosi i dalje odbijaju namjerno, i svaka je odbijenica izričit neuspjeh spremanja:
- Komentari ili instrukcije obrade unutar živih paketa
datasetsiliform, jer se njihove izvorne pozicije ne mogu mapirati u svježe izvezeni sadržaj - DTD deklaracije i XMLDSig potpisi, jer prereivanje XDP-a ne može zadržati XML potpis valjanim
- Neispravno UTF-8 ili UTF-16 kodiranje, nedovršene oznake, neispravne reference znakova, nepoznati entiteti i deformirane instrukcije obrade, koji se odbijaju umjesto da se tiho poprave
Single-stream izlaz jest UTF-8 i čuva XML model sadržaja, ne izvorni raspored bajtova ni deklaraciju kodiranja
Zadnji defekt sjedio je ispod XFA-e. Nativni pisac datoteka izlaz međusprema u blokove od 32 KB i konačni nepuni blok ispirao je tek u svojem destruktoru, nakon što je pisac dokumenta već javio uspjeh. Greška punog diska ili I/O na tom zadnjem bloku bila je nevidljiva pozivatelju. Od v3.125.2 u V8 runtimeu i v3.125.3 u običnom runtimeu, to konačno ispiranje dio je rezultata spremanja, a XFA oznaka izmjene briše se tek nakon stvarnog uspjeha. Na Delphi strani, TPdf.SaveAs(const FileName: string; Option: TSaveOption = saNone; PdfVersion: TPdfVersion = pvUnknown): Boolean piše u privremenu datoteku pokraj ciljane i premješta je na mjesto tek kad spremanje vrati True, pa palo spremanje ostavlja prethodnu datoteku netaknutom
Zašto se dinamička XFA forma otvara s manje stranica?
Dinamička XFA forma otvara se s manje stranica kad njezin korijenski subform ne deklarira restoreState="auto", a to je odluka pri pisanju forme, a ne defekt PDFium Componenta. U XFA 3.3, restoreState na korijenskom subformu po zadanom je manual. Pod manual-om, XFA procesor iz spremljenog form paketa obnavlja samo ograničeno stanje, a ostalo prepušta autorovim skriptama. Spremljene vrijednosti polja i brojevi instanci ponavljajućih subformi i dalje se vraćaju, ali geometrijska svojstva postavljena u vrijeme izvođenja ne
Slučaj koji je ovo izložio bila je forma od tri stranice čija je skripta rasprstila subform na h="450pt". Spremljeni form paket držao je novu visinu, vrijednosti i brojeve instanci. Pri ponovnom otvaranju, međutim, layout je ponovno izgrađen iz visina predloška i forma se prelijevala na dvije stranice. Runtime je bio u pravu: predložak nikad nije tražio automatsku obnovu. Deklariranje na korijenskom subformu popravlja 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 mijenjati h ili dodavati instance u izvođenju -->
</subform>
</subform>
</template>
Ako ne posjedujete predložak, ne krpite oko njega u pregledniku: forma koja se oslanja na manual mod očekuje da vlastite skripte obnove stanje. Repaginacija uživo dok korisnik tipka zasebna je tema, pokrivena u kako PDFium Component prati dinamičke XFA brojeve stranica i preseljena polja
Kako provjeriti XFA spremanje u Delphiju?
Jedina pouzdana provjera XFA spremanja jest ponovno otvoriti spremljenu datoteku u svježoj instanci TPdf i pročitati spremljene podatke natrag. TPdf.GetXfaDatasets vraća datasets paket onako kako je spremljen u dokumentu, a ne živi XFA model podataka, pa ga zovete li prije spremanja pokazuje stare vrijednosti. Nakon ponovnog otvaranja pokazuje točno ono što je zapisano. Single-stream dokument nema zasebno imenovanih paketa: PDFium cijeli XDP izvještava kao jedan paket s praznim imenom, pa GetXfaPacketByName('datasets') i GetXfaDatasets ne vraćaju ništa, a fallback čita cijeli 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; // layout polja paketa
if Length(Bytes) = 0 then
begin
Packets := Pdf.GetXfaFormPackets; // jedan tok: jedan neimenovani 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); // spremljeni XDP izlaz je UTF-8
finally
Pdf.Free;
end;
end;
Spremna rutina zatim predaje zatečenu izmjenu, provjerava rezultat SaveAs i uspoređuje ponovno otvorenu vrijednost. TPdf.ClearFormFieldFocus ubija fokus forme, što je trenutak kad PDFium predaje međuspremnik uređivanja fokusiranog polja. TPdf.SetFocusedFormFieldText(const Value: WString): Boolean popunjava fokusirano polje programski, ali se oslanja na fokus koji omotač prati kroz FocusFormField, koji šeta anotacije widgeta. Dinamička XFA stranica obično ih nema, pa tamo tekst obično stiže kroz tipkovnički unos u TPdfView, a funkcija vraća False kad nijedno praćeno polje nema fokus
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
// Neobavezan skriptirani fill; 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; // predaj međuspremnik uređivanja
if not Pdf.SaveAs(FileName) then // uključuje konačni flush (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;
Test podniza tretirajte kao smoke test. Prazan element može se serijalizirati kao <Tag/>, atributi se mogu pojaviti na podatkovnim elementima, i escapiranje izvan & i < izbor je serijalizatora. Za produkcijske provjere učitajte ponovno otvoreni XML pravim XML parserom i usporedite tekstovni čvor vezanog podatkovnog elementa. Pokrenite provjeru i dvaput zaredom, jer je defekt novog reda puni oblik pokazao tek na drugoj generaciji
Brza referenca: kontrolni popis vjernosti XFA spremanja
- Isporučujte
pdfium.v8.dlliz v3.125.2 ili novijeg za XFA forme, a v3.125.3 ili noviji za običnipdfium.dll, da popravak konačnog pisanja bude u oba - Usmjerite
LibraryNamena punu putanju i postaviteEnableV8Enginena True; nedostajuća putanja pada umjesto da učita drugu kopiju - Potvrdite
TPdf.XFAiTPdf.XfaRuntimeAvailablenakon otvaranja dokumenta - Zovite
ClearFormFieldFocusprijeSaveAsda se fokusirano polje predaje - Nikad ne ignorirajte Boolean rezultat
SaveAs-a; rezultat False ostavlja prethodnu datoteku na mjestu - Provjerite ponovnim otvaranjem u novi
TPdfi čitanjemGetXfaDatasets, s padom natrag naGetXfaFormPacketsza single-stream XFA - Testirajte s praznim vrijednostima, vodećim razmacima, višerednim tekstom,
&i znakom iz dopunske ravnine, kroz dvije generacije spremanja - Očekujte izričite neuspjehe spremanja za DTD-ove, XMLDSig i komentare unutar živih paketa single-stream XFA-e
- Ako dinamička forma izgubi geometriju izvođenja pri ponovnom otvaranju, provjerite korijenski subform za
restoreState="auto"prije nego posumnjate na biblioteku
Za strukturu povratnih poziva koju XFA runtime očekuje od host aplikacije pogledajte FPDF_FORMFILLINFO verziju 2 i XFA ABI u Delphiju. V8 runtime, Delphi i C++Builder omotač i kontrola preglednika svi su dio PDFium Componenta za Delphi i C++Builder, koji uključuje oba Windows runtimea za Win32 i Win64