PDFium Component, v3.125.2 ve sonrasıyla gelen Windows V8 runtime'ı pdfium.v8.dll'i koşturduğunda, düzenlenmiş XFA form değerlerini kaydetme ve yeniden açma boyunca harfi harfine saklar. Eski runtime'lar field değerlerine satır beslemeleri ekliyor, emoji'yi ilgisiz bir BMP karakterine kısaltıyor, tek-stream XFA kayıtlarını sessizce atlıyor ve başarısız bir son yazımı yutabiliyordu. Tek bir yeniden açma belirtisi hiçbir kütüphane kusuru değildir: kök subform'unda restoreState="auto" bulunmayan dinamik bir form, layout'unu şablondan yeniden kurar
Bunun hata bildirimlerinin hepsi birbirine benziyordu. Bir müşteri, bir Delphi görüntüleyicide bir XFA talep formunu doldurur, kaydeder, yeniden açar ve bir şey hafifçe şaşmıştır. Boş bir yorum kutusu artık bir boş satır tutar ve ikinci kayıttan sonra ikisini tutar. Bir emojiyle yazılmış bir ad, özel kullanım alanı bir glifle geri gelir. Kimse hata almaz; bu hataları pahalı kılan da budur: kayma, haftalar sonra bir başkasının dışa aktarımında görünür
Bir XFA formu kaydedilip yeniden açıldığında ne ters gider?
Yerel XFA kayıt yolundaki dört ayrı kusur değer kaymasına neden oldu ve her biri, başarılı görünen bir kaydın arkasına saklandı. İkisi serileştirmeden, biri tek-stream saklama düzeninden, biri de PDF yazıcısının kendisinden geldi. Tablo her belirtiyi nedenine ve PDFium Component'in düzelttiği sürüme eşler
| Yeniden açma sonrası belirti | Neden | Düzeltildiği sürüm |
|---|---|---|
| Boş alan bir satır beslemesi tutar; değerler her kayıtta bir satır sonu büyür | Her iki XFA yazıcısı da başlangıç etiketlerinden sonra layout satır sonları ekledi | v3.125.2, pdfium.v8.dll |
| U+1F642, U+F642 olarak geri gelir ya da emoji form paketinden kaybolur | Çözümlemede 16 bitlik wchar_t kısaltması; form serileştiricide surrogate filtrelemesi | v3.125.2, pdfium.v8.dll |
| Tek-stream XFA belgesindeki düzenlemeler sadece yoktur | Yerel kayıt stream düzenini reddetti ama dönüş değeri yok sayıldı | v3.125.2; yorumlar ve işlem talimatları v3.126.0'dan beri korunur |
| Kayıt başarı bildirdiği hâlde kırpılmış dosya | Yazıcı çoktan başarı döndürdükten sonra son tamponlu yazım başarısız oldu | v3.125.2 V8 runtime; v3.125.3 sıradan pdfium.dll |
| Üç sayfalık dinamik form iki sayfa olarak yeniden açılır | Kök subform restoreState="auto" istemiyor | Form yazarlığı, kütüphane kusuru değil |
Önceki yazılar, XFA field düzenlemelerinin PDFium ile hiçbir şekilde kalıcı kılınamayacağı sonucuna varmıştı; o günün runtime'ları için doğruydu. Daha yeni V8 runtime XFA değerlerini yerel olarak kaydeder; dolayısıyla canlı formda yapılan bir düzenleme, sizin tarafınızda paket cerrahisi olmadan kaydedilen datasets paketine ulaşır
Hangi PDFium runtime'ı XFA değerlerini kaydeder?
XFA kayıt sadakati Delphi sarmalayıcısına değil yerel DLL'ye bağlıdır; dolayısıyla ilk kontrol, sürecinizin gerçekte hangi runtime'ı yüklediğidir. PDFium Component, mimari başına iki Windows derlemesi taşır: V8'siz ve XFA'sız derlenen sıradan pdfium.dll ile JavaScript motorunu ve XFA form runtime'ını taşıyan pdfium.v8.dll. Yalnızca pdfium.v8.dll bir XFA formu koşturabilir; dolayısıyla burada anlatılan her XFA düzeltmesi orada yaşar — v3.125.2'deki yeniden derlenmiş Win32 ile Win64 V8 kütüphanelerinden başlayarak
Son-yazım düzeltmesi genel PDF yazıcı kodudur; dolayısıyla sıradan belgeler için de önemlidir. v3.125.3, sıradan pdfium.dll kütüphanelerini aynı tamiri taşımaları için yeniden derledi. Paylaşılan kaynak, paylaşılan davranışın kanıtı değildir: binary yeniden derlenene dek eski DLL eski hatayı taşır
İkinci bir tuzak yükleyicide oturuyordu. v3.125.2 öncesinde EnableV8Engine'i True yapmak, bağlamayı varsayılan pdfium.v8.dll adını seçmeye ve LibraryName'deki tam yolu yok saymaya itiyordu. Taze dağıtılmış bir runtime'ı gösteren bir uygulama, başka bir klasördeki eski bir kopyayı yüklemeye devam edebiliyordu. v3.125.2'den beri bir dizin içeren bir LibraryName, iki motor modunda da tam olarak o dosyayı seçer ve eksik bir yol, başka bir paketli kütüphaneye düşmek yerine başarısız olur
uses
System.SysUtils, PDFium;
procedure SelectXfaRuntime;
begin
// LibraryName'deki bir dizin tam bu dosyayı sabitler (v3.125.2 ve sonrası);
// dosya eksikse yükleme, geri düşmek yerine exception yükseltir
{$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; // ilk kayıtta değil, başlangıçta başarısız ol
end;
Bir belgeyi açtıktan sonra TPdf.XFA dosyanın XFA içerdiğini, TPdf.XfaRuntimeAvailable ise yüklenen DLL'nin onu gerçekten çalıştırabildiğini söyler. Statik ile dinamik formları ayırt etmeniz de gerekiyorsa TPdf.FormType, ftXfaFull ya da ftXfaForeground döndürür; XFA formlarını saptama ve Delphi'de XFA paketlerini çıkarma yazısı o yoklamayı ayrıntılı işler
Kaydedilen XFA field'ları neden fazladan satır beslemeleri kazanır?
Kaydedilen XFA field'ları satır beslemeleri kazandı, çünkü iki yerel XFA yazıcısı da — genel XML element yazıcısı ile form paketi serileştiricisi — çıktılarını başlangıç etiketlerinden sonra bir satır sonuyla güzel-baskılıyordu. Çoğu XML'de o boşluk kozmetiktir. XFA verisinde değildir: datasets paketi yeniden ayrıştırıldığında <Comments> ile </Comments> arasındaki metin, satır sonu dâhil, field değeridir. Bu yüzden boş bir alan tek bir LF tutarak yeniden açılıyordu ve her sonraki kaydet-aç döngüsü bir tane daha ekleyebiliyordu
Bariz tamir olan, yüklemede değerleri kırpmak yanlış olurdu. Kullanıcılar XFA field'larına baştaki boşluklar, sondaki boşluklar ve kasıtlı çok satırlı metin yazar ve bir adres bloğu ya da sabit genişlikli bir kod bayt bayt hayatta kalmak zorundadır. v3.125.2 düzeltmesi bu yüzden yalnızca serileştiricinin etiketlerin çevresinde kendisinin sentezlediği boşluğu kaldırır. Kullanıcı değerleri, var olan metin node'ları ve CDATA bölümleri dokunulmadan geçer; dolayısıyla " indented" girintili kalır ve kasıtlı boş bir alan boş kalır
Bir emoji neden başka bir karakter olarak geri gelir?
Bir emoji yanlış geri geldi, çünkü Windows'ta wchar_t 16 bit genişliğindedir ve iki çözümleme yolu tam bir Unicode skaler değerini tek bir wchar_t'de saklıyordu. UTF-8 stream decoder'ı ile 🙂 gibi sayısal karakter referanslarının parser'ı ikisi de bunu yaptı. U+1F642, hafifçe gülümseyen yüz, 16 bit'e sığmaz; dolayısıyla yüksek bitler döküldü ve yerine U+F642 belirdi: çoğu fontun kutu ya da hiçbir şey olarak render ettiği Private Use Area'da bir kod noktası
Form serileştiricisinin sorunu tersydu. Karakterleri birer wchar_t filtreliyordu; yalnız başına geçersiz olan iki surrogate kod birimi görüyor ve ikisini de atıyordu; dolayısıyla emoji form paketinden tümüyle kayboluyordu. v3.125.2'de decoder her skaler değerini tümden tüketir ve düzgün bir surrogate çifti boşaltır. Yalnızca bir çıkış yuvası kaldığında alçak surrogate'i bekletir ve o birim hâlâ tampondayken stream sonu bildirmez. Okuma blokları arasında bölünmüş bir UTF-8 dizisi atılmak yerine sonraki okumaya taşınır. Form exporter'ı artık geçerli surrogate çiftlerini birlikte tutar ve sayısal karakter referansları da doğru çiftler üretir
Latin-1 test verisi bunların hiçbirini göstermez; dolayısıyla her XFA round-trip testinin en azından bir tamamlayıcı düzlem karakteri gerekir
Tek-stream XFA ve kimsenin görmediği kayıt başarısızlıkları
Tek-stream bir XFA belgesi düzenlemelerini kaybetti; çünkü yerel kayıt yardımcısı o saklama düzenini reddediyor ve çağıranı başarısızlığı yok sayıyordu. ISO 32000-1 §12.7.8, etkileşimli form sözlüğünün /XFA girdisinin ya paket adları ile stream'lerden oluşan bir dizi ya da bütün XDP belgesini tutan tek bir stream olmasına izin verir. Paket dizileri sıradan durumdur ama tek stream'ler tamamen meşrudur ve PDF kaydı, form verisi eski değerlerinde kalırken hiçbir şey olmamış gibi tamamlanıyordu
v3.125.2'den beri V8 runtime, desteklenen tek-stream alt kümesini işler. Önce iki canlı paketi — datasets ile form — bir staging alanına export eder ve doğrular; ancak ondan sonra özgün XDP içindeki eşleşen paketleri değiştirir. Öteki paketler ile kök namespace bildirimleri korunur. Staging başarısız olursa kalıcı XFA stream'ine hiç dokunulmaz ve belge değişiklik işaretini korur
XML yorumları ile işlem talimatları ekstra özen istedi; çünkü dahili XML DOM onları düşürür. v3.125.2'de varlıkları, kaydı içerik sessizce kaybetmek yerine tümüyle başarısız kılıyordu. v3.126.0 onları korur: ayrıştırmadan önce her yorum ya da işlem talimatı, özgün metinde hiçbir yerde geçmeyen bir önekten kurulan bir işaretçiyle değiştirilir. Canlı paketler değiştirildikten sonra özgün token geri yüklenip stream yazılmadan önce her işaretçinin tam bir kez görünmesi zorunludur. Değiştirilen paketlerin dışındaki token'lar bu yüzden metinlerini ve sıralarını korur; prologdaki, template'teki ve öteki paketlerdeki token'lar dâhil
Bazı girdiler hâlâ bilerek reddedilir ve her red, açık bir kayıt başarısızlığıdır:
- Canlı
datasetsya daformpaketlerinin içindeki yorumlar ya da işlem talimatları; çünkü özgün konumları taze export edilmiş içeriğe haritalanamaz - DTD bildirimleri ile XMLDSig imzaları; çünkü XDP'yi yeniden yazmak bir XML imzasını geçerli tutamaz
- Geçersiz UTF-8 ya da UTF-16 kodlaması, eksik etiketler, geçersiz karakter referansları, bilinmeyen entity'ler ve hatalı kurgulu işlem talimatları; sessizce onarılmak yerine reddedilirler
Tek-stream çıktısı UTF-8'dir ve XML içerik modelini korur; özgün bayt düzenini ya da kodlama bildirimini değil
Son kusur, XFA'nın altında oturuyordu. Yerel dosya yazıcısı çıktıyı 32 KB'lik bloklarda tamponluyor ve son kısmi bloğu yalnızca yıkıcısında, belge yazıcısı çoktan başarı bildirdikten sonra boşaltıyordu. O son bloktaki bir disk-dolu ya da I/O hatası çağırana görünmüyordu. V8 runtime'da v3.125.2'den, sıradan runtime'da v3.125.3'ten beri o son boşaltım kayıt sonucunun parçasıdır ve XFA değişiklik işareti yalnızca gerçek bir başarıdan sonra temizlenir. Delphi tarafında TPdf.SaveAs(const FileName: string; Option: TSaveOption = saNone; PdfVersion: TPdfVersion = pvUnknown): Boolean, hedefin yanındaki geçici bir dosyaya yazar ve onu yalnızca kayıt True döndürdüğünde yerine taşır; dolayısıyla başarısız bir kayıt önceki dosyayı olduğu gibi bırakır
Dinamik bir XFA formu neden daha az sayfayla yeniden açılır?
Dinamik bir XFA formu, kök subform'u restoreState="auto" bildirmediğinde daha az sayfayla yeniden açılır; bu da bir PDFium Component kusuru değil form yazarlığı kararıdır. XFA 3.3'te kök subform'daki restoreState varsayılan olarak manual'dır. manual altında XFA işlemcisi, kaydedilmiş form paketinden yalnızca sınırlı durumu geri yükler ve gerisini yazarın script'lerine bırakır. Kaydedilen field değerleri ile yinelenen-subform instance sayıları yine de geri gelir ama çalışma zamanında set edilen geometrik property'ler gelmez
Bunu açığa vuran durum, script'i bir subform'u h="450pt"'ye büyüten üç sayfalık bir formdu. Kaydedilen form paketi yeni yüksekliği, değerleri ve instance sayılarını tutuyordu. Ama yeniden açılışta layout şablon yüksekliklerinden yeniden kuruluyordu ve form iki sayfaya yeniden akıyordu. Runtime haklıydı: şablon otomatik geri yüklemeyi hiç istememişti. Onu kök subform'da bildirmek yeniden açmayı düzeltir:
<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">
<!-- field'lar; script'ler h'yi değiştirebilir ya da çalışma zamanında instance ekleyebilir -->
</subform>
</subform>
</template>
Şablonun sahibi değilseniz görüntüleyicide etrafını yamamayın: manual moduna bel bağlayan bir form, durumunu kendi script'lerinin yeniden kurmasını bekler. Kullanıcı yazarken canlı yeniden sayfalama ayrı bir konudur; PDFium Component'in dinamik XFA sayfa sayılarını ve taşınan field'ları nasıl izlediği yazısında işlenir
Delphi'de bir XFA kaydını nasıl doğrularsınız?
Güvenilir tek XFA kayıt kontrolü, kaydedilen dosyayı taze bir TPdf instance'ında yeniden açıp saklanan veriyi geri okumaktır. TPdf.GetXfaDatasets, datasets paketini belgede saklandığı hâliyle döndürür; canlı XFA veri modelini değil, dolayısıyla kaydetmeden önce çağırmak eski değerleri gösterir. Yeniden açtıktan sonra yazılanı harfi harfine gösterir. Tek-stream bir belgenin ayrıca adlandırılmış paketleri yoktur: PDFium bütün XDP'yi boş adlı tek bir paket olarak bildirir; dolayısıyla GetXfaPacketByName('datasets') ile GetXfaDatasets hiçbir şey döndürmez ve fallback, bütün stream'i GetXfaFormPackets üzerinden okur
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-dizisi düzeni
if Length(Bytes) = 0 then
begin
Packets := Pdf.GetXfaFormPackets; // tek stream: adlandırılmamış tek 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); // kaydedilen XDP çıktısı UTF-8'dir
finally
Pdf.Free;
end;
end;
Kayıt rutini sonra bekleyen düzenlemeyi işler, SaveAs sonucunu kontrol eder ve yeniden açılan değeri karşılaştırır. TPdf.ClearFormFieldFocus form odağını öldürür; PDFium'un odaklı field'ın düzenleme tamponunu işlediği an budur. TPdf.SetFocusedFormFieldText(const Value: WString): Boolean odaklı field'ı programatik olarak doldurur ama wrapper'ın FocusFormField üzerinden izlediği bir odağa bel bağlar; o da widget annotation'larını gezer. Dinamik bir XFA sayfasında normalde hiçbiri yoktur; dolayısıyla orada metin çoğunlukla TPdfView'deki klavye girişiyle gelir ve izlenen hiçbir field odaklı değilken fonksiyon False döndürür
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
// İsteğe bağlı scriptli doldurma; False, izlenen hiçbir field'ın odaklı olmadığı demektir
if (Pdf.FocusedFormFieldIndex >= 0) and
not Pdf.SetFocusedFormFieldText(Expected) then
raise EPdfError.Create('Could not write the focused field');
Pdf.ClearFormFieldFocus; // düzenleme tamponunu işle
if not Pdf.SaveAs(FileName) then // son boşaltımı içerir (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;
Substring testini bir duman testi sayın. Boş bir element <Tag/> diye serileştirilebilir, attribute'lar data elementlerinde görünebilir ve & ile <'nin ötesindeki kaçış bir serileştirici seçimidir. Üretim kontrolleri için yeniden açılan XML'i gerçek bir XML parser'ıyla yükleyin ve bağlı data elementinin metin node'unu karşılaştırın. Kontrolü üst üste iki kez de koşturun; çünkü satır sonu kusuru tam biçimini yalnızca ikinci nesilde gösterdi
Hızlı başvuru: XFA kayıt sadakati kontrol listesi
- XFA formları için v3.125.2 ve sonrasından
pdfium.v8.dll'i, sıradanpdfium.dlliçin v3.125.3 ve sonrasını dağıtın; böylece son-yazım düzeltmesi ikisinde de olur LibraryName'i tam bir yola işaretletin veEnableV8Engine'i True'ya set edin; eksik bir yol, başka bir kopyayı yüklemek yerine başarısız olur- Belgeyi açtıktan sonra
TPdf.XFAileTPdf.XfaRuntimeAvailable'i teyit edin - Odaklı field işlenmiş olsun diye
SaveAs'ten önceClearFormFieldFocusçağırın SaveAs'in Boolean sonucunu asla yok saymayın; False bir sonuç önceki dosyayı yerinde bırakır- Yeni bir
TPdf'de yeniden açıpGetXfaDatasetsokuyarak doğrulayın; tek-stream XFA içinGetXfaFormPackets'e düşerek - Boş değerlerle, baştaki boşluklarla, çok satırlı metinle,
&ile ve bir tamamlayıcı düzlem karakteriyle, iki kayıt nesli boyunca test edin - DTD'ler, XMLDSig ve tek-stream XFA'nın canlı paketleri içindeki yorumlar için açık kayıt başarısızlıkları bekleyin
- Dinamik bir form, yeniden açılışta çalışma zamanı geometrisini kaybederse kütüphaneden şüphelenmeden önce kök subform'da
restoreState="auto"olup olmadığına bakın
XFA runtime'ının bir ana uygulamadan beklediği callback yapısı için FPDF_FORMFILLINFO sürüm 2 ve Delphi'de XFA ABI'si yazısına bakın. V8 runtime, Delphi ile C++Builder sarmalayıcısı ve görüntüleyici kontrolü, Win32 ile Win64 için her iki Windows runtime'ını da içeren Delphi ve C++Builder için PDFium Component'in parçalarıdır