HotPDF Delphi Component, yüklü bir PDF'teki mevcut bir AcroForm alanını THotPDF.SetFormFieldValue ile doldurur; alana ya sıfır tabanlı alan indeksiyle ya da tam nitelikli alan adıyla erişilir. Yeni /V girdisini yazmak kolay kısmıdır; bu çağrıyı gerçek dünya formlarında güvenilir kılan şey, aynı yöntemin yanlış gidene kadar görünmeyen üç durum parçasını da tutarlı tutmasıdır: alanın, ASCII dışı bir adın hiç bulunabilmesi için çözülmüş kimliği, checkbox ve radio widget'larındaki /AS görünüm durumu ve seçim alanlarındaki /I seçim indeksi dizisi. Görünen görünüm akışı ise EnsureLoadedFieldAppearanceStream üzerinden ayrı ve açık bir adımdır
Senaryo sıradan olanıdır: bir müşteri size kendi formunu gönderir, bir vergi beyannamesi, bir sigorta talebi, birilerinin yıllar önce Acrobat'ta kurduğu bir satın alma siparişi; Delphi uygulamanız onu bir veritabanından doldurup her yerde doğru açılan bir dosya olarak geri vermek zorundadır. Formun nasıl yazıldığı üzerinde hiçbir denetiminiz yoktur. Alan adları UTF-16 kodlanmış olabilir, checkbox dışa aktarma değerleri Yes yerine 2 olabilir ve combo kutuları [export display] seçenek çiftlerini kullanabilir. Bu ayrıntıların her birinin ISO 32000-1'de bir kuralı vardır ve SetFormFieldValue artık her kuralı sizin yerinize işler. Bu yazı ne yaptığını, neden yaptığını ve nerede durduğunu anlatır. Henüz var olmayan alanları oluşturma sorunu için Delphi'de yüklü bir PDF'e AcroForm alanı ekleme yazısına bakın
SetFormFieldValue ASCII dışı adlı bir alanı neden bulamaz?
v2.752.1'den önce yanıt kodlamaydı: alan dosyada onaltılık bir UTF-16BE adı altında yaşıyordu ve ad önbelleği metin yerine onaltılık yazımı saklıyordu. ISO 32000-1 §12.7.3.1 kısmi alan adı /T alanını bir metin dizesi olarak tanımlar ve §7.9.2.2 bir metin dizesinin başta FE FF bayt sırası işaretiyle UTF-16BE olabileceğini söyler. Yazma araçları böyle adları §7.3.4.3 uyarınca rutin olarak onaltılık dizeler hâlinde serileştirir, dolayısıyla Straße adlı bir alan <FEFF005300740072006100DF0065> olarak gelir. HotPDF içinde THPDFStringObject.Value, IsHexadecimal set edilmişse ham onaltılık metni tutar; bu, özgün sözlüğün kayıpsız gidiş dönüşü için tam istediğiniz şeydir ve arama anahtarı olarak tam istemediğiniz şeydir. HPDFLoadedFormTextName iki kaygıyı ayırır. İlişki önbelleği kurulurken her /T değeri ondan geçer: dize nesnesi onaltılıksa HPDFHexToBytes bayt dizisini geri getirir; baytlar FE FF ile başlıyor ve uzunluk çiftse yük UTF-16BE olarak çözülür ve UTF-8 olarak yeniden kodlanır; sonuç ardından §12.7.3.1'in tarif ettiği tam nitelikli adı oluşturmak üzere ebeveyn adına noktayla eklenir, böylece Address adlı bir ebeveynin altındaki City adlı çocuk Address.City olarak kaydedilir. Önbellek anahtarı küçük harfe normalleştirilir ve bu, SetFormFieldValue('address.city', ...) çağrısının da başarılı olmasını sağlar; spesifikasyon adları büyük-küçük harfe duyarlı saydığı için bu, standardın ötesinde bir kolaylıktır. En önemlisi, yalnızca önbellek anahtarı değişir. Alan sözlüğündeki /T nesnesi onaltılık kodlamasını korur, böylece belgeyi kaydetmek yalnızca doldurduğunuz bir alanın kimliğini yeniden yazmaz
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('claim-form.pdf') <= 0 then Exit;
// Nitelikli adlar UTF-16BE /T dizelerinden çözülür ve
// noktalarla eklenir, böylece iç içe ve ASCII dışı adlar bulunur
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Pdf.SetFormFieldValue('Applicant.Straße', 'Hauptstraße 12');
// Latin-1 olmayan değerler FEFF önekli UTF-16BE hex olarak taşınır
// ve PDF onaltılık dizesi olarak yazılır
Pdf.SetFormFieldValue('Applicant.City', 'FEFF004D00FC006E006300680065006E');
Pdf.SaveLoadedDocument('claim-form-filled.pdf');
finally
Pdf.Free;
end;
end;
SetFormFieldValue aslında ne yazar?
Her iki aşırı yükleme de aynı beş adımı çalıştırır: alan sözlüğünü bul, /V değerini HPDFSetDictFormValue üzerinden yaz, seçim indekslerini uyumla, sözlüğü kirli işaretle, düğme görünüm durumlarını uyumla ve son olarak alan indeksini NoteLoadedFormFieldDirty üzerinden kaydet. Son adım, form hesaplama betikleri taşıyorsa önemlidir, çünkü parametresiz RecalculateLoadedFormFieldsIncremental aşırı yüklemesinin tükettiği kirli küme, yalnızca değişen bir alanı dolaylı olarak okuyan hesaplamaları yeniden çalıştırmaya yarar. HPDFSetDictFormValue kendisi de yerine geçtiği nesne türü konusunda dikkatlidir. Mevcut /V bir ad nesnesiyse ki checkbox ve radio alanları dışa aktarma değerleri için bunu kullanır, yeni değer ad olarak yazılır, asla dize olarak değil, çünkü PDF adları yapı gereği yalnızca ASCII olabilir. Aksi hâlde bir dize nesnesi yazar ve verdiğiniz değeri inceler: FEFF ile başlayan, uzunluğu çift olan ve tümüyle onaltılık basamaklardan oluşan bir dize, §7.9.2.2'nin UTF-16BE tel biçimi sayılır ve IsHexadecimal set edilerek saklanır, böylece literal bir (FEFF...) yerine <FEFF...> olarak serileştirilir. Yukarıdaki City satırının dayandığı mekanizma budur; başka her dize verdiğiniz baytlarla literal dize olarak saklanır, dolayısıyla düz Latin metni için düz metin geçirirsiniz
Bir checkbox değeri değiştikten sonra neden eski işaretini korur?
Çünkü bir düğme alanında neyin çizileceğine tek başına değer karar vermez. ISO 32000-1 §12.7.4.2.3, bir checkbox widget'ının /AP /N içindeki hangi akışın gösterildiğini adlandıran bir /AS görünüm durumu taşıdığını söyler ve görüntüleyiciler /V değerinden değil /AS durumundan boyar. /V değerini Yes yapıp /AS durumunu Off bırakırsanız dosya kendi içinde çelişir ve düzleştirme, form verisi işaretli derken bayat işaretsiz görünümü sayfaya mutlulukla gömer. ReconcileLoadedButtonAppearanceStates tam bu boşluğu kapatmak için vardır: /FT alanı Btn olan bir alan için hem alan sözlüğünü hem /Kids dizisindeki her girdiyi ziyaret eder, açık durum adını /AP /N içinden okur ve /AS durumunu, alan değeriyle eşleşiyorsa o ada, eşleşmiyorsa Off değerine yazar
Gerçek formlardan çıkan iki ayrıntı v2.752.3 düzeltmesini biçimlendirdi. Birincisi, normal bir görünüm sözlüğünün yalnızca açık durumu içermesine izin verilir; §12.7.4.2.3 kapalı görünümü Off olarak adlandırır ama yazma araçları akışını sık sık atlar ve görüntüleyicinin hiçbir şey çizmemesine izin verir. Önceki kod sözlük ikiden az girdi tuttuğunda pes ediyordu, dolayısıyla o tek durumlu checkbox'lar sessizce eski işaretlerini koruyordu. Kontrol artık basitçe sözlüğün boş olmamasıdır ve açık durum adı Off olmayan ilk anahtar olarak alınır. İkincisi, açık durum adı yazarın seçtiği şeydir. Gerçek formlar 2, Yes, On ya da yerelleştirilmiş bir sözcük kullanır, dolayısıyla karşılaştırma sabit kodlanmış bir Yes ile değil, gerçek anahtarla ve büyük-küçük harfe duyarsız olarak yapılır. Radio düğmeleri §12.7.4.2.4'te anlatılan bir kırışıklık daha ekler: seçim, ebeveyn alanın /V alanında yaşar; tek tek çocuklar ise widget'lara sahiptir ve genellikle kendi /V alanları yoktur. Bu yüzden iç içe InheritedButtonValue yardımcısı, boş olmayan bir değer bulana kadar /Parent zincirini 64 düzeye kadar yukarı yürür, böylece her çocuk ait olduğu grubun değeriyle karşılaştırılır. Ebeveyni bir çocuğun dışa aktarma değerine ayarlamak tam olarak o çocuğu açar ve kardeşlerinin hepsini kapatır
// Checkbox: dışa aktarma değeri /AP /N içindeki açık durum anahtarıyla
// eşleşmeli (çoğu kez 'Yes', ama gerçek formlar '2' ya da 'On' kullanır)
Pdf.SetFormFieldValue('Consent', 'Yes');
// Radio grubu: /V ebeveyne yazılır; her çocuk widget kendi dışa
// aktarma adına ya da Off'a ayarlanmış bir /AS alır
Pdf.SetFormFieldValue('PaymentMethod', 'Card');
// Bir checkbox'ı temizlemek: hiçbir açık durumla eşleşmeyen değer /AS Off üretir
Pdf.SetFormFieldValue('Newsletter', 'Off');
Seçim alanları: /I değerini /V ile aynı adımda tutmak
Bir combo kutusu ya da liste kutusu için seçimin kaydedildiği tek yer /V değildir. §12.7.4.4'teki Tablo 231, /I alanını, seçili öğeleri belirten ve /Opt içine sıfır tabanlı indekslerden oluşan bir dizi olarak tanımlar ve /V seçenek 3'ü adlandırırken /I alanını seçenek 0'ı gösterir bulan bir görüntüleyici yanlış satırı vurgulayabilir. v2.754.1'den bu yana HPDFReconcileChoiceSelection her SetFormFieldValue çağrısının içinde çalışır ve miras alınan /FT alanı Ch ise /I dizisini yeni değerden yeniden kurar. İşlem sırası bilinçlidir. Yerel /I girdisi önce, içeriğine dokunulmadan silinir: eski dizi başka bir alanla paylaşılan dolaylı bir nesneyse yerinde değiştirmek diğer alanın seçimini bozar, bu yüzden rutin başvuruyu düşürüp taze ve doğrudan bir dizi oluşturur. Ardından /Opt alanını /Parent zinciri üzerinden çözer, çünkü seçim seçenekleri miras alınabilir, ve girdileri tarar. Çıplak bir dize seçeneği doğrudan karşılaştırılır; [export display] çifti dışa aktarma elemanı üzerinden karşılaştırılır ve ikiden az elemanlı bir çift atlanır. Her iki taraf da HPDFLoadedFormTextName üzerinden geçer, böylece onaltılık UTF-16 bir seçenek, onları birebir yazmanıza gerek kalmadan onaltılık UTF-16 bir değerle eşleşir. İlk eşleşmede tek elemanlı bir /I yazılır ve tarama durur; skaler bir değer, MultiSelect bayrağından bağımsız olarak önceki çoklu seçimi her zaman değiştirir
Hiçbir şey eşleşmediğinde hiç /I yazılmaz. Bu, §12.7.4.4'ün kullanıcının seçenek listesinin dışında bir değer yazmasına izin verdiği düzenlenebilir bir combo kutusu için doğru sonuçtur; böyle bir değerin indeksi yoktur ve bayat bir indeks hiç olmamasından kötü olurdu. Eşleştirilmiş bir seçenek listesine dışa aktarma değeri yerine görünen etiket geçirirseniz de aynı şey olur; bu yüzden bir combo kutusu seçiminizi göstermeyi reddediyorsa çiftin hangi yarısını verdiğinize bakın
// /Opt [[US United States] [CA Canada] [MX Mexico]] biçiminde:
// dışa aktarma değeri üzerinden eşleşir ve /I [1] olur
Pdf.SetFormFieldValue('Country', 'CA');
// /Opt dışında bir değere sahip düzenlenebilir combo: /V yazılır,
// /I kaldırılır ve hiçbir indeks uydurulmaz
Pdf.SetFormFieldValue('Title', 'Principal Engineer');
Değer ve görünüm iki ayrı işlemdir
SetFormFieldValue bir metin ya da seçim alanının görünüm akışına hiç dokunmaz. Çağrıdan sonra /V yeni metni tutarken /AP /N hâlâ eskisini çizer ve görüntüleyicinin hangisini göstereceği, AcroForm sözlüğünün §12.7.3.3 uyarınca /NeedAppearances true taşıyıp taşımamasına ve görüntüleyicinin buna uyup uymamasına bağlıdır. Dosyanın yeni değeri, bayrağı yok sayan düzleştiriciler ve küçük resim üreteçleri dahil her okuyucuda render etmesini istiyorsanız alan indeksiyle EnsureLoadedFieldAppearanceStream çağırın. Bu yordam miras alınan /DA dizesinden, /Q hizalamasından, /MaxLen tarak düzeninden ve değerden bir Form XObject kurar, adlandırılmış yazı tipini AcroForm /DR kaynakları üzerinden çözer ki bir Type0 yazı tipi Helvetica'ya düşmek yerine kendi alt yazı tipini korusun, ve en az bir widget akış aldıysa True döndürür. SetFormFieldValue fonksiyonunun adla çağrılan aşırı yüklemesi size indeks geri vermez, bu yüzden bir indeksi, sahibi siz olduğunuz ve serbest bırakmanız gereken bir THPDFLoadedFormField döndüren GetFormField üzerinden alın. v2.752.1 değişikliğinin regresyon paketi bu ayrım konusunda açıktır: bir değer ayarlar, EnsureLoadedFieldAppearanceStream çağırır, sonra sayfayı render edip widget dikdörtgeninin içindeki piksellerin değiştiğini, dışındakilerin değişmediğini denetler. /V değerinin değiştiğini doğrulamak, kullanıcının ne göreceği konusunda hiçbir şey kanıtlamaz
var
Field: THPDFLoadedFormField;
begin
Pdf.SetFormFieldValue('Applicant.FullName', 'Maria Schneider');
Field := Pdf.GetFormField('Applicant.FullName');
try
// Yeni değeri /AP içine çiz ki /NeedAppearances'ı yok sayan
// görüntüleyiciler de onu göstersin
if not Pdf.EnsureLoadedFieldAppearanceStream(Field.Index) then
raise Exception.Create('No widget rectangle to paint into');
finally
Field.Free;
end;
Pdf.SaveLoadedDocument('claim-form-filled.pdf');
end;
Bunun üzerine kurulmadan önce bilinmesi gereken sınırlar
ReconcileLoadedButtonAppearanceStates, adreslediğiniz sözlüğün yerel /FT alanını sınar, dolayısıyla radio ebeveyni üzerinde ya da kendi /FT alanını taşıyan bir checkbox üzerinde etki eder; /FT yalnızca ebeveyninde olan ve tek başına adreslenen bir çocuk widget bu yoldan uyumlanmaz. HPDFReconcileChoiceSelection tek bir skaler değeri işler ve en fazla bir indeks yazar; birden fazla girdisi seçilmiş çoklu seçim liste kutuları SetFormFieldValue fonksiyonunun modellediği şeyin dışındadır. İki rutin de verdiğiniz değeri /Opt alanına ya da açık durum anahtarlarına karşı doğrulamaz, dolayısıyla bir yazım hatası istisna yerine Off bir checkbox ya da indekssiz bir combo üretir. Ve GetFormFieldValue, saklanan /V metnini sözlükte durduğu gibi döndürür; onaltılık kodlanmış bir değer için bu, çözülmüş metin değil onaltılık yazımdır
Değerler girilip görünümler çizildikten sonra, doğal iki sonraki adım bu işlemin iki yanında durur. Alan verisini tek tek SetFormFieldValue çağrılarıyla değil de dış sistemlerle toplu olarak alıp vermek Delphi'de XFDF içe ve dışa aktarma yazısının konusudur. Doldurulmuş form nihayetlendiğinde ve artık düzenlenebilir olmaması gerektiğinde ise Delphi'de AcroForm ve XFA alanlarını düzleştirme, burada anlatılan /AS durumlarını ve görünüm akışlarını tam olarak statik sayfa içeriğine gömer; düzleştirmeden önce onları tutarlı hâle getirmenin isteğe bağlı olmamasının sebebi budur
Bu yazıdaki yüklü form düzenleme API'si, SetFormFieldValue, EnsureLoadedFieldAppearanceStream ve artımlı yeniden hesaplama grafiği dahil, Delphi ve C++Builder için HotPDF Delphi Component ile birlikte gelir