Teknik Makale

Linux'ta /AP Olmayan PDF Form Alanlarını Çizmek

Alan değerleri olan ama görünüm akışı olmayan bir PDF formu, bir Linux çizim işinden boş kutular, yanlış yazı tipi ya da hizalama ya da "Widget field requires a name field type" okuyan bir EHPDFHeadlessRenderError olarak çıkabilir. HotPDF'in başlıksız (headless) çizicisi metin, düğme ve seçim widget'ları için geçici görünümler çizer; /FT, /V, /DA ve /Q'yu alan ağacı üzerinden devralır

Bir widget alan ayarlarını nerede tutar

Asıl bela, alan ile widget'ındaki bölünmede başlar. Alan sözlüğü, form denetiminin mantıksal kısmını taşır: türü, değeri, bayrakları ve varsayılan görünümü. Widget ek açıklaması, bir sayfanın ihtiyaç duyduğunu taşır: /Rect, kenarlık ve kendi sayfasına bir işaretçi. Tek widget'lı bir alan genellikle tek sözlüğe birleşir. Aynı değerin birkaç sayfada görünmesi gerektiğinde widget'lar alanın çocukları olur ve çoğu zaman geometri ile bir /Parent başvurusundan başka bir şey taşımaz. ISO 32000-1 §12.7.3.1, /FT, /Ff, /V ve /DV'yi o zincir boyunca devralınabilir kılar (Tablo 220); §12.7.3.3 ise Tablo 222'deki devralınabilir kümeye yalnızca iki değişken metin özniteliği ekler: zorunlu olan ama bir atadan gelebilen /DA ile /Q. Etkileşimli form sözlüğü (Tablo 218), /DA ile /Q için belge geneli varsayılanlar ve /DA yazı tipi adlarının arandığı varsayılan kaynak sözlüğü /DR sağlar. /DR Tablo 222'de yoktur; o, formun bütününe aittir. Yalnızca widget sözlüğünü okuyan bir çizici, aşağıdaki dosyada tür, değer ya da /DA bulamaz ve kendi varsayılan yazı tipine düşer

1 0 obj  % katalog
<< /Type /Catalog /Pages 2 0 R
   /AcroForm << /Fields [10 0 R] /DA (/Helv 0 Tf 0 g) /Q 0
                /DR << /Font << /Helv 20 0 R /Cour 21 0 R >> >> >> >>
endobj
10 0 obj  % uç alan: tür, değer ve görünüm varsayılanları burada
<< /FT /Tx /T (invoice_no) /V (INV-2026-0417)
   /DA (/Cour 10 Tf 0 0 0.6 rg) /Q 2
   /Kids [11 0 R 12 0 R] >>
endobj
11 0 obj  % 1. sayfadaki widget: yalnızca geometri; köşeler sağ üstten başlar
<< /Type /Annot /Subtype /Widget /Parent 10 0 R
   /Rect [300 720 160 700] /F 4 /P 3 0 R >>
endobj
12 0 obj  % 2. sayfadaki widget: yalnızca geometri
<< /Type /Annot /Subtype /Widget /Parent 10 0 R
   /Rect [160 60 300 80] /F 4 /P 4 0 R >>
endobj

HotPDF her alan girdisini widget'ta arar, bulamazsa /Parent'ı izleyerek bir sözlük birden yukarı tırmanır; en yakın değer kazanır ve zincir tükendiğinde yalnızca /DA ile /Q, AcroForm sözlüğüne düşer. Yukarıdaki dosya için beklenen: iki widget da INV-2026-0417'yi 10 punto Courier, koyu maviyle sağa yaslı gösterir; nesne 10'un kendi /DA'sı olduğundan belge geneli /Helv 0 Tf hiç devreye girmez. Bir alan 12 punto siyah Helvetica ile çıkıyorsa, bariz şüpheli Linux kutusunda eksik bir Courier yazı tipidir. Boyut ele verir: bu, HotPDF'in widget'ta, hiçbir atada ya da AcroForm sözlüğünde /DA bulamadığında çizdiği şeyin ta kendisidir. O yüzden widget'ı dökün, /Parent'ını elle izleyin ve üreticinin /DA'yı nereye koyduğunu bulun; örneğin widget'ın hiç geri bağlanmadığı bir alan sözlüğüne

Delphi'de HotPDF başlıksız çizimi için AcroForm nesne grafiği: yalnızca geometri taşıyan widget'lar, /FT, /V, /DA ve /Q taşıyan alana /Parent üzerinden tırmanır
/Parent zinciri boyunca en yakın değer kazanır; zincir tükendiğinde yalnızca /DA ile /Q, AcroForm sözlüğüne düşer

/DA'dan gelen yazı tipi adları, sırayla widget'ta ya da bir ebeveyn alanda bir /DR, AcroForm /DR'si ve sayfanın yazı tipi kaynakları karşısında çözülür. Alan düzeyi bir /DR Tablo 222'nin dışındadır ama var olduğunda belirsiz değildir; HotPDF onu kabul eder. Tırmanış sınırlıdır: ağaçtaki bir döngü "Widget field inheritance contains a cycle" ile durur, sözlük olmayan bir /Parent "Widget Parent requires a dictionary" ile, çizim seçeneklerindeki (öntanımlı 32) MaxDepth'ten derin bir zincir ise "Widget field inheritance depth budget exceeded" ile. VCL bileşeni, devralınan AcroForm değerlerini okuyup alanları sıfırlarken aynı devralımı uygular

Başlıksız çizici ne çizeceğine nasıl karar verir?

HPDFRenderHeadlessPagePNG ile dosya ikizi, yedek yolu yalnızca /AP'si eksik ya da null olan görünür bir widget için çalıştırır; ISO 32000-1 §7.3.7, null bir sözlük değerinin yok bir girdiyle aynı olduğunu söyler ve çizici /AP null'a öyle davranır. Var olan normal bir görünüm her zaman kazanır, /NeedAppearances ne derse desin; böylece önceki bir doldurmadan kalma bayat bir görünüm, onu bir şey yeniden üretene kadar gösterilmeye devam eder. Yüklenen belge hiç değiştirilmez. Ne çizileceği, çözümlenen alan türüne ve bayraklarına bağlıdır:

AlanSentezlenen görünüm
Tek satır metin, /FT /Tx/Rect içinde çözümlenen /DA ve /Q ile dizilen /V dizgisi
Onay kutusu, /FT /Btn/Border'dan köşe yarıçaplı dikdörtgen kenarlık, /MK /BG dolgusu ve açıkken konturlu bir vektör tik işareti
Radyo düğmesi, 16. bitli BtnAçıkken dolu bir noktayla dairesel kenarlık
İtiş düğmesi (pushbutton), 17. bitli Btn/MK /CA altyazısı ortalanmış dikdörtgen kenarlık
Açılır liste (combo box), 18. bitli /FT /ChSeçili /Opt girdisinin görüntü metni, ortalanmış bir altyazı olarak
Liste kutusu, 18. biti temiz Ch/TI'den itibaren seçenek başına bir satır; her seçili satırın arkasında bir vurgu bandı

Tik işareti ile radyo noktası, pek çok üreticinin çizdiği gibi ZapfDingbats glifleri değil vektör yollardır; ince bir Linux kapsayıcısında ZapfDingbats kurulu olmayabilir. İtiş düğmesi ile açılır liste yüzleri, aynı alan için HotPDF'in VCL görünüm üreticisinin çizdiğiyle eşleşir; böylece bir form, imajı hangi taraf ürettiyse benzer görünür. Altyazı yazı tipini, boyutunu ve rengini /DA'dan alır; altyazı düzeni otomatik sığmadığından 0 boyutlu bir /DA, kutu yüksekliğinin yüzde 70'ine dönüşür ve 12 puanda tavan yapar. Onay kutusu ile radyo durumu widget'ın kendi /AS'inden gelir, devralınan /V'ye düşer ve Off dışındaki her durum işareti çizer. Her radyo çocuğuna kendi /AS'ini verin. Ebeveynin /V'si bütün grup için seçili durumu adlandırır ve bir çocuğu ötekinden ayıramaz

Delphi için HotPDF başlıksız çizicisinde karar akışı: var olan bir /AP her zaman kazanır, yoksa çözümlenen alan türü sentezlenmiş bir yüz seçer
Geçici görünümler yalnızca /AP eksik ya da null'ken çizilir ve yüklenen belgenin kendisi hiç değiştirilmez
uses
  SysUtils, HPDFHeadlessDocument, HPDFHeadlessCairoDevice;

function RenderFormPage(const FileName, PngName: string; PageIndex: Integer;
  out Diagnostic: string): Boolean;
var
  Doc: THPDFHeadlessDocument;
  Options: THPDFHeadlessRenderOptions;
  Info: THPDFHeadlessRenderResult;
begin
  Result := False;
  Diagnostic := '';
  Doc := THPDFHeadlessDocument.Create;
  try
    Doc.LoadFromFile(FileName);
    Options := THPDFHeadlessRenderOptions.Default;  // RenderAnnotations = True, MaxDepth = 32
    Options.Usage := hruPrint;  // bir görüntüleyicinin baskısı gibi: Print bayrağı olmayan widget'lar atlanır
    try
      // PageIndex sıfır tabanlıdır
      Info := HPDFRenderHeadlessPagePNGFile(Doc.GetRenderAccess(PageIndex),
        PngName, Options);
      Diagnostic := Format('%d appearances drawn', [Info.Appearances]);
      Result := True;
    except
      on E: EHPDFHeadlessRenderError do
        // örn. 'Widget field requires a name field type'; dolu bir form için,
        // RenderAnnotations := False ile yeniden denemeyin; her widget'ı değeriyle düşürür
        Diagnostic := E.Message;
    end;
  finally
    Doc.Free;
  end;
end;

Metin alanı yedeği, dizgi değeri tutan tek satır alanları çizer. Daha zengin her metin düzeni için görünümü, doldurma anında Windows tarafında üretin; EnsureLoadedFieldAppearanceStream orada eksik /AP akışını, dosya düzleştirilmeden ya da yola çıkmadan önce kurar; Linux işi böylece gerçek görünüm akışları alır ve başlıksız çizici, hâlâ birine ihtiyacı olan her alanla karşılaştığında onu adlandırır. Yine de Linux çıktısını Linux çizicisiyle yargılayın. RenderLoadedPageToBitmap, Windows GDI arka ucuyla çizer ve bu yedeği hiç çalıştırmaz; orada doğru görünen bir sayfa, Cairo aygıtının aynı dosyadan ne üreteceği hakkında hiçbir şey söylemez

Alan bayrağı bitleri 1'den itibaren numaralanır

ISO 32000-1 §12.7.3.1, /Ff sözcüğündeki bit konumlarının düşük sırada 1'den başlayıp 32'ye kadar koştuğunu söyler ve Tablo 226, 228 ile 230 hep o numaralamayı kullanır. Delphi'de N. bit bu yüzden 1 shl (N - 1)'dir. Bit numarasının kendisiyle kaydırın; her test bir konum yukarı kayar: "radio, bit 16" için yazılan 1 shl 16 aslında pushbutton bayrağını sınar ve "combo, bit 18" için yazılan 1 shl 18, Edit'i sınar. İki yanlış maske de gerçek bayraktır. Kod derler, çalışır ve alanları yanlış türe ayırır

PDF alan bayrakları Delphi'de bitleri 1'den sayar: cetvel, radyonun neden 1 shl 15 olduğunu ve 1 shl 16'nın pushbutton'a ulaştığını gösterir
/Ff sözcüğünün N. biti, Delphi maskesi olarak 1 shl N eksi bir alır; her bir kaymış maske, komşu bitin gerçek bir bayrağıdır
BayrakTabloBitDelphi maskesi
Multiline228 (metin)131 shl 12 = 4096
Password228 (metin)141 shl 13 = 8192
Comb228 (metin)251 shl 24 = 16777216
RichText228 (metin)261 shl 25 = 33554432
Radio226 (düğme)161 shl 15 = 32768
Pushbutton226 (düğme)171 shl 16 = 65536
Combo230 (seçim)181 shl 17 = 131072
uses
  Classes, SysUtils, HPDFHeadlessDocument;

const
  // ISO 32000-1, Ff bitlerini 1'den (düşük sıra) sayar; N. bit 1 shl (N - 1)
  TxMultiline   = 1 shl 12;  // bit 13
  TxPassword    = 1 shl 13;  // bit 14
  TxFileSelect  = 1 shl 20;  // bit 21
  TxDoNotScroll = 1 shl 23;  // bit 24
  TxComb        = 1 shl 24;  // bit 25
  TxRichText    = 1 shl 25;  // bit 26
  TxProducerLayout = TxMultiline or TxPassword or TxFileSelect or
    TxDoNotScroll or TxComb or TxRichText;

procedure ListTextFieldsForProducerAppearance(const FileName: string;
  Report: TStrings);
var
  Doc: THPDFHeadlessDocument;
  Field: THPDFHeadlessFormField;
  I: Integer;
begin
  Doc := THPDFHeadlessDocument.Create;
  try
    Doc.LoadFromFile(FileName);
    for I := 0 to Doc.FormFieldCount - 1 do
    begin
      // FieldType, Flags ve MaxLength atalardan devralınmış olarak gelir
      Field := Doc.GetFormField(I);
      if Field.FieldType <> 'Tx' then
        Continue;
      if (Field.Flags and TxProducerLayout) <> 0 then
        Report.Add(Format('%s: flags $%.8x, needs a producer /AP',
          [Field.Name, Field.Flags]))
      else if Field.MaxLength > 0 then
        Report.Add(Format('%s: /MaxLen %d, needs a producer /AP',
          [Field.Name, Field.MaxLength]))
      else if Field.Value = '' then
        Report.Add(Format('%s: blank, fill it or give it an /AP', [Field.Name]));
    end;
  finally
    Doc.Free;
  end;
end;

Tarama, alan düzeyi bayrakları okur; üreticiler onları oraya koyar. Çizici ayrı bir widget üzerindeki bir /Ff'yi de dikkate alır; listeyi bir planlama yardımcısı, çizim tanısını son söz olarak görün. Boş denetimi önemli olabilir; çünkü /V'si ve /AP'si olmayan bir metin alanının sentezleyecek hiçbir şeyi yoktur: çizim "A text-field widget without a value has no appearance to synthesize" ile durur ve çare, dosya çiziciye varmadan bir değer ya da bir görünüm koymaktır

"Degenerate appearance geometry" ne demek?

Dikdörtgenin ya da görünüm kutusunun alanı yok demektir; ters bir /Rect sebep değildir. ISO 32000-1 §7.9.5, bir dikdörtgenin çapraz karşıt herhangi iki köşeyi adlandırabilmesine izin verir ve notu, işleme yazılımının bunları normalleştirmeye hazır olması gerektiğini söyler. Genişliği x2 - x1 olarak hesaplayan naif bir çizici, [300 720 160 700]'den negatif bir sayı alır ve ya ek açıklamayı reddeder ya da görünümünü aynalar. HotPDF, sentezlenen yüzler için de var olan /AP akışları için de herhangi bir boyut denetiminden önce koordinatları sol alt ve sağ üst sırasına çevirir; dolayısıyla yukarıdaki 11 numaralı widget, köşeleri konvansiyonelmiş gibi çizilir. Hata yalnızca, normalleştirmeden sonra /Rect'in genişliği ya da yüksekliği sıfırsa ya da akışın /BBox'ı kendi /Matrix'i altında bir çizgiye ya da noktaya çökerse, görünüm akışı olan bir ek açıklama için fırlatılır. Göründüğünde widget'ın /Rect'ini akışın /BBox'ı ve /Matrix'i ile birlikte dökün; üçünden birinin sıfır boyutu vardır

/AP'si olmayan ve sıfır alanlı bir /Rect'i olan widget başka bir yol izler: yedek yol onu atlar ve sayfayla devam eder. ISO 32000-2 §12.5.2, görünüm zorunluluğundan yalnızca Popup, Projection ve Link ek açıklamalarını ile tek bir noktaya çöken bir dikdörtgeni muaf tutar. Yalnızca bir çizgi olan bir dikdörtgen PDF 2.0 altında yine görünüm borçludur; çizici onu da atlar, sadece boyanacak bir alan yoktur

Alan türü bulunamadığında

"Widget field requires a name field type", çizicinin widget'tan /Parent bağlarının bittiği yere kadar tırmandığını ve bir /FT bulamadığını demektir. Çizici, sayfanın /Annots dizisinden başlar; yukarı doğru /Parent bağı, alana giden tek yoludur. Yükleme öbür yönde yürür; AcroForm /Fields dizisinden aşağı, /Kids üzerinden. Bu yüzden LoadFromFile, sayfası sonra çizilemeyen bir dosyada başarılı olabilir. Tipik sebep, bir alanın /Kids'inde listelenip geri işaret etmeyen bir widget'tır. ISO 32000-1 Tablo 188 bunun için biraz örtü sağlar: /Parent'ı bir widget üzerinde yalnızca widget birkaç çocuktan biri olduğunda ister ve "absent otherwise" der; dolayısıyla onu harfi harfine okuyan bir yazıcı, tek ayrı widget'lı bir alan için bağı düşürür. ISO 32000-2 aynı girdiyi "optional otherwise"e gevşetmiştir. Biz zaten, alanıyla birleşmemiş her widget'a /Parent yazardık. ISO 32000-2 buna izin verir ve sayfa güdümlü bir okuyucunun — bizim ya da bir başkasının — alana ulaşmasının tek yolu budur. Üreticiyi düzeltemiyorsanız, yalnız widget'ı alan sözlüğüyle birleştirmek aynı sonucu verir

Ebeveynine bağlanan ve yukarı doğru bir yerde bir /DA bulan bir widget, HotPDF Delphi component'ün başlıksız çizicisinde hiçbir /AP olmadan doğru çizilir; bu da bir Linux form hattında önce doğru kurulacak şeyin, görünüm akışlarından çok, alan ağacı olduğunu gösterir