帳票を 8 万ページ連結したデータを取引先の印刷会社へ渡したところ、返ってきた差し戻しはたった一行です。「PDF/VT ではないので、RIP でキャッシュできない」。手元のどのビューアでも問題なく開き、色も合っていて、差し込みデータも正しく結合されています。ですが、デジタル印刷機が求めているのはそこではありません。高速な可変データ印刷では、1 ページ目の顧客ロゴのブロックが 4 万ページ目の同じブロックとバイト単位で同一オブジェクトだと印刷機が認識し、一度だけ描画して再利用できるかどうかが決定的です。PDF/VT は、その約束を機械的に検証可能にする標準です。そして「見た目が正しい」という感覚こそが落とし穴です。RIP が読む構造は画面上では見えないからです
PDFiumPas は TPdf にある小さな API 面としてこの構造を公開しています。SaveAsPdfVT が書き込み、ValidatePdfVT が検査します。この記事では、この 2 つのメソッドが実際にディスクへ何を書き出し、何を検査しているのか、ISO 16612-2 が見た目以上に厳密な箇所はどこか、そしてどこまでが正直な構造アンカーで、どこから先がクライアントに請求できる完全なプリフライトではないのかを整理します
PDF/VT が標準化するものと、なぜ先に PDF/X が必要なのか
PDF/VT(ISO 16612-2:2010)は新しいファイル形式ではありません。PDF/X ファイルに最適化メタデータの層を追加したものであり、この順序には意味があります。規格は 3 つの適合レベルを定義していますが、PDF ファイルそのものを指すのは 2 つだけです。PDF/VT-1 は単一の自己完結型ドキュメントで、PDF/VT-2 はページが共有外部リソースを参照するファイルセット型です。もう 1 つ見かけることのある PDF/VT-2sPDF/VT-2sGTS_PDFVTVersion = "PDF/VT-2s" は、そもそもファイルレベルの値ではありません。Annex A にある MIME ストリームヘッダー内で使われます。もしコードが
GTS_PDFVTVersion = "PDF/VT-2s"/ID を文書の XMP に書き込んでいるなら、そのコードは誤りです。SaveAsPdfX/ID
SaveAsPdfX
最小の呼び出しに必要なのは有効な文書だけです。TPdfVTSaveOptions.DefaultTPdfVTSaveOptions.Defaultpvc1pvc1/ID/ID
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;
TPdfVTSaveOptionsTPdfVTSaveOptions で渡します
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;
そのコード片で 1 つ重要なのは、議論できる制限ではなく、意図的に設けられたガードレールだという点です。Opt.Conformance := pvc2Opt.Conformance := pvc2pvc1pvc1pvc1pvc1pvc2pvc2ValidatePdfVTValidatePdfVT
DPart ツリー: RIP が実際に読む構造
PDF/VT の核心は Document Part(DPart)階層です。これにより印刷機は長いジョブをレコード単位に区切り、レコードを受取人や郵送束ごとにまとめ、Document Part Metadata を付けて下流装置が各ピースを振り分けて課金できるようになります。ISO 16612-2 §6.5 は配線をこう定義しています。カタログは /DPartRoot/DPartRoot/DPartRootNode/DPartRootNode/NodeNameList/NodeNameList/DPart/DPart
元の文書にすでに利用可能な階層があるなら、SaveAsPdfVT はそれを保持します。ない場合は、ライターが最小構成の階層を合成します。現在のページツリー全体を順に覆う単一の文書レベル DPart を作り、各有効ページオブジェクトに /DPart/DPart/NodeNameList [/Document] の逆参照を追加し、1 レベルだけの /NodeNameList/NodeNameList [/Document]
キーの有無だけで終わらない検証
ValidatePdfVTValidatePdfVTTPdfVTValidationResultTPdfVTValidationResultConformanceConformanceIssuesIssuesIsCompliantIsCompliant
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;
深く理解しておく価値があるのは、適合レベルの組み合わせ確認と DPart 走査の 2 つです。どちらも以前は緩すぎたため、規格に合わせて厳密化されました。組み合わせ側では、バリデータは「何らかの PDF/X ならよい」ではなく完全一致を求めます。PDF/VT-1 ファイルは PDF/X-4PDF/X-4PDF/X-4pPDF/X-4pPDF/X-5gPDF/X-5gPDF/X-5pgPDF/X-5pg
DPart 走査こそが厳密さの大半を担う部分です。カタログに /DPartRoot/DPartRootHasValidDPartHierarchyHasValidDPartHierarchyValidateDPartNodeValidateDPartNode/Start/Start/DParts/DParts/DPart/DPartpvviMissingDPartRootpvviMissingDPartRoot
現在のバリデータが強制する 3 つの構文トラップ
§6.5 Table 4 に対する検証を繰り返した結果、以前の版では受理していたが規格では認められない形が見つかりました。手作業で DPart ツリーを組むと起こりやすいので、明示しておく価値があります
/DParts/DParts 外側の配列の各要素は、それ自体が間接参照の配列でなければなりません。フラットな/DParts [9 0 R]/DParts [9 0 R]/DParts [[9 0 R] [10 0 R]]/DParts [[9 0 R] [10 0 R]]/End/End リーフ DPart が/End/End/Start/Start/End/End/Start/Start/Start 3 0 R /End 3 0 R/Start 3 0 R /End 3 0 R/NodeNameList/NodeNameList/Bad#20Name/Bad#20Nameは空白を含む形へ展開され、これは有効なトークンではありません。実装では、英字、数字、..--__::
XMP マーカー: 同じプロパティを 2 通りで書く
PDF/VT の識別情報は、pdfvtidpdfvtidGTS_PDFVTVersionGTS_PDFVTVersionGTS_PDFVTModDateGTS_PDFVTModDatexmp:CreateDatexmp:CreateDatexmp:ModifyDatexmp:ModifyDate<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion><pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>GTS_PDFVTModDateGTS_PDFVTModDatexmp:ModifyDatexmp:ModifyDatepvviModDateMismatchpvviModDateMismatch
同じ条項にはもう 1 つルールがあります。未知の GTS_PDFVTVersionGTS_PDFVTVersionpvcUnknownpvcUnknownpvcNonepvcNonepvcNonepvcNonepvcUnknownpvcUnknownPDF/VT-2sPDF/VT-2s
どこまでを保証できるのか
これらのメソッドが約束する境界は正確に理解しておくべきです。可変データ印刷の適合性には実際の費用がかかるからです。DPart と組み合わせの検査はバイトレベルの構造検証です。最適化用スケルトン、PDF/X-4 のベースマーカー、OutputIntent、XMP が存在し、内部的に整合していることを確認します。しかし、これは内容レベルの PDF/X-4 プリフライトではありません。すべての色が宣言された出力条件内に収まっているか、すべてのフォントが埋め込まれているか、禁止された透明合成の端ケースが紛れ込んでいないかまでは検証しません。契約印刷機へ流すジョブであれば、PDFiumPas の構造検証に専用の PDF/X プリフライトエンジンとテスト印刷を組み合わせてください。他の適合性主張を健全に確認するときと同じです。構造層が捕まえるのは、RIP キャッシュを静かに壊す種類の失敗です。完全な確認の半分ではありますが、全体ではありません
これらの検査をより広いリリースゲートへ組み込むなら、同じバイトレベル走査の考え方が、ライブラリのほかの標準対応にも生きています。たとえば オブジェクトストリームとクロスリファレンスストリームの検証 はファイルがプリフライトへ到達する前の段階で役立ち、Form XObjects を使った再利用可能なページスタンプ の背後にある共有オブジェクト規律は、文書をそもそも RIP に優しいものにします。ここで説明した PDF/VT と PDF/X の保存および検証 API は、Delphi と C++Builder 向け PDFium VCL component の一部であり、製品ページには完全な適合性リファレンスが掲載されています