Teknik Makale

PDF Form Alan Değerleri Neden Geri Döner: Doğrudan Alanlar

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ı

Delphi'de PDFium Component: iki doğrudan sözlük tek alan sahipleniyor, yazım yükseltilmiş annotationa çarpıyor ve alan ağacı hâlâ /Fields içindeki eski kopyayı okuyor
yazım, alan hiyerarşisinin sahiplenmediği bir nesneye sadakatle serialize edildi; bayat değer, kaydetme ve yeniden açmadan hayatta kalıyor

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 /NM ister ve aynı adı tam olarak bir sayfa annotationı taşıyabilir
  • Doğrudan kopya ile sayfa annotationı, /Parent ve PDFium tarafından yeniden üretilen /AP dışı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 /AcroForm giriş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 için PDFium Component doğrudan AcroForm alan sahipliğini nasıl birleştiriyor: anlık görüntü, NM kimlik kontrolü, başvuru birleştirme, değer yazımı ve açık red yollarıyla doğrulanmış yayınlama
doğrudan alan sözlükleri, /V değişmeden önce yükseltilir ve tek bir dolaylı sahibe yeniden bağlanır; belirsiz sahiplik temizce reddedilir

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

Delphi'de PDFium Component ile PDF form alanları için yaz ve doğrula deseni: tipli SetFormFieldValue, FormField üzerinden geri okuma, ardından kaydet ve yeniden aç kontrolleri
yalnızca bir True dönüşü, sözlüğün değiştiğini kanıtlar: alan ağacı üzerinden geri okuyun ve bir kaydet ve yeniden aç sınayın
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
  • False ile birlikte gelen ErrorMessagei 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 /NM verin; belirsizlik kaynağında ortadan kalkar
  • Çok alanlı ya da sayfalar arası doldurmalar için SetFormFieldValues kullanın; böylece geç bir başarısızlık, yarım form güncellenmiş halde bırakamaz
  • AnsiString içinde tutulan ham PDF baytları üzerinde asla Trim ya da öteki string RTL 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