Technical Article

Pencetakan Data Variabel PDF/VT di Delphi dengan PDFium VCL

Sebuah percetakan transaksional mengembalikan run tagihan 80.000 halaman Anda dengan penolakan satu baris: "bukan PDF/VT, RIP tidak bisa cache". File-nya terbuka normal di setiap viewer di meja Anda, warnanya benar, datanya tergabung dengan tepat. Semua itu bukan yang diminta mesin cetak digital. Pencetakan data variabel berkecepatan tinggi hidup atau mati oleh kemampuan press mengenali bahwa blok logo pelanggan di halaman 1 adalah objek yang sama persis byte demi byte dengan yang di halaman 40.000, merendernya sekali, lalu memakainya ulang. PDF/VT adalah standar yang membuat janji itu bisa diperiksa mesin, dan "terlihat benar" justru jebakannya, karena struktur yang dibaca RIP tidak terlihat di layar

PDFiumPas mengekspos struktur itu lewat permukaan kecil pada TPdf: SaveAsPdfVT menuliskannya, ValidatePdfVT memeriksanya. Artikel ini membahas apa yang sebenarnya ditaruh ke disk dan diperiksa oleh dua metode itu, di mana ISO 16612-2 lebih ketat daripada yang tampak pada awalnya, dan bagian mana yang benar-benar menjadi jangkar struktural, bukan preflight penuh yang bisa Anda tagihkan ke klien

Apa yang distandardisasi PDF/VT, dan mengapa PDF/X didahulukan

PDF/VT (ISO 16612-2:2010) bukan format file baru. Ini adalah lapisan metadata optimasi yang ditempelkan di atas file PDF/X, dan urutan itu sangat penting. Standar ini mendefinisikan tiga tingkat konformansi, tetapi hanya dua yang benar-benar menamai file PDF: PDF/VT-1, satu dokumen mandiri, dan PDF/VT-2, model file-set tempat halaman merujuk ke resource eksternal yang dibagi. Token ketiga yang mungkin Anda lihat, PDF/VT-2s, sama sekali bukan nilai tingkat file; token itu hidup di header stream MIME yang dijelaskan di Lampiran A. Jika Anda menemukan kode yang menuliskan GTS_PDFVTVersion = "PDF/VT-2s" ke XMP dokumen, kode itu salah

Aturan yang tidak bisa ditawar untuk satu file adalah basis PDF/X. ISO 16612-2 §6.2.1 mewajibkan setiap file PDF/VT-1 juga menjadi file PDF/X-4 yang valid. Menurut §6.2.2, file-set PDF/VT-2 harus berada di atas PDF/X-4p, PDF/X-5g, atau PDF/X-5pg. Itulah sebabnya penulis PDF/VT tidak bisa sekadar menambahkan beberapa key identitas: ia harus membawa seluruh set marker PDF/X-4, yang berarti sebuah OutputIntent, profil ICC tujuan yang tertanam, entri XMP dan Info dokumen yang cocok, trailer /ID, dan tanpa enkripsi. Lewati salah satunya dan Anda punya file yang mengaku PDF/VT tetapi gagal begitu konsumen yang patuh memeriksa basisnya. PDFiumPas memperlakukan lapisan PDF/X-4 sebagai bagian dari penyimpanan PDF/VT, jadi Anda tidak memanggil SaveAsPdfX terpisah lebih dulu; injector menulis kedua lapisan dalam satu lintasan

Menulis file dengan SaveAsPdfVT

Pemanggilan minimal hanya membutuhkan dokumen yang aktif, karena TPdfVTSaveOptions.Default menyediakan profil ICC sRGB bawaan dan konformansi pvc1. Proses simpan berjalan dalam tiga langkah internal: ia menghapus semua keamanan (menyuntikkan marker teks biasa ke aliran objek terenkripsi akan merusaknya), ia menjembatani Info dictionary dokumen yang sudah ada dan trailer /ID ke dalam set marker supaya nilai XMP dan Info selaras, lalu ia menambahkan objek PDF/X-4 dan PDF/VT melalui incremental update

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    if Pdf.LoadFromFile('statements-merged.pdf') then
    begin
      // Default options: built-in sRGB OutputIntent, PDF/VT-1, synthesised DPart
      if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
        Writeln('PDF/VT-1 written')
      else
        Writeln('Save failed (document not active?)');
    end;
  finally
    Pdf.Free;
  end;
end;

Untuk output produksi yang sebenarnya, Anda hampir selalu ingin mengganti OutputIntent dengan karakterisasi press Anda, bukan fallback sRGB generik. Berikan byte ICC dan pengenal kondisi melalui TPdfVTSaveOptions:

var
  Pdf: TPdf;
  Opt: TPdfVTSaveOptions;
  Icc: TBytes;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.LoadFromFile('directmail-merged.pdf');
    Icc := LoadIccProfile('GRACoL2013_CRPC6.icc');  // your own loader

    Opt := TPdfVTSaveOptions.Default;
    Opt.Conformance := pvc1;            // pvc2 is normalised to pvc1 on write
    Opt.IccProfileData := Icc;
    Opt.OutputConditionIdentifier := 'CGATS21_CRPC6';
    Opt.OutputCondition := 'Commercial print, coated, CRPC6';
    Opt.RegistryName := 'http://www.color.org';
    Opt.Title := 'Spring 2026 Direct Mail Run';
    Opt.Trapped := ptvFalse;           // PDF/X Info /Trapped state

    Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
  finally
    Pdf.Free;
  end;
end;

Satu detail pada cuplikan itu adalah pagar pengaman yang disengaja, bukan batasan yang bisa diperdebatkan. Menetapkan Opt.Conformance := pvc2 tidak menghasilkan file PDF/VT-2. Writer menormalkan setiap permintaan non-pvc1 kembali ke pvc1, karena PDF/VT-2 adalah format file-set dan writer satu file yang hanya menambahkan satu dokumen output secara fisik tidak bisa merakit set resource eksternal yang dituntut §6.2.2. Nilai pvc2 ada untuk jalur baca, supaya ValidatePdfVT bisa mengenali dan melaporkan dokumen file-set yang sudah ada; itu bukan target tulis

Tree DPart: struktur yang benar-benar dibaca RIP

Inti PDF/VT adalah hierarki Document Part (DPart). Inilah yang memungkinkan press membagi run panjang menjadi record, mengelompokkan record menjadi penerima atau bundel surat, dan melampirkan Document Part Metadata supaya peralatan downstream bisa merutekan dan menagih tiap bagian. ISO 16612-2 §6.5 menjabarkan sambungannya: katalog membawa /DPartRoot, node DPart root membawa /DPartRootNode dan /NodeNameList yang menamai tiap level hierarki, DPart leaf mencakup rentang page tree, dan setiap halaman yang menjadi bagian sebuah part menunjuk kembali ke leaf-nya melalui entri /DPart di tingkat halaman

Ketika dokumen sumber Anda sudah memuat hierarki yang bisa dipakai, SaveAsPdfVT mempertahankannya. Saat tidak ada, writer menyintesis satu yang minimal: satu DPart level dokumen yang membentang sepanjang page tree saat ini, dengan back-reference /DPart ditambahkan ke setiap objek halaman yang aktif dan satu level /NodeNameList [/Document]. Jujurlah pada diri sendiri tentang apa arti tree minimal itu. Ia adalah jangkar struktural yang memenuhi syarat bentuk §6.5; ia bukan metadata bisnis. Ia tidak bisa menciptakan penerima, batas mail-piece, atau batch produk, karena informasi itu memang tidak pernah ada di sumber. Jika Anda punya data per penerima, Anda diharapkan membangun tree DPart yang lebih dalam sendiri dan memperluas /NodeNameList agar cocok dengan level yang Anda buat

Validasi yang melampaui keberadaan key

ValidatePdfVT mengembalikan record TPdfVTValidationResult dengan tiga hal: Conformance yang terdeteksi, satu set Issues, dan helper IsCompliant yang bernilai true hanya ketika konformansi adalah level yang nyata dan set issue kosong. Enumerasi issue sengaja dibuat spesifik, jadi hasil gagal memberi tahu Anda clause mana yang terlewat, bukan sekadar "invalid":

var
  Pdf: TPdf;
  Res: TPdfVTValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.LoadFromFile('statements-pdfvt.pdf');
    Res := Pdf.ValidatePdfVT;

    if Res.IsCompliant then
      Writeln('PDF/VT compliant: ', VTLevelName(Res.Conformance))
    else
    begin
      if pvviMissingDPartRoot in Res.Issues then
        Writeln('DPart hierarchy missing or unusable');
      if pvviMissingPdfXIdentifier in Res.Issues then
        Writeln('PDF/X-4 base identifier absent');
      if pvviMissingOutputIntent in Res.Issues then
        Writeln('OutputIntent / ICC profile missing');
      if pvviEncryptionPresent in Res.Issues then
        Writeln('Encrypted - PDF/X forbids this');
    end;
  finally
    Pdf.Free;
  end;
end;

Dua pemeriksaan yang layak dipahami secara mendalam adalah pasangan konformansi dan penelusuran DPart, karena keduanya dulu terlalu longgar dan kemudian diperketat agar cocok dengan spesifikasi. Pada sisi pairing, validator melakukan pencocokan tepat, bukan "asal PDF/X apa pun boleh": file PDF/VT-1 hanya diterima di basis PDF/X-4, dan file PDF/VT-2 hanya di PDF/X-4p, PDF/X-5g, atau PDF/X-5pg. Marker PDF/VT-1 yang duduk di atas basis PDF/X-1a dilaporkan, bukan diloloskan

Penelusuran DPart adalah tempat sebagian besar ketelitian itu berada. Cukup tidak hanya bagi katalog untuk memiliki key /DPartRoot, karena objek kosong yang dipalsukan atau yang tidak punya tautan halaman tetap tidak bisa dikonsumsi. HasValidDPartHierarchy dan ValidateDPartNode yang rekursif menelusuri seluruh struktur: mereka mengikuti parent link, menolak child duplikat dan cycle, menegakkan bahwa /Start dan /DParts saling eksklusif, dan mensyaratkan rentang halaman leaf mencakup page tree dalam urutan depth-first dengan /DPart setiap halaman menunjuk ke leaf yang memuatnya. Semua fault internal itu dipadatkan menjadi bit issue tunggal pvviMissingDPartRoot alih-alih memperluas enum publik, jadi perlakukan flag itu sebagai "hierarki DPart tidak dapat dipakai", bukan secara harfiah "root key tidak ada"

Tiga jebakan sintaks yang kini ditegakkan validator

Pemeriksaan berulang terhadap §6.5 Table 4 menemukan bentuk yang dulu diterima tetapi tidak sesuai standar. Ini jenis kesalahan yang sering dibuat tree DPart rakitan tangan, jadi layak disebutkan secara eksplisit:

  • /DParts adalah array of arrays, bukan array datar. Setiap elemen array luar harus berupa array referensi tak langsung juga. /DParts [9 0 R] yang datar ditolak; bentuk yang sesuai adalah /DParts [[9 0 R] [10 0 R]]. Ini mencegah struktur non-hierarkis menyamar sebagai level yang valid
  • /End hanya menandai rentang multi-halaman yang benar. DPart leaf boleh membawa /End hanya jika juga memiliki /Start, dan /End harus berada lebih akhir daripada /Start dalam urutan page tree. Kasus degenerat /Start 3 0 R /End 3 0 R sekarang membuat hierarki tidak dapat dipakai, bukan terbaca sebagai part satu halaman
  • Nama /NodeNameList harus lolos unescaping nama PDF sebagai XML NMTOKEN. Nama seperti /Bad#20Name berkembang menjadi nama yang berisi spasi, yang bukan token valid. Implementasinya melakukan pemeriksaan ASCII ringan (huruf, angka, ., -, _, :, plus byte non-ASCII) yang menangkap kesalahan whitespace dan delimiter tanpa menolak nama lokal atau vendor-specific yang sah

Penanda XMP: dua cara menulis properti yang sama

Identifikasi PDF/VT hidup di XMP di bawah namespace pdfvtid, khususnya GTS_PDFVTVersion dan GTS_PDFVTModDate, bersama xmp:CreateDate dan xmp:ModifyDate standar. Detail halus yang memunculkan laporan "missing" palsu di pembaca naif adalah bahwa semuanya bisa diserialkan dengan dua cara: sebagai teks elemen (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) atau sebagai atribut RDF pada elemen deskripsi. PDFiumPas membaca kedua bentuk, jadi file yang ditulis alat lain dengan gaya atribut tidak dihukum. Ia juga menegakkan aturan konsistensi §6.3 bahwa GTS_PDFVTModDate harus sama dengan xmp:ModifyDate; ketidakcocokan memicu pvviModDateMismatch

Satu aturan lagi dari klausul yang sama: nilai GTS_PDFVTVersion yang tidak dikenal dipertahankan sebagai pvcUnknown alih-alih dilipat kembali menjadi pvcNone. Perbedaan itu penting secara operasional. pvcNone berarti "tidak ada marker PDF/VT sama sekali, hanya PDF biasa", sedangkan pvcUnknown berarti "ada sesuatu yang menandai versi yang tidak dikenali validator ini" (termasuk kasus PDF/VT-2s). Menyamakan keduanya akan menyembunyikan file yang salah bentuk ke dalam keranjang yang sama dengan dokumen polos

Di mana jaminannya berakhir

Penting untuk tepat soal batas janji metode ini, karena kepatuhan cetak data variabel memang bernilai uang. Pemeriksaan DPart dan pairing adalah validasi struktural level byte. Keduanya mengonfirmasi bahwa skeleton optimasi, marker basis PDF/X-4, OutputIntent, dan XMP ada dan konsisten secara internal. Keduanya bukan preflight PDF/X-4 di level konten: mereka tidak memverifikasi apakah setiap warna berada dalam kondisi output yang dideklarasikan, apakah semua font tertanam, atau apakah tidak ada kasus tepi blending transparansi yang dilarang lolos. Untuk pekerjaan yang akan Anda kirim ke press kontrak, pasangkan validasi struktural PDFiumPas dengan mesin preflight PDF/X khusus dan test print, sama seperti Anda akan memeriksa kewajaran klaim kepatuhan lainnya. Lapisan struktural menangkap kegagalan yang diam-diam mematahkan caching RIP; itu hanya satu setengah dari pemeriksaan lengkap, bukan semuanya

Jika Anda membangun pemeriksaan ini ke dalam gate rilis yang lebih luas, pendekatan pemindaian level byte yang sama menjadi dasar pekerjaan standar lain dari library ini, termasuk memvalidasi object dan cross-reference streams sebelum file mencapai preflight, dan disiplin shared object di balik stempel halaman yang dapat dipakai ulang dengan Form XObjects yang membuat dokumen ramah RIP sejak awal. API save dan validation PDF/VT dan PDF/X yang dibahas di sini merupakan bagian dari PDFium VCL component untuk Delphi dan C++Builder, dan halaman produknya memuat referensi kepatuhan lengkap