Teknik Makale

Delphi'de AcroForm miras alan değerleri ve resetler

Bir anket formu, Acrobat'ta iki açılır liste gösterir: group.a ile group.b. Kendi /FTsini taşıyan her düğümü bitmiş bir alan sayan kodla /AcroForm /Fields listesini yürüyün; sayı 1 döner: iki widget'lı, group adında tek bir alan. group.byi adıyla isteyin, arama başarısız olur. Çocuğun sözlüğünü açın; içinde ne /FT vardır, ne /Ff, ne seçenek listesi. Bozuk olan hiçbir şey yoktur. Tür, bayraklar ve seçenekler bir seviye üstte, ebeveynde durur ve PDF şartnamesi orada durmalarına açıkça izin verir

Bu düzen yasal ve ekonomiktir. Bir yazım aracı, /FT /Ch ile /Ff 131078 ve bir /Opt dizisi taşıyan bir grup düğümünü bir kez yazar, sonra altına iki adlandırılmış çocuk asar; her biri yalnızca /T, /Parent, /Rect ve kendi /Vsini tutan, alan artı widget birleşik sözlüğüdür. Böyle bir formu doğru okumak üç şey gerektirir: devralınabilir her anahtar için /Parent izlemek, alan ağacının gerçekten nerede bittiğine karar vermek ve bir resetin doğru PDF nesne türünde bir değer yazdığından emin olmak. Bu yazı, Delphi ile yüklenmiş bir PDF'te form alanı değerleri atama yazısının bıraktığı yerden devam eder

Bir alan ebeveynden hangi AcroForm girdilerini devralır?

ISO 32000-1 §12.7.3.1 (Tablo 220), /FT, /Ff, /V ve /DVyi devralınabilir işaretler ve §12.7.4.3'teki Tablo 229, bir metin alanının /MaxLeni için aynısını yapar. Seçenek dizileri de burada aynı işleme girer; bir seçim listesini çocuklar arasında paylaşan üreticiler /Optı grupta saklar. Yalnızca yerel sözlüğe bakan bir okuyucu bu yüzden, son derece geçerli bir çocuk için yanlış tür, 0 bayraklar ve boş bir değer raporlar

HotPDF Delphi Component bu okumaların her birini aynı şekilde yanıtlar. Anahtarı alanın kendi sözlüğünde arar; anahtar oradaysa dolaylı referansı çözümler ve durur. Değilse /Parent izler ve yeniden dener, en fazla 128 düzey. O sınır süs değildir. Bozuk dosyalarda /Parent işaretçileri, /Kidsle hiçbir ilgisi olmayan bir döngü kurabilir ve onlara koşulsuz güvenen bir okuyucu, karşılaştığı ilk böyle dosyada asılır

O yürüyüşte bir kuralı ters anlamak kolaydır: anahtarı içeren ilk sözlük kazanır, orada saklanan değer boş bir dize bile olsa. group.b üzerindeki yerel bir /V (), yazarın "bu çocuk boş" demesidir ve yürüyüş, ebeveynin belki taşıdığı bir değere tırmanmak yerine orada durur. Boşu "eksik" saymak, temizlenmiş bir çocuğun ebevenin seçimini göstermesine yol açardı; yazarın yazdığının tam tersi

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

Herkese açık getter'lar hep o aramaya oturur: GetFormFieldType, GetFormFieldValue, GetLoadedFormFieldFlags, IsFormFieldRequired, IsFormFieldNoExport, GetLoadedFormFieldMaxLength, GetLoadedFormFieldDefaultValue ve seçenek yardımcıları GetLoadedFormFieldOptionCount ile GetLoadedFormFieldOptions. Bu yüzden anket grubunun çocuğunu okumak özel kod gerektirmez:

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;

GetFormField her seferinde yeni bir THPDFLoadedFormField sarmalayıcısı döndürür ve ad bilinmediğinde istisna fırlatır: çağrıyı sarmalayıcının try bloğunun dışında tutun ve sarmalayıcıyı kendiniz serbest bırakın

Alan ağacının nerede bittiğini bulmak

Sonlanmış bir alan için yerel bir /FT yanlış testtir; çünkü bir ebeveyn türü kendisi sağlayıp adlandırılmış çocuk alanlara da sahip olabilir. Baştan çıkarıcı dolaşım, bir düğümün kendi /FTsi olduğunda ya da /Kidsi olmadığında inmeyi bırakır. Anket formunda group, hem /FT /Ch hem /Kids taşır; o dolaşım onu, iki widget anotasyonlu tek bir alan olarak kaydeder. group.a ve group.b adları kaybolur. Alan sayısı 1'dir. Çocuk adıyla arama başarısız olur ve bir değer yazımı yalnızca paylaşılan ebeveyne konabilir

HotPDF bunun yerine çocuklara bakar. Bir kid, kendi /Tsi varsa, kendi /Kidsi varsa ya da bir /Subtype /Widget sözlüğü değilse çocuk alan sayılır. Hiçbir kid uygun düşmediğinde düğüm sonlanmıştır ve kidleri o zaman widget olarak işlenir. Kuralın ardındaki iki uç vaka da birleşik sözlüklerden gelir; §12.7.3.1, bir alan tam olarak tek widget'a sahipken buna izin verir:

  • Adlandırılmış bir birleşik sözlük /Subtype /Widget taşır ve yine de bir çocuk alandır. Subtype tek başına onu ebeveynin anonim widget listesine itemez; kararı /T verir
  • Bazı üreticiler ebeveynin /FTsini her anonim widget'ta yineler. Demek ki bir widget'taki /FTyi "burada yeni bir alan başlar" diye okumak da mümkün değildir

Aynı sınıflandırma alan indeksini, FormFieldExistsi ve RemoveFormFieldi yönlendirir. O yürüyüşlerin her biri ayrıca ziyaret ettiği sözlükleri kaydeder ve 128 düzeyin ötesinde pes eder. Grubun 5 nesnesi olduğunu ve kendi kidleri arasında iki kez kendini listelediğini varsayın: /Kids [5 0 R 5 0 R 6 0 R 7 0 R]. Yine de tam olarak iki alan raporlar: 6 ve 7 çocukları. Sonsuz özyineleme yoktur ve kendine referanstan gelen hayalet bir üçüncü alan da yoktur

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

RemoveFormField yalnızca adını verdiğiniz çocuğu siler; çünkü keşif ile silme, sonlanmış alanın tek bir tanımını paylaşır. Adla çalışan overload, alan listesinden bir alan indeksi çözer, sonra silinecek sözlüğü bulmak için /AcroForm /Fields listesini ikinci kez, sonlanmış alanları sayarak yürür. İkinci yürüyüş baştan çıkarıcı /FT kuralını kullanırken ilki kid tabanlı kuralı kullanmış olsaydı indeksler çelişirdi: silme yürüyüşü groupu 0. alan görür ve group.ayı silmek ebeveyni, kardeşi ve iki widget'ı da götürürdü

Silme yürüyüşü bu yüzden keşifle aynı çocuk alan testini ve aynı ziyaret kümesini uygular. Silinen çocuğa ait widget anotasyonlarını toplar, her sayfanın /Annotsundan yalnızca onları ayıklar ve ebeveyni yalnızca /Kids dizisi boş kalınca kaldırır. group.b hâlâ listedeyken ebeveyn kalır ve group.bnin devraldığı tür, bayraklar ve seçenekler onunla birlikte kalır

HotPDF RemoveFormField kardeşin hayatta kalması şeması: silme yürüyüşü keşifle aynı çocuk alan testini ve ziyaret kümesini kullanır, AcroForm /Fields listesinden ve sayfa /Annotsundan yalnızca adlandırılmış group.a çocuğunu ayıklar ve /Kids dizisi hâlâ hayatta kalan group.byi tuttuğu sürece paylaşılan ebeveyni korur
Keşif ile silme, sonlanmış alanın ne olduğunda anlaşır; böylece bir adlandırılmış çocuğu silmek, tam bir yeniden yazımdan ya da artımlı bir güncellemeden sonra kardeşinin değerini ve görünümünü sağlam bırakır
// Adlandırılmış bir çocuğu sil; kardeşi ve paylaşılan ebeveyn hayatta kalır
Pdf.RemoveFormField('group.a');

// Kendinizin ekleyeceği akıl sağlığı kontrolleri, kütüphanenin parçası değil
Assert(Pdf.GetFormFieldCount = 1);
Assert(Pdf.FormFieldExists('group.b'));
// Tür, bayraklar ve seçenekler hâlâ ebeveyn üzerinden çözümlenir
Assert(Pdf.GetFormFieldType('group.b') = lfftChoice);

Pdf.SaveLoadedDocument('survey-trimmed.pdf');

Kendi formlarınızda bir silmeyi denetlerken üç yere bakın; çünkü her biri başka bir hatayı yakalar: ebeveynin /Kidsi, sayfa /Annotsu ve hayatta kalan kardeşin değeriyle görünümü. Bunu SaveLoadedDocument sonrasında bir, SaveIncrementalUpdate sonrasında bir kez yapın; çünkü artımlı bir kayıt yalnızca değişen nesneleri ekler ve ebeveynin /Kidsi aralarında olmak zorundadır

ResetLoadedFormField devralınan bir /DV'den neyi kopyalar

ResetLoadedFormField, devralınan /DVnin taze bir kopyası olan yerel bir /V yazar, aynı PDF nesne türüyle, ve alana dokunmadan önce öntanımlının tamamını doğrular. Bir checkbox öntanımlısı /Yes gibi bir name'dir. Bir multi-select liste kutusu öntanımlısı bir dize dizisidir. Bir metin öntanımlısı <FEFF...> gibi onaltılık bir UTF-16 dizesi olabilir. Bunlardan herhangi birini GetLoadedFormFieldDefaultValue ile SetFormFieldValue üzerinden gidiş dönüşe sokun; name'in yerine bir dize, dizinin yerine boş bir dize ya da ham hex rakamları alırsınız

Reset bu yüzden devralınan /DVnin aslında ne olduğuna göre dallanır:

  • Dize öntanımlılı metin ve choice alanları, özgünin onaltılık bayrağını koruyan yeni bir dize nesnesi alır
  • Dizi öntanımlılı choice alanları, yeni dizelerden kurulu yeni bir dizi alır; dizelerden başka bir şey içeren bir dizi istisna fırlatır
  • Checkbox ile radio düğmeleri yeni bir name nesnesi alır
  • Başka herhangi bir türden bir öntanımlı istisna fırlatır ve /V ile /I oldukları gibi kalır
HotPDF türü belirtilmiş reset şeması: ResetLoadedFormField, devralınan /DV nesne türüne göre dallanır; checkbox için taze bir name nesnesi, multi-select bir choice için yeni dizelerden kurulu yeni bir dizi, hex metin için IsHexadecimal bayrağını koruyan bir dize yazar, /DV yokken boş bir dize ya da /Off yazar ve tür uyuşmazlığında /V ile /I'ya dokunmadan fırlatır
Ebeveynin nesnelerini işaretlemek yerine kopyalamak, sonraki bir değer düzenlemesinin öntanımlıyı sessizce değiştirmesini önler; pushbutton ile imza alanları düz dize yolunu izler

Referans vermek yerine kopyalamak bilinçli bir tercihtir ve tek güvenli seçenek odur. Ebeveynin /DV dizisini işaret eden bir çocuk /Vsi, tek bir nesneyi "güncel değer" ile "öntanımlı değer" arasında paylaşır olurdu; o çocuk üzerindeki bir sonraki SetFormFieldValue, her kardeşin öntanımlısını sessizce yeniden yazardı. Reset alanı başına birkaç fazladan bayt, ikisini ayrı tutmanın ucuz bedelidir. Değeri olmayan pushbutton'lar (Tablo 226, bit 17) ile imza alanları, kendi /DVlerinin bir dize kopyasını yazan düz yolda kalır

Zincirin hiçbir yerinde bir /DV yoksa metot yerel bir boş dize yazar, checkbox ya da radio alanı için /Off. Çocuğun /Vsini silmek daha derli toplu görünürdü ve yanlış cevabı verirdi. Ebeveyn güncel bir değer tutuyorsa çocuğun ezmesini kaldırmak o değeri olduğu gibi geri getirir ve "resetlenen" alan, kullanıcının ona hiç yazmadığı bir şey gösterir olur

var
  Field: THPDFLoadedFormField;
begin
  // Ebeveyn 'palette', /Opt [(b) (g) (r)] ve /DV [(b) (r)] taşıyan
  // bir MultiSelect liste kutusudur; çocuk yalnızca /T ve /V tutar
  Field := Pdf.GetFormField('palette.primary');
  try
    try
      Pdf.ResetLoadedFormField(Field.Index);
      // Çocuğun artık kendi /V [(b) (r)] ve /I [0 2]sidir;
      // ebeveynin /DV dizisine dokunulmadı
    except
      on E: Exception do
        // Bozuk öntanımlı (yanlış tür, dize olmayan dizi ögesi):
        // /V ve /I oldukları gibi bırakıldı
        Writeln('Reset skipped: ', E.Message);
    end;
    // Skaler getter bir dizi öntanımlısını temsil edemez
    Writeln(Pdf.GetLoadedFormFieldDefaultValue(Field.Index)); // boş
  finally
    Field.Free;
  end;
  Pdf.SaveLoadedDocument('survey-reset.pdf');
end;

Tek alanlı bir reset, ayrıca §12.7.5.3'ün ResetForm eyleminden farklı bir şeydir; görüntüleyici, kullanıcı bir düğmeye tıkladığında o eylemi bir alan kümesi üzerinde koşturur. O eylem HotPDF ile AcroForm alanları ve eylemleri kurma yazısında ele alınır. ResetLoadedFormField yüklü tek bir alanı düzenler, öntanımlı yok vakası için kendi kuralını izler ve alanı değişmiş işaretler; artımlı yeniden hesaplama böylece onu alır

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

Bir reset, yalnızca seçim indeksi ile görünüm durumu değeri izlerken doğrudur ve ResetLoadedFormField, SetFormFieldValue ile aynı iki takip adımıyla bitirir. Choice alanlar için çocuğun yerel /Isini kaldırır, her değeri her /Opt girdisinin dışa aktarma yarısıyla eşler ve yeni, sıralı bir /I yazar. b, g, r seçeneklerine karşı [(b) (r)]e resetlemek /I [0 2] verir. Ebeveynin devralınan /Isi, varsa, olduğu gibi kalır

Düğmeler devralınan türden çalışır. /FT /Btnsi ebeveynde yaşayan bir çocuk checkbox yine de /AS güncellemesi alır. Yazma tarafında SetFormFieldValue ile SetLoadedFormFieldDefaultValue, devralınan pushbutton olmayan bir düğme için bir name nesnesi saklar; çocuk, türü kopyalayacak yerel bir girdi taşımasa bile. Ve EnsureLoadedFieldAppearanceStream düğme görünümlerini yeniden kurarken /AS /Off yazar — değer on durumuna eşleşmedikçe — ve her durum akışına /Type /XObject, /Subtype /Form ve bir /BBox verir. O /Off denetimi olmasaydı, bir resetten sonra görünümleri yeniden üretmek, dosya kaydedilmemişken kutuyu yeniden işaretli gösterirdi

Skaler getter'lar ve plana dahil edilecek diğer uçlar

Dize getter'ları skaler kalır ve çıktılarını karşılaştıran kod, neyi düzleştirdiklerini bilmelidir. GetFormFieldValue ile GetLoadedFormFieldDefaultValue, bir dizi değeri için boş dize döndürür, sayıları ve booleanları 42 ya da trueya çevirir ve hex kodlu bir dizeyi onaltılık yazımıyla raporlar. Bir liste kutusu öntanımlısının iki seçim mi hiç mi tuttuğunu bilmeniz gerekiyorsa öntanımlı getter bunu söyleyemez: ikisi de boş dize olarak döner

Bir /Parent döngüsü aramayı fırlatmadan bitirir; türü bir döngünün içinde kaybolan bir alan lfftUnknown ve 0 bayraklar raporlar. Adlandırılmış bir alandaki lfftUnknownu, günlüğe yazmaya değer bir bozuk dosya işareti sayın. SetFormFieldValue ile ResetLoadedFormField daima adreslediğiniz çocuğa yazar ve bir değeri asla paylaşılan ebeveyne yükseltmez. İki anket açılır listesi gibi bağımsız çocuklar için bu doğrudur. Radio grupları farklıdır: seçim, grubun sahibi alana aittir; kidslerinden birine değil o alana adres verin

Her çağrı ayrıca tek bir alanı kendi başına işler. Yirmi alan üzerinden bir reset döngüsü, yirmi bağımsız düzenlemedir; beşincisi bozuk bir öntanımlıda fırlatırsa ilk dördü çoktan uygulanmıştır. Toplu iş için önce her hedefin türünü ve öntanımlısını okuyun, hangi alanlara dokunacağınıza karar verin ve ancak ondan sonra yazmaya başlayın

Devralınan öznitelik araması, kid tabanlı alan ağacı sınıflandırması ve türü belirtilmiş reset, HotPDF Delphi Componentin yüklü-form API'sine aittir; API ayrıca mevcut bir belgeye yeni alanlar eklemesi gereken Delphi ve C++Builder projelerini kapsar, Delphi'de yüklenmiş bir PDF'e AcroForm alanları ekleme yazısında gösterildiği gibi