技术文章

Delphi 里 PDF/A 的设备色判定:输出意图、Default 与混合空间

veraPDF 会在一份从不画 RGB 的文件上报出 ISO 19005-1 6.2.3.3,会把一个裸 re f 判成未校准,还会在两页内容完全相同时放行第 1 页、判挂第 2 页。PDF/A 按每页绘制的内容来裁这些规则,所以 Delphi 版 PDFium Component PDF/A 验证器在裁定颜色与透明之前,把每一页都追进 form、图案、字形和注解里

6.2.3.3 与 6.2.4.3 到底要求什么

ISO 19005-1 6.2.3.3 把三条独立规则塞进同一段:合规文件可以用 DeviceRGB 或 DeviceCMYK,但不得两者都用;只要用了任何未校准颜色空间,文件就必须带一份 PDF/A-1 输出意图;DeviceRGB 只能配 profile 为 RGB 的意图,DeviceCMYK 只能配 CMYK 的。DeviceGray 不在这份名单里,因为该条款接着就讲了两种意图下灰色的转换处理。意图本身是可选的:6.2.2 说文件可以声明一个,并且只认 OutputIntents 条目——它要带 /S /GTS_PDFA1,且 /DestOutputProfile 里装着有效的 ICC profile 流。所以,逼每个文件都交意图的检查器会拒掉那些从不绘制设备色的合规文档。Part 1 没有提 DefaultRGB 或 DefaultCMYK,但经它们重映射的颜色按 CIE 颜色空间渲染(ISO 32000-1 §8.6.5.6),满足 6.2.3.1 的设备无关要求;veraPDF 的 6.2.3.3 测试套件把它当已校准,PDFium Component 对该条款也是这么读的。混用禁令另算:一份既画 RGB 又画 CMYK 的 PDF/A-1 文件,即便 Default 条目把两者都校准了也照样挂。ISO 19005-2 6.2.4.3 撤掉这条禁令、每个空间按各自条件校准,如下表右列所示,所以 PDFium Component 对每个部分跑一次扫描,混用检查只施加于 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 输出意图
DeviceCMYK作用域内有 DefaultCMYK 或 CMYK 意图;不得与 DeviceRGB 并存作用域内有 DefaultCMYK,或一份 CMYK 输出意图
DeviceGray作用域内有 DefaultGray,或任意 PDF/A-1 输出意图作用域内有 DefaultGray,或任意 PDF/A 输出意图

怎样才算使用了一个颜色空间?

使用一个颜色空间,指的是在阅读器会渲染的路径上用它绘制。光声明不算数。veraPDF 套件用一个 6.2.3.3 通过文件把这一点讲得很清楚:一个具名 Form XObject 用 K 和 k 设 CMYK、待在页面的资源字典里、页面内容从不引用它,而文件的输出意图是 RGB。按文件全局搜设备色名的做法会给这份文件判挂,又会漏掉那种在 CMYK 意图下只画 DeviceRGB、两个名字并不冲突的文件。PDFium Component 改走绘制图,建立在它的字节级 PDF/A 预检扫描器之上。从每一页出发,遍历统计以下途径绘制的颜色:

  • 页面内容流,以及它们用 Do 调用的每个 Form XObject,嵌套 form 也算
  • 用 scn 或 SCN 选中的图案,按图案自己的 Resources 解读
  • Type 3 字体的 CharProcs——前提是该字体被 Tf 选中
  • 图像 XObject 与行内图像,按各自字典点名的颜色空间
  • 注解外观流,画在页面之上;widget 的 /AP /N 是状态字典时每个状态都算数,因为翻转字段画的就是 /AS 没选中的那个

可达性更微妙的一半,是内容从未点名的颜色。q 0 0 10 10 re f Q 里没有任何颜色算子,照样在画。图形状态的初始颜色空间是 DeviceGray(ISO 32000-1 表 52),所以这次填充是 DeviceGray 黑,只要没有 PDF/A 输出意图或 DefaultGray,在每个部分下都算未校准。于是扫描还要跟踪“是否已经有谁选过颜色”:rg、k、g 及其描边形式选中一个设备空间,cs 与 CS 选中它们点名的任何空间,在任何选择之前到达的填充、描边或文字算子记为 DeviceGray。/CS0 cs 1 0 0 /P0 scn 已经挑过空间,那个 scn 就不是灰

PDFium Component 在 Delphi 里裁定 PDF/A 设备色之前走查的可达绘制路径:form、图案、Type 3 字形、图像与注解外观流
声明颜色空间不算数——遍历统计每页实际绘制的内容,包括裸 re f 从初始状态接手的设备灰

行内图像把颜色空间和原始样本都带进内容流本身,扫描器两边都得小心读。/CS 可以是缩写(G、RGB、CMYK、I),也可以是资源名——比如解析到 [/Indexed /DeviceRGB 255 13 0 R] 的名字,其 base 要计入;而 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 声明的预期:pvaiMissingOutputIntent 伴随 pvaiMissingIccProfile,由第 2 页的 RGB 和两页的灰驱动。这个 form 没有贡献;一旦被调用,它的 CMYK 还会叠上 PDF/A-1 混用禁令

Default 颜色空间归绘制时用的那个资源字典

Default 映射在当前资源字典的 ColorSpace 子字典里查(ISO 32000-1 §8.6.5.6),所以它们的作用范围与资源本身一样局部;文档没有全局默认。图像 XObject 没有自己的 Resources,字典写着 /ColorSpace /DeviceCMYK 的图像,由画它的那个页面或 form 的 DefaultCMYK 校准——同一张图像从两处绘制,可以一处已校准、另一处未校准。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 输出意图时,pvaiMissingOutputIntent 与 pvaiMissingIccProfile 一起出现。pvaiMissingIccProfile 单独出现,意思是意图在但没带 profile,6.2.2 不认这种 PDF/A 意图。pvaiMixedDeviceColorSpaces 同时覆盖 PDF/A-1 混用禁令和分量数不匹配:四分量意图下的未校准 DeviceRGB,或三分量意图下的 DeviceCMYK。这个集合报的是规则而不是页面,所以在长文档或合并文档里,要对比哪些页声明了 Default 条目、哪些没声明

Delphi 里同一张 DeviceCMYK 图像从两个资源字典绘制:一个页面由 DefaultCMYK 校准、另一个未校准,PDFium Component 如实报告
default 颜色空间归绘制时用的资源字典,同一张图像可以页面画它时已校准、form 画它时未校准

透明页什么时候才需要自己的混合空间?

只有文档没有 PDF/A 输出意图时,透明页才需要自己的混合颜色空间。ISO 19005-2 6.2.10(PDF/A-4 里是 6.2.9)把输出意图定为 ISO 32000-1 §11.3.4 意义上的默认混合颜色空间;没有意图时,每一页含透明的页面都必须带一个 Group,其属性字典(ISO 32000-1 表 147)含 CS。这个 CS 同样受 6.2.4 约束,所以 sRGB 意图下 /DeviceCMYK 的页面组按缺失上报,两种情况 PDFium Component 都抛 pvaiBlendSpaceUndefined;CalRGB 或 ICCBased 的组空间没有这种依赖。PDF/A-1 摸不到这个问题,因为 ISO 19005-1 6.4 把透明整个排除在外

难在 contains transparency 这个说法。ISO 19005-2 附录 A 是规范性的,从页面往外测;按文件全局数 /SMask 键或 /ca 值,回答的是另一个问题。字节搜索还会撞上透明语法之外的同名构造:/S /Trans 这个被禁止的翻页动作是每个 /S /Transparency 组的前缀,/Screen 既是被禁止的注解子类型又是标准混合模式,名字不配上键就没有意义。按附录 A,从页面可达以下任意一项时,PDFium Component 判该页透明:

  • 内容用 gs 选中的 ExtGState,带软蒙版字典、ca 或 CA 低于 1、或 Normal 之外的混合模式;只声明从未选中的不算
  • 用 Do 调用的 Form XObject,其 /Group 带 /S /Transparency,或其自身内容命中这些条件之一;平铺图案按 form 同样判定
  • 带 SMask 流或 SMaskInData 值大于 0 的图像 XObject
  • 页面用它显示文字的 Type 3 字体的字形描述,按 form 同样判定
  • 注解外观流——每个 widget 状态都在内——以及注解字典本身上的混合模式

组还会反过来影响设备色规则,语料库在这里纠正过我们。veraPDF 的 6.2.4.3 套件里,一些不带任何输出意图、绘制设备 RGB、预期通过的 PDF/A-4 文件之所以通过,是因为每页的 /Group 点名了一个已校准的 CS。我们原以为只有隔离(isolated)的组能校准——非隔离组要合成进背景——这对页面组是错的:表 147 指出,直接施加到输出介质上的页面无论 I 如何都视同隔离,它的 CS 照算。在 Form XObject 或注解外观上,这个标志还真起决定作用;套件里有一对仅差 /I true 的通过与挂掉文件。DeviceGray 不从组拿校准,因为 6.2.4.3 要的是 DefaultGray 或意图,而 CS 是设备空间的组自己还需要校准。于是 PDFium Component 让页面组无视 /I 一律校准,让 form 或外观组只在隔离时校准,把灰色挡在组校准之外,同时仍然检查组点名的设备空间

Delphi 的 PDF/A 判定矩阵:透明页何时需要自己的混合 CS,以及 PDFium Component 如何裁定页面组、form 组与外观组的校准
输出意图就是默认混合空间;页面组无论隔离标志如何都校准设备色,form 组则需要 /I true

一份 OutputIntent,胜过每页一个组

如果归档由你自己的代码来写,先放一份文档级 PDF/A 输出意图。一个目录条目处处校准 DeviceGray,在每一页覆盖匹配的设备空间,又是每个透明页的默认混合空间。页面组和 Default 条目各只作用于一个资源字典,后来合并进来的、没带它们的页面正是会挂的那页。PDF 2.0 把 OutputIntents 加到了页对象上(ISO 32000-2 §7.7.3.3 与 §14.11.5);目录里没有时,PDFium Component 对 PDF/A-4 接受这种页面级意图,而建立在 PDF 1.4 到 1.7 上的 Part 1 到 3 只看目录。RGB 内容方面,SaveAsPdfAToStream 会加一份建立在PDF/A 写出器的内嵌 sRGB ICC profile 上的目录意图,验证输出就能看出内容是否真的只有 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 profile
    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;这份文档需要绘制它的页面带 DefaultCMYK 映射,或一份 CMYK 输出 profile,PDFium Component 才会把它当 PDF/A-2b 接受