PDFium Component, statik XFA formlarını Delphi tarafından FPdfDocumentXfaFlatten ünitesindeki FlattenPdfXfaToAcroForm ile sıradan AcroForm alanlarına dönüştürür. Fonksiyon XFA template ile datasets paketlerini okur, mutlak konumlu her alanı kendi appearance stream'ine sahip bir AcroForm terminal alanına eşler ve sonucu artımlı bir revizyon olarak ekler; dönüştürülen PDF böylece XFA'yı hiç desteklememiş görüntüleyicilerde normal bir form olarak açılır
Motivasyon genellikle yol haritası maddesi değil bir destek kaydıdır. Devlet ya da sigorta formu, onu XFA olarak kaydeden bir tasarımcıda yazılmıştır; bir masaüstü okuyucuda çalışır, her başka yerde ise belgenin daha yeni bir görüntüleyici gerektirdiğini söyleyen boş bir sayfa olarak açılır. XFA, PDF 2.0'da (ISO 32000-2) kullanımdan kaldırıldı; tarayıcılar ile mobil görüntüleyiciler onu hiç uygulamadı ve PDFium'un kendisi XFA'yı yalnızca V8 etkin derlemeyle render eder. Düzeni tasarım zamanında sabitlenen statik XFA formları, AcroForm alanları olarak yeniden kurulmaya yetecek geometriyi template içinde taşır ve bu dönüştürücünün ele aldığı dilim tam olarak budur
FlattenPdfXfaToAcroForm gerçekte ne üretir?
FlattenPdfXfaToAcroForm, değişmemiş bir kaynak kopyasına eklenen bayt düzeyinde bir AcroForm revizyonu üretir; özgün dosyanın yeniden yazımını asla. PDFium form alanı oluşturmak için genel bir API açmaz; bileşen bu iş için hiç PDFium'dan geçmez: etkin revizyonu kendi nesne okuyucusuyla okur, alan sözlüklerini, widget açıklamalarını, appearance XObject'lerini ve bir /Helv font kaynağını ham PDF nesneleri olarak kurar ve ISO 32000-1 §7.5.6 anlamında seyrek bir artımlı güncellemeyle serileştirir
Yeni /AcroForm sözlüğü /Fields, /DA ile /DR taşır ve hiçbir /XFA girdisi taşımaz. Kaynak AcroForm'unu dolaylı bir nesne olarak tutuyorsa o nesne yerinde yeniden yazılır; satır içi bir sözlük kataloğun içinde değiştirilir. Her iki durumda da etkin revizyon artık XFA paketlerini işaret etmez; her iki modeli de anlayan bir görüntüleyici, XFA formunu dizmeye çalışmak yerine AcroForm alanlarını açar. Özgün XFA baytları hâlâ önceki revizyondadır — artımlı güncellemenin anlamı budur — ama geçerli revizyonda onlara referans veren hiçbir şey yoktur
uses
System.SysUtils, System.Classes, System.IOUtils, FPdfDocumentXfaFlatten;
procedure ConvertXfaForm(const SourceFile, TargetFile: string);
var
Source: TFileStream;
Options: TPdfXfaFlattenOptions;
OutputPdf: TBytes;
Report: TPdfXfaFlattenReport;
Fields: TPdfXfaFlattenFields;
begin
Options := TPdfXfaFlattenOptions.Default;
Options.MaxFields := 1024; // varsayılan 512; izin verilen aralık 1..4096
Source := TFileStream.Create(SourceFile, fmOpenRead or fmShareDenyWrite);
try
if not FlattenPdfXfaToAcroForm(Source, Options, OutputPdf, Report, Fields) then
raise Exception.Create('XFA conversion failed: ' + Report.ErrorText);
finally
Source.Free;
end;
TFile.WriteAllBytes(TargetFile, OutputPdf);
Writeln(Format('%d template fields, %d bound to datasets, %d written on %d pages',
[Report.TemplateFields, Report.DatasetBound, Report.FieldsWritten, Report.PagesWritten]));
end;
Fonksiyon, reddedilen ya da bozuk her girdi için False döndürür ve Report.ErrorText'i doldurur; çıkarken kaynak akışın konumunu geri koyar. İptal, yayılan tek istisna yoludur: Options.CancellationToken tetiklenirse rapor doldurulduktan sonra EPdfOperationCancelled yeniden fırlatılır. TPdfXfaFlattenReport ayrıca SourceSha256 ile OutputSha256 taşır; toplu işin hangi girdinin hangi dönüştürülmüş dosyayı ürettiğini kanıtlaması gerektiğinde işe yarar. TPdfXfaFlattenOptions.Default 8 MiB paket bütçesi, 512 alan ve 512 MiB çıktı tavanı ayarlar; kaynak şifreli olmamalıdır
Template ile datasets paketlerini okumak
PDFium Component, ISO 32000-1 §12.7.8'in /XFA girdisi için izin verdiği her iki düzeni kabul eder: paket adları ile akış referanslarını sıralayan dizi biçimi ve tek akışın bütün <xdp:xdp> belgesini tuttuğu tek akışlı XDP biçimi. Paket akışları sıkıştırılmamış olabilir ya da tahmincisi (predictor) olmayan bir /FlateDecode olabilir; açma, MaxPacketBytes'le sınırlıdır
Tek akışlı durum, naif XML işlemesinin kırıldığı yerdir. Tasarımcılar genellikle <xfa:datasets>'i ad alanı önekiyle yazar ama template öneksizdir; sabit etiket adına göre dilimlemek bir paketi bulur ötekini kaçırır. Dönüştürücü öğe ile öznitelik adlarını yerel adla eşler; öncesinde açma ve kapama etiketlerinin tam önekli adlarıyla eşleştiğini kontrol eden ayrı bir denetim yapılır. Paketleri dönüştürmek yerine yalnızca incelemeniz gerekiyorsa PDFium VCL ile XFA formlarını algılama ve paketlerini çıkarma rehberi o salt okunur yolu anlatır
Alan değerleri iki yerden gelir. Template içindeki bir <value> öğesi varsayılanı verir; datasets paketi, alan adına göre bağlanan doldurulmuş veriyi verir. Alan adları gerçek formlarda yinelenir; bağlayıcı bu yüzden adlandırılmış <subform> ebeveyn zincirini verideki ebeveyn yoluyla karşılaştırır ve çelişmeyen en uzun eşleşmeyi alır. İki yaprak alan farklı subform'lar altında aynı adı paylaşıyorsa AcroForm /T'si SubformPath.Name olur; iki alan böylece dönüştürmeden sonra ayrı kalır
choiceList neden açık bir Combo bayrağı ister?
XFA ile PDF, seçim alanları için ters varsayılanlara sahiptir; hiçbir bayrağı kopyalamayan bir dönüştürücü her açılır listeyi liste kutusuna çevirir. Varsayılan open="userControl" ile ya da open="onEntry" ile açılmış bir XFA <choiceList> açılır menüdür (drop-down); bayrağı olmayan bir PDF /FT /Ch alanı kaydırmalı liste kutusudur ve onu combo box yapan tek şey Combo bayrağıdır (ISO 32000-1 Tablo 230'da bit konumu 18, değer 131072; PDFium'un FPDF_FORMFLAG_CHOICE_COMBO dediği bit). Dönüştürücü bu yüzden açılır menü durumları için Combo bayrağını ayarlar ve bayrakları yalnızca template open="always" ya da open="multiSelect" söylüyorsa boş bırakır; bunlar listeyi sürekli açık tutar. 3.181.1 öncesi derlemeler Combo ile Pushbutton değerlerini yer değiştirmişti ve standart dışı bir open="on" arıyordu; onlarla düzleştirilmiş formları yeniden çalıştırmaya değer
UI eşlemesinin geri kalanı ISO 32000-1 §12.7.4'ün alan bayrağı tablolarını izler:
textEdit,numericEditiledateTimeEdit/FT /Txolur; çok satırlı metin alanı Multiline bayrağını (/Ff 4096, Tablo 228) ve satıra bölünmüş bir appearance akışı alırpasswordEdit, Password bayrağıyla (/Ff 8192) UTF-16 kod birimi başına değil Unicode skaler başına bir yıldız çizen bir görünümle/FT /TxolurcheckButton, bağlı veri1iken değeri/Yesolan, aksi hâlde/Offolan bir/FT /Btnonay kutusu olur<exclGroup>,/V,/DV,/Optile/Kidstaşıyan bir/FT /Btn /Ff 32768radyo ebeveyni olur (Tablo 226); artı üye başına kendi/AS'i ile iki durumlu/AP'si olan bir çocuk widgetimageEdit, görünümü görseli boyayan bir pushbutton olur; baseline bir JPEG/DCTDecodealtında olduğu gibi gömülür; PNG ise çözülür ve PNG alfa ya datRNSsaydamlığı taşıyorsa ek bir/SMaskile/FlateDecodepikselleri olarak yeniden kodlanır
Satır sonları dahil düz basılabilir ASCII olmayan değerler <FEFF...> UTF-16BE dizgeleri olarak yazılır; bir Japonca ad ya da iki satırlık adres yolculuktan sağ çıkar. Fields çıktısı yazılanı aynalar; kullanışlı bir denetim izi olması bundandır
procedure DumpConvertedFields(const Fields: TPdfXfaFlattenFields);
const
KindNames: array[TPdfXfaFlattenFieldKind] of string = (
'text', 'check', 'choice', 'image', 'numeric', 'dateTime', 'password', 'radio');
var
I: Integer;
F: TPdfXfaFlattenField;
begin
for I := 0 to High(Fields) do
begin
F := Fields[I];
Writeln(Format('%s.%s [%s] page %d at %.1f,%.1f size %.1f x %.1f',
[F.SubformPath, F.Name, KindNames[F.Kind], F.PageIndex + 1, F.X, F.Y, F.W, F.H]));
case F.Kind of
fxfkChoice:
Writeln(Format(' %d items, list box: %s', [Length(F.Choices), BoolToStr(F.ListBox, True)]));
fxfkRadio:
Writeln(Format(' %d radio widgets, value "%s"', [Length(F.RadioWidgets), F.Value]));
fxfkImage:
Writeln(Format(' %d image bytes', [Length(F.ImageData)]));
else
Writeln(' value "' + F.Value + '"');
end;
end;
end;
Geometri, ölçü birimleri ve sayfa yönlendirme
XFA geometrisi, her alan kendi sayfasının MediaBox yüksekliğine karşı çevrilerek PDF user space'e dönüştürülür; çünkü XFA sayfanın tepesinden aşağı doğru, PDF alttan yukarı doğru ölçer. İç içe <subform> öğelerinden gelen ofsetler template içinde aşağı inerken birikir ve her alan pozitif genişlik ile yükseklikle bitmek zorundadır; aksi hâlde dönüştürme reddedilir
Ölçümler gerçek tasarımcıların yazdığı birimleri kabul eder: çıplak sayılar ile pt; ayrıca in (72 nokta), mm (72 / 25.4), cm (72 / 2.54), pc (12), px (0.75) ve mp (0.001, milipoint). Ayrıştırma yerelden bağımsızdır; Almanca bir Windows kurulumu 8.5in'i anlamsızlığa çevirmez. Bilinmeyen bir birim eki, alanı sessizce yanlış yere koyacak olan nokta olarak okunmak yerine reddedilir
Sayfa yönlendirme <pageSet>'i izler: kardeş <pageArea> öğeleri bildirim sırasına göre birden itibaren numaralanır ve altlarına yuvalanmış alanları sahiplenir; <pageArea> ebeveyni olmayan alanlar birinci sayfada kalır. Her sayfa kendi /Annots güncellemesini alır. PDF'in son sayfasının ötesine yönlendirilen bir alan, birinci sayfaya bırakılmış bir widget değil bir hatadır
Dönüştürücü hangi XFA formlarını reddeder?
Dönüştürücü, düzeni XFA düzen motorunu çalıştırmadan bilinemeyecek her şeyi reddeder. Bu, bir <subform> ya da <exclGroup> üzerinde position dışı bir layout özniteliği (dinamik formların kurulduğu akan tb, lr-tb ve dostları), herhangi bir <script> öğesi ve yukarıda sayılan yedi dışında herhangi bir UI türü demektir. Çalışma zamanında sayfa büyüten dinamik formlar başka bir araç zincirine aittir; PDFium Component'te dinamik XFA yeniden sayfalama ve sayfa sayıları makalesi o canlı düzenin ne içerdiğini gösterir
Reddetme bilinçlidir. Akarak dizilen bir subform'un sabit koordinatları yoktur; herhangi bir "dönüştürme" konumları uydurmak zorunda kalırdı ve betikli bir form çoğu zaman statik bir AcroForm'un yeniden üretemeyeceği toplamlar hesaplar ya da görünürlük anahtarlar. Sessizce hesaplanmış bir toplam kaybetmiş, tamamlanmış görünen bir PDF üretmek, Report.ErrorText'teki net bir hatadan kötüdür. XFA 3.3 spesifikasyonu bu alt kümeden çok daha fazlasını tanımlar ve dönüştürücü öyle değilmiş gibi davranmaz
AcroForm revizyonunu yazarken ve doğrularken tuzaklar
Yazıcı kurulurken iki sorun ortaya çıktı ve ikisi de elle artımlı güncelleme üreten herkesi ilgilendirir. İlki: katalog, baytları değişmese bile — AcroForm başka yerde yeniden yazılan dolaylı bir nesne olduğundan — daima güncelleme kümesinin parçası olmalıdır. Seyrek güncelleme yazıcısı trailer /Root'unu taşıdığı nesnelerden türetir; kök nesnesi olmayan bir güncelleme, görüntüleyicilerin demirleyemediği bir revizyon üretir
İkincisi doğrulamayla ilgilidir. Dönüştürülen dosyayı PDFium sarmalayıcısı üzerinden yüklemek, belgenin bir /AcroForm'u olduğu anda form ortamı başlatmasını tetikler ve test kurulumundaki ev sahibi pdfium.dll'de FORM_GetTextDirection dışa aktarımı yoktu; tek bir alan kontrol edilmeden başlatma başarısız oluyordu. Geri okuma kontrolleri bu yüzden bileşen nesne okuyucusunu kullanır; o, etkin revizyonu form katmanına dokunmadan ayrıştırır
uses
System.SysUtils, System.Classes, FPdfCompress;
function HasActiveAcroForm(const Pdf: TBytes): Boolean;
var
Stream: TBytesStream;
Objects: TPdfActiveObjects;
Revision: TPdfSparseRevisionInfo;
ErrorText: string;
begin
Stream := TBytesStream.Create(Pdf);
try
if not ReadPdfActiveObjects(Stream, TPdfActiveObjectReadOptions.Default,
Objects, Revision, ErrorText) then
raise Exception.Create(ErrorText);
// etkin katalog bir AcroForm'a referans vermeli; dönüştürücünün yazdığı
// satır içi AcroForm /XFA girdisi taşımaz
Result := Pos('/AcroForm', string(Revision.CatalogDictionary)) > 0;
finally
Stream.Free;
end;
end;
Appearance akışları bilinçli olarak basittir: metin ile seçim değerleri için 10 punto Helvetica; onay kutuları için konturlu bir kare; seçili radyo durumu için dolgulu bir iç kare. Dönüştürülen formu her yerde okunur kılarlar ama özgün XFA render'ının piksel kopyası değildirler; template'te tanımlı fontlar, renkler ve kenarlıklar taşınmaz. Bir form, tasarımcı çıktısıyla özdeş görünmek zorundaysa üretim hattını devirmeden önce onu bir kez XFA derlemesiyle render edip karşılaştırın
SSS
FlattenPdfXfaToAcroForm, PDFium'un V8 ya da XFA derlemesini gerektirir mi?
Hayır. FlattenPdfXfaToAcroForm paketleri ayrıştırır ve revizyonu PDFium Component koduyla yazar; standart pdfium.dll ile çalışır ve hiçbir XFA betiği çalıştırmaz
Dönüştürücü şifreli XFA belgelerini işleyebilir mi?
Doğrudan hayır. Şifreli bir kaynak hata ile reddedilir; belgeyi önce şifresini çözün ve çözülmüş kopyayı dönüştürün
Dönüştürülen alanlar hâlâ düzenlenebilir mi?
Evet. Çıktı, değerleri ile appearance akışlarıyla gerçek AcroForm metin, onay kutusu, radyo, seçim ve pushbutton alanları içerir; kullanıcılar formu AcroForm destekleyen herhangi bir görüntüleyicide doldurmaya devam edebilir
Statik XFA dönüştürmesi, XFA paket çıkarma ve V8 etkin XFA çalışma zamanının tamamı Delphi ve C++Builder için PDFium Component'in parçasıdır; bileşen Delphi, C++Builder ve Lazarus projeleri için PDFium'u sarmalar