Teknik Makale

PDFium ile Delphi'de Atomik Toplu PDF Form Doldurma

PDFium Component for Delphi, bir AcroForm'u TPdf.SetFormFieldValues ile atomik doldurur: sayfa ve widget adresli, 1 ile 256 arası tipli istek geçirirsiniz ve bileşen her değeri tek bir belge değiştiminde uygular ya da hiçbirini uygulamaz. Bir istek geçersizse TPdfFormValuesWriteReport.FailedRequest, toplu işi batıran isteği adlandırır

O güvence, kimsenin izlemediği işlerde en çok değer taşır. Gece içe aktarması, CRM satırlarını bir hasar formuna iter ya da bir baskı servisi, poliçe verilerini önceden basılmış bir şablonun üstüne bindirir. Bunları alan alan koşarsanız ilk kötü değer, geçerli bir radio export değeri olmayan bir kod, alanın MaxLen değerinden uzun bir dizge, size yarısı eski yarısı yeni veri taşıyan bir PDF bırakır. Hiçbir şey çökmez, o yüzden kimseyi uyaran da olmaz. Toplu iş API'si tam bunun için var: bir başarısızlık, yarısı yazılmış bir belge değil, temiz bir red olsun diye

Alan alan form doldurma veri içe aktarımı için neden tehlikeli?

Alan alan doldurma tehlikelidir, çünkü her yazım kendi başına taahhüt eder; döngünün ortasındaki bir hata, ne kaynak kayıtla ne orijinal şablonla uyuşan bir belge bırakır. Klasik desen, her biri PDFium'un yerel form doldurma ortamından birer widget geçen, FormField[Index] atamaları üzerinde bir döngüdür. 1 ile 17 arasındaki değerler düşer, 18. değer istisna fırlatır ve çağıran, onları geri almak için tam olarak hangi değerlerin girdiğini bilmek zorunda kalır

Çoğu çağıran o geri alma kodunu hiç yazmaz; yazanlar da genelde ince bir şekilde yanlış yazar: paylaşımlı bir alan, göründüğü her sayfada çoktan değişmiştir ve checkbox durumları çoktan çevrilmiştir. Kayıt bir finally bloğunda çalışıyorsa yarısı dolmuş dosya, tamamlanmış gibi diske gider. Overprint işinde dosya yokluğundan daha kötüdür, çünkü basılır ve biri imzalar

Öteki sessiz sorun odaktır. Klasik atayıcı, PDFium'un etkileşimli düzenleyicisini sürer: mevcut odağı öldürür, hedef widgeta odaklanır, bütün metni seçer, değiştirir ve odağı yeniden öldürür. Headless bir iş için sorun değil, ama görüntüleyicide kullanıcının başka bir alanda açık tuttuğu düzenleme oturumunu da bitirir. PDFium Component 3.231.0 ile gelen tipli yazım yolu yerel alan odağından hiç geçmez ve 3.233.0, onu tek alanlardan sayfalar arası toplu işlere genişletti

SetFormFieldValues toplu işi tek değiştimeyle nasıl uygular?

TPdf.SetFormFieldValues, her isteği belgenin yakalanan tek bir anlık görüntüsüne karşı sahneler, bütün alan sözlüklerini tek geçişte yazar ve bitmiş sonucu tek bir değiştimeyle devreye alır. Orijinal belge, çok son adıma dek canlı kalır; o adımdan önceki her başarısızlık onu dokunulmamış bırakır

Delphi için PDFium Component'te beş aşamalı atomik toplu form doldurma dizisi: preflight'tan doğrulama ve değiştimeye, hep ya da hiç sonuçlarıyla
her istek tek bir anlık görüntüye karşı sahneye konur ve canlı belgeye yalnızca son doğrulanmış değiştimede dokunulur

Dizi, hem güvenceyi hem bedeli açıklıyor:

  1. Preflight kontrolleri önce çalışır: belge etkin olmalı, istek sayısı 1..256 arası olmalı, açık bir sayfa nesnesi ya da düzenleme günlüğü işlemi olmamalı, hiçbir yerel alan odağı taşımamalı ve belge fill-forms ya da modify izni vermelidir
  2. Her istek sayfa aralığı ve girdi bütçesi için doğrulanır ve FailedRequest, kontrol edilen indeksi izler
  3. Bileşen bir düzenleme anlık görüntüsü yakalar, o kopyadaki AcroForm'u normalleştirir ve her isteği sırayla uygular; /V yazar, choice seçimleri için /I ve alanı paylaşan her widget için /AS yazar
  4. Değişiklikler, anlık görüntüye ISO 32000-1 §7.5.6 anlamında tek bir seyrek artımlı güncelleme olarak eklenir; böylece ilgisiz nesneler bayt bayt taşınır
  5. Ayrı bir doğrulayıcı örnek, aday baytları önce yükler; yalnızca temiz açıldığında bileşen canlı belgeyi boşaltır ve yenisini yükler, o yükleme başarısız olursa orijinal anlık görüntüye geri düşer

İç değiştirme adımı, bileşenin düzenleme günlüğü anlık görüntüleri için kullandığı makinedir; bu yüzden undo ve çökme kurtarma günlüğü ile bir toplu yazım üst üste binemez: önce günlük işlemini bitirin ya da iptal edin. Belge değiştirilirken otomatik yaşam döngüsü eylemleri bastırılır; böylece bir belge açma betimi, değiştirmenin ortasında alanları yeniden yazamaz ya da odağı alamaz

Delphi'de tipli bir istek toplu işi kurmak

Toplu iş, TPdfFormValueRequest kayıtlarından oluşan dinamik bir dizidir; her kayıt 1 tabanlı bir Page, 0 tabanlı sayfa yerel bir FieldIndex ve tipli bir TPdfFormValue taşır. Değer kaydı, fvkText, fvkChoice, fvkCheckBox ya da fvkRadioButton olan bir Kind taşır; Text, ChoiceIndices ve Checked üyeleri o türe göre kullanılır

İçe aktarma verisi genelde alan adlarıyla anahtarlanır; ilk iş, adları sayfa ve widget adreslerine çevirmektir. FieldIndex, bir sayfadaki widget annotationlarını FormFieldCount ile FormFieldInfo ile aynı sırada sayar; basit bir tarama iş görür. Yardımcı, bir ad için bulduğu ilk widgeti alır; bu, paylaşımlı alanlar için önemlidir, bir sonraki bölümün konusu

type
  TFieldAddress = record
    Page, FieldIndex: Integer;
  end;

function FindFieldAddress(Pdf: TPdf; const FieldName: WString;
  out Address: TFieldAddress): Boolean;
var
  P, I: Integer;
begin
  Result := False;
  for P := 1 to Pdf.PageCount do
  begin
    Pdf.PageNumber := P;
    for I := 0 to Pdf.FormFieldCount - 1 do
      if Pdf.FormFieldInfo[I].Name = FieldName then
      begin
        Address.Page := P;
        Address.FieldIndex := I;
        Exit(True);      // yalnızca ilk widget: alan başına bir istek
      end;
  end;
end;

procedure AddRequest(var Requests: TPdfFormValueRequests;
  const FieldName: WString; Pdf: TPdf; const Value: TPdfFormValue);
var
  Address: TFieldAddress;
  N: Integer;
begin
  if not FindFieldAddress(Pdf, FieldName, Address) then
    raise Exception.CreateFmt('Template has no field "%s"', [FieldName]);
  N := Length(Requests);
  SetLength(Requests, N + 1);
  Requests[N].Page := Address.Page;
  Requests[N].FieldIndex := Address.FieldIndex;
  Requests[N].Value := Value;
end;

AddRequest çağrısının bilinmeyen bir adı atlamak yerine istisna fırlattığına dikkat edin. Toplu iş adresleri doğrular, sizin eşlemenizi değil; şablonda olmayan bir alan burada başarısız olmalı, yoksa hiç başarısız olmaz. Adresler çözümlendikten sonra değerleri kurmak ve taahhüt etmek kısadır

var
  Pdf: TPdf;
  Requests: TPdfFormValueRequests;
  Report: TPdfFormValuesWriteReport;
  V: TPdfFormValue;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'claim-template.pdf';
    Pdf.Active := True;

    V := Default(TPdfFormValue);
    V.Kind := fvkText;
    V.Text := 'Maria Lopez';
    AddRequest(Requests, 'Claimant.Name', Pdf, V);

    V := Default(TPdfFormValue);
    V.Kind := fvkCheckBox;
    V.Checked := True;
    AddRequest(Requests, 'Consent', Pdf, V);

    V := Default(TPdfFormValue);
    V.Kind := fvkRadioButton;
    V.Text := 'Express';                // seçilecek düğmenin export değeri
    AddRequest(Requests, 'Shipping', Pdf, V);

    V := Default(TPdfFormValue);
    V.Kind := fvkChoice;
    SetLength(V.ChoiceIndices, 1);
    V.ChoiceIndices[0] := 2;            // alanın Opt dizisine 0 tabanlı indeks
    AddRequest(Requests, 'Region', Pdf, V);

    if Pdf.SetFormFieldValues(Requests, Report) then
      Pdf.SaveAs('claim-filled.pdf')
    else
      LogBatchFailure(Requests, Report);
  finally
    Pdf.Free;
  end;
end;

Tipli türler, hiçbir şey yazılmadan önce alan türüne karşı kontrol edilir. Checkboxa yönelen bir metin değeri, sade bir checkboxa yönelen bir radio değeri ya da bir push buttona yapılan yazım reddedilir. Choice indeksleri geçerli, tekil ve artan olmak zorundadır; birden fazla indeks MultiSelect bayrağını gerektirir ve bir choice alanındaki serbest metni yalnızca düzenlenebilir bir combo box kabul eder. Alanın MaxLen değerinden uzun bir metin değeri kırpılmak yerine reddedilir ve salt okunur alanlar külliyen reddeder. NoToggleToOff bayraklı radio gruplarında boş bir export değeri, yani "grubu temizle", yine reddeder

Paylaşımlı bir alana iki kez yazmak tüm toplu işi neden başarısız kılıyor?

TPdf.SetFormFieldValues, aynı terminal alana çözümlenen iki isteği reddeder, çünkü ikisi de tek bir değer yazacaktır ve bileşen, bir isteğin öbürünü sessizce üzerine yazmasına izin vermez. Kontrol, widget üzerinden değil alan sahibi üzerinden çalışır; farklı iki sayfadaki tek bir alanın farklı iki widgeti hâlâ yineleme sayılır

Sebep, ISO 32000-1 §12.7.3.1 içindeki alan modelidir. Terminal alan değeri sahiplenir; widget annotationları yalnızca o değerin gösterildiği yerlerdir. Her sayfanın başlığında görünen PolicyNumber adlı alan, birkaç widgetli tek bir alandır ve o widgetlerin her biri aynı /V değerini gösterir. Bir radio grubu da aynı çalışır: her düğme bir widget, grup ise alandır. PolicyNumber alanına sayfa 1 widgeti üzerinden ve yeniden sayfa 9 widgeti üzerinden yazmak, iki kez yazılmış tek bir alandır ve kazananı seçen her kural, eşlemenizdeki bir hatayı gizler

Bu yüzden toplu iş onu hata sayar ve ikinci isteği FailedRequest içinde adlandırır. Pratikte çözüm yukarı akıştadır: her alan adını, FindFieldAddress yaptığı gibi tam olarak tek bir adrese çözümleyin ve bileşenin değeri öteki widgetlere yaymasına izin verin. Bileşen bunu yazımın parçası olarak yapar; paylaşımlı bir checkbox ya da radio grubunun her widgetında /AS kurar, böylece görünüm durumu alan değeriyle uyuşur; aynı bölünme checkbox alan değeri ile widget durumu yazısında kapsanıyor

Bir terminal alan değeri sahiplenir, iki sayfadaki widgetler onu görüntüler ve yinelenen bir istek, Delphi için PDFium Component'te toplu işi başarısız kılar
terminal alan başına bir istek: bileşen /V değerini bir kez yazar ve onu paylaşan her widgetta /AS kurar

Başarısızlık raporunu okumak

Başarısız bir toplu iş False döndürür, istisna fırlatmaz ve orijinal belgeyi ile her alan değerini tam olarak eski hallerinde bırakır. TPdfFormValuesWriteReport sonra Succeeded = False, hem UpdatedFields hem UpdatedWidgets için sıfır, suçlu isteğin 0 tabanlı indeksini FailedRequest içinde ve insanca okunan bir gerekçeyi ErrorMessage içinde raporlar

procedure LogBatchFailure(const Requests: TPdfFormValueRequests;
  const Report: TPdfFormValuesWriteReport);
var
  R: Integer;
begin
  R := Report.FailedRequest;
  if R >= 0 then
    Log(Format('Request %d (page %d, widget %d) rejected: %s',
      [R, Requests[R].Page, Requests[R].FieldIndex, Report.ErrorMessage]))
  else
    Log('Batch rejected as a whole: ' + Report.ErrorMessage);
end;

-1 olan bir FailedRequest, redün isteğe değil işleme ait olduğu anlamına gelir: yerel odak etkindi, belge imzalı, form XFA ya da bir izin kontrolü başarısız oldu. Negatif olmayan bir değer tek bir girişi işaret eder. Toplam bütçe taşımında, toplu işi sınırın üstüne iten isteğe işaret eder; o, yanlış olan istek olmak zorunda değildir. Requests yanına kaynak sütun adlarından paralel bir dizi tutun; içe aktarma günlüğünde indeks 14'ü doğrudan "satır 2087, sütun ShipDate"e çevirebilirsiniz

Başarıda UpdatedFields istek sayısına eşittir ve UpdatedWidgets, paylaşımlılar dahil dokunulan her widgeti sayar; şablona karşı ucuz bir tutarlılık kontrolü

Çarpacağınız ön koşullar ve sınırlar

Görüntüleyici uygulamasındaki en yaygın red yerel odaktır: kullanıcı bir alana yazıyorsa SetFormFieldValues, anlık görüntüyü yakalamadan önce bile reddeder ve bekleyen düzenleyici tamponu sapasağlam hayatta kalır. Bileşen odağı arkanızdan düşürmez, çünkü o, kullanıcının bitirmedği metni ya taahhüt eder ya kaybederdi. ClearFormFieldFocus çağrısını bilerek yapın; o, alanın dışına tıklamanın yapacağı gibi düzenleyici oturumunu bitirir, sonra yeniden deneyin

Bütçeler sabit ve belgelenmiştir; içe aktarmalarınızı onlara göre boyutlandırabilirsiniz:

  • Toplu iş başına 1 ile 256 istek; daha büyük kayıtları birkaç toplu işe bölün ve atomikliğin o zaman toplu iş başına geçerli olduğunu kabullenin
  • İstek başına en çok 262.144 UTF-16 metin birimi ve 4.096 choice indeksi
  • Bütün toplu iş boyunca en çok 1.048.576 UTF-16 metin birimi ve 16.384 choice indeksi
  • Anlık görüntü çözücüsü için alışılmış belge bütçeleri hâlâ geçerlidir; 64 MiB kaynak ve 65.536 nesne dahil

Başarılı bir değiştirme ayrıca, çağrıdan önce alınmış yerel tanıtıcıları, aramaları ve seçimleri geçersiz kılar; çünkü altındaki belge yenedir. Sayfa ve görünüm etkinleştirme ile mantıksal alan imleçleri korunur; annotation ya da sayfa nesnesi tanıtıcısı gibi önbelleğe aldığınız her şeyin yeniden alınması gerekir

Bu, XFDF içe aktarımından nasıl farklı?

TPdf.ImportXFDF ile TPdf.SetFormFieldValues ikisi de veriyi alanlara koyar, ama XFDF içe aktarımı hoşgörülü bir birleştirmedir, toplu iş ise katı bir işlemdir. XFDF içe aktarımı değerleri alanlara adla eşler, bulamadığı adları atlar, yinelemeleri yok sayar ve kaç girişi uyguladığını döndürür. Bir inceleyicinin yorumlarını birleştirmek için tam da doğrudur; orada kısmi bir sonuç hâlâ faydalıdır

Toplu iş her noktada tersini yapar. Widgetleri açıkça adresler, bilinmeyen ya da yanlış tipli hedefleri reddeder, yinelemeleri hata sayar ve kısmi bir sonucu yoktur. Veri başka bir araçtan gelen bir değişim dosyasıysa XFDF kullanın; eşleme sizin kodunuza aitse ve yarım uygulanmış bir kayıt bir kusur olacaksa SetFormFieldValues kullanın

XFDF içe aktarımı ile Delphi için PDFium Component'te atomik SetFormFieldValues toplu işini karşılaştıran karar matrisi: hedefleme, bilinmeyen ve yinelenen hedefler, sonuç ve en uygun kullanım
XFDF değişim dosyalarını hoşgörülü birleştirir; toplu iş ise bilinmeyen adları, yinelemeleri ve kısmi sonuçları reddeder

Toplu yazım neyi kapsamıyor?

Toplu iş sınırlı bir AcroForm profilidir ve o profilin dışında tahmin yerine hayır der. XFA formları reddeder ve kendi adanmış düzenleyicilerini ister. İmzalı belgeler reddeder; çünkü mevcut bir imza altında alan sözlüklerini yeniden yazmak, bir imza iş akışıdır, doldurma işlemi değil; şifreli anlık görüntüler de profilin dışındadır. Bozuk ya da belirsiz düğme export dizileri reddeder ve birden fazla düğmeyle eşleşen radio export değerleri, grup RadiosInUnison kurmadıkça reddedilir. İşlem ayrıca yazmadan önce doğrudan alan sözlüklerini normalleştirir; geri dönmüş görünen alan değerlerinin arkasındaki durum budur

Görünümler bir cümlelik özene değer. Değişen metin ve choice widgetleri için toplu iş, eski değeri gösteren bir görünüm dağıtmak yerine bayat /AP değerini düşürür ve NeedAppearances değerini kapatır, böylece PDFium formdaki ilgisiz her widgeti yeniden kurmaz. PDFium eksik görünümleri render ederken yeniden üretir. Dolgulu PDF, görünümleri hiçbir zaman sentezlemeyen katı bir baskı hattına gidiyorsa önce PDFium üzerinden render edin ya da düzleştirin ve o yolu kendi şablonlarınızla test edin

SetFormFieldValues ile tipli değer kayıtları; render, düzenleme ve görüntüleyici kontrollerinin yanında, Delphi ve Lazarus için PDFium Component içinde gelir; ürün sayfası, tam form doldurma ve AcroForm API'sini listeler