Teknik Makale

Delphi ve PDFlibPas ile PDF/VCR-1 Şablon Yer Tutucuları

PDFlibPas, PDF/VCR-1'i etiketli bir yapı profili olarak uygular: değiştirme tanımı StructTreeRoot altında doğrudan bir alt dal olarak yaşar; şablon kökü alan kümesini bildirmek için /O /GTS_Template taşır ve her yer tutucu, bir veri alanını bağlamak için /O /GTS_Replacement taşır. Bu konuda sayfa düzeyi sözlük diye bir şey yoktur

O tek cümle, çoğu uygulamanın yanlış yaptığı kısımdır ve yanlış yapmak başarısız bir doğrulamadan daha pahalıya mal olur. Değiştirme planı yanlış okunan değişken verili bir iş çökmez. Basar — hacimli şekilde — yanlış alanı yanlış kutuya koyarak ve bunu ilk fark eden, elli bin adet posta elinde olan müşteridir

Değiştirme tanımı gerçekte nerede yaşar?

PDF/VCR-1, değiştirme modelini özel bir sayfa sözlüğüne değil, ISO 32000-1 §14.7'de anlatılan mantıksal yapı hiyerarşisine koyar. Şablon kökü, StructTreeRoot'a doğrudan asılı, GTS_Template öznitelik sahibiyle ayırt edilen bir yapı öğesidir ve tüm alan sözlüğünü tek yerde GTS_Fields artı sayfa seçimini süren alanı adlandıran isteğe bağlı bir GTS_Pages ile bildirir

Yer tutucular, o kökün içine yuvalanmış, GTS_Replacement öznitelik sahipli yapı öğeleridir. Her biri bir GTS_Generator (PDFlibPas'ın PassThrough olmasını gerektirdiği), tam olarak bir bildirilmiş alanı adlandıran bir GTS_Data değeri ve isteğe bağlı bir GTS_BBox taşır. Sayfaya bağlanma da bir sayfa açıklaması değildir: öğe sayfasına /Pg üzerinden, işaretli içerik dizisine ise /K üzerinden işaret eder. Yapıyı bir sayfa sözlüğüne tercih etmek, ifade etmesi kolay ve hafife alması kolay bir sonuç taşır: yapı ağacı, katalogdan ulaşılan genel bir graftır; bu yüzden profili tüketen her şey, bir sözlük okuyucu değil, tüm tehlikeleriyle birlikte bir ağaç yürüyücüsü olmak zorundadır. Daha önce yapı ağacıyla çalışmadıysanız, mekanikler etiketli PDF yapısı ve erişilebilirlik yazısında anlatılıyor

PDFlibPas'ın PDF/VCR-1 değiştirme modelini koyduğu yer: şablon kökü, GTS_Template öznitelik sahibiyle StructTreeRoot'a doğrudan asılır ve GTS_Fields ile isteğe bağlı GTS_Pages'ı bildirir; içe yuvalanmış her yer tutucu ise GTS_Replacement, PassThrough değerinde bir GTS_Generator, bir GTS_Data alanı, isteğe bağlı bir GTS_BBox taşır ve /Pg üzerinden sayfasına, /K üzerinden içeriğine işaret eder
Değiştirme tanımı bir sayfa sözlüğü yerine mantıksal yapı hiyerarşisinde yaşar; bu, her tüketiciyi bir ağaç yürüyücüsü yapar ve öğeyi bir saldırganın şekillendirebileceği bir grafik düğümüne dönüştürür

Yalnızca ilk üst düzey dalı kontrol etmek neden kötü dosyaları geçirir?

Çünkü StructTreeRoot'un çok sayıda doğrudan çocuğu olabilir ve şablon kökünü bulduktan sonra duran bir doğrulayıcı diğerlerini asla görmez. Bu, PDFlibPas'ın kaçınmak için kurulduğu belirli başarısızlıktır ve hiç kontrol etmemekten daha kötü olan türden sahte geçişi tam olarak üretir: uyumlu rapor eden ama ikinci bir şablon kökü içeren ya da hiçbir tüketicinin onurlandırmayacağı şablon dalının dışında oturan bir yer tutucu içeren bir dosya

Bu yüzden PDFlibPas'taki ValidatePDFVCRStructure aynı ağaç üzerinde iki geçiş yapar. İlk geçiş StructTreeRoot'un her doğrudan çocuğunu sıralar ve GTS_Template taşıyanları sayar; tek bir yer tutucu okunmadan önce tam olarak birden farklı her şey reddedilir. İkinci geçiş yalnızca şablon dalına değil tüm üst düzey dallara iner ve aynı anda iki şeyi reddeder: kök olmayan ama GTS_Template taşıyan yuvalanmış bir öğe ve şablon dalının dışında bulunan herhangi bir GTS_Replacement öğesi

Dolaşım 64 düzey derinlik ve 1.000.000 düğümle sınırlanır ve ziyaret ettiği her öğenin nesne ve üretim numarasını kaydeder; böylece bir döngü veya tekrarlanan başvuru izlenmek yerine rapor edilir. Bu sayılar performans ayarı değildir. Bir yapı ağacı, bir saldırganın kontrol ettiği bir nesne grafıdır ve tavansız özyineleyen bir doğrulayıcı, dosya uzantısı olan bir yığın taşmasıdır

PDFlibPas'te ValidatePDFVCRStructure iki geçişli doğrulaması: ilk geçiş StructTreeRoot'un GTS_Template taşıyan doğrudan çocuklarını sayar ve tam olarak birden farklı her şeyi reddeder, ikinci geçiş yuvalanmış bir şablon kökünü ve şablon dalının dışındaki herhangi bir GTS_Replacement'ı reddetmek için her üst düzey dala iner, 64 düzey ve bir milyon düğümle sınırlıdır
İlk şablon kökünü bulduktan sonra duran bir doğrulayıcı ikincisini asla görmez; bu yüzden dolaşım tüm üst düzey dalları kapsar ve bir döngünün izlenmek yerine raporlanması için nesne numaralarını kaydeder

Şablon yazmak ve API'nin eğilmeyi reddettiği kısıtlamalar

PDFlibPas yazmayı, yapısal kuralları kayıt anında keşfetmek yerine yazma anında uygulayan bir köşeli API üzerinden sunar. SetPDFVCRMode belgeyi profile alır; bu dahili olarak ISO 15930-7'nin PDF/X-4 taban modunu gerektirir ve işaretli içerik olmayan bir PDF/VCR-1 dosyasının yer tutucunun işaret edeceği hiçbir şeyi olmadığı için mark-info bayrağını açar. Oradan BeginPDFVCRTemplate alan listesini ve sayfa seçim alanını alır ve veri şemanızı tasarlamadan önce bilmeye değer yollarla katıdır. Alan adları, yalnızca ASCII harfleri, rakamları, alt çizgiyi, kısa çizgiyi ve noktayı kabul eden PDFVCRValidFieldName'den geçer, çünkü kaçış belirsizliği olmadan bir PDF adına serileşen karakterler bunlardır. PDFVCRNormalizeFieldList listeyi büyük/küçük harfe duyarlı tutar ve kopyaları doğrudan reddeder; sayfa seçim alanının kendisi de bildirilen listede görünmek zorundadır

var
  Lib: TPDFlib;
  Diag: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;

    if Lib.SetPDFVCRMode(1) <> 1 then
      raise Exception.Create('PDF/VCR mode was refused');

    Lib.NewPage;
    if Lib.BeginPDFVCRTemplate('Salutation,FullName,AccountNo',
                               'AccountNo') <> 1 then
      raise Exception.Create('Field list rejected');

    // Bir yer tutucu tam olarak bir bildirilmiş alanı bağlar.
    Lib.BeginPDFVCRPlaceholder('FullName', 72, 690, 320, 712);
    Lib.DrawText(72, 694, 'SAMPLE NAME');
    Lib.EndPDFVCRPlaceholder;

    Lib.EndPDFVCRTemplate;

    Diag := Lib.GetPDFVCRDiagnostics;
    if Diag <> '' then
    begin
      Writeln('PDF/VCR blockers: ', Diag);
      Exit;
    end;

    Lib.SaveToFile('campaign-template.pdf');
  finally
    Lib.Free;
  end;
end;

Köşeli yapının size ne kazandırdığına dikkat edin. BeginPDFVCRPlaceholder, aktif bir şablon ve tam olarak bir düzey derinliğinde bir etiket yığını olmadan açılmayı reddeder; PDFlibPas'ın yer tutucuların şablon kökünün doğrudan çocukları olmasını ve asla birbirinin içine yuvalanmamasını garanti etme yolu budur. Koordinatlar da doğrulanır: NaN, sonsuzluk ve ters çevrilmiş dikdörtgenler reddedilir; böylece kötü bir sınır kutusu bir hafta sonraki preflight raporunda değil çağrıda başarısız olur

Bir yer tutucu neden kendi MCID serileştirme yoluna ihtiyaç duyar?

Çünkü PDF/VCR-1 /K'nın çıplak bir tamsayı olmasını ister ve genel amaçlı bir etiketli PDF yazıcısının bir yapı öğesi ile içeriği arasındaki bağı serileştirme biçimi bu değildir. ISO 32000-1 §14.6 altında, bir yapı öğesi işaretli içeriğe normalde ya doğrudan bir MCID tamsayısı üzerinden — öğe sayfasını zaten /Pg ile tanımlıyorsa — ya da sayfayı açıkça adlandıran bir işaretli içerik başvuru sözlüğü üzerinden başvurur. Birçok amaç için etiketli çıktı yayan bir kütüphane, sayfalar arasında daha iyi birleştiği için sözlük biçimini standartlaştırma eğilimindedir

PDFlibPas burada genel seçenek yerine dar seçeneği aldı. SetCurrentStructElemDirectMCIDKid geçerli yapı öğesini işaretler ve yalnızca bu bayrağı taşıyan öğeler /K'yı, sayfa /Pg üzerinde taşınarak doğrudan bir MCID olarak serileştirir; geri kalan her şey mevcut MCR sözlüğü yoluna dokunmadan devam eder. Bu bilinçli bir takastır ve yüksek sesle adlandırmaya değer türdendir: bir profil eklemek, diğer her etiketli belgenin zaten ürettiği çıktının baytlarını değiştirmemelidir; bu yüzden profil küresel bir anahtar yerine kendi yolunu alır. Mevcut her etiketli dosya önceki serileştirmesini korur ve regresyon yüzeyi yeni özelliğin boyutunda kalır

Tek dolaşım, iki tüketici

PDFlibPas, değiştirme manifestini ve uygunluk kararını aynı iki geçişli yürüyüşten türetir; manifestin bir iş planı olarak güvenilebilmesinin nedeni budur. Dolaşım doğrularken bir TPDFVCRManifestData kaydı doldurur: şablon kökünden Fields, bildirildiğinde PageSelectionField ve FieldName, PageNumber, MCID, HasBBox ve BBox taşıyan bir TPDFVCRPlaceholderInfo girdileri dizisi olan Placeholders. Uygunluk giriş noktası CheckCompliancePDFVCR(InputStream, Password, Options, ResultList)'dır ve kontrollerini hepsini birden değil bağımlılık sırasıyla çalıştırır: önce tam PDF/X kontrolü, sonra GTS_PDFXVersion'ı PDF/X-4'e karşı, sonra http://www.npes.org/pdfvcr/ns/id/ ad alanından okunan GTS_PDFVCRVersion ve ancak ondan sonra yapı yürüyüşü. Options'ı 1 geçmek ilk sorunda durdurur; bir toplu iş kapısında istediğiniz şey budur. 0 geçmek her şeyi toplar; bir raporda istediğiniz şey budur

PDFlibPas'te tek dolaşım, iki tüketici: aynı sınırlı yürüyüş TPDFVCRManifestData'yı Fields, PageSelectionField ve bir TPDFVCRPlaceholderInfo girdileri dizisi olan Placeholders ile doldurur; CheckCompliancePDFVCR ise tam PDF/X kontrolünü, GTS_PDFXVersion'ı, npes.org ad alanından GTS_PDFVCRVersion'ı ve ancak ondan sonra yapı yürüyüşünü çalıştırır ve dosya yalnızca nitelikli olduğunda 100 döndürür
Manifest bir sözleşmedir; BuildPDFVCRManifest yalnızca sorun listesi boş döndüğünde JSON üretir, aksi hâlde 608 hatası raporlar ve uyumlu olmayan bir dosyanın güvenilir bir iş planı kılığına girmesini engeller
// Ürettiğiniz değil aldığınız bir şablonu doğrulayın
var
  Issues: TStringList;
  Stream: TFileStream;
  I: Integer;
begin
  Issues := TStringList.Create;
  Stream := TFileStream.Create('supplier-template.pdf',
                               fmOpenRead or fmShareDenyWrite);
  try
    if CheckCompliancePDFVCR(Stream, '', 0, Issues) <> 100 then
      for I := 0 to Issues.Count - 1 do
        Writeln('PDF/VCR: ', Issues[I]);
  finally
    Stream.Free;
    Issues.Free;
  end;
end;

Manifest bir sözleşmedir, bu yüzden yalnızca uyumlu dosyalar için var olur

BuildPDFVCRManifest yalnızca sorun listesi boş döndüğünde JSON döndürür, aksi hâlde boş bir dize döndürür. Bu kendi başına savunmacı kodlama değildir. Bir manifest, ondan gerçek çıktı sürecek bir kompozisyon motoru tarafından tüketilir ve iki şablon kökü olan bir dosyadan çıkarılan kısmi bir alan listesini geri vermek, motoru uyumlu olmayan bir yapıyı güvenilir bir değiştirme planı olarak ele almaya davet eder. Belge API'si üzerinden GetPDFVCRManifest(InputFileName, Password) aynı çağrıyı sarar ve dosya nitelikli olmadığında son hata kodunu PDFLIB_ERROR_PDFVCR_COMPLIANCE'a (608) ayarlar; böylece başarısız bir çıkarma ile boş bir şablon ayırt edilebilir. JSON, fields, bir dize veya null olan pageSelectionField ve girdileri field, page, mcid ile dört sayılı bir bbox veya null veren bir placeholders dizisi rapor eder

var
  Lib: TPDFlib;
  Manifest: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Manifest := Lib.GetPDFVCRManifest('campaign-template.pdf', '');
    if Manifest = '' then
      // 608, dosyanın PDF/VCR-1 uyumlu olmadığı,
      // 401, dosyanın açılamadığı anlamına gelir
      Writeln('No manifest, error ', Lib.LastErrorCode)
    else
      Writeln(Manifest);
  finally
    Lib.Free;
  end;
end;

Tüketicinize taşıyacağınız bir ayrıntı: page değeri, kütüphane genelindeki sıfır tabanlı sayfa kuralını izler; bir görüntüleyicide insanın gördüğü bir tabanlı numaralandırmayı değil. Bunları karıştırmak, içeriği yanlış sayfaya yerleştiren sessiz bir off-by-one'dır ve hiçbir doğrulayıcı yakalamaz, çünkü iki sayı da yapısal olarak geçerlidir

Sınırlar nerede?

PDFlibPas'taki PDF/VCR-1 desteği şablon yazmayı, kaydetme kapısını, bağımsız preflight'ı ve manifest çıkarmayı kapsar. Değiştirmeyi yapmaz. Veriyi yer tutuculara ikame edip kişiselleştirilmiş örnekleri üretmek kompozisyon adımıdır ve bilerek kütüphanenin dışında yaşar, çünkü manifest tam olarak bir kompozisyon motorunun bağımsız yazılabilmesini sağlayan arayüzdür. Kaydetme kapısı da onarmak yerine reddeder. Yanlış bir açıklama bayrağının sessizce normalleştirilebildiği profillerin aksine, PDFVCRReadyForSave'un uyguladığı her PDF/VCR-1 kısıtı, tahmin yürütmenin belgenin anlamını değiştireceği türdendir: bir şablon kökü uydurmak ya da bağlanmamış bir yer tutucunun hangi alana ait olduğunu seçmek, doğrulayan ve yalan söyleyen bir dosya üretir. Aynı onar-yerine-reddet çizgisi farklı bir profil için PDF/E-1 yazar modu ve sınırlı preflight yazısında tartışılıyor ve tek bir belge üzerinde birkaç uygunluk kontrolü çalıştırma iş akışı uygunluk ve imzalama tezgâhı yazısında ele alınıyor

Yazma, sınırlı yapı yürüyüşü, kaydetme kapısı ve manifest çıkarıcı — hepsi PDFlibPas Delphi PDF kütüphanesi içinde gelir; bu, bir şablonun profil altında üretilebileceği ve ardından yazar durumunu hiç görmemiş ayrı bir kod yoluyla doğrulanabileceği anlamına gelir ve bu ayrım, bir uygunluk iddiasının herhangi bir değer taşımasının tek nedenidir