技術記事

PDF/A のデバイス色はページが塗ったもので決まる

RGB を一度も描かないファイルに veraPDF が ISO 19005-1 6.2.3.3 の旗を立てたり、裸の re f を未較正と呼んだり、同一内容のページ 1 が合格なのにページ 2 が落ちたりします。PDF/A はこれらの規則を、各ページが塗るもので判定します。だから Delphi 向け PDFium Component の PDF/A バリデーターは、色と透明性の判定の前に、すべてのページをフォーム、パターン、グリフ、注釈まで追跡します

6.2.3.3 と 6.2.4.3 が実際に要求するもの

ISO 19005-1 6.2.3.3 は、3 つの独立した規則を 1 つの段落に詰め込んでいます。準拠ファイルは DeviceRGB か DeviceCMYK を使ってよいが、両方は使ってはならない。未較正の色空間を 1 つでも使ったなら、ファイルは PDF/A-1 の OutputIntent を含まねばならない。そして DeviceRGB はプロファイルが RGB のインテントと一緒のときだけ、DeviceCMYK は CMYK のものと一緒のときだけ許される。DeviceGray がこのリストから外れているのは、この条が続いて、どちらのインテントの下でもグレーを変換できると説明するからです。インテント自体は任意です。6.2.2 は、ファイルがインテントを指定してもよいとし、/S /GTS_PDFA1 と、/DestOutputProfile の中の有効な ICC プロファイルストリームを持つ OutputIntents エントリだけを数えます。だからすべてのファイルにインテントを要求するチェッカーは、デバイス色を一度も塗らない準拠文書を拒んでしまいます。Part 1 は DefaultRGB や DefaultCMYK に触れません。しかしそれらを通じて再マップされた色は CIE ベースの空間で描画され(ISO 32000-1 §8.6.5.6)、6.2.3.1 のデバイス非依存の要求を満たします。6.2.3.3 の veraPDF テストスイートはそれを較正済みと扱い、PDFium Component も同じ読み方をします。混在の禁止は別物です。RGB と CMYK の両方を塗る PDF/A-1 ファイルは、Default エントリが両方を較正していても落ちます。ISO 19005-2 6.2.4.3 はその禁止を外し、各空間をそれ自体の条件で較正します。下の表の右の列のとおりです。だから PDFium Component はパートごとに 1 回の走査を行い、混在チェックは PDF/A-1 の主張にだけ適用します

描かれたデバイス空間PDF/A-1(ISO 19005-1 6.2.3.3)PDF/A-2 以降(ISO 19005-2 6.2.4.3)
DeviceRGBスコープ内の DefaultRGB か RGB のインテント。DeviceCMYK と並ぶのは不可スコープ内の DefaultRGB、または RGB の OutputIntent
DeviceCMYKスコープ内の DefaultCMYK か CMYK のインテント。DeviceRGB と並ぶのは不可スコープ内の DefaultCMYK、または CMYK の OutputIntent
DeviceGrayスコープ内の DefaultGray、または任意の PDF/A-1 OutputIntentスコープ内の DefaultGray、または任意の PDF/A OutputIntent

色空間を使っているとは何を指すのか

色空間を使うとは、リーダーが描画するパスの上でそれで塗ることです。宣言だけでは足りません。veraPDF スイートは、6.2.3.3 をパスするファイルでこれを示しています。名前付きの Form XObject が K と k で CMYK を設定し、ページのリソース辞書の中に座り、ページコンテンツからは一切参照されず、ファイルの OutputIntent は RGB です。ファイル全体のデバイス色名検索はそのファイルを落ちとし、CMYK インテントの下で DeviceRGB だけを塗るファイルを見逃します。そこでは 2 つの名前が衝突しないからです。PDFium Component は代わりに描画グラフを歩きます。その足場は、バイトレベルの PDF/A プレフライトスキャナーです。各ページから、このウォークは次のものが塗る色を数えます:

  • ページコンテンツストリームと、それらが Do で呼び出すすべての Form XObject。ネストしたフォームも含みます
  • scn か SCN で選択されたパターン。パターン自身の Resources で読みます
  • Tf で選択された Type 3 フォントの CharProcs
  • 画像 XObject とインライン画像。自分の辞書が名指す色空間で数えます
  • 注釈アピアランスストリーム。ページの上に塗られます。ウィジェットの /AP /N が状態辞書のときは、すべての状態を数えます。フィールドを切り替えると、/AS が選ばなかった状態が塗られるからです

到達可能性のより微妙な半分は、コンテンツが名指ししない色です。q 0 0 10 10 re f Q は色演算子を含みません。それでも塗ります。グラフィックス状態の初期色空間は DeviceGray(ISO 32000-1 Table 52)なので、その塗りは DeviceGray の黒で、PDF/A OutputIntent か DefaultGray が存在しない限り、どのパートでも未較正です。だからスキャンは、何かがすでに色を選んだかどうかを追跡します。rg、k、g とそのストローク版がデバイス空間を選び、cs と CS が名指すものを選び、どの選択より先に到達した塗り、ストローク、テキスト演算子は DeviceGray と記録します。/CS0 cs 1 0 0 /P0 scn はすでに空間を選んだあとなので、その scn はグレーではありません

PDF/A のデバイス色を判定する前に Delphi の PDFium Component が歩く到達可能な塗りパス。フォーム、パターン、Type 3 グリフ、画像、注釈アピアランスストリーム
色空間の宣言だけでは足りません — ウォークは各ページが塗るものを数えます。裸の re f が初期状態から拾うデバイスグレーも含めて

インライン画像は、色空間と生のサンプルをコンテンツストリーム自身の中に持ち込みます。スキャナーは両方を注意深く読む必要があります。/CS は省略形(G、RGB、CMYK、I)でも、[/Indexed /DeviceRGB 255 13 0 R] に解決するリソース名でもよく、そのベースを数えます。そして ID と EI の間のバイトは任意のバイナリなので、EI が画像を終わらせるのは空白に挟まれたときだけです。さもないと、圧縮されたサンプルの中の rg が幽霊演算子になります

% ページ 1:DefaultRGB がスコープ内、そして何も呼び出さない XObject
4 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R
           /Resources << /ColorSpace << /DefaultRGB 9 0 R >>
                         /XObject << /Fm0 6 0 R >> >> >>
endobj
% ページ 2:同じコンテンツストリーム、ColorSpace サブ辞書なし
7 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R /Resources << >> >>
endobj
5 0 obj << /Length 46 >> stream
1 0 0 rg 72 72 200 100 re f
q 0 0 10 10 re f Q
endstream
endobj
6 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 50 50] /Length 24 >> stream
0 0 0 1 k 0 0 50 50 re f
endstream
endobj
9 0 obj [/ICCBased 10 0 R]
endobj

冒頭の症状はどれも、この断片から出てきます。/Fm0 は Do と一度も出会わないので、その k は無関係です。両ページの裸の re f は、較正するものが何もない DeviceGray です。ページ 1 の rg は、ページ 1 が塗るときに使うリソースの中の DefaultRGB にカバーされています。ページ 2 は、同じストリームを、そのマッピングを欠くリソースに対して走らせるので、同じバイトがそこでは未較正になります。PDF/A-1b の主張に対する期待結果は、ページ 2 の RGB と両ページのグレーが駆動する pvaiMissingOutputIntent と pvaiMissingIccProfile の組です。フォームは何も寄与しません。呼び出されたら、その CMYK が PDF/A-1 の混在禁止を上に加えるでしょう

デフォルト色空間は、塗る側のリソース辞書に属する

Default マッピングは、そのときのリソース辞書の ColorSpace サブ辞書の中で探されます(ISO 32000-1 §8.6.5.6)。だからその局所性は、リソースそのものと寸分違わず同じです。文書にグローバルなデフォルトはありません。画像 XObject には自分の Resources がないので、辞書に /ColorSpace /DeviceCMYK と書かれた画像は、それを描くページかフォームの DefaultCMYK で較正されます。同じ画像を 2 つの場所から描けば、片方では較正され、もう片方ではされません。Form XObject、タイルパターン、Type 3 グリフ手続きは自分の Resources を運ぶので、ページの DefaultCMYK はその中には届かず、自分の辞書の中のマッピングなら届きます。PDFium Component はマッピングを同じくスコープします。判定は TPdfAValidationResult レコードとして返ります。検出された Conformance と Issues セットです。フォルダーをトリアージするには十分です

uses
  System.SysUtils, System.Classes, System.TypInfo, FPdfPdfa;

procedure ReportColorFindings(const FileName: string; Log: TStrings);
const
  ColorRules: TPdfAValidationIssues = [pvaiMissingOutputIntent,
    pvaiMissingIccProfile, pvaiMixedDeviceColorSpaces, pvaiBlendSpaceUndefined];
var
  Source: TFileStream;
  Res: TPdfAValidationResult;
  Found: TPdfAValidationIssues;
  Issue: TPdfAValidationIssue;
begin
  Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfACompliance(Source);   // 位置 0 から読む
  finally
    Source.Free;
  end;
  Log.Add(ExtractFileName(FileName) + ': ' +
    GetEnumName(TypeInfo(TPdfAConformance), Ord(Res.Conformance)));
  Found := Res.Issues * ColorRules;
  for Issue in Found do
    case Issue of
      pvaiMissingOutputIntent:
        Log.Add('  reachable uncalibrated device color, no PDF/A OutputIntent');
      pvaiMissingIccProfile:
        Log.Add('  no OutputIntent with a DestOutputProfile ICC stream');
      pvaiMixedDeviceColorSpaces:
        Log.Add('  RGB/CMYK use disagrees with the intent, or PDF/A-1 paints both');
      pvaiBlendSpaceUndefined:
        Log.Add('  transparent page without a blending color space');
    end;
  if Found = [] then
    Log.Add('  device color and blending rules satisfied');
end;

ウォークが未較正のデバイス色に届き、PDF/A OutputIntent が存在しないとき、pvaiMissingOutputIntent と pvaiMissingIccProfile は一緒に現れます。pvaiMissingIccProfile 単独は、インテントは存在するがプロファイルを運んでいないことを意味し、6.2.2 はそれを PDF/A インテントとして受け付けません。pvaiMixedDeviceColorSpaces は、PDF/A-1 の混在禁止とコンポーネント数の不一致の両方をカバーします。4 コンポーネントのインテントの下の未較正 DeviceRGB、あるいは 3 コンポーネントの下の DeviceCMYK です。セットはページではなく規則の名前を持ちます。長い文書やマージされた文書では、どのページが Default エントリを宣言しどのページがしないかを比べてください

Delphi で 2 つのリソース辞書から描かれる 1 つの DeviceCMYK 画像。PDFium Component が報告するとおり、片方のページでは DefaultCMYK で較正され、もう片方では未較正
デフォルト色空間は塗る側のリソース辞書に属します。だから同じ画像が、ページが描くところでは較正され、フォームが描くところでは未較正になれます

透明ページに固有のブレンド空間が要るのはいつか

透明なページが自分のブレンド色空間を要するのは、文書に PDF/A OutputIntent がないときだけです。ISO 19005-2 6.2.10(PDF/A-4 では 6.2.9)は、インテントを、ISO 32000-1 §11.3.4 の意味での既定ブレンド色空間にします。インテントがなければ、透明を含むすべてのページが、属性辞書(ISO 32000-1 Table 147)に CS を含む Group を運ばねばなりません。その CS も 6.2.4 の縛りを受けます。だから sRGB インテントの下の /DeviceCMYK のページグループは、欠けているものとして報告され、PDFium Component は両方に pvaiBlendSpaceUndefined を上げます。CalRGB や ICCBased のグループ空間にはその依存がありません。PDF/A-1 はこの問いに届きません。ISO 19005-1 6.4 が透明性を丸ごと排除するからです

難しいのは透明を含むという句です。ISO 19005-2 の Annex A は規定文書で、ページの外側から内側へ向かって試します。/SMask キーや /ca 値のファイル全体数え上げは、別の問いに答えます。バイト検索は、他の場所の透明性構文とも衝突します。禁止されたトランジションアクション /S /Trans は、すべての /S /Transparency グループの接頭辞です。そして /Screen は、禁止された注釈サブタイプでもあり、標準ブレンドモードでもあります。だから名前は、キーと対になって初めて意味を持ちます。Annex A に従い、PDFium Component は、次のどれかがページから到達できるとき、そのページを透明と扱います:

  • コンテンツが gs で選択する ExtGState で、ソフトマスク辞書を持つか、1 未満の ca か CA を持つか、Normal 以外のブレンドモードを持つもの。宣言だけで選択されないものは数えません
  • Do で呼び出される Form XObject で、その /Group が /S /Transparency を持つか、自分のコンテンツがこれらの条件のどれかに当てはまるもの。タイルパターンはフォームと同じように判定します
  • SMask ストリームを持つか、0 超の SMaskInData 値を持つ画像 XObject
  • ページがテキストを表示するのに使う Type 3 フォントのグリフ記述。フォームと同じように判定します
  • 注釈アピアランスストリーム。すべてのウィジェット状態を含みます。そして注釈辞書自身の上のブレンドモード

グループはデバイス色の規則にも効いてきます。そしてここで、コーパスが私たちを訂正しました。6.2.4.3 の veraPDF スイートは、OutputIntent を一切持たずにデバイス RGB を塗り、合格が期待される PDF/A-4 ファイルを抱えています。各ページの /Group が較正済みの CS を名指すからです。私たちは、isolate されたグループだけが較正できると決め込んでいました。isolate されていないグループは背景に合成されるからです。ページグループについては、それは間違いでした。Table 147 は、出力媒体に直接施されるページは I の値にかかわらず実質的に isolated だと注記しており、だからその CS は尊重されるのです。Form XObject や注釈アピアランスの上では、フラグがやはりものを言います。スイートには /I true だけが違う、合格と不合格のファイルの組があります。DeviceGray はグループから較正を取りません。6.2.4.3 は DefaultGray かインテントを求め、CS がデバイス空間であるグループは自分自身の較正を必要とするからです。だから PDFium Component は、ページグループには /I の値にかかわらず較正を認め、フォームやアピアランスのグループには isolated のときだけ認め、グレーをグループ較正の外に保ち、グループが名指すデバイス空間はやはりチェックします

Delphi 向け PDF/A マトリクス。透明なページに自分のブレンド CS が要るとき、そして PDFium Component がページ、フォーム、アピアランスのグループ較正を判定するしかけ
出力インテントが既定のブレンド空間です。ページグループは isolation フラグにかかわらずデバイス色を較正し、フォームグループは /I true を必要とします

すべてのページのグループの代わりに、1 つの OutputIntent

自分のコードでアーカイブを書くなら、文書レベルの PDF/A OutputIntent から始めてください。カタログの 1 つのエントリが、どこでも DeviceGray を較正し、すべてのページで一致するデバイス空間をカバーし、すべての透明ページの既定ブレンド空間になります。ページグループと Default エントリはそれぞれ 1 つのリソース辞書にスコープされ、後のマージがそれらなしで追加するページこそが落ちるページです。PDF 2.0 はページオブジェクトに OutputIntents を追加しました(ISO 32000-2 §7.7.3.3 と §14.11.5)。PDFium Component は、カタログにないとき PDF/A-4 についてはそのページレベルのインテントを受け入れます。PDF 1.4 から 1.7 を土台とするパート 1 から 3 は、カタログだけを見ます。RGB コンテンツには、SaveAsPdfAToStream が PDF/A ライターの埋め込み sRGB ICC プロファイルを土台にしたカタログインテントを追加し、出力を検証すれば、コンテンツが本当に RGB だけだったかが分かります

uses
  System.SysUtils, System.Classes, PDFium, FPdfPdfa;

function ArchiveWithSrgbIntent(const InFile, OutFile: string;
  out Remaining: TPdfAValidationIssues): Boolean;
var
  Pdf: TPdf;
  Opts: TPdfASaveOptions;
  Output: TMemoryStream;
  Check: TPdfAValidationResult;
begin
  Result := False;
  Remaining := [];
  Pdf := TPdf.Create(nil);
  Output := TMemoryStream.Create;
  try
    Pdf.FileName := InFile;
    Pdf.Active := True;
    if not Pdf.Active then
      Exit;                           // ファイルを開けなかった
    Opts := TPdfASaveOptions.Default;
    Opts.Conformance := pac2b;        // PDF/A-1 なら透明性は丸ごと禁止される
    // IccProfileData は空のまま:内蔵の sRGB IEC61966-2.1 プロファイルが使われる
    if not Pdf.SaveAsPdfAToStream(Output, Opts) then
      Exit;
    Check := ValidatePdfACompliance(Output);
    Remaining := Check.Issues;
    // RGB だけのコンテンツに対する期待:pvaiMissingOutputIntent と
    // pvaiBlendSpaceUndefined は消える。pvaiMixedDeviceColorSpaces は今後、
    // DefaultCMYK が較正しない DeviceCMYK を到達可能なパスが塗ることの意味
    if Check.IsCompliant then
    begin
      Output.SaveToFile(OutFile);
      Result := True;
    end;
  finally
    Output.Free;
    Pdf.Free;
  end;
end;

sRGB インテントを経ても残る pvaiMixedDeviceColorSpaces は、ウォークが到達可能なパスの上の DeviceCMYK を報告しているということです。その文書は、PDFium Component が PDF/A-2b として受け入れるために、それを塗るページの上の DefaultCMYK マッピングか、CMYK の出力プロファイルを必要とします