Teknik Makale

Delphi'de AcroForm miras alan değerleri ve resetler

HotPDF Delphi Component, yüklü bir AcroForm alanındaki /FT, /Ff, /V ve /DV girdilerini kalıtsal öznitelikler sayar ve /Parent zincirini yürüyerek çözer. v2.754.3 ve v2.754.4'ten bu yana türü ebeveynden gelen adlandırılmış bir çocuk tek tek adreslenebilir kalıyor, RemoveFormField kardeşlerine dokunmuyor ve ResetLoadedFormField devralınan varsayılanı özgün PDF nesne türüyle kopyalıyor. Öncesinde sıradan formların şaşırtıcı sayıda olanı yanlış okunuyordu

Tüm bunları ortaya çıkaran form egzotik değildir. Bir yazım aracı, /FT /Chyi, alan bayraklarını ve seçenek listesini bir kez taşıyan bir grup düğümü group kurar ve altına a ile b adlı iki adlandırılmış çocuk asar; her biri /T, /Parent, /Rect ve kendi /Vsinden başka bir şey tutmayan birleşik alan-artı-widget sözlüğüdür. Bu, öznitelik paylaşmanın tümüyle meşru bir yoludur ve tam olarak, Delphi ile yüklü bir PDF'te form alanı değerleri ayarlamak yazısının Limits bölümünün işlenmemiş olarak işaretlediği vakadır: düğme uyumlaması yalnızca yerel /FTye bakıyordu. Bu yazı o yazının kaldığı yerden devam eder; alan ağacının nasıl sınıflandırıldığını, kalıtsal değerlerin nasıl okunduğunu ve tek alan resetinin neyi yazmaya izni olduğunu kapsar

Bir alan ebeveynden hangi AcroForm girdilerini devralır?

ISO 32000-1 §12.7.3.1, Tablo 220, /FT, /Ff, /V ve /DVyi kalıtsal işaretler ve §12.7.4.3'teki Tablo 229 aynı şeyi bir metin alanının /MaxLeni için yapar; dolayısıyla yalnızca yerel sözlüğe bakan her okuyucu, tümüyle geçerli bir çocuk için yanlış tür, yanlış bayraklar ve boş değer bildirir. HotPDF tüm bu okumaları tek bir iç resolver'dan, HPDFLoadedInheritedFieldObject üzerinden geçirir; bu, sözlükte anahtara bakar, dolaylı referans bulursa çözer, aksi hâlde en çok 128 düzey /Parent izler, çünkü bozuk dosyalar /Kids ile hiçbir ilgisi olmayan /Parent döngüleri kurabilir. Herkese açık getter'lar onun üzerindedir: GetFormFieldType, GetFormFieldValue, GetLoadedFormFieldFlags, IsFormFieldRequired, IsFormFieldNoExport, GetLoadedFormFieldMaxLength, GetLoadedFormFieldDefaultValue ve seçenek yardımcıları GetLoadedFormFieldOptionCount ile GetLoadedFormFieldOptions; bunlar ebeveynde saklanan bir /Opt dizisini de yakalar. Resolver'daki bir kuralı kaçırmak kolaydır: yürüyüş, anahtarı içeren ilk sözlükte durur, oradaki değer boş bir dize bile olsa. Yerel bir /V (), ebeveyni maskelyen bilinçli bir geçersiz kılmadır; ağacın daha yukarısından doldurulacak bir boşluk değil

HotPDF kalıtsal AcroForm öznitelikleri şeması: grup düğümü /FT, /Ff ve /Opt'ı bir kez taşırken group.a ile group.b adlı çocuklar yalnızca /T, /Parent, /Rect ve yerel bir /V tutar; HPDFLoadedInheritedFieldObject /Parent'i 128 düzeye kadar yürür, anahtarı tutan ilk sözlük kazanır ve boş yerel değer ebeveyni maskeler
HotPDF /FT, /Ff, /V, /DV ve /Opt'ı tek bir ebeveyn-yürüyen resolver üzerinden çözer; adlandırılmış çocuk adreslenebilir kalırken yerel boş değer, üstündeki grubun taşıdığı her şeyi bilinçli olarak geçersiz kılar
var
  Pdf: THotPDF;
  Field: THPDFLoadedFormField;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('survey.pdf') <= 0 then Exit;
    // 'group' /FT /Ch, /Ff 131078 ve /Opt taşır; çocuk
    // 'group.b' yalnızca /T, /Parent, /Rect ve kendi /V'sini taşır
    Field := Pdf.GetFormField('group.b');
    try
      if Pdf.GetFormFieldType(Field.Index) = lfftChoice then
      begin
        // 131078 = Combo (bit 18) + NoExport (bit 3) + Required (bit 2)
        Writeln(Pdf.GetLoadedFormFieldFlags(Field.Index));
        Writeln(Pdf.IsFormFieldRequired(Field.Index));    // TRUE
        Writeln(Pdf.GetLoadedFormFieldOptionCount(Field.Index));
        Writeln(Pdf.GetFormFieldValue(Field.Index));       // yerel /V
      end;
    finally
      Field.Free;
    end;
  finally
    Pdf.Free;
  end;
end;

Yerel /FT neden terminal alan için yanlış testtir?

Çünkü bir ebeveyn türü sağlarken hâlâ adlandırılmış çocuk alanlara sahip olabilir; /FTnin varlığı, alan ağacının nerede bittiği hakkında hiçbir şey söylemez. Eski dolaşım, kendi /FTsine sahip olan ya da /Kidsi olmayan her düğümü terminal ilan ediyordu. Yukarıdaki formda group, hem /FT /Ch hem /Kidse sahipti; bu yüzden iki widgetlı group adında tek alan olarak kaydediliyor ve tam nitelikli group.a ile group.b adları ortadan kayboluyordu. GetFormFieldCount 1 döndürüyordu, çocuk adıyla arama başarısız oluyordu ve SetFormFieldValue yalnızca paylaşılan ebeveyni yazabiliyordu. Yerini alan test olan HPDFLoadedFieldHasChildFields, ebeveyne değil çocuklara bakar: bir kid, kendi /Tsine sahipse, kendi /Kidsine sahipse ya da hiç /Subtype /Widget sözlüğü değilse bir çocuk alanıdır. Hiçbir kid bu koşulu sağlamıyorsa düğüm terminaldir ve kidleri widget anotasyonları sayılır

O kuralı biçimlendiren iki uç vaka da birleşik sözlüklerden gelir; §12.7.3.1, bir alan tek widgetlıyken buna izin verir. Adlandırılmış bir birleşik sözlük /Subtype /Widget taşır ve yine de bir çocuk alanıdır; subtype tek başına onu ebeveynin adsız widget listesine gönderemez, /T kazanır. Tersi de olur: bazı üreticiler ebeveynin /FTsini her adsız widgetta yineler, dolayısıyla /FT bir widgetın yeni alan başlattığına dair kanıt olarak da kullanılamaz. Sınıflandırma, ilişki önbelleği ile FormFieldExists ve RemoveFormField tarafından paylaşılır ve bu yürüyüşlerin her biri artık ziyaret ettiği sözlükleri kaydeder ve 128 düzeyin ötesinde durur. Grubun kendini iki kez listelediği, /Kids [5 0 R 5 0 R 6 0 R 7 0 R] biçimindeki bir regresyon dosyası, sonsuza dek özyinelemek ya da aynı düğümü iki kez saymak yerine tam olarak iki alan bildirir

RemoveFormField kardeş alanları silmekten nasıl kaçınır?

RemoveFormField artık yalnızca adını verdiğiniz çocuğu siler, çünkü keşif ve silme sonunda bir terminal alanın ne olduğunda anlaştı. O anlaşma göründüğünden daha önemli. Adla çağrılan aşırı yükleme, dizini ilişki önbelleği üzerinden çözer ve sonra /AcroForm /Fields üzerinde ikinci bir yürüyüşte terminal alanları sayar. Önbellek group.a ile group.byi görecek şekilde düzeltildiğinde, düzeltilmemiş bir silme yürüyüşü groupu hâlâ tek terminal alan sayacaktı ve 0 indeksi ebeveyni her kardeşiyle ve tüm widgetlarıyla birlikte kaldıracaktı. Silme yürüyüşü artık aynı HPDFLoadedFieldHasChildFields testini ve aynı ziyaret kümesini kullanır, yalnızca kaldırılan çocuğun widget anotasyonlarını toplar, bunları her sayfanın /Annotsından soyar ve ebeveyni yalnızca /Kids dizisi boş kalırsa kaldırır. Regresyon, bir hatanın görüneceği üç yeri de sınar: ebeveynin /Kidsi, sayfanın /Annotsı ve hayatta kalan kardeşin değeri ile görünümü; hem tam yeniden yazımdan hem artımlı güncellemeden sonra

HotPDF RemoveFormField kardeş hayatta kalma şeması: silme yürüyüşü HPDFLoadedFieldHasChildFields ile keşiften gelen ziyaret kümesini yeniden kullanır, AcroForm /Fields'ten ve sayfa /Annots'tan yalnızca group.a adlı çocuğu soyar ve /Kids dizisi hâlâ hayatta kalan group.b tuttuğu sürece paylaşılan ebeveyni korur
Keşif ve silme sonunda bir terminal alanın ne olduğunda anlaşıyor; bir adlandırılmış çocuğu kaldırmak, tam yeniden yazımdan ya da artımlı güncellemeden sonra kardeşinin değerini ve görünümünü dokunulmamış bırakıyor
// Bir adlandırılmış çocuğu kaldır; kardeşi ve paylaşılan ebeveyn hayatta kalır
Pdf.RemoveFormField('group.a');

Assert(Pdf.GetFormFieldCount = 1);
Assert(Pdf.FormFieldExists('group.b'));
// Tür, bayraklar ve seçenekler hâlâ ebeveyn üzerinden çözülür
Assert(Pdf.GetFormFieldType('group.b') = lfftChoice);
Pdf.SaveLoadedDocument('survey-trimmed.pdf');

Varsayılan devralınmışken ResetLoadedFormField ne yazar?

ResetLoadedFormField, devralınan /DVnin taze bir kopyası olan ve aynı PDF nesne türünü taşıyan yerel bir /V yazar ve alana dokunmadan önce varsayılanın tamamını doğrular. Nesne türü önemlidir, çünkü skaler getter'lar her şeyi metne düzleştirir. Checkbox varsayılanı /Yes gibi bir addır, multi-select liste kutusu varsayılanı bir dize dizisidir ve metin varsayılanı onaltılık bir UTF-16 dizesi olabilir; herhangi birini GetLoadedFormFieldDefaultValue üzerinden kopyalamak adı dizeye, diziyi boş dizeye ve onaltılık dizeyi literal basamaklarına çevirirdi. Reset bu yüzden devralınan türe göre dallanır: metin ve choice alanları IsHexadecimal bayrağını koruyan yeni bir dize nesnesi alır, dizi varsayılanlı choice alanları yeni dizelerden oluşan yeni bir dizi alır ve pushbutton olmayan düğmeler yeni bir ad nesnesi alır. Ebeveynin nesnelerini işaret etmek yerine kopyalamak bilinçlidir: ebeveynin /DV dizisini ya da nesne numarasını paylaşan bir /V, bir dahaki sefere değer düzenlendiğinde varsayılanı değiştirirdi. Yanlış türde bir varsayılan ya da dize dışında bir şey içeren bir choice dizisi istisna fırlatır ve /V ile /Iyi tam olarak bıraktıkları yerde bırakır. Değeri olmayan pushbuttonlar (Tablo 226, bit 17) ve imza alanları eski yalnızca-dize yoluna döner

HotPDF türü temelli reset şeması: ResetLoadedFormField devralınan /DV nesne türüne göre dallanır; checkbox için taze bir ad nesnesi, multi-select choice için yeni dizelerden yeni bir dizi, hex metin için IsHexadecimal'i koruyan bir dize, /DV yokken boş dize ya da /Off yazar ve tür uyuşmazlığında /V ile /I'ye dokunmadan istisna fırlatır
Ebeveynin nesnelerini işaretlemek yerine kopyalamak, sonradan yapılan bir değer düzenlemesinin varsayılanı sessizce değiştirmesini önler; pushbuttonlar ile imza alanları eski yalnızca-dize yoluna döner

Zincirin hiçbir yerinde /DV yokken yöntem, temizleme sözleşmesini yerel bir boş dize yazarak ya da checkbox ile radio alanları için /Off yazarak sürdürür. Yerel /Vyi silmek daha derli görünür ve yanlış olur: ebeveyn güncel bir değer tutuyor olabilir ve çocuğun geçersiz kılmasını kaldırmak o değeri sessizce geri getirirdi. Tek alan reseti, HotPDF ile AcroForm alanları ve aksiyonlar kurmak yazısında anlatılan, kullanıcının bir düğmeye tıklamasıyla görüntüleyicinin bir alan kümesi üzerinde çalıştırdığı §12.7.5.3'ün ResetForm aksiyonu da değildir. ResetLoadedFormField, yüklü tek bir alan üzerinde bir düzenleme işlemidir; varsayılan yokluğu vakası için kendi kuralı vardır ve alanı NoteLoadedFormFieldDirty üzerinden kaydeder, böylece artımlı yeniden hesaplama değişikliği görür

var
  Field: THPDFLoadedFormField;
begin
  Field := Pdf.GetFormField('group.a');
  try
    // Ebeveyn, MultiSelect liste kutusunda /DV [(b) (r)] tutuyor: group.a
    // kendi /V [(b) (r)]ini ve taze bir /I [0 2] alır; ebeveyne dokunulmaz
    Pdf.ResetLoadedFormField(Field.Index);
    // Skaler getter'lar dizi varsayılanını temsil edemez
    Writeln(Pdf.GetLoadedFormFieldDefaultValue(Field.Index)); // boş
  finally
    Field.Free;
  end;
  Pdf.SaveLoadedDocument('survey-reset.pdf');
end;

/V, /I ve /AS'i uyumda tutmak

Reset yalnızca seçim indeksi ve görünüm durumu değeri izlediğinde doğrudur; bu yüzden ResetLoadedFormField, SetFormFieldValue ile aynı iki uyumlayıcıyla bitirir. HPDFReconcileChoiceSelection artık bir dizi değeri kabul eder: yerel /Iyi onu mutasyona uğratmadan siler, her değeri her /Opt girdisinin dışa aktarma yarısıyla eşler ve tek bir yeni sıralı /I yazar; böylece b, g, r seçeneklerine karşı [(b) (r)]e reset, /I [0 2] verir. ReconcileLoadedButtonAppearanceStates artık devralınan türü ister; böylece /FT /Btni ebeveynde yaşayan bir çocuk checkbox sonunda /ASini set edilmiş alıyor. Yazma tarafında SetFormFieldValue ile SetLoadedFormFieldDefaultValue, devralınmış pushbutton olmayan bir düğme için türü kopyalayacak yerel girdisi olmayan çocuğa bile bir ad nesnesi saklar. EnsureLoadedFieldAppearanceStream düğme görünümlerini yeniden kurarken değeri açık durumla eşleşmiyorsa /AS /Off yazar ve her durum akışına düzgün bir /Type /XObject, /Subtype /Form ve /BBox verir; v2.754.4 öncesinde resetten sonra görünümü yeniden üretmek, dosya kaydedilmeden kutuyu yeniden işaretleyebiliyordu

Üzerine inşa etmeden önce bilinmesi gereken sınırlar

Skaler getter'lar skaler kalır. GetFormFieldValue ile GetLoadedFormFieldDefaultValue, dizi değeri için boş dize döndürür; sayıları ve booleanları 42 ya da true olarak dizgeleştirir ve hex kodlu bir dizeyi onaltılık yazımıyla bildirir. Bir /Parent döngüsü yürüyüşü istisnasız bitirir; türü bir döngüde kaybolan alan, başarısız olmak yerine lfftUnknown ve 0 bayrakları bildirir. SetFormFieldValue ile ResetLoadedFormField daima adreslediğiniz çocuğu yazar ve değeri asla paylaşılan ebeveyne yükseltmez; bağımsız çocuklar için doğru olan budur ama radio gruplarının seçime sahip alan üzerinden adreslenmesi gerektiği anlamına gelir. Ve her çağrı tek alanı kendi başına işler; buradaki hiçbir şey bir reset yığınını transactional kılmaz

Burada anlatılan kalıtsal öznitelik çözümü, birleşik alan ağacı sınıflandırması ve türü temelli reset, Delphi ve C++Builder için HotPDF Delphi Componentin yüklü form API'sinin parçalarıdır; Delphi'de yüklü bir PDF'e AcroForm alanları eklemek yazısında kapsanan alan oluşturma da yanında durur