HotPDF užpildo dinamines XFA formas Delphi per TXFAWidgetRuntime — nuo aplinkos nepriklausomą valdiklių sluoksnį, kuris kiekvieną lauko redagavimą traktuoja kaip vieną transakciją: momentinė kopija, patikrinimas, skaičiavimas, pertvarkymas, o tada publikavimas arba visiškas atstatymas. Jis veikia viengija režimu jūsų pačioje VCL ar FMX programoje, nereikalauja įdiegto Acrobat ir kiekvieną limitą įgyvendina prieš skirdamas išteklių
Tą scenarijų pažįsta visi, kas dokumentinę programinę įrangą tiekė valdžios ar draudimo sektoriui. Žalos atlyginimo forma ar mokesčių deklaracija atkeliauja kaip PDF, kurio puslapio turinys — vienintelis pranešimas „Please wait... if this message is not eventually replaced", o visi tikri laukai slepiasi XFA pakete, kurį atvaizduoja tik Adobe Acrobat. Jūsų vartotojai nori jį užpildyti jūsų programoje. Išeities rastravimu irgi nėra, nes forma augina eilutes, kol renkami duomenys, o išdėstymas po trečios eilutės jau nebe tas, kuris buvo faile
Kodėl dinaminė XFA vis dar yra problema, kurią verta spręsti
Dinaminė XFA išgyvena todėl, kad įdiegtos formos gyvena ilgiau nei formatas, jas nešęs. ISO 32000-1 §12.7.8 aprašo XFA kaip /XFA įrašą AcroForm žodyne, laikantį XDP paketo srautą, o ISO 32000-2 visą mechanizmą pažymi kaip pasenusį; tai jį nuėmė iš planų, bet ne iš lauko, ir formos, sukurtos pagal XFA 3.3 specifikaciją, tebėra išduodamos ir tebėra teisiškai rišančios. Statinę XFA galima sutraukti iki paprastų valdiklių anotacijų, ir HotPDF tą padaro, kai iškviečiate ApplyXFAAsAcroForm — kompromisai aprašyti straipsnyje XFA formų suplokštinimas į AcroForm laukus. Dinaminė XFA yra kita žvėris: jos occur ribos, augantis tekstas ir calculate scenarijai padaro laukų rinkinį duomenų funkcija, todėl fiksuoto anotacijų sąrašo, į kurį būtų galima plojti, nėra, kol vartotojas nesibaigė rašyti. Tą spragą ir užpildo TXFAWidgetRuntime: laiko XFA DOM gyvą, po kiekvieno priimto redagavimo perskaičiuoja išdėstymą ir jūsų programai paduoda plokščią pozicionuotų valdiklių masyvą, kurį galima piešti ir tikrinti spustelėjimus
Ką vykdymo aplinka paduoda programai?
Ji paduoda geometriją ir būseną — ir nieko, kas manytų UI biblioteką. TXFAWidgetRuntime atveria WidgetCount ir Widgets[I] kaip TXFAWidgetState įrašus su ID, Name, Kind, PageIndex, Bounds PDF taškais, Value, EditValue ir požymiais Focused, Editing, ReadOnly, Valid, o piešimas, žymeklio braižymas ir klaviatūros maršrutizavimas lieka jūsų kode. Valdiklio tapatumas stabilus ir eilinis: kiekvienas valdiklis gauna ID pavidalu name[n], kur n skaičiuoja ankstesnius to lauko pavadinimo pasikartojimus išdėstymo tvarka, tad antra kartojamo subformos eilutė yra amount[1]. Būtent tas tapatumas išgyvena perkūrimą ir yra tai, ką kalba FocusWidget, BeginEdit, DispatchEvent ir HitTest. Dokumentui, jau atidarytam THotPDF egzemplioriuje, CreateLoadedXFAWidgetRuntime ištraukia XDP paketus, pirmą puslapio dėžutę paima kaip išdėstymo puslapio dydį ir grąžina nil, kai faile XFA iš viso nėra
var
Pdf: THotPDF;
Runtime: TXFAWidgetRuntime;
WidgetID: AnsiString;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('claim-dynamic.pdf');
Runtime := Pdf.CreateLoadedXFAWidgetRuntime; // nil, kai nėra /XFA
if Runtime = nil then
Exit;
try
for I := 0 to Runtime.WidgetCount - 1 do
Memo1.Lines.Add(Format('%s p%d [%.1f %.1f %.1f %.1f] = %s',
[string(Runtime.Widgets[I].ID), Runtime.Widgets[I].PageIndex,
Runtime.Widgets[I].Bounds.Left, Runtime.Widgets[I].Bounds.Top,
Runtime.Widgets[I].Bounds.Right, Runtime.Widgets[I].Bounds.Bottom,
string(Runtime.Widgets[I].Value)]));
// hit test puslapio erdvėje, laimi viršutinis valdiklis
if Runtime.HitTest(0, 120.0, 96.0, WidgetID) then
Runtime.BeginEdit(WidgetID);
finally
Runtime.Free;
end;
finally
Pdf.Free;
end;
end;
Kas turi būti atominė, patvirtinant lauką?
Viskas, ką redagavimas gali paliesti, — o tai žymiai daugiau nei lauko reikšmė. CommitEdit prieš rašydamas ką nors iškviečia CaptureSnapshot, ir ta kopija dengia keturis dalykus: serializuotą XFA DOM iš TXFADocument.SaveToBytes, visą TXFAWidgetState sąveikos įrašų masyvą, skaitiklius LastCalculationPasses ir LastReflowPasses bei dabartinį Warnings.Count. Išsaugoti vien mazgų reikšmes — viliojantis trumpasis kelias, ir jis neteisingas, nes calculate scenarijus ar neišspręsta susiejimas gali iškviesti EnsureValueNode ir materializuoti duomenų mazgus, kurių redagavimo pradžioje nebuvo; atstatymas tik reikšmėms jų nepašalins, todėl atmestas redagavimas paliktų nuolatines struktūrines liekanas datasets pakete. Pats patvirtinimo seka griežta — įrašyti kandidato reikšmę, paleisti validate redaguotam laukui, vykdyti calculate iki fiksuoto taško, tada pertvarkyti, kol išdėstymas stabilus — ir bet koks žlugimas bet kuriame etape eina per FailAndRestore, kuri kopijos baitus užkrauna į šviežią TXFADocument, perkonstruoja valdiklių sąrašą, vėl pritaiko užfiksuotas sąveikos būsenas, nuresetina skaitiklius ir Warnings sutrumpina atgal iki kopijos ilgio. LastDiagnostic laiko priežastį nesėkmės atveju, o patologiniu atveju, kai pakyla pats atstatymas, — literalą XFA transaction rollback failed
function EditAmount(Runtime: TXFAWidgetRuntime;
const AWidgetID: AnsiString; const AText: UnicodeString): Boolean;
var
Current: UnicodeString;
begin
Result := False;
if not Runtime.BeginEdit(AWidgetID) then
Exit; // tik skaitymui, arba tokio valdiklio nėra
Current := Runtime.Widgets[Runtime.FocusedIndex].EditValue;
if not Runtime.ReplaceSelection(0, Length(Current), AText) then
begin
Runtime.CancelEdit; // blogas intervalas arba perpjauta surrogatų pora
Exit;
end;
Result := Runtime.CommitEdit; // viskas arba nieko
if not Result then
// dokumentas, valdikliai, skaitikliai ir įspėjimai jau grąžinti į
// prieš redagavimo būseną; fokusuotas valdiklis tiesiog pažymimas negaliojančiu
ShowMessage(Runtime.LastDiagnostic);
end;
ReplaceSelection nusipelno atskiros pastabos, nes būtent čia netinkama įvestis atmetama pigiausiai. Jis atmeta žymėjimą, perkeliantį UTF-16 surrogatų poros ribą, atmeta pakeitimo tekstą su nesuporuotu aukštuoju ar žemuoju surrogatu ir atmeta bet kokį rezultatą, ilgesnį nei MaxValueChars. To sulaikymas klavišų lygyje reiškia, kad transakcijos mechanizmui niekada neteks atšaukti pusiau įrašyto astralinės plokštumos simbolio
Perkūrimas į privatų sąrašą, publikavimas vienu sukeitimu
Valdiklių perkūrimas niekada negali būti stebimas pusiau baigtas, todėl RebuildWidgets sukonstruoja visiškai atskirą nuosavą TObjectList ir pabaigoje vienu priskyrimu jį sukeičia į vietą. Priežastis ne estetika: TXFALayoutEngine.ComputeLayout veikia, kol perkūrimas dar vyksta, ir per jūsų paduotą MeasureText funkciją iškviečia programos kodą, o pasiekus valdiklių ribą gali pakelti EXFAWidgetRuntimeError. Jei vykdymo aplinka keistų savo gyvą sąrašą vietoje, bet kuri iš tų kelių paliktų programą laikančią sąrašą, iš dalies seno ir iš dalies naujo išdėstymo, su DataNode rodyklėmis į dokumentą, kuris tuoj bus atstatytas. Pertvarkymo konvergenciją tada sprendžia LayoutSignature — eilutė, sudaryta iš valdiklių skaičiaus ir kiekvieno ID, puslapio indekso bei apibrėžiančios dėžutės, suapvalintos iki keturių dešimtųjų: CommitEdit perkonstruoja, palygina parašus ir kartoją, kol du iš eilės parašai sutampa arba išsenka etapų biudžetas. Kai parašas iš viso nesikeitė, LastReflowPasses lieka 0 — taip atskirsite tik reikšmės redagavimą nuo to, kuris iš tikrųjų paaugino formą, o sąveikos būsena kiekvieno perkūrimo metu pernešama pagal valdiklio ID, tad fokusuotas ir vykstantis redagavimas išgyvena eilutės įterpimą
Kodėl susietas laukas skaitytų netinkamą įrašą?
Nes scenarijus ėjo be duomenų konteksto. Laukas, nešantis aiškų <bind match="dataRef" ref="$record.actual"/>, ir laukas, pavadintas to paties duomenų mazgo vardu, yra du skirtingi valdikliai, rodyantys į vieną reikšmę, o kartojanti subforma su <occur max="2"/> pagamina kelis valdiklius, besidalijančius vienu vardu ir besiskiriančius tik tuo, kuriai duomenų eilutei jie priklauso; vertinkite patikrinimą ir skaičiavimą nuo dokumento šaknies, ir kiekvienas iš jų this išspręs į pirmą sutinkantį mazgą visame datasets pakete, tad antra eilutė be jokio įspėjimo patikrins pirmąją. HotPDF to išvengia, kai išdėstymas sukuria valdiklį, išsaugodamas išspręstą DataNode kiekvieno valdiklio įraše, o paskui tą mazgą perduoda per abu HPDFXFAEvaluateFieldScript iškvietimus — ir xfskValidate, ir xfskCalculate. Tas pats kontekstas sprendžia, prie kurio mazgo EnsureValueNode kūrs naują, kai skaičiavimas taiko susiejimą, kurio dar nėra, o kai susiejimo išspręsti nepavyksta, patvirtinimas žlunga švariai su XFA calculation target is not bound, užuot rašęs į netinkamą eilutę. Tų scenarijų FormCalc semantika atkartoja tai, ką AcroForm dokumentams duoda veiksmai, aprašyti AcroForm formatas ir calculate scenarijai, bet čia sprendimo taisyklės apibrėžtos XFA kontekste, o ne lauko pavadinimo kontekste
Biudžetai tikrinami prieš šalutinius poveikius, o ne po jų
Kiekvienas vykdymo aplinkos limitas yra išankstinė sąlyga, nes biudžetas, įgyvendintas jau įvykus išteklių skyrimui, nėra biudžetas. TXFAWidgetRuntimeOptions.Default tiekia MaxWidgets 10000, MaxValueChars 1048576, MaxCalculationPasses 16 ir MaxReflowPasses 4, o numatytieji TXFAFormScriptOptions neša MaxOperations 100000 su MaxElapsedMilliseconds 500. Giliau XFA DOM taiko savus TXFADOMLimits: 128 MB lubos išglaudintai įvesčiai ir išvesčiai, ne daugiau kaip 1024 susiūti paketai, 1000000 mazgų ir 256 gylis. Dvi detalės svarbesnės už pačius skaičius. Pirma, scenarijų biudžetai yra visos transakcijos, o ne atskiro scenarijaus: CommitEdit pasėja vieną likusių operacijų skaitiklį ir vieną monotoninį terminą, ir kiekvienas validate ir calculate iškvietimas eikvoja tą patį skaitiklį ir gauna tik likusias milisekundes, todėl forma su dviem šimtais skaičiuojančių laukų negali išleisti pilnus 500 ms du šimtus kartų. Antra, terminas ateina iš įstatomos MonotonicMilliseconds funkcijos — būtent todėl praėjusio laiko elgseną testų rinkinyje galima atkurti, o ne palikti likimui užimtame build agente
var
Options: TXFAWidgetRuntimeOptions;
Runtime: TXFAWidgetRuntime;
begin
Options := TXFAWidgetRuntimeOptions.Default;
Options.MaxWidgets := 2000; // numatytasis 10000
Options.MaxCalculationPasses := 8; // numatytasis 16
Options.MaxReflowPasses := 2; // numatytasis 4
Options.ScriptOptions.Limits.MaxOperations := 20000; // visa transakcija
Options.ScriptOptions.Limits.MaxElapsedMilliseconds := 200;
Options.MeasureText :=
function(const AText: UnicodeString; const AFont: TXFAFontSpec;
AMaxWidth: Double): TXFATextExtent
begin
Result := MeasureWithHostCanvas(AText, AFont, AMaxWidth);
end;
Runtime := TXFAWidgetRuntime.Create(XDPBytes, 612, 792, Options);
try
Runtime.OnLayoutChanged :=
procedure
begin
RepaintAllPages; // sužebimas tik tada, kai pertvarkymas iš tikrųjų perkėlė valdiklius
end;
// ... valdome formą ...
finally
Runtime.Free;
end;
end;
Kur vykdymo aplinka stoja ir kodėl tai sako garsiai
Vykdymo aplinka sąmoningai nėra universalus XFA scenarijų variklis. DispatchEvent veiklas enter ir exit apdoroja natyviai perkeliant fokusą, o dėl kiekvienos kitos veiklos, nešančios scenarijų, atsisako su konkrečia, stabilia diagnostika vietoje vaidinimo: scenarijai, minintys addInstance, removeInstance ar instanceManager, grąžina XFA runtime does not support event-driven instance mutation, scenarijai, liečiantys .presence, grąžina atitinkamą presence atitikmenį, o visa kita grąžina XFA runtime does not support this event script. Nuspėjamas atsisakymas, nuo kurio galima šakoti logiką, geriau už dalinę emuliaciją, kuri veikia jūsų pavyzdiniame faile, o kliento faile nukrysta
Gijų modelis toks pat tiesmukas: vienas vykdymo aplinkos egzempliorius priklauso vienai gijai, be jokio vidinio užrakinimo, nes išdėstymo variklis pasiekia atgal į programos matavimo atgalinius kvietimus, o užraktas aplink tai — aklavietė, laukianti persipiešimo. Turtingas turinys laukuose laikosi tos pačios konservatyvios linijos kaip ir kitur bibliotekoje, kur exData duomenys apdorojami taip, kaip aprašyta XFA exData raiškusis tekstas ir hipersaitai, o parašo ir mygtukų valdikliai grįžta kaip ReadOnly, nepalaikomi UI tipai pasirodo kaip xwkUnsupported, o ne kaip redaguojamas teksto laukelis, kuris tyliai praranda duomenis
Sudėjus viską, tai dirbantis atsakymas į dinaminę XFA Delphi: laikykite DOM gyvą, paverskite kiekvieną redagavimą transakcija, kuri arba įgyvendinama iki galo, arba nepalieka nieko, apribokite kiekvieną etapą ir aiškiai sakykite, kas neįeina į taikymo sritį. Jei vertinate tai žalos, mokesčių ar pašalpų procesams, XFA vykdymo aplinka tiekiama kaip HotPDF Delphi PDF component dalis, kartu su AcroForm, suplokštinimo ir atvaizdavimo keliais, kurių tie projektai paprastai ir prireikia kartu