Delphi向けのPDFium Componentは、TPdf.ValidatePdfXを介して、印刷対応のPDF/Xドキュメントを検証します。この関数は、2つのレイヤーでISO 15930チェックを実行します。1つは、8つのバイトレベルのコンテンツチェック(禁止されているLZW圧縮、JavaScript, フォームフィールド、OPI参照、TrimBoxの欠落、設定されていないTrappedキーなど)であり、もう1つは、FPDFFont_GetIsEmbeddedを使用してすべてのページのすべてのテキストオブジェクトに対してフォントの埋め込みを検証するPDFiumオブジェクトモデルのパスです。検証結果は、検出された準拠レベルと各違反項目を列挙型でリストするTPdfXValidationResultレコードとして返されます。これにより、Delphiアプリケーションは、印刷所に送る前に、ファイルがなぜ印刷エラーになるのかを正確にユーザーへ提示できます
商業印刷所にデータを納品した後に、「TrimBoxがありません」、「フォントが埋め込まれていません」、「Trappedが設定されていません」といった短い拒否メッセージとともにデータが返送されてきた経験があるなら、問題の発見が遅れることのコストの大きさを知っているはずです。PDF/Xは、アーカイブ向けの規格であるPDF/Aに対するプリプレス(印刷前工程)向けの規格です。PDF/Aが数十年後も同じようにレンダリングされることを保証するのに対し、PDF/Xは、他の印刷環境(RIP)でも同様に正しく色分解、描画、および裁断が行われることを保証します。この2つの規格は多くの仕組み(XMP識別、OutputIntents、埋め込みICCプロファイルなど)を共有していますが、解決する課題が異なるため、本コンポーネントではそれぞれ個別の検証機能を提供しています(PDF/A側についてはPDFium ComponentによるPDF/Aプリフライト検証で解説しています)
ISO 15930は印刷対応PDFに何を求めているのか?
ISO 15930は、ブラインド交換(事前のやり取りなしでのファイル受け渡し)を可能にするために策定されました。デザイナーが面識のない印刷所にファイルを渡し、印刷所は電話での確認やフォント欠落のメール、デザイナーのノートPCに取り残されたリンク画像などのトラブルなしに、正しい印刷物を制作できることを目指しています。規格内のすべてのルールはこの目標のために機能しています。受け取り側のRIPが同じフォントを所有しているとは限らないため、フォントは必ず埋め込まれていなければなりません。ファイル自体でデータが完結している必要があるため、外部への参照は禁止されます。印刷インクにはクリックイベント用のハンドラは存在しないため、インタラクティブ機能は禁止されます
PDFium Componentは、3つの準拠ファミリーを認識し、検証結果のTPdfXConformance列挙型を介して報告します。すなわち、PDF 1.3/1.4をベースにした厳格なCMYKおよびスポットカラー基準であるPDF/X-1a:2001(ISO 15930-1)を示すpxc1a、RGB、Lab、およびICC管理カラーを許容するPDF/X-3:2002(ISO 15930-3)を示すpxc3、そしてPDF 1.6をベースに生の透明効果やレイヤーの使用を許可したPDF/X-4:2010(ISO 15930-7)を示すpxc4です。PDF/Xの識別情報を持たないファイルはpxcNoneと報告されます。これ自体も有用な情報であり、そのドキュメントが最初から印刷対応ファイルとして作成されていなかったこと、および規格に準拠させるために何が必要であるかを把握できます
印刷出力システム(RIP)の立場で考えれば、これらの禁止事項の理由が分かります。互換性やライセンス上の課題を持つフィルタへの依存を避けるため、すべてのPDF/Xバリアントにおいて/LZWDecodeが禁止されています。Flate圧縮が代替として同じ役割を果たします。印刷用ファイルは紙の上の描画マークを固定して記述したものであるべきであり、ファイルを開いたタイミングで見た目が変化する余地を残すと「校正紙と同じものが印刷される」という保証が崩れてしまうため、JavaScript、AcroFormフィールド、および/AA追加アクション辞書は禁止されます。OPI(Open Prepress Interface)のプレースホルダーは、設計上、別の場所に保存されている高解像度画像への参照を意味しますが、この「別の場所」こそが、データ完結性を求めるブラインド交換において禁止される要素です
なぜ印刷所はTrimBoxのないPDFを拒否するのか?
TrimBox(仕上がりサイズ)は、裁断後の最終的な紙面を示す矩形です。すべてのPDFページが持つMediaBox(メディアサイズ)は単なる用紙であり、裁ち落とし(ブリード)、トンボ(クロップマーク)、レジストレーションマーク、カラーバーなどが含まれます。面付けソフトウェアは、各ページのTrimBoxを基準に用紙上にページを配置します。TrimBoxがないと、オペレーターは名刺などの仕上がりが実際にどこで終わるかを推測しなければならず、推測を誤ると裁ち落とし部分が切り落とされたり、端に白い余白が残ったりします。そのため、ISO 15930はすべてのページでTrimBox(またはArtBox)を要求しており、ドキュメント内のいずれかのページで/TrimBoxキーが見つからない場合、ValidatePdfXはpvxiMissingTrimBoxを発生させます
/Trappedキーは、別の生産現場の課題に対応するためのものです。トラッピングは、印刷時のわずかなズレによって色の境目に白い隙間ができないように、隣接する色をわずかに重ね合わせるプリプレス技術です。印刷オペレーターは、このトラッピング処理がすでに完了しているかどうかを知る必要があります。すでにトラッピング済みのファイルに対して重ねて処理を行うと重ねが二重になり、未処理のファイルに対して処理をスキップすると隙間ができるリスクがあります。そのため、PDF/XはInfo辞書内に/Trapped /Trueまたは/Trapped /Falseを明示することを要求しています。このキーが欠落しているか/Unknownに設定されている場合、オペレーターが手動でファイルを確認する必要が生じ、ブラインド交換の目的が果たせなくなります。本コンポーネントは、この問題をpvxiTrappedNotSetとしてフラグ付けします
TPdf.ValidatePdfXによる2レイヤー検証の実行
TPdf.ValidatePdfXは引数を取らず、Conformance(検出されたPDF/Xのタイプ)、Issues(TPdfXValidationIssue値のPascal集合データ)、およびIsCompliantヘルパーの3つのメンバーを持つTPdfXValidationResultレコードを返します。内部的には、ロードされたドキュメントを一時的にメモリストリームにシリアル化し、そのデータに対してバイトレベルの検査を実行した後に、PDFiumのオブジェクトモデルを巡回してフォントごとの埋め込みチェックを実行します。最小構成のプリフライト処理のコード例は以下のようになります
uses PDFium, FPdfPdfx;
procedure CheckPrintReadiness(const FileName: string);
var
Pdf: TPdf;
Res: TPdfXValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
Res := Pdf.ValidatePdfX;
Writeln('Detected conformance: ',
Ord(Res.Conformance)); // pxc1a, pxc3, pxc4, pxcNone...
if Res.IsCompliant then
Writeln('PDF/X checks passed')
else
begin
if pvxiMissingTrimBox in Res.Issues then
Writeln('REJECT: no /TrimBox on the pages');
if pvxiTrappedNotSet in Res.Issues then
Writeln('REJECT: /Trapped missing or /Unknown');
if pvxiPdfiumFontNotEmbedded in Res.Issues then
Writeln('REJECT: a page uses a non-embedded font');
if pvxiLzwForbidden in Res.Issues then
Writeln('REJECT: LZWDecode filter present');
end;
finally
Pdf.Free;
end;
end;
Issuesは通常のPascal集合データであるため、ワークフローの要求に合わせて自由にデータを仕分けることができます。例えば、構造上の致命的な問題を即時却下(リジェクト)とし、規格上の「SHOULD(推奨)」項目であるpvxiMissingTitleなどは警告として扱い、他はログに出力するなどの処理が可能です。これと同じレコード型はコンポーネントのレポート生成エンジンにも渡されるため、列挙型の判定コードを書く代わりに人間に読めるドキュメントを生成したい場合は、PDFium Componentによる一括プリフライトレポートCLIの構築に記載されているパターンをそのままPDF/Xに適用できます
バイトレベルの検査レイヤーで検出できる内容と、検出できない内容
バイトレベルの検査レイヤーは、ストリームの中身を除外した上でドキュメントの構造データに対して実行されるトークンスキャンです。これにより、JPEG画像データの中に偶然/JavaScriptというバイト配列が含まれていたとしても、誤検出を引き起こすことはありません。マーカーチェック(XMPのpdfxid:GTS_PDFXVersion、埋め込みICCプロファイルを持つOutputIntent、トレイラーの/ID、暗号化禁止の確認など)に加えて、以下の8つのコンテンツチェックが実行され、それぞれの検出項目に対応した列挙値が設定されます
pvxiLzwForbidden:ファイル内のいずれかの箇所に`/LZWDecode`フィルタが出現する場合(すべてのPDF/Xバリアントで禁止)pvxiJavaScriptForbidden:`/JavaScript`のアクションまたは名前ツリーが存在する場合pvxiFormFieldsForbidden:`/AcroForm`辞書または`/XFA`エントリが存在する場合pvxiAdditionalActions:`/AA`追加アクション辞書が存在する場合pvxiEmbeddedFilesForbidden:`/EmbeddedFiles`または`/FileAttachment`(ファイル添付)注釈が存在する場合pvxiOpiForbidden:`/OPI`または`/Alternates`エントリが置き換え可能な画像コンテンツを参照している場合pvxiMissingTrimBox:いずれのページにも`/TrimBox`が見つからない場合pvxiTrappedNotSet:`/Trapped`キーが存在しないか、`/Unknown`に設定されている場合
バイトスキャン処理は高速で、描画用のレンダリングエンジンも必要としませんが、フォントに関して重大な死角を持っています。このレベルの検査では、ファイル内に「埋め込みフォントが1つも存在しない」場合にのみフラグを立てる、大まかなヒューリスティック判定しか実行できません。9つのフォントが埋め込まれ、1つのシステムフォントが混入しているようなファイルであっても、バイトスキャンでは正常と判定されてしまいます。この重大な隙間を埋めるために、第2の検証レイヤーが存在します
PDFiumオブジェクトモデルを介したフォントごとの埋め込み検証
PDFium Componentのオブジェクトモデルレイヤーは、このフォントに関する疑問に正確な回答を示します。バイトレベルのパスが完了した後に、TPdf.ValidatePdfXは各ページを反復処理し、FPDFPage_CountObjectsでオブジェクトリストを取得し、各テキストオブジェクトについてFPDFTextObj_GetFontでフォントハンドルを特定し、FPDFFont_GetIsEmbeddedで問い合わせを行います。ドキュメント内のどこかに1つでも埋め込まれていないフォントがあれば、検出項目にpvxiPdfiumFontNotEmbeddedが追加されます。この走査処理は2段階のショートカット(問題が確定した瞬間にそのページ内のスキャンを停止し、かつ次のページ以降の読み込みも停止する)を適用しているため、違反を含んでいる300ページのカタログファイルであっても、多くの場合最初の1ページをロードした段階で判定結果が得られます
知っておくべき2つの限界事項があります。第1に、このレイヤーの機能はPDFiumライブラリがロードされていることを前提としており、FPDFFont_GetIsEmbeddedがエクスポートされているビルドを必要とします。このエクスポートがない環境では、チェックはエラーにならずにスキップされるため、古いDLLを使用していても誤って却下判定されることはありません。第2に、このチェックが判定するのは「埋め込まれているかどうか」のみであり、サブセット化されているかどうかの区別や、グリフの収録状況(カバー率)の検証などは行いません。検証が失敗した際、どのページのどのフォントが原因であるかを突き止めるには、DelphiでのPDFiumによるPDFフォントプロパティの分析で解説している列挙手法を使用してください
ドキュメントやDLLをロードせずにストリームを検証する
バイトレベルの検査エンジンは、FPdfPdfxユニット内の独立した関数ValidatePdfXCompliance(Source: TStream)としても公開されており、PDFium DLLに依存しない純粋なObject Pascalコードで記述されています。これにより、レンダリングエンジンの導入が難しい環境(Webサーバー上の軽量なアップロード判定処理、生成されたデザインデータをチェックするCIタスク、あるいはバイナリを動かしたくない環境のLazarusサービスなど)にも組み込むことができます。シーク可能なストリームを渡すだけで動作します
uses Classes, FPdfPdfx;
function QuickPdfXGate(const FileName: string): Boolean;
var
Fs: TFileStream;
Res: TPdfXValidationResult;
begin
Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Res := ValidatePdfXCompliance(Fs);
Result := Res.IsCompliant and (Res.Conformance <> pxcNone);
finally
Fs.Free;
end;
end;
このアプローチには技術的なトレードオフがあります。独立した関数を使用するパスでは、マーカーチェックおよび8つのコンテンツチェックは実行されますが、フォントごとのPDFiumレイヤーの検査はスキップされるため、フォントの埋め込み判定は大まかなヒューリスティック方式に戻ります。合理的なアーキテクチャ設計としては、まず軽量な最初のゲートとしてValidatePdfXComplianceを使用し、それを通過したファイルに対してのみ、完全なTPdf.ValidatePdfXによる精査を実行します
本検証エンジンの対応範囲と、本格的なプリフライト処理との境界
プリフライトツールの信頼性に関わるため、検証範囲の境界線について説明します。ValidatePdfXは識別マーカー、構造上の禁止項目、ページジオメトリのキー、Trapped宣言、および個別のテキストオブジェクトレベルまでのフォント埋め込み状態を検証します。インク総量(総インク網点面積率)の測定、指定されたバリアントにおいてすべてのカラースペースが適法であるかどうかのチェック(例えばX-1aにおけるCMYK限定ルールなど)、スクリーン線数に対する画像解像度のチェック、およびオーバープリントや透明効果のフラット化(ラスタライズ)処理の評価などは行いません。これらを検証するにはカラーマネジメント対応のプリフライト専用エンジンが必要であり、最終的な適合証明にはそれらとの併用をお勧めします。本コンポーネントが提供する2レイヤーのチェックは、印刷エラー原因の8割を占める構造上の問題を事前にミリ秒単位で検出し、印刷所からのデータ差し戻しメールが届く前にDelphiコード上で素早く発見するためのものです
2つの検証レイヤー、適合するPDFを出力するためのPDF/Xマーカー挿入API、および同様のアーキテクチャを共有するPDF/A、PDF/UA、PDF/E、PDF/VTの各検証機能は、すべてDelphiおよびC++Builder向けのPDFium Componentに含まれています。描画から印刷前のゲートキーピングまで、1つのコンポーネントで完結します