Komponenta PDFium Component shrani urejene vrednosti obrazcev XFA točno, skozi shranjevanje in ponovno odpiranje, kadar poganja izvorni pogon Windows V8 pdfium.v8.dll, dostavljen v v3.125.2 ali pozneje. Starejši pogoni so vrednostim polj dodali pomike v novo vrstico, emoji skrajšali na nepovezan znak BMP, tiho izpustili shranjevanja enojnega toka XFA in lahko pogoltnili spodletel končni zapis. En simptom ponovnega odpiranja pa sploh ni napaka knjižnice: dinamični obrazec, katerega korenska podforma ne navaja restoreState="auto", svojo postavitev znova zgradi iz predloge
Hroščja poročila za to so si bila vsa podobna. Stranka izpolni obrazec za prijavo XFA v pregledovalniku Delphi, shrani, ponovno odpre in nekaj je rahlo narobe. Prazno polje za pripombe zdaj drži prazno vrstico, po drugem shranjevanju pa dve. Ime, vtipkano z emojijem, pride nazaj z znakom iz zasebnega območja uporabe. Nikomur se ne prikaže napaka, kar te hrošče dela drage: odplavjanje se pokaže šele tedne kasneje v tujem izvozu
Kaj gre narobe, kadar se obrazec XFA shrani in ponovno odpre?
Štirje ločeni defekti v izvorni poti shranjevanja XFA so povzročili odplavjanje vrednosti, vsak pa se je skrival za shranjevanjem, ki je izgledalo uspešno. Dva sta prišla iz serizacije, eden iz postavitve shrambe enojnega toka in eden od samega zapisovalnika PDF. Tabela vsak simptom preslika na vzrok in na izdajo, v katero ga je komponenta PDFium Component popravila
| Simptom po ponovnem odpiranju | Vzrok | Popravljeno v |
|---|---|---|
| Prazno polje drži pomik v novo vrstico; vrednosti zrastejo novo vrstico na shranjevanje | Oba zapisovalnika XFA sta vstavila postavitvene pomike za odpirajočimi oznakami | v3.125.2, pdfium.v8.dll |
| U+1F642 pride nazaj kot U+F642, ali pa emoji izgine iz paketa obrazca | 16-bitno krajšanje wchar_t pri dekodiranju; filtriranje nadomestnih enot v serizatorju obrazca | v3.125.2, pdfium.v8.dll |
| Urejanja v dokumentu XFA z enim tokom so preprosto izginila | Izvorno shranjevanje je zavrnilo postavitev toka, vrnjena vrednost pa je bila ignorirana | v3.125.2; komentarji in obdelovalna navodila obdržani od v3.126.0 |
| Okrnjena datoteka, čeprav je shranjevanje poročalo uspeh | Končni medpomnjen zapis je spodletel, potem ko je zapisovalnik že vrnil uspeh | v3.125.2 pogon V8; v3.125.3 navadni pdfium.dll |
| Tristranski dinamični obrazec se ponovno odpre kot dvostranski | Korenska podforma ne zahteva restoreState="auto" | Izdelava obrazca, ne napaka knjižnice |
Zgodnejši zapisi so sklepali, da urejanj polj XFA s PDFiumom sploh ni mogoče obstojiti, kar je bilo za takratne pogone točno. Novejši pogon V8 vrednosti XFA shrani izvorno, zato urejanje, narejeno v živem obrazcu, pride do shranjenega paketa podatkovnih množic brez operacij na paketih na vaši strani
Kateri pogon PDFium shrani vrednosti XFA?
Vernost shranjevanja XFA je odvisna od izvorne DLL, ne od ovijalca Delphi, zato je prvi preizkus to, kateri pogon je vaš proces dejansko naložil. Komponenta PDFium Component dostavlja dve gradnji Windows na arhitekturo: navadni pdfium.dll, zgrajen brez V8 in XFA, ter pdfium.v8.dll, ki nosi pogon JavaScript in formulski pogon XFA. Samo pdfium.v8.dll zna pognati obrazec XFA, zato vsak tukaj opisani popravek XFA živi tam, začenši s ponovno zgrajenimi knjižnicami V8 Win32 in Win64 v v3.125.2
Popravek končnega zapisa je splošna koda zapisovalnika PDF, zato šteje tudi za navadne dokumente. v3.125.3 je znova zgradila navadne knjižnice pdfium.dll, da nosijo isti popravek. Skupni vir ni dokaz skupnega obnašanja: dokler binarna datoteka ni znova zgrajena, stari DLL obdrži stari hrošč
Druga past je sedela v nalagalniku. Pred v3.125.2 je nastavitev EnableV8Engine na True naredila, da je vezava izbrala privzeto ime pdfium.v8.dll in ignorirala polno pot v LibraryName. Aplikacija, usmerjena v sveže razporejen pogon, je lahko še naprej nalagala starejšo kopijo iz druge mape. Od v3.125.2 LibraryName, ki vsebuje imenik, izbere točno tisto datoteko v katerem koli načinu pogona, manjkajoča pot pa spodleti namesto da bi rezervirala na drugo zbirno knjižnico
uses
System.SysUtils, PDFium;
procedure SelectXfaRuntime;
begin
// Imenik v LibraryName pripne to točno datoteko (v3.125.2 in pozneje);
// če datoteka manjka, nalaganje sproži napako namesto rezerve
{$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; // spodleti ob zagonu, ne pri prvem shranjevanju
end;
Po odprtju dokumenta vam TPdf.XFA pove, da datoteka vsebuje XFA, TPdf.XfaRuntimeAvailable pa, da naloženi DLL zmore to dejansko izvesti. Če morate ločiti tudi statične in dinamične obrazce, TPdf.FormType vrne ftXfaFull ali ftXfaForeground; članek o zaznavanju obrazcev XFA in izločanju paketov XFA v Delphiju to povraho podrobno opisuje
Zakaj shranjena polja XFA dobijo dodatne pomike v novo vrstico?
Shranjena polja XFA so dobila pomike v novo vrstico, ker sta oba izvorna zapisovalnika XFA, splošni zapisovalnik elementov XML in serizator paketa obrazca, izpis izdelala lepo-oblikovano z novo vrstico za odpirajočimi oznakami. V večini XML je ta prazen prostor kozmetičen. V podatkih XFA ni: ko se paket podatkovnih množic spet razčleni, je besedilo med <Comments> in </Comments> vrednost polja, skupaj s pomikom v novo vrstico. Prazno polje se je torej ponovno odprlo z enim samim LF in vsak nadaljnji krog shranjevanja ter ponovnega odpiranja je lahko dodal še enega
Očitni popravek — obrezovanje vrednosti ob nalaganju — bi bil narobe. Uporabniki v polja XFA vtipikajo vodilne presledke, končne presledke in namerno večvrstično besedilo, blok naslova ali koda fiksne širine pa mora preživeti bajt za bajtom. Popravek v v3.125.2 zato odstrani samo prazen prostor, ki ga je sam serizator sintetiziral okoli oznak. Uporabniške vrednosti, obstoječa besedilna vozlišča in odseki CDATA gredo skozi nedotaknjena, zato " indented" ostane zamaknjen in namerno prazno polje ostane prazno
Zakaj emoji pride nazaj kot drugačen znak?
Emoji je prišel nazaj narobe, ker je Windows wchar_t širok 16 bitov, dve dekodirni poti pa sta shranili polno skalar vrednost Unicode v en sam wchar_t. Dekodirnik toka UTF-8 in razčlenjevalnik številskih znakovnih referenc, kot je 🙂, sta to delala oba. U+1F642, rahlo nasmehnjen obraz, ne gre v 16 bitov, zato so visoki biti padli off in namesto tega se je pojavil U+F642: kodna točka v zasebnem območju uporabe (Private Use Area), ki jo večina pisav izriše kot škatlo ali nič
Serizator obrazca je imel obratno težavo. Znake je filtriral po enem wchar_t naenkrat, videl dve nadomestni kodni enoti, ki sta v izolaciji neveljavni, in odvrgel obe, tako da je emoji izginil iz paketa obrazca v celoti. V v3.125.2 dekodirnik vsako skalar vrednost porabi v celoti in izda pravi par nadomestnih enot. Ko je ostane le še en izhodni prostor, obdrži nizko nadomestno enoto v zalogi in ne poroča konca toka, medtem ko je ta enota še v medpomnilniku. Zaporedje UTF-8, razdeljeno čez bloke branja, se prenese na naslednje branje namesto da bi bilo odvrženo. Izvoznik obrazca zdaj veljavne pare nadomestnih enot drži skupaj in številske znakovne reference prav tako izdelajo prave pare
Preskusni podatki Latin-1 tega nikoli ne pokažejo, zato vsak krog testov XFA potrebuje vsaj en znak z dopolnilne ravnine
Enojni tok XFA in spodletitve shranjevanja, ki jih ni videl nihče
Dokument XFA z enim tokom je izgubil svoja urejanja, ker je izvorni pomožnik shranjevanja zavrnil to postavitev shrambe in njen klicatelj ignoriral spodletitev. ISO 32000-1 §12.7.8 dovoljuje, da je vnos /XFA interaktivnega formulskega slovarja bodisi tabela imen paketov in tokov bodisi en sam tok, ki drži celoten dokument XDP. Taberi paketov so običajni primer, enojni tokovi pa so povsem legalni, shranjevanje PDF pa se je zaključilo, kot da se ni nič zgodilo, medtem ko so formulska podatki ostali na starih vrednostih
Od v3.125.2 pogon V8 opravi s podprto podmnožico enojnega toka. Najprej izvozi oba živa paketa, datasets in form, v pripravljalno območje in ju preveri, šele nato pa zamenja ujemajoče pakete v izvirnem XDP. Drugi paketi in deklaracije korenskega imenskega prostora ostanejo. Če priprava spodleti, vztrajni tok XFA ni nikoli dotaknjen in dokument obdrži svoj znak spremembe
Komentarji XML in obdelovalna navodila so potrebovala posebno nego, ker jih notranji DOM XML odvrže. V v3.125.2 je njihova navzočnost naredila, da je shranjevanje čisto spodletelo, namesto da bi vsebino tiho izgubila. v3.126.0 jih ohrani: pred razčlenjevanjem se vsak komentar ali obdelovalno navodilo zamenja za označevalnik, zgrajen iz predpone, ki se v izvirnem besedilu ne pojavi nikjer. Ko so živi paketi zamenjani, se mora vsak označevalnik prikazati točno enkrat, preden se izvirni žeton povrne in tok zapiše. Žetoni zunaj zamenjanih paketov torej obdržijo svoje besedilo in vrstni red, vključno z žetoni v prologu, predlogi in drugih paketih
Nekateri vnosi so še vedno zavrnjeni namenoma in vsaka zavrnitev je izrecna spodletitev shranjevanja:
- Komentarji ali obdelovalna navodila znotraj živih paketov
datasetsaliform, ker njihovih izvirnih položajev ni mogoče preslikati v sveže izvoženo vsebino - Deklaracije DTD in podpisi XMLDSig, ker prepisovanje XDP ne more obdržati podpisa XML veljavnega
- Neveljavno kodiranje UTF-8 ali UTF-16, nedokončane oznake, neveljavne znakovne reference, neznane entitete in deformirana obdelovalna navodila, ki so zavrnjena namesto tiho popravljena
Izhod enojnega toka je UTF-8 in ohranja vsebinski model XML, ne izvirne bajtne postavitve ali deklaracije kodiranja
Zadnji defekt je sedel pod XFA. Izvorni zapisovalnik datotek izhod medpomni v blokih 32 KB in končni delni blok izplaknil šele v svojem destruktorju, potem ko je zapisovalnik dokumenta že poročal uspeh. Napaka polnega diska ali V/I na tem zadnjem bloku je bila klicatelju nevidna. Od v3.125.2 v pogonu V8 in v3.125.3 v navadnem pogonu je ta končni izplak del rezultata shranjevanja in znak spremembe XFA se počisti šele po pravem uspehu. Na strani Delphi TPdf.SaveAs(const FileName: string; Option: TSaveOption = saNone; PdfVersion: TPdfVersion = pvUnknown): Boolean piše v začasno datoteko ob tarči in jo prestavi na mesto samo, kadar shranjevanje vrne True, tako da spodletelo shranjevanje pusti prejšnjo datoteko nedotaknjeno
Zakaj se dinamični obrazec XFA ponovno odpre z manj stranmi?
Dinamični obrazec XFA se ponovno odpre z manj stranmi, kadar njegova korenska podforma ne deklarira restoreState="auto", in to je odločitev izdelave obrazca, ne defekt komponente PDFium Component. V XFA 3.3 restoreState na korenski podformi privzeto znači manual. Pod manual procesor XFA iz shranjenega paketa obrazca povrne samo omejeno stanje in ostalo pusti skriptom avtorja. Shranjene vrednosti polj in števci instanc ponavljajočih podform še pridejo nazaj, geometrijske lastnosti, nastavljene med tekom, pa ne
Primer, ki je to izpostavil, je bil tristranski obrazec, katerega skript je podformo zrasel na h="450pt". Shranjeni paket obrazca je držal novo višino, vrednosti in števce instanc. Ob ponovnem odpiranju pa je bila postavitev znova zgrajena iz višin predloge in obrazec se je prelil na dve strani. Pogon je imel prav: predloga nikoli ni zahtevala samodejne povrnitve. Deklaracija na korenski podformi popravi ponovno odpiranje:
<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; skripti med tekom lahko spremenijo h ali dodajo instance -->
</subform>
</subform>
</template>
Če predloge ne lastite, je ne obvozite v pregledovalniku: obrazec, ki se zanaša na način manual, pričakuje, da bodo njegovi skripti stanje znova zgradili. Živa ponovna paginacija medtem ko uporabnik tipka je ločena tema, pokrita v kako komponenta PDFium Component sledi števcem strani dinamičnega XFA in preseljenim poljem
Kako preverite shranjevanje XFA v Delphiju?
Edini zanesljiv preizkus shranjevanja XFA je, da shranjeno datoteko ponovno odprete v sveži instanci TPdf in shranjene podatke preberete nazaj. TPdf.GetXfaDatasets vrne paket podatkovnih množic, kot je shranjen v dokumentu, ne živega podatkovnega modela XFA, zato klic pred shranjevanjem pokaže stare vrednosti. Po ponovnem odpiranju pokaže točno tisto, kar je bilo zapisano. Dokument z enim tokom nima posebej poimenovanih paketov: PDFium poroča celoten XDP kot en paket s praznim imenom, zato GetXfaPacketByName('datasets') in GetXfaDatasets ne vrneta nič, rezerva pa prebere celoten tok skozi 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; // postavitev tabele paketov
if Length(Bytes) = 0 then
begin
Packets := Pdf.GetXfaFormPackets; // enojni tok: en 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); // shranjen izhod XDP je UTF-8
finally
Pdf.Free;
end;
end;
Rutina shranjevanja potem zaveže obvisjajoče urejanje, preizkusi rezultat SaveAs in primerja ponovno odprto vrednost. TPdf.ClearFormFieldFocus ubije žarišče obrazcev, kar je trenutek, ko PDFium zaveže medpomnilnik urejanja polja v žarišču. TPdf.SetFocusedFormFieldText(const Value: WString): Boolean programsko zapolni polje v žarišču, zanaša pa se na žarišče, ki ga ovijalec sledi prek FocusFormField, ki prehodi anotacije widgetov. Dinamična stran XFA jih običajno nima, zato tam besedilo običajno prispe skozi vnos s tipkovnico v TPdfView in funkcija vrne False, kadar nobeno sledeno polje ni v žarišču
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
// Izbirna skriptna izpolnitev; False pomeni, da nobeno sledeno polje ni v žarišču
if (Pdf.FocusedFormFieldIndex >= 0) and
not Pdf.SetFocusedFormFieldText(Expected) then
raise EPdfError.Create('Could not write the focused field');
Pdf.ClearFormFieldFocus; // zaveže medpomnilnik urejanja
if not Pdf.SaveAs(FileName) then // vključuje končni izplak (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;
Preizkus na podniz obravnavajte kot preizkus dima. Prazen element se lahko serizira kot <Tag/>, atributi se lahko pojavijo na podatkovnih elementih, ubeževanje čez & in < pa je izbira serizatorja. Za produkcijske preizkuse naložite ponovno odprti XML s pravim razčlenjevalnikom XML in primerjajte besedilno vozlišče vezanega podatkovnega elementa. Preizkus poženite tudi dvakrat zapored, ker je defekt nove vrstice polno obliko pokazal šele v drugi generaciji
Hiter pregled: kontrolni seznam vernosti shranjevanja XFA
- Razporedite
pdfium.v8.dlliz v3.125.2 ali pozneje za obrazce XFA in v3.125.3 ali pozneje za navadnipdfium.dll, da je popravek končnega zapisa v obeh - Usmerite
LibraryNamev polno pot in nastaviteEnableV8Enginena True; manjkajoča pot spodleti namesto da bi naložila drugo kopijo - Potrdite
TPdf.XFAinTPdf.XfaRuntimeAvailablepo odprtju dokumenta - Pokličite
ClearFormFieldFocuspredSaveAs, da se polje v žarišču zaveže - Logičnega rezultata
SaveAsnikoli ne ignorirajte; rezultat False pusti prejšnjo datoteko na mestu - Preverite z ponovnim odpiranjem v novi
TPdfin branjemGetXfaDatasets, z rezervo naGetXfaFormPacketsza XFA z enim tokom - Testirajte s praznimi vrednostmi, vodilnimi presledki, večvrstičnim besedilom,
&in znakom z dopolnilne ravnine, čez dve generaciji shranjevanja - Pričakujte izrecne spodletitve shranjevanja za DTD, XMLDSig in komentarje znotraj živih paketov XFA z enim tokom
- Če dinamični obrazec ob ponovnem odpiranju izgubi geometrijo med tekom, preverite korensko podformo za
restoreState="auto", preden obtožite knjižnico
Za strukturo povratnih klicev, ki jo pogon XFA pričakuje od gostiteljske aplikacije, glej FPDF_FORMFILLINFO različico 2 in ABI XFA v Delphiju. Pogon V8, ovijalec za Delphi in C++Builder ter nadzorni element pregledovalnika so vsi del komponente PDFium Component za Delphi in C++Builder, ki vključuje oba pogona Windows za Win32 in Win64