HotPDF, Delphi içinde dinamik XFA formlarını TXFAWidgetRuntime aracılığıyla doldurur: her alan düzenlemesini tek bir işlem olarak ele alan, konaktan bağımsız bir widget katmanı — anlık görüntü, doğrulama, hesaplama, yeniden akış, ardından bütün olarak yayınlama ya da geri alma. Kendi VCL ya da FMX konağınızın içinde tek iş parçacıklı çalışır, Acrobat kurulu olmasını gerektirmez ve herhangi bir ayırma yapmadan önce her bütçeyi zorlar
Bu senaryo, devlet ya da sigortacılık işine belge yazılımı göndermiş herkese tanıdıktır. Bir hasar formu ya da vergi beyannamesi, sayfa içeriği tek bir "Please wait... if this message is not eventually replaced" uyarısı olan bir PDF olarak gelir ve her gerçek alan, yalnızca Adobe Acrobat ürününün işlediği bir XFA paketinde yaşar. Kullanıcılarınız onu uygulamanızın içinde doldurmak ister. Rasterleştirerek de kurtulamazsınız; çünkü form veri girildikçe satır büyütür ve üçüncü satırdan sonraki yerleşim, dosyayla gelen yerleşim değildir
Dinamik XFA neden hâlâ çözmeye değer bir sorun
Dinamik XFA kalıcıdır; çünkü konuşlandırılmış formlar, onları taşıyan biçimden daha uzun yaşar. ISO 32000-1 §12.7.8, XFA biçimini AcroForm sözlüğünde XDP paket akışı tutan bir /XFA girdisi olarak tanımlar; ISO 32000-2 ise tüm mekanizmayı kullanımdan kaldırır. Kullanımdan kaldırma onu yol haritasından sildi, sahadan değil; XFA 3.3 belirtimine göre yazılmış formlar hâlâ düzenleniyor ve hâlâ hukuken bağlayıcı. Statik XFA sıradan widget ek açıklamalarına indirgenebilir ve HotPDF, ApplyXFAAsAcroForm çağırdığınızda bunu yapar; ödünleşimler XFA formlarını AcroForm alanlarına düzleştirme konusunda ele alınıyor. Dinamik XFA farklı bir yapıdadır: occur aralıkları, büyüyebilen metin ve calculate betikleri, alan kümesini verinin bir işlevi haline getirir; bu yüzden kullanıcı yazmayı bitirene dek düzleştirilecek sabit bir ek açıklama listesi yoktur. TXFAWidgetRuntime tam da bu boşluğu doldurur: XFA DOM yapısını canlı tutar, kabul edilen her düzenlemeden sonra yerleşimi yeniden hesaplar ve konağınıza çizip isabet testi yapacağınız konumlandırılmış widget dizisini düz bir dizi olarak verir
Çalışma zamanı konak uygulamaya ne verir?
Size geometri ve durum verir; bir UI araç seti varsayan hiçbir şey vermez. TXFAWidgetRuntime, WidgetCount ve Widgets[I] üzerinden ID, Name, Kind, PageIndex, PDF punto cinsinden Bounds, Value, EditValue ile Focused, Editing, ReadOnly, Valid bayraklarını taşıyan TXFAWidgetState kayıtları sunar; boyama, imleç çizimi ve klavye yönlendirmesi ise sizin kodunuzda kalır. Widget kimliği kararlı ve sıralıdır: her widget, name[n] biçiminde bir ID alır; burada n, o alan adının yerleşim sırasındaki önceki geçiş sayısını sayar, böylece yinelenen bir alt formun ikinci satırı amount[1] olur. Yeniden kurulumdan sağ çıkan şey bu kimliktir ve FocusWidget, BeginEdit, DispatchEvent ile HitTest yöntemlerinin hepsinin konuştuğu dil budur. Bir THotPDF örneğinde zaten açık olan bir belge için CreateLoadedXFAWidgetRuntime, XDP paketlerini çıkarır, ilk sayfa kutusunu yerleşim sayfa boyutu olarak alır ve dosyada hiç XFA yoksa nil döndürür
var
Pdf: THotPDF;
Runtime: TXFAWidgetRuntime;
WidgetID: AnsiString;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('claim-dynamic.pdf');
Runtime := Pdf.CreateLoadedXFAWidgetRuntime; // /XFA yoksa nil
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)]));
// sayfa uzayında isabet testi, en üstteki widget kazanır
if Runtime.HitTest(0, 120.0, 96.0, WidgetID) then
Runtime.BeginEdit(WidgetID);
finally
Runtime.Free;
end;
finally
Pdf.Free;
end;
end;
Bir alan işlendiğinde neyin atomik olması gerekir?
Düzenlemenin dokunabileceği her şey; bu da alan değerinden hayli fazlasıdır. CommitEdit, bir şey yazmadan önce CaptureSnapshot çağırır ve bu anlık görüntü dört şeyi kapsar: TXFADocument.SaveToBytes çıktısı olan serileştirilmiş XFA DOM, TXFAWidgetState etkileşim kayıtlarının tam dizisi, LastCalculationPasses ve LastReflowPasses sayaçları ile geçerli Warnings.Count değeri. Yalnızca düğüm değerlerini kaydetmek cazip bir kestirmedir ve yanlıştır; çünkü bir calculate betiği ya da çözümlenememiş bir bağlama, EnsureValueNode çağırarak düzenleme başladığında var olmayan veri düğümlerini somutlaştırabilir. Yalnızca değer geri yüklemesinin bunları kaldırma yolu yoktur; bu yüzden reddedilen bir düzenleme, datasets paketinde kalıcı yapısal artık bırakır. İşleme dizisinin kendisi katıdır: aday değeri yaz, düzenlenen alan için validate çalıştır, calculate işlemini sabit noktaya kadar çalıştır, ardından yerleşim kararlı olana dek yeniden akış yap. Herhangi bir aşamadaki herhangi bir hata FailAndRestore üzerinden geçer; bu da anlık görüntü baytlarını yeni bir TXFADocument içine geri yükler, widget listesini yeniden kurar, kayıtlı etkileşim durumlarını yeniden uygular, sayaçları sıfırlar ve Warnings listesini anlık görüntü uzunluğuna kırpar. LastDiagnostic hata durumunda nedeni tutar; geri yükleme kendisi hata fırlattığında ise patolojik durum olarak XFA transaction rollback failed değişmezini tutar
function EditAmount(Runtime: TXFAWidgetRuntime;
const AWidgetID: AnsiString; const AText: UnicodeString): Boolean;
var
Current: UnicodeString;
begin
Result := False;
if not Runtime.BeginEdit(AWidgetID) then
Exit; // salt okunur ya da böyle bir widget yok
Current := Runtime.Widgets[Runtime.FocusedIndex].EditValue;
if not Runtime.ReplaceSelection(0, Length(Current), AText) then
begin
Runtime.CancelEdit; // geçersiz aralık ya da bölünmüş vekil
Exit;
end;
Result := Runtime.CommitEdit; // ya hep ya hiç
if not Result then
// belge, widget listesi, sayaçlar ve uyarılar zaten düzenleme öncesi
// duruma döndü; odaklı widget yalnızca geçersiz olarak işaretlenir
ShowMessage(Runtime.LastDiagnostic);
end;
ReplaceSelection kendi notunu hak eder; çünkü bozuk girdinin reddedilmesinin en ucuz olduğu yer burasıdır. UTF-16 vekil çiftini bölen bir seçimi reddeder, eşleşmemiş yüksek ya da düşük vekil içeren yedek metni reddeder ve MaxValueChars değerinden uzun her sonucu reddeder. Bunu tuş vuruşu katmanında yakalamak, işlem mekanizmasının yarım yazılmış bir astral düzlem karakterini asla geri sarmak zorunda kalmaması demektir
Özel bir listeye yeniden kur, tek takasla yayınla
Widget yeniden kurulumu asla yarım bitmiş gözlemlenemez; bu yüzden RebuildWidgets tamamen ayrı, sahipli bir TObjectList kurar ve sonda tek bir atamayla yerine takar. Nedeni estetik değildir: TXFALayoutEngine.ComputeLayout, yeniden kurulum sürerken çalışır ve sağladığınız MeasureText işlevi üzerinden konak koduna geri çağrı yapar; widget sınırına ulaşıldığında EXFAWidgetRuntimeError fırlatabilir. Çalışma zamanı canlı listesini yerinde değiştirseydi, iki yol da konağın elinde kısmen eski kısmen yeni yerleşimden oluşan ve geri alınmak üzere olan bir belgeye DataNode işaretçileri taşıyan bir liste bırakırdı. Yeniden akış yakınsamasına sonra LayoutSignature karar verir: widget sayısı artı her ID, sayfa dizini ve dört ondalığa yuvarlanmış sınırlayıcı kutudan kurulan bir dizge. CommitEdit yeniden kurar, imzaları karşılaştırır ve ardışık iki imza eşleşene ya da geçiş bütçesi tükenene dek yineler. İmza hiç değişmediyse LastReflowPasses 0 kalır; yalnızca değer düzenlemesini formu gerçekten büyüten düzenlemeden böyle ayırt edersiniz. Etkileşim durumu her yeniden kurulum boyunca widget ID ile taşınır; böylece odak ve süren düzenleme bir satır eklenmesinden sağ çıkar
Bağlı bir alan neden yanlış kaydı okusun?
Çünkü betik veri bağlamı olmadan çalıştı. Açık bir <bind match="dataRef" ref="$record.actual"/> taşıyan alan ile aynı veri düğümünün adını taşıyan alan, tek bir değere işaret eden iki farklı widget olur; <occur max="2"/> içeren yinelenen bir alt form ise adı paylaşan ve yalnızca ait oldukları veri satırıyla ayrılan birden çok widget üretir. Doğrulama ve hesaplamayı belge köküne karşı değerlendirirseniz hepsi this değerini tüm datasets paketindeki ilk eşleşen düğüme çözümler; böylece ikinci satır sessizce birinci satırı doğrular. HotPDF bunu, yerleşim ürettiğinde çözümlenmiş DataNode değerini her widget girdisinde saklayarak ve ardından o düğümü HPDFXFAEvaluateFieldScript çağrılarının ikisinden de — xfskValidate ve xfskCalculate için — geçirerek önler. Aynı bağlam, bir hesaplama henüz var olmayan bir bağlamayı hedeflediğinde EnsureValueNode işlevinin hangi düğüme karşı oluşturulacağına da karar verir; hiçbir bağlama çözümlenemediğinde işlem, yanlış satıra yazmak yerine XFA calculation target is not bound ile temiz biçimde başarısız olur. Bu betiklerin arkasındaki FormCalc anlambilimi, AcroForm belgelerinin AcroForm format ve calculate betikleri konusunda anlatılan eylemlerden aldıklarına benzer; ancak buradaki çözümleme kuralları alan adı kapsamlı değil XFA kapsamlıdır
Bütçeler yan etkilerden önce denetlenir, sonra değil
Çalışma zamanındaki her sınır bir ön koşuldur; çünkü ayırma gerçekleştikten sonra uygulanan bütçe bütçe değildir. TXFAWidgetRuntimeOptions.Default, MaxWidgets için 10000, MaxValueChars için 1048576, MaxCalculationPasses için 16 ve MaxReflowPasses için 4 değeriyle gelir; varsayılan TXFAFormScriptOptions ise MaxOperations için 100000 ve MaxElapsedMilliseconds için 500 taşır. Altta XFA DOM kendi TXFADOMLimits değerlerini uygular: açılmış girdi ve çıktıda 128 MB üst sınırı, en çok 1024 birleştirilmiş paket, 1000000 düğüm ve 256 iç içelik derinliği. İki ayrıntı sayıların kendisinden daha önemlidir. Birincisi, betik bütçeleri betik başına değil işlem genelindedir: CommitEdit tek bir kalan işlem sayacı ve tek bir tekdüze son tarih tohumlar; her validate ve calculate çağrısı aynı sayacı azaltır ve yalnızca kalan milisaniyeleri alır, böylece iki yüz hesaplamalı alanı olan bir form tam 500 ms süreyi iki yüz kez harcayamaz. İkincisi, son tarih enjekte edilebilir bir MonotonicMilliseconds işlevinden gelir; geçen süre davranışını yoğun bir derleme aracısında yazı tura yerine test paketinde tekrar üretilebilir kılan da budur
var
Options: TXFAWidgetRuntimeOptions;
Runtime: TXFAWidgetRuntime;
begin
Options := TXFAWidgetRuntimeOptions.Default;
Options.MaxWidgets := 2000; // varsayılan 10000
Options.MaxCalculationPasses := 8; // varsayılan 16
Options.MaxReflowPasses := 2; // varsayılan 4
Options.ScriptOptions.Limits.MaxOperations := 20000; // tüm işlem
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; // yalnızca yeniden akış widget konumlarını gerçekten değiştirdiğinde tetiklenir
end;
// ... formu sür ...
finally
Runtime.Free;
end;
end;
Çalışma zamanının durduğu yer ve bunu neden açıkça söylediği
Çalışma zamanı bilerek genel amaçlı bir XFA betik motoru değildir. DispatchEvent, enter ve exit etkinliklerini odağı taşıyarak yerel olarak işler; betik taşıyan diğer her etkinlikte ise rol yapmak yerine belirli ve kararlı bir tanıyla reddeder: addInstance, removeInstance ya da instanceManager sözcüklerini içeren betikler XFA runtime does not support event-driven instance mutation döndürür, .presence değerine dokunan betikler presence eşdeğerini döndürür ve geri kalan her şey XFA runtime does not support this event script döndürür. Üzerinde dallanabileceğiniz öngörülebilir bir ret, örnek dosyanızda çalışıp müşterininkinde ayrışan kısmi bir öykünmeden iyidir
İş parçacığı modeli aynı derecede nettir: bir çalışma zamanı örneği tek bir iş parçacığına aittir ve dahili kilitleme yoktur; çünkü yerleşim motoru konak ölçüm geri çağrılarına geri uzanır ve bunun etrafındaki bir kilit, yeniden çizim bekleyen bir kilitlenmedir. Alanların içindeki zengin içerik, kitaplığın geri kalanıyla aynı tutucu çizgiyi izler: exData yükleri XFA exData zengin metin ve köprüler konusunda anlatıldığı gibi işlenir; imza ve düğme widget türleri ReadOnly olarak döner, desteklenmeyen UI türleri ise veriyi sessizce kaybeden düzenlenebilir bir metin kutusu yerine xwkUnsupported olarak yüzeye çıkar
Bütün olarak bu, Delphi içinde dinamik XFA için uygulanabilir bir yanıttır: DOM yapısını canlı tutun, her düzenlemeyi ya tamamen yerleşen ya da geride hiçbir şey bırakmayan bir işlem yapın, her geçişi sınırlayın ve kapsam dışında olanı açıkça belirtin. Bunu bir hasar, vergi ya da sosyal yardım iş akışı için değerlendiriyorsanız, XFA çalışma zamanı HotPDF Delphi PDF component paketinin parçası olarak gelir; bu projelerin genelde birlikte ihtiyaç duyduğu AcroForm, düzleştirme ve işleme yollarıyla birlikte