Bir PDF form alan değeri, alan doğrudan bir sözlük olarak saklandığında "geri döner": PDFium, sayfa /Annots dizisindeki kopyayı bir dolaylı nesneye yükseltir, /Fields ise ikinci bir doğrudan kopyayı tutar. Delphi için PDFium Component, tipli alan yazımlarını, her iki başvuruyu yazmadan önce tek bir dolaylı sahibe birleştirerek hallediyor
Belirti genelde perili bir ev gibi okunur. Yazım True döndürür ve hemen ardından gelen okuma, eski değeri uzatır. Kaydedin, yeniden açın; eski değer hâlâ oradadır. Hiçbir şey istisna fırlatmamıştır ve PDF, elinizdeki her görüntüleyicide tamamen sıradan görünür
Belirti: yapışmayan başarılı bir yazım
Başarısızlık deseni dardır ama çok tekrarlanabilir: yalnızca alan sözlüklerinin numaralı dolaylı nesneler yerine satır içi (doğrudan nesne olarak) yazıldığı formlarda görünür. Çoğu yazar aracı dolaylı alanlar yazar; sorun aylarca saklanır ve sonra tek bir üreticiden, sık sık /AcroForm tamamını satır içi yazan elle yazılmış bir dışa aktarıcıdan gelen dosyalarda ortaya çıkar
İki gözlem birlikte nedeni işaret eder: yazım başarı raporlar ve alanın form katmanı üzerinden yerel bir okuması önceki değeri döndürür. İkisini birlikte görürseniz dizge kodlamanızdan şüphe etmeyi bırakın ve nesne sahipliğine bakmaya başlayın
Başarılı bir yazımdan sonra bir PDF form alan değeri neden geri döner?
Bir PDF form alan değeri geri döner, çünkü iki ayrı sözlük aynı alan olmak iddiasındadır ve yazım, alan ağacının danışmadığı kopyayı güncellemiştir. Bir alanın değeri /V girişinde yaşar ve ISO 32000-1 §12.7.3.1, o girişin nerede yetkili olduğunu tanımlar: /AcroForm /Fields üzerinden ulaşılan, /Kids üzerinden terminal alana inilen ve kalıtsal girişleri /Parent üzerinden çözümlenen alan sözlüğünde
Aynı madde, bütün belaya yol açan bir kolaylığa izin verir. Bir alanın tam olarak bir widget annotationı olduğunda, alan sözlüğü ile widget annotation sözlüğü tek bir sözlükte birleştirilebilir. İyi biçimli bir dosyada o birleşik sözlük tek bir dolaylı nesnedir ve iki yerden başvurulur: /Fieldsden (ya da bir atanın /Kidsinden) ve sayfanın /Annots dizisinden. İki başvuru, bir nesne, bir /V
Tek sözlüğün iki kopyası
ISO 32000-1 §7.3.10, paylaşılabilecek bir kimliğin yalnızca dolaylı bir nesnede olduğunu açıkça söyler. Doğrudan bir sözlüğün nesne numarası yoktur; bir üretici, birleşik alan/widget sözlüğünü hem /Fields hem /Annots içinde satır içi yazdığında dosya, iki kez başvurulan bir nesne içermez. Aynı anahtar ve değerlere rastlantısal olarak sahip, birbirine benzeyen iki sözlük içerir
PDFium buna hoşgörü gösterir ve yaparken bölünmeyi kalıcı kılar. Annotationa bir tanıtıcı gerektiğinde PDFium, sayfa /Annots dizisindeki doğrudan widget sözlüğünü bir dolaylı nesneye yükseltebilir; ama eşdeğer doğrudan kopya, /AcroForm /Fields içindeki (ya da atanın /Kids içindeki) yerinde kalır. O andan itibaren belge, tek bir mantıksal alan için iki sahip taşır. Sayfa annotationı üzerindeki /Vyi düzenleyen bir yazar, sözlük düzeyinde gerçek bir başarı alır. Form katmanının ve sonraki her okuyucunun doğruluğun kaynağı saydığı alan ağacı ise değişikliği hiç görmez
Bayat değer, kaydetme ve yeniden açmadan bu yüzden hayatta kalıyor: yeni değer, alan hiyerarşisinin sahiplenmediği bir nesneye sadakatle yazılmıştı
PDFium Component tek alan sahibini nasıl geri getiriyor?
PDFium Component, her tipli yazımdan önce doğrudan alan sahipliğini normalleştirir: doğrudan /AcroForm ve alan sözlüklerini dolaylı nesnelere yükseltir, her doğrudan widgeti, sayfasındaki eşleşen annotationa bağlar ve /Fields, /Kids, /Parent ile /Annots değerlerini, her başvuru aynı dolaylı nesneyi gösterecek biçimde yeniden yazar. Ancak ondan sonra /Vyi değiştirir; böylece alan değeri, seçilen widget ve kaydedilen belge tek bir sahibi paylaşır
Zor kısım kimliktir. İçeriği özdeş iki doğrudan sözlük, aynı widget olduklarını kanıtlamaz; bir form, birbirine benzeyen iki widgeti meşru olarak içerebilir. Normalleştirici bu yüzden tahmin etmeyi reddeder ve her doğrudan widgetta açık, tekil bir /NM annotation adı (ISO 32000-1 §12.5.2) ister. Yazım yolunun uyguladığı kurallar katıdır:
- Doğrudan bir widget, en çok 1024 UTF-16 birimlik boş olmayan bir
/NMister ve aynı adı tam olarak bir sayfa annotationı taşıyabilir - Doğrudan kopya ile sayfa annotationı,
/Parentve PDFium tarafından yeniden üretilen/APdışında özdeş serialize olmalıdır; her öteki fark yazımı reddeder - İki ata bağlantısı ayrı ayrı kontrol edilir; böylece sayfa annotationındaki doğru bir
/Parent, orijinal doğrudan kopyadaki çelişen birini maskemez - Eksik bir
/Parent, yalnızca tekil alan ağacı sahipliği cevabı belirsiz kılıyorsa tamamlanır ve eşdeğer doğrudan ata kopyaları, yükseltilmiş sahiplerine yeniden bağlanır - Yinelenen tam nitelemeli alan adları, döngülü ya da çoklu sahipli alan ağaçları ve imza alanları, hiçbir şey yayınlanmadan önce reddedilir
- İlgisiz annotationlar, örneğin aynı sayfadaki bağlantılar, ve ilgisiz
/AcroFormgirişleri dokunulmadan korunur
Normalleştirici ayrıca katı bütçeler altında çalışır: 4096 alan ağacı ziyareti, sayfa ya da dizi üyesi, 64 iç içe geçme derinliği, 256 yeni dolaylı nesne, yükseltilen sözlük başına 1 MiB, toplamda 4 MiB yükseltilmiş bayt, incelenen sözlük baytı için 128 MiB ve tam alan adı başına 4096 UTF-16 birimi ile form genelinde 262144. Düşmanca ya da bozuk bir dosya bir bütçeye çarpar ve form doldurmayı bir hizmet reddine çevirmek yerine bir hata mesajıyla başarısız olur
Bütün işlem işlemseldir. TPdf.SetFormFieldValues bir anlık görüntü yakalar, normalleştirilmiş nesneleri ve yeni değerleri taşıyan bir artımlı güncelleme kurar, o adayı yükler ve canlı belgeyi yalnızca aday doğrulandığında değiştirir. Normalleştirme dosyayı reddederse ya da toplu işteki sonraki bir istek başarısız olursa orijinal belge, değerleri ve mantıksal alan imleci tam olarak eski hallerinde kalır
Delphi'de doğrudan bir alan değerini yazmak ve doğrulamak
Güvenli desen önce yazmak, sonra yerel form katmanı üzerinden geri okumak ve uyuşmazlığı, yazım True döndürmüş olsa bile bir başarısızlık saymaktır. TPdf.SetFormFieldValue alanı, geçerli sayfadaki indeksle adresler, tipli bir TPdfFormValue alır ve bir TPdfFormValueWriteReport doldurur; TPdf.FormField[Index] değeri, PDFium'un form katmanı üzerinden geri okur ve o katman, widgeta güvenmek yerine alan ağacını çözümler
uses
System.SysUtils, PDFium;
procedure WriteTextField(Pdf: TPdf; PageNo, FieldIndex: Integer;
const NewText: WString);
var
Value: TPdfFormValue;
Report: TPdfFormValueWriteReport;
begin
Pdf.PageNumber := PageNo; // SetFormFieldValue, geçerli sayfayı hedefler
Value := Default(TPdfFormValue);
Value.Kind := fvkText;
Value.Text := NewText;
if not Pdf.SetFormFieldValue(FieldIndex, Value, Report) then
raise Exception.CreateFmt('Field %d on page %d rejected: %s',
[FieldIndex, PageNo, Report.ErrorMessage]);
// Kendi değişkenimize değil, alan ağacına karşı doğrula
if Pdf.FormField[FieldIndex] <> NewText then
raise Exception.CreateFmt('Field "%s" did not keep the new value',
[Pdf.FormFieldInfo[FieldIndex].Name]);
end;
Çözülemeyen sahiplikli bir dosyada, örneğin /NMsi olmayan bir doğrudan widgetta SetFormFieldValue çağrısı False döndürür, Report.UpdatedWidgets sıfır olur ve Report.ErrorMessage sorunu adlandırır; örneğin "Direct widgets require an explicit unique NM identity". İstediğiniz davranış budur: sessiz bir yarım yazım değil, net bir red. Bilinmeye değer bir ön koşul daha var. Tipli yazım annotationa özgüdür ve yerel odağı kazanmaz; dolayısıyla yerel bir alan düzenleyicisi hâlâ odaktaysa bütün işlem, anlık görüntü alınmadan reddedilir. Döngüde yeniden denemek yerine önce bilerek Pdf.ClearFormFieldFocus çağırın
Birkaç alan için, özellikle sayfalar boyunca toplu biçimi kullanın. TPdf.SetFormFieldValues 1 ile 256 arası istek kabul eder, hepsini tek bir adaya uygular ve hepsini ya da hiçbirini yayınlar. TPdfFormValuesWriteReport.FailedRequest, hangi isteğin toplu işi kırdığını söyler
procedure FillInvoiceHeader(Pdf: TPdf);
var
Requests: TPdfFormValueRequests;
Batch: TPdfFormValuesWriteReport;
begin
SetLength(Requests, 2);
Requests[0] := Default(TPdfFormValueRequest);
Requests[0].Page := 1;
Requests[0].FieldIndex := 0;
Requests[0].Value.Kind := fvkText;
Requests[0].Value.Text := 'ACME Tooling Inc.';
Requests[1] := Default(TPdfFormValueRequest);
Requests[1].Page := 2;
Requests[1].FieldIndex := 0;
Requests[1].Value.Kind := fvkText;
Requests[1].Value.Text := '2026-10-27';
if not Pdf.SetFormFieldValues(Requests, Batch) then
raise Exception.CreateFmt('Request %d failed: %s',
[Batch.FailedRequest, Batch.ErrorMessage]);
// Batch.UpdatedFields = 2; Batch.UpdatedWidgets, eşitlenen her widgeti sayar
end;
Aynı terminal alana yönelen iki istek, son yazım kazansın dediği için değil yinelenen sahip olarak reddedilir ve kaynak belge korunur. Checkbox ve radio değerleri aynı kuralları izler; düğmeler için /V ile /AS bölünmesi, PDFium'da checkbox alan değeri ile widget durumu yazısında kapsanıyor
Değerin kaydetme ve yeniden açmadan hayatta kaldığını kanıtlamak
Aynı oturumdaki bir geri okuma, canlı belgenin tutarlı olduğunu kanıtlar. Kaydetme ve yeniden açma, dosyanın tutarlı olduğunu kanıtlar; kontrol, kendi entegrasyon testlerinizde tutulmaya değecek kadar ucuzdur
procedure SaveAndReopenCheck(Pdf: TPdf; const Target: string;
PageNo, FieldIndex: Integer; const Expected: WString);
var
Reopened: TPdf;
begin
if not Pdf.SaveAs(Target, saNoIncremental, pvUnknown) then
raise Exception.Create('Save failed');
Reopened := TPdf.Create(nil);
try
Reopened.FileName := Target;
Reopened.Active := True;
Reopened.PageNumber := PageNo;
if Reopened.FormField[FieldIndex] <> Expected then
raise Exception.Create('Saved file lost the field value');
finally
Reopened.Free;
end;
end;
İki bayt düzeyi tuzak daha: Trim ve trailerdaki /ID
Doğrudan alanları normalleştirmek, ham sözlük baytlarını yeniden okumak demektir; PDF sözdizimine dokunan herhangi bir Delphi kodu için ilgili iki küçük tuzağı gündeme getirir
İlki, bir AnsiString içinde tutulan ham PDF baytları üzerinde SysUtils.Trim çağrısıdır. Bir choice seçeneği gibi bir literal dizge, meşru olarak FE FF bayt sırası işaretli ve gömülü NUL baytlı UTF-16BE metni içerebilir. Unicode Delphi'de SysUtils.Trim bir string alır; çağrı bu yüzden AnsiStringi etkin ANSI kod sayfası üzerinden sessizce UTF-16'ya çevirir ve sonucu geri çevirir. O kod sayfasına göre gidiş dönüş, yüksek baytları yeniden yazabilir, BOM'u zedeleyebilir ve hatta ardındaki sınırlayıcıyı bile oynatabilir. Güvenli yaklaşım, çözümleyiciyi baytlar üzerinde tutar: PDF boşluklarını konumla atlayın (ISO 32000-1 §7.2.2, NUL, tab, LF, FF, CR ve boşluğu listeler), Copy ile dilimleyin ve bir değerin PDF metin dizgesi olduğu kesinleşince çözer
// Ham PDF baytlarında riskli: AnsiString -> string -> AnsiString (W1057/W1058)
Token := Trim(Token);
// Bayt güvenli: PDF boşluklarını konumla atla, sonra dilimle
P := 1;
while (P <= Length(Token)) and (Token[P] in [#0, #9, #10, #12, #13, ' ']) do
Inc(P);
Token := Copy(Token, P, MaxInt);
İkinci tuzak, artımlı güncellemenin kendi içinde durur. ISO 32000-1 §7.5.5 (Tablo 15) ile §14.4, trailerdaki /ID değerinin iki bayt dizgesinden oluşan bir dizi olmasını ister. Seyrek güncelleme tanımlayıcısını kurulan iç yardımcı, yalnızca iki dizgenin içeriğini döndürür; /ID anahtarı ya da dizi köşeli parantezleri olmadan ve trailer onu açıkça /ID [ ... ] olarak sarmak zorundadır. PDFium'un hoşgörülü yükleyicisi ve sonraki tam yeniden kaydetme, eksik bir sarmalayıcıyı gizler; güvenilir kontrol, aday ve kaydedilen dosyaları bağımsız katı bir çözümleyiciyle parse etmek ve iki 16 baytlık dizge aramaktır. Artımlı güncellemeleri kendiniz yazıyorsanız aynı tuzak geçerlidir; şifreli PDF'ler için artımlı güncelleme yazısı, trailer zincirini daha derinden ele alıyor
Kendi Delphi form kodunuzda ne yapmalısınız?
- Bir yazımdan sonra değeri daima
FormField[Index]ile geri okuyun; testlerde ayrıca kaydedin, yeniden açın ve tekrar okuyun Falseile birlikte gelenErrorMessagei bilgi sayın:NM, bir ata çelişkisi ya da yinelenen ad adlandıran bir red, dosyanın sahipliğinin gerçekten belirsiz olduğu anlamına gelir- PDF üretiyorsanız alanları ve widgetleri dolaylı nesne olarak yazın ve her widgete tekil bir
/NMverin; belirsizlik kaynağında ortadan kalkar - Çok alanlı ya da sayfalar arası doldurmalar için
SetFormFieldValueskullanın; böylece geç bir başarısızlık, yarım form güncellenmiş halde bırakamaz AnsiStringiçinde tutulan ham PDF baytları üzerinde aslaTrimya da ötekistringRTL yardımcılarını çalıştırmayın
Sınırlar hakkında da açık olun. İmzalı belgeler, XFA formları, şifreli anlık görüntüler ve widget sahipliği tekil çözülemeyen dosyalar beyan edilen profilin dışında kalır ve zorlanmak yerine reddedilir. İş akışınız alanları belgeler arasında taşıyorsa sahiplik soruları daha da zorlaşır; AcroForm alanlarını PDF belgeleri arasında aşklama o durumu ele alıyor
PDFium Component bu tipli, işlemsel form yazımını; render, metin çıkarımı ve belge düzenlemenin yanında Delphi, C++Builder ve Lazarus uygulamalarına getiriyor. Sürüm geçmişi, lisans ve indirmeler Delphi için PDFium Component ürün sayfasındadır