PDFium Component redaguotas XFA formos reikšmes išsaugoja tiksliai, per išsaugojimą ir atvėrimą, kai lekia v3.125.2 ar naujesnėje pristatytoji Windows V8 vykdymo aplinka pdfium.v8.dll. Senesnės vykdymo aplinkos prie lauko reikšmių prikimšdavo eilučių lūžių, emoji nukirpdavo iki nesusijusio BMP simbolio, tyliai praleisdavo vieno srauto XFA išsaugojimus ir galėdavo praryti nepavykusį galutinį rašymą. Vienas atvėrimo simptomas apskritai nėra bibliotekos defektas: dinaminė forma, kurios šakninis poformis be restoreState="auto", savo išdėstymą atkuria iš šablono
Visos šio dalyko klaidų ataskaitos atrodė vienodai. Klientas užpildo XFA deklaracijos formą Delphi peržiūrovoje, išsaugoja, atveria dar kartą, ir kažkas kiek nuklysta. Tuščias komentarų laukas dabar laiko vieną tuščią eilutę, o po antro išsaugojimo – dvi. Vardas, įrašytas su emoji, grįžta su privataus naudojimo rašmeniu. Niekas negauna klaidos – būtent todėl šios klaidos brangios: išsinešiojimas pasirodo po kelių savaičių kažkieno kito eksporte
Kas suyra, kai XFA forma išsaugojama ir atveriama?
Keturi atskiri defektai savojoje XFA išsaugojimo atkarpoje sukėlė reikšmių išsinešiojimą, ir kiekvienas slėpėsi už sėkmingai atrodančio išsaugojimo. Du atkeliavo iš serijizacijos, vienas – iš vieno srauto saugojimo išdėstymo, o vienas – iš pačio PDF rašytojo. Lentelė kiekvieną simptomą susieja su priežastimi ir su leidimu, kuriame PDFium Component tai sutvarkė
| Simptomas po atvėrimo | Priežastis | Sutvarkyta |
|---|---|---|
| Tuščias laukas laiko eilutės lūžį; reikšmės prie kiekvieno išsaugojimo paauga eilute | Abu XFA rašytojai po atveriamųjų žymių įterpdavo išdėstymo eilučių lūžius | v3.125.2, pdfium.v8.dll |
| U+1F642 grįžta kaip U+F642, arba emoji dingo iš formos paketo | 16 bitų wchar_t nukirpimas dekoduojant; pakaitinių filtravimas formos serijizatoriuje | v3.125.2, pdfium.v8.dll |
| Vieno srauto XFA dokumento redagavimai tiesiog dingsta | Savasis išsaugojimas atmetė srauto išdėstymą, bet grąžinama reikšmė buvo ignoruojama | v3.125.2; komentarai ir apdorojimo instrukcijos išlaikomi nuo v3.126.0 |
| Nukirptas failas, nors išsaugojimas pranešė sėkmę | Galutinis sukaupptasis rašymas žlugo, kai rašytojas jau buvo grąžinęs sėkmę | v3.125.2 V8 vykdymo aplinka; v3.125.3 paprastoji pdfium.dll |
| Trijų puslapių dinaminė forma atveriama kaip dviejų | Šakninis poformis neprašo restoreState="auto" | Formos rašymas, ne bibliotekos defektas |
Ankstesni apibendrinimai darė išvadą, kad XFA lauko redagavimų su PDFium apskritai negalima išsaugoti, ir tai buvo tiesa tų laikų vykdymo aplinkoms. Naujesnioji V8 vykdymo aplinka XFA reikšmes išsaugoja natyviai, tad gyvoje formoje padarytas redagavimas pasiekia išsaugotąjį datasets paketą be jokios paketų chirurgijos iš jūsų pusės
Kurioji PDFium vykdymo aplinka išsaugoja XFA reikšmes?
XFA išsaugojimo tikslumas priklauso nuo savosios DLL, o ne nuo Delphi aplinkkalbės, tad pirmoji patikra – kurią vykdymo aplinką jūsų procesas iš tikrųjų pakrauna. PDFium Component kiekvienai architektūrai pristato du Windows variantus: paprastąjį pdfium.dll, sukurtą be V8 ir XFA, ir pdfium.v8.dll, nešantį JavaScript variklį ir XFA formų vykdymo aplinką. Tik pdfium.v8.dll gali paleisti XFA formą, tad kiekviena čia aprašyta XFA pataisa gyvena joje – pradedant perstatytomis Win32 ir Win64 V8 bibliotekomis v3.125.2
Galutinio rašymo pataisa – bendrojo PDF rašytojo kodas, tad svarbi ir paprastiems dokumentams. v3.125.3 perstatė paprastąsias pdfium.dll bibliotekas, kad jos neštų tą patį remontą. Bendras šaltinis – ne bendro elgesio įrodymas: kol dvejetainis failas nepertestas, senoji DLL laiko senąją klaidą
Antri spąstai tupėjo krautuve. Iki v3.125.2 EnableV8Engine nustatymas į True priversetų aplinkkalbę rinktis numatytąjį pdfium.v8.dll vardą ir ignoruoti pilnąjį kelią LibraryName viduje. Taikomoji programa, rodžiusi į šviežiai išdėstytą vykdymo aplinką, galėjo toliau krauti senesnę kopiją iš kito aplanko. Nuo v3.125.2 LibraryName, turintis katalogą, abiejuose variklio režimuose renkasi būtent tą failą, o dingęs kelias žlunga vietoj atsitraukimo į kitą pridėtąją biblioteką
uses
System.SysUtils, PDFium;
procedure SelectXfaRuntime;
begin
// Katalogas LibraryName viduje prisega šį tikslųjį failą (v3.125.2 ir vėliau);
// jei failo nėra, krovimas kelia išimtį vietoj atsitraukimo
{$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; // žlugti starto metu, o ne pirmojo išsaugojimo metu
end;
Atvėrus dokumentą, TPdf.XFA sako, jog faile yra XFA, o TPdf.XfaRuntimeAvailable – jog pakrautoji DLL ją iš tikrųjų geba vykdyti. Jei dar reikia atskirti statines ir dinamines formas, TPdf.FormType grąžina ftXfaFull arba ftXfaForeground; straipsnis XFA formų aptikimas ir XFA paketų ištraukimas Delphi tą žvalgymą dengia detaliai
Kodėl išsaugotieji XFA laukai prisidirba papildomų eilučių lūžių?
Išsaugotieji XFA laukai prisidirbdavo eilučių lūžių, nes abu savosieji XFA rašytojai – bendrasis XML elementų rašytojas ir formos paketo serijizatorius – savo išvestį gražindavo spausdindami su eilutės lūžiu po atveriamųjų žymių. Daugumoje XML tas tarpas – kosmetika. XFA duomenyse – ne: kai datasets paketas vėl išanalizuojamas, tekstas tarp <Comments> ir </Comments> yra lauko reikšmė, kartu su eilutės lūžiu. Tuščias laukas todėl atverdavosi laikydamas vieną LF, o kiekvienas tolesnis išsaugojimo ir atvėrimo ratas galėjo pridėti dar vieną
Akivaizdusis remontas – reikšmių apkirpimas kraunant – būtų neteisingas. Naudotojai į XFA laukus įrašo priekinius ir galinius tarpus bei sąmoningą kelių eilučių tekstą, ir adreso blokas ar fiksuoto pločio kodas turi išgyventi baitą į baitą. v3.125.2 pataisa todėl šalina tik tą tarpą, kurį pats serijizatorius susintetino aplink žymes. Naudotojų reikšmės, esamieji teksto mazgai ir CDATA sekcijos praeina neliečiami, tad " indented" lieka įtrauktas, o sąmoningai tuščias laukas lieka tuščias
Kodėl emoji grįžta kaip kitas simbolis?
Emoji grįždavo sugedęs, nes Windows wchar_t yra 16 bitų pločio, o dvi dekodavimo atkarpos visą Unicode skaliarą susidėdavo į vieną wchar_t. Taip darydavo ir UTF-8 srauto dekoderis, ir skaitinių simbolių nuorodų, tokių kaip 🙂, analizatorius. U+1F642 – šiek tiek besišypsantis veidas – į 16 bitų netelpa, tad aukštieji bitai nukrisdavo, ir vietoj jo pasirodydavo U+F642: kodo taškas Private Use Area srityje, kurį dauguma šriftų atvaizduoja kaip langelį arba nieko
Formos serijizatorius turėjo priešingą problemą. Jis simbolius filtruodavo po vieną wchar_t, matydavo dvi izoliuotai neteisėtas pakaitines kodo grupes ir abi išmesdavo, tad emoji dingsdavo iš formos paketo visai. v3.125.2 dekoderis kiekvieną skaliaro reikšmę suvartoja pilnai ir išduoda teisingą pakaitinių porą. Kai belieka viena išvesties vieta, žemąją pakaitinę laiko laukiančią ir srauto pabaigos nepraneša, kol ta grupė dar podėlyje. UTF-8 seka, perskelta tarp skaitymo blokų, perduodama į kitą skaitymą vietoj išmetimo. Formos eksportuotojas dabar teisėtas pakaitines poras laiko kartu, o skaitinės simbolių nuorodos duoda teisingas poras taip pat
Latin-1 bandomieji duomenys nieko iš to niekada neparodo, tad kiekvienam XFA apvalinimo testui reikia bent vieno papildomos plokštumos simbolio
Vieno srauto XFA ir išsaugojimo nesėkmės, kurių niekas nematė
Vieno srauto XFA dokumentas prarasdavo savo redagavimus, nes savasis išsaugojimo pagalbininkas atmesdavo tą saugojimo išdėstymą, o jo kvientėjas nesėkmės nepastebėdavo. ISO 32000-1 §12.7.8 leidžia sąveikios formos žodyno /XFA įrašą – arba paketų vardų ir srautų masyvą, arba vieną srautą, laikantį visą XDP dokumentą. Paketų masyvai – dažnasis atvejis, bet vieni srautai visiškai teisėti, ir PDF išsaugojimas pasibaigdavo tarsi nieko neįvyko, kol formos duomenys likdavo senosiose reikšmėse
Nuo v3.125.2 V8 vykdymo aplinka tvarko palaikomąją vieno srauto aibę. Ji pirmiau abu gyvuosius paketus – datasets ir form – išeksportuoja į paruošimo zoną ir patikrina, ir tik tada pakeičia atitinkamus paketus originaliajame XDP. Kiti paketai ir šakninės vardų tarpų deklaracijos išlaikomi. Jei paruošimas žlunga, liekamąją XFA srautą niekas neliečia, o dokumentas išlaiko savo keitimo žymę
XML komentarai ir apdorojimo instrukcijos reikalavo ypatingos priežiūros, nes vidinis XML DOM juos išmeta. v3.125.2 jų buvimas verčia išsaugojimą žlugti tiesiai, vietoj tylio turinio praradimo. v3.126.0 juos išlaiko: prieš analizavimą kiekvienas komentaras ar apdorojimo instrukcija apkeičiamas žymekliu, pastatytu iš priešdėlio, kurio originaliame tekste niekur nėra. Kai gyvieji paketai pakeisti, kiekvienas žymeklis turi pasirodyti tiksliai kartą, dar prieš atstatant originaliąją leksemą ir rašant srautą. Leksemos už pakeistųjų paketų ribų todėl išlaiko savo tekstą ir tvarką, įskaitant leksemas prologe, šablone ir kituose paketuose
Kai kurios įvestys vis dar atmetamos tyčia, ir kiekvienas atmetimas – aiški išsaugojimo nesėkmė:
- Komentarai arba apdorojimo instrukcijos gyvųjų
datasetsarbaformpaketų viduje, nes jų originaliųjų pozicijų neįmanoma susieti su šviežiai eksportuotu turiniu - DTD deklaracijos ir XMLDSig parašai, nes XDP perrašymas negali išlaikyti XML parašo teisėtu
- Neteisėta UTF-8 arba UTF-16 koduotė, neišbaigtos žymės, neteisėtos simbolių nuorodos, nežinomi objektai ir iškraipytos apdorojimo instrukcijos – atmetamos vietoj tylio remonto
Vieno srauto išvestis – UTF-8 ir išlaiko XML turinio modelį, o ne originalųjį baitų išdėstymą ar koduotės deklaraciją
Paskutinis defektas tupėjo žemiau XFA. Savasis failų rašytojas išvestį kaupia 32 KB blokais, o galutinį nepilnąjį bloką nuplaudavo tik savo destruktoriuje, kai dokumento rašytojas jau buvo pranešęs sėkmę. Disko pilnumo ar I/O klaida tame paskutiniajame bloke kvientėjui buvo nematoma. Nuo v3.125.2 V8 vykdymo aplinkoje ir v3.125.3 paprastojoje ta galutinė nuplauka yra išsaugojimo rezultato dalis, o XFA keitimo žymė nuimama tik po tikros sėkmės. Delphi pusėje TPdf.SaveAs(const FileName: string; Option: TSaveOption = saNone; PdfVersion: TPdfVersion = pvUnknown): Boolean rašo į laikinąjį failą šalia tikslo ir perkelia jį į vietą tik tada, kai išsaugojimas grąžina True, tad nesėkmingas išsaugojimas ankstesnįjį failą palieka nepaliestą
Kodėl dinaminė XFA forma atveriama su mažiau puslapių?
Dinaminė XFA forma atveriama su mažiau puslapių, kai jos šakninis poformis nedeklaruoja restoreState="auto", ir tai formos rašymo sprendimas, o ne PDFium Component defektas. XFA 3.3 restoreState ant šakninio poformio pagal numatymą yra manual. Esant manual, XFA procesorius iš išsaugotojo formos paketo atkuria tik ribotą būseną, o likusį palieka autoriaus scenarijams. Išsaugotosios laukų reikšmės ir kartojamųjų poformių egzempliorių skaičiai vis tiek grįžta, bet vykdymo metu nustatytosios geometrinės savybės – ne
Atvejis, atskleidęs tai, buvo trijų puslapių forma, kurios scenarijus poformį paaugino iki h="450pt". Išsaugotasis formos paketas laikė naująjį aukštį, reikšmes ir egzempliorių skaičius. Bet atvėrus išdėstymas buvo atstatytas iš šablono aukščių, ir forma persisodino į du puslapius. Vykdymo aplinka buvo teisi: šablonas niekada neprašė automatinio atstatymo. Jo deklaravimas ant šakninio poformio atvėrimą sutvarko:
<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">
<!-- laukai; scenarijai vykdymo metu gali keisti h arba pridėti egzempliorių -->
</subform>
</subform>
</template>
Jei šablono nesavaldote, negydykite jo aplink peržiūrovoje: forma, pasitikinti manual režimu, tikisi savų scenarijų, atstatysiančių būseną. Gyvasis persiskirstymas, kol naudotojas rašo, – atskira tema, dengiama straipsnyje kaip PDFium Component seka dinaminio XFA puslapių skaičių ir perkeltus laukus
Kaip Delphi patikrinti XFA išsaugojimą?
Vienintelė patikima XFA išsaugojimo patikra – išsaugotąjį failą atverti šviežiame TPdf egzemplioriuje ir perskaityti saugomuosius duomenis atgal. TPdf.GetXfaDatasets grąžina datasets paketą tokį, koks jis saugomas dokumente, o ne gyvąjį XFA duomenų modelį, tad pakvietus prieš išsaugojimą matysite senąsias reikšmes. Po atvėrimo jis rodo tiksliai tai, kas parašyta. Vieno srauto dokumentas atskirai pavadintų paketų neturi: PDFium visą XDP praneša kaip vieną paketą su tuščiu vardu, tad GetXfaPacketByName('datasets') ir GetXfaDatasets negrąžina nieko, o atsarginis kelias pilnąjį srautą skaito per 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; // paketų masyvo išdėstymas
if Length(Bytes) = 0 then
begin
Packets := Pdf.GetXfaFormPackets; // vienas srautas: vienas bevardis paketas
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); // išsaugotoji XDP išvestis – UTF-8
finally
Pdf.Free;
end;
end;
Tada išsaugojimo procedūra patvirtina laukiantįjį redagavimą, tikrina SaveAs rezultatą ir lygina atvėrusiąją reikšmę. TPdf.ClearFormFieldFocus numarina formos fokusą – tai akimirka, kai PDFium patvirtina sufokusuoto lauko redagavimo buferį. TPdf.SetFocusedFormFieldText(const Value: WString): Boolean sufokusuotąjį lauką užpildo programiškai, bet remiasi fokusu, kurį aplinkkalbė seka per FocusFormField, apeinantį valdiklių anotacijas. Dinaminis XFA puslapis paprastai jų neturi, tad ten tekstas paprastai atkeliauja per klaviatūros įvestį TPdfView viduje, o funkcija grąžina False, kai joks sekamas laukas fokusą neturi
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
// Pasirinktinis scenarijinis užpildymas; False reiškia, jog joks sekamas laukas fokusą neturi
if (Pdf.FocusedFormFieldIndex >= 0) and
not Pdf.SetFocusedFormFieldText(Expected) then
raise EPdfError.Create('Could not write the focused field');
Pdf.ClearFormFieldFocus; // patvirtinti redagavimo buferį
if not Pdf.SaveAs(FileName) then // įskaita galutinę nuplauką (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;
Poalletės testą laikykite dūmų testu. Tuščias elementas gali būti serijizuotas kaip <Tag/>, atributai gali pasirodyti ant duomenų elementų, o kaušeliai už & ir < ribų – serijizatoriaus pasirinkimas. Gamybinėms patikroms atvėrusįjį XML pakraukite tikru XML analizatoriumi ir lyginkite susietojo duomenų elemento teksto mazgą. Patikrą paleiskite ir du kartus iš eilės, nes eilučių lūžių defektas visą savo formą parodė tik antroje kartoje
Trumpa atmintinė: XFA išsaugojimo tikslumo kontrolinis sąrašas
- Išdėstykite
pdfium.v8.dlliš v3.125.2 ar naujesnės XFA formoms, o v3.125.3 ar naujesnę – paprastajaipdfium.dll, kad galutinio rašymo pataisa būtų abiejose LibraryNamenukreipkite į pilnąjį kelią ir nustatykiteEnableV8Engineį True; dingęs kelias žlunga vietoj kitos kopijos krovimo- Patvirtinkite
TPdf.XFAirTPdf.XfaRuntimeAvailablepo dokumento atvėrimo - Kvieskite
ClearFormFieldFocuspriešSaveAs, kad sufokusuotasis laukas būtų patvirtintas - Niekada neignoruokite
SaveAsBoolean rezultato; False rezultatas ankstesnįjį failą palieka vietoje - Tikrinkite atverdami naujame
TPdfir skaitydamiGetXfaDatasets, vieno srauto XFA atveju atsitraukdami įGetXfaFormPackets - Bandykite su tuščiomis reikšmėmis, priekiniais tarpais, kelių eilučių tekstu,
&ir papildomos plokštumos simboliu, per dvi išsaugojimo kartas - Tikėkitės aiškių išsaugojimo nesėkmių dėl DTD, XMLDSig ir komentarų gyvųjų vieno srauto XFA paketų viduje
- Jei dinaminė forma atvėrusi praranda vykdymo metu nustatytą geometriją, patikrinkite šakninį poformį dėl
restoreState="auto", dar nepalejusi bibliotekos
Apie atgalinių kvietimų struktūrą, kurios iš hosto taikomosios programos tikisi XFA vykdymo aplinka, žiūrėkite FPDF_FORMFILLINFO versija 2 ir XFA ABI Delphi. V8 vykdymo aplinka, Delphi ir C++Builder aplinkkalbė ir peržiūrovos valdiklis – visos PDFium Component for Delphi and C++Builder dalys, apimančios abi Windows vykdymo aplinkas Win32 ir Win64