Technical Article

Delphi'de XFA Zengin Metin Köprülerini PDF Bağlantılarına Düzleştirme

XML Form Mimarisi (XML Forms Architecture - XFA) kullanımdan kaldırılmıştır. ISO 32000-1, PDF 2.0'dan kaldırıldığı notuyla bunu §12.7'de taşımaktadır ve modern görüntüleyiciler XFA motorlarını birer birer devre dışı bırakmaktadır. Bunların hiçbiri arşivleri boşaltmadı. Devlet başvuru formları, sigorta başvuruları ve banka ekstreleri yaklaşık yirmi yıl boyunca XFA olarak yazıldı ve bu dosyalar bugün hala gelen kutularına ve belge işlem hatlarına gelmeye devam ediyor. Bunları işlemek için kullanılan görüntüleyici bunu yapmayı bıraktığında, form "lütfen farklı bir okuyucuda açın" yer tutucusu içeren boş bir sayfaya dönüşür. Kalıcı çözüm, XFA'yı herhangi bir okuyucunun boyayabileceği statik PDF içeriğine düzleştirmektir (flatten)。

Bu düzleştirmenin zor kısmı alanlar değildir. Metin kutuları ve onay kutuları AcroForm bileşenleriyle (widgets) yeterince temiz eşleşir. Zor kısım, XFA'nın bir çizim öğesinin içinde, bir <exData contentType="text/html"> bloğunda depoladığı zengin metindir. Bu blok, satır içi stil oluşturma ve sıklıkla çapa (anchor) içeren bir HTML alt kümesidir. Bunu sayfaya aktarmak, hem stillendirilmiş metni hem de canlı köprüleri yeniden üretmek anlamına gelir ve köprüler, çoğu uygulamanın sessizce vazgeçtiği yerdir。

XFA zengin metni gerçekte nasıl görünür

Bir exData gövdesi, XHTML'nin küçük bir dilimidir. Bir paragraf <p>; stillendirilmiş bir karakter aralığı, kalınlık, duruş, renk ve boyut için kendi satır içi CSS'sine sahip bir <span>; ve bir köprü, görünür metinini saran bir <a href="..."> öğesidir. Tek bir satır, her biri farklı stillere sahip arka arkaya birkaç span tutabilir ve bunlardan biri bir çapa (anchor) olabilir. Stil oluşturma, bırakılabilecek bir dekorasyon değildir. Yasal bir uyarı olduğu için kalın kırmızı renkte işlenen bir hüküm, düzleştirmeden sonra da kalın ve kırmızı kalmalıdır, aksi takdirde düzleştirilmiş belge orijinali yanlış temsil eder。

So the flatten engine cannot treat the block as one string. It has to walk the inline structure, resolve each run's effective style by layering the span's inline CSS over the draw element's base font, and lay the runs out one after another across the line. HotPDF models each of these laid-out fragments as an internal TXFARichRun record. The record carries the run's text, its resolved style, its measured box, and, for an anchor, the Href it points at。

Dizileri soldan sağa düzenleme

Konumlandırma, zengin metnin bir ayrıştırma sorunu olmaktan çıkıp bir dizgi sorunu haline geldiği yerdir. Diziler bir satırı paylaşır, dolayısıyla her dizi bir öncekisinin bittiği yerde başlar. Bu konumları kaydeden bir işaretleme yoktur; ölçülmeleri gerekir. Motorun dahili LayoutRichText rutini, her diziyi daha sonra boyayacak olan aynı yazı tipi metrikleriyle ölçer, ardından dizinin yatay sapmasını (offset) önceki tüm dizi genişliklerinin çalışan toplamına ayarlar. Birinci dizi çizim kutusu başlangıcında başlar, ikinci dizi birinci dizinin genişliğinde başlar, üçüncü dizi ilk ikisinin birleşik genişliğinde başlar ve satır boyunca bu şekilde devam eder。

Ölçüm yazı tipi hizalamasının bu kadar önemli olmasının nedeni budur. Düzenleme geçişi ilerlemeleri (advances) ölçer; ayrı bir işleme geçişi glifleri çizer. Bu iki geçiş yazı tipi konusunda uyuşmazsa, düzenin hesapladığı kutular oluşturucunun boyadığı gliflerin altında oturmayacaktır. HotPDF, dahili RunStyleToFontSpec yardımcısı aracılığıyla her dizinin çözümlenmiş stilini oluşturucunun 10 puntoluk Arial varsayılanlarıyla eşleşen bir yazı tipi özelliğine eşleyerek bunları adım adım tutar. Ölçülen ilerleme ile çizilen metin daha sonra uyuşur ve bir dizinin hesaplanan kutusu gerçekten okuyucunun gördüğü karakterleri kapsar。

// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
  TRichRunInfo = record
    Dx, Dy : Double;       // top-left, relative to the draw-box origin
    W, H   : Double;       // measured run box (width from the layout pass)
    Text   : AnsiString;   // the run's visible characters
    Href   : AnsiString;   // URI target for an <a> run, '' otherwise
  end;

Bir çapa dizisinden bir PDF Bağlantı ek açıklamasına

Bitmiş bir PDF'deki köprü, sayfa içeriğinin bir parçası değildir. ISO 32000-1 §12.5.6.5'te açıklanan Bağlantı ek açıklaması (Link annotation) adında ayrı bir nesnedir. Ek açıklama, sayfadaki tıklanabilir dikdörtgeni tanımlayan bir /Rect'e ve dikdörtgen tıklandığında tetiklenen bir eyleme sahiptir. Harici bir bağlantı için eylem bir URI eylemidir: hedef adres /URI dizesi olacak şekilde /S /URI. Altındaki görünür metin sıradan sayfa içeriğidir; ek açıklama ise üzerine yerleştirilen görünmez etkin bölgedir。

Düzleştirme yolu tam olarak bu modeli takip eder. Bir dizi bir Href taşıdığında, HotPDF önce stillendirilmiş metni çizer, ardından dizinin kutusu üzerine bir Bağlantı ek açıklaması oluşturur. Bu ek açıklama için genel giriş noktası, /URI eylemiyle /Type /Annot /Subtype /Link nesnesini oluşturan ve ek açıklama sözlüğünü döndüren sayfa yöntemi AddURILink'tir. Dikdörtgeni, çizim öğesinin yerel koordinatlarından sayfa koordinatlarına çevrilen dizinin ölçülen kutusudur. Sonuç, tam olarak çapa metnine denk gelen ve başka hiçbir yere gitmeyen bir bağlantıdır。

// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
  LinkRect: TRect;
  Annot: THPDFDictionaryObject;
begin
  LinkRect := Rect(72, 690, 268, 706);  // page-space hit box for the run
  Annot := Pdf.CurrentPage.AddURILink(LinkRect,
    'https://www.example.gov/appeal', 'File an appeal online');
end;

Tıklama kutusunun neden ölçülen genişliklerden gelmesi gerektiği

Sayfada görünür metnini arayarak bağlantıyı bulmayı ve bulunan şeyin etrafına dikdörtgen çizmeyi hayal etmek caziptir. Bu çalışmaz ve nedeni, düzleştirilmiş metnin nasıl depolandığıyla temelden ilgilidir. Stillendirilmiş diziler gömülü alt küme yazı tipleriyle (subset fonts) boyanır. Alt küme yazı tipi tuttuğu glifleri yeniden numaralandırır, bu nedenle sayfa içerik akışı orijinal karakter kodlarını değil, onaltılık CID kodlarını tutar. Sayfadaki baytlar bir insanın okuduğu harfler değildir ve metin olarak aranamazlar. Çapanın başlığı için yapılan arama hiçbir şey bulamaz, çünkü bu başlık akışın hiçbir yerinde kelimesi kelimesine metin olarak mevcut değildir。

Dikdörtgen için tek güvenilir çapa, düzenleme geçişinin zaten üretmiş olduğu geometridir. Her dizinin sapması (offset) ve ölçülen genişliği, herhangi bir glif yeniden numaralandırılmadan önce satır akarken hesaplanmıştır ve metnin fiziksel olarak nerede görüneceğini tanımlarlar. HotPDF, bağlantı dikdörtgenini herhangi bir metin aramasından değil, doğrudan dizinin yerleştirilmiş kutusundan alır. Ölçümde işleme yazı tipi kullanıldığından, alt kümelemeden bağımsız olarak kutu doğrudur. Geometri kodlamadan kurtulur; metin kurtulamaz. Ölçülen genişlik konumlandırmasının tüm argümanı budur ve metin aramasıyla bağlantıları sonradan uydurmaya çalışan bir düzleştiricinin kayan veya kaybolan tıklama bölgeleri üretmesinin nedeni budur。

Düzleştirmeyi kodunuzdan yönlendirme

Zaten bir XFA paketi içeren bir PDF için giriş noktası FlattenLoadedXFA'dır. Belgeyi yükleyin, yöntemi çağırın ve sonucu kaydedin. Editable parametresi form alanlarına ne olacağına karar verir: bunları doldurulabilir AcroForm bileşenleri (widgets) olarak tutmak için True, çıktının dondurulmuş bir kayıt olması amacıyla her bileşeni salt okunur olarak işaretlemek için False değerini iletin. Stilli dizileri ve bağlantı ek açıklamalarıyla zengin metin çizim blokları her iki şekilde de üretilir. Fonksiyon çıkardığı bileşenlerin sayısını döndürür。

var
  Pdf: THotPDF;
  Emitted, i: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('xfa_appeal_form.pdf');
    // True keeps fields fillable; False freezes them read-only.
    Emitted := Pdf.FlattenLoadedXFA(True);

    // Anything the engine could not map is reported, not raised.
    for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
      Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);

    Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
    Writeln('Widgets emitted: ', Emitted);
  finally
    Pdf.Free;
  end;
end;

Çağrıdan sonra her zaman XFAFlattenWarnings değerini okuyun. Liste her düzleştirmenin başında temizlenir ve motorun işlemeyi reddettiği her öğe için bir satır biriktirir: desteklenmeyen bir alan türü, kodu çözülmeyen bir çizim resmi, kullanılabilir span'i olmayan bir exData bloğu. Bunların hiçbiri bir istisna oluşturmaz, bu nedenle boş bir uyarı listesi her şeyin eşleştiğinin kanıtıdır ve boş olmayan bir liste tam olarak hangi orijinalleri incelemeniz gerektiğini söyler. Ham XFA'yı yüklü bir PDF yerine XDP baytları olarak tuttuğunuzda, kardeş yöntem ApplyXFAAsAcroForm bu baytları doğrudan alır ve aynı kod yolunu ve aynı uyarı davranışını paylaşır. Tamamlayıcı AddXFAPacket yöntemi ise diğer yöne giderek, oluşturduğunuz bir belgeye bir XFA paketi gömer。

Sonucu bir okuyucuda onaylama

Düzleştirilmiş dosyayı Acrobat'ta veya herhangi bir geçerli görüntüleyicide açın ve iki şeyi kontrol edin. İlk olarak, zengin metin stili bozulmadan işlendi: kalın diziler kalın, renkli diziler kendi rengini taşıyor ve span'ler kutunun dışına taşmak veya üst üste binmek yerine satırda doğru sırada duruyor. İkinci olarak, köprüler canlı. Bir çapanın üzerine gelin ve durum çubuğu hedef adresi göstermelidir; tıklayın ve URI eylemi onu açmalıdır. Her birinin, /Rect'i çapa metnini kucaklayan, formla işlenmiş XFA yerine artık düz boyanmış gliflerden oluşan içeriğin üzerinde oturan gerçek bir /Link ek açıklaması olduğunu onaylamak için görüntüleyicinin ek açıklama inceleyicisini kullanın. Doğru dikdörtgenler üzerindeki stilli statik metin artı gerçek Bağlantı ek açıklamaları kombinasyonu, düzleştirilmiş belgenin artık ihtiyaç duymadığı XFA motorlarından daha uzun yaşamasını sağlayan şeydir。

Form alanlarının kendilerini (bu zengin metni çevreleyen metin kutuları, onay kutuları ve seçim listeleri) düzleştirmek, XFA formlarını AcroForm bileşenlerine düzleştirme kılavuzumuzda ele alınmıştır. Düzleştirme yolunun ürettiklerinin ötesinde, elle Bağlantı ek açıklamaları oluşturma ve yerleştirmenin daha geniş hikayesi için, HotPDF'de PDF ek açıklamalarıyla çalışma konusuna bakın. Her ikisi de, Delphi ve C++Builder için HotPDF Bileşeni ile birlikte gönderilen aynı ek açıklama ve form modeline dayanır。