把 PDFXCompliance 设为 'X-1a' 后,HotPDF 在任务收尾时校验内容颜色:EndDoc 会调用 THotPDF.EnsurePDFX1aContentColorSpaces,拒绝任何页面任何位置上的 DeviceRGB、CalRGB 或 ICCBased RGB 内容,并抛出点名问题资源的异常。文件绝不会以一份会被印厂退回的 PDF/X-1a 文档形式写出去
最后这半句才是重点。一个混进 RGB logo 的 PDF/X-1a 文件在屏幕上毫无异样,随手检查也能过,然后在开机档期前两天在印厂的 preflight 上栽掉。在 Delphi 构建里就抓住它,错误信息还告诉你哪一页哪张图,比从恼火的印前操作员那里得知便宜太多了
PDF/X-1a 为什么拒绝 RGB 内容?
PDF/X-1a 拒绝 RGB,是因为这个格式承诺每个颜色都已经针对某个特定 CMYK 印刷条件定稿。ISO 15930-1:2001 §6.2.3 把内容颜色限制为 DeviceCMYK、DeviceGray 和专色着色剂,即 alternate 为 CMYK 或 Gray 的 Separation 与 DeviceN 空间。文件里内嵌的 GTS_PDFX OutputIntent 点名那个印刷条件,比如 FOGRA39 铜版胶印,接收方工作流有权把这些数值不做任何颜色转换直接送去制版
RGB 破坏的正是这份契约。RGB 值描述的是相对某个显示器或工作空间的颜色,不是油墨配方,下游总得有人决定怎么分色,而 X-1a 的存在恰恰就是为了把这一决定权从接收方手里拿走。ISO 32000-1 §8.6 把 PDF 颜色空间分成 device、CIE-based 和 special 三族,RGB 成员出现在不止一族里:DeviceRGB 是 device 空间,CalRGB 和三分量 ICCBased 是 CIE-based,Indexed 或 Separation 空间底下也可能藏着 RGB 的 base 或 alternate。只盯着 rg 操作符的检查器会漏掉其中大半。后续的 PDF/X-1a:2003 版(ISO 15930-4)保留同样的颜色规则;注意 HotPDF 的 'X-1a' 值往 XMP 包里写的是 PDF/X-1a:2001 标识
如果你的内容本来就是 RGB 而且理应如此,X-1a 就不是正确的目标。HotPDF 把这项检查限定在 'X-1a' 上,允许 ICC 和校准颜色的 'X-3' 与 'X-4' 不受影响
EnsurePDFX1aContentColorSpaces 到底检查什么
EnsurePDFX1aContentColorSpaces 把一个绘制期标志与对每页资源的递归遍历结合起来,在 PDF/X 门禁里最后运行。EndDoc 先落实 PDF/X 的一般要求:Title 必须设置,Trapped 必须是 'True'、'False' 或 'Unknown',必须存在 GTS_PDFX OutputIntent(EnsurePDFXOutputIntent),每页都需要 TrimBox 或 ArtBox(EnsurePDFXPageBoxes)。这些全过了之后才轮到颜色检查
第一条路径是文档级标志。HotPDF 的 RGB 颜色设置器、位图图像嵌入和注释颜色条目在绘制时就记录 DeviceRGB 的使用,覆盖了那些从不出现在资源字典里的内联 rg 与 RG 操作符。第二条路径遍历每页的 /Resources 并检查:
/ColorSpace中的命名颜色空间,包括作为 DeviceRGB 同义词的内联图像缩写RGB- 每个 Image XObject 的
/ColorSpace - Form XObject 与平铺 Pattern,通过递归进入它们自己的嵌套
/Resources - Shading Pattern 与页面
/Shading字典中的条目,经由 shading 的/ColorSpace - Indexed 的 base 空间、Separation 与 DeviceN 的 alternate,以及 Pattern 的 base 空间,一路追到嵌套深度 8
被拒绝的有三种:DeviceRGB、CalRGB,以及 profile 流声明 N = 3 的 ICCBased 空间。Form XObject 与 pattern 之间共享的资源字典只访问一次,互相引用的一组 form 不会把遍历带进死循环。该方法在 THotPDF 上是公开的,但正常使用中你永远不必自己调它,EndDoc 会替你做
在 Delphi 里画出能过检查的 X-1a 页面
一份合规的 HotPDF X-1a 文档需要 CMYK OutputIntent、每页 TrimBox,以及只通过 CMYK、Gray 和专色 API 设置的颜色。下面的骨架用的是 FOGRA39 profile;AddPDFXOutputIntent 的参数是输出条件标识符、信息字符串、ICC profile 流、分量数和 alternate 空间
var
Pdf: THotPDF;
Profile: TFileStream;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'brochure-x1a.pdf';
Pdf.Title := 'Spring brochure, press edition';
Pdf.Trapped := 'False';
Pdf.PDFXCompliance := 'X-1a';
Pdf.BeginDoc;
Profile := TFileStream.Create('CoatedFOGRA39.icc', fmOpenRead or fmShareDenyWrite);
try
Pdf.AddPDFXOutputIntent('FOGRA39 (ISO 12647-2:2004)',
'Coated FOGRA39 offset', Profile, 4, 'DeviceCMYK');
finally
Profile.Free;
end;
Pdf.CurrentPage.SetTrimBox(0, 0, 612, 792); // 每一页都必须有
Pdf.CurrentPage.SetCMYKFillColor(0, 0.5, 0.8, 0.1);
Pdf.CurrentPage.Rectangle(72, 600, 468, 120);
Pdf.CurrentPage.Fill;
Pdf.CurrentPage.SetGrayFillColor(0.2); // DeviceGray 文字
Pdf.CurrentPage.SetFont('Arial', [], 14);
Pdf.CurrentPage.TextOut(72, 560, 0, 'Spring catalog, press edition');
Pdf.EndDoc; // 颜色检查在这里运行
finally
Pdf.Free;
end;
end;
在这个语境下,所有基于 TColor 的调用都是陷阱。SetRGBFillColor、SetRGBStrokeColor 和 SetRGBColor 都吃 GDI 颜色并输出 DeviceRGB,多年前为屏幕报告写的助手例程,一旦被复用就会污染印刷任务。请用分量在 0 到 1 范围内的 SetCMYKFillColor、SetCMYKStrokeColor 或 SetCMYKColor,或对应的 Gray 版本
专色与 CMYK 渐变
X-1a 允许专色着色剂,只要其 alternate 是 CMYK 或 Gray。RegisterSeparation 构建 Separation 空间并返回其资源名;RegisterAxialGradient 按传入分量数选择 shading 颜色空间,四个分量即 DeviceCMYK
var
SpotCS, Ramp: AnsiString;
begin
// 专色油墨,alternate 用近似 CMYK 组合
SpotCS := Pdf.RegisterSeparation('Corporate Red', 'DeviceCMYK',
[0, 1, 0.8, 0.05]);
Pdf.CurrentPage.SetFillColorSpace(SpotCS);
Pdf.CurrentPage.SetFillColor([1.0]); // 100% 色调
Pdf.CurrentPage.Rectangle(72, 420, 200, 80);
Pdf.CurrentPage.Fill;
// 每个停点 4 个分量:shading 是 DeviceCMYK,不是 DeviceRGB
Ramp := Pdf.RegisterAxialGradient(72, 300, 540, 300,
[0, 0, 0, 0], [1, 0.6, 0, 0.2]);
Pdf.CurrentPage.SetFillPattern(Ramp);
Pdf.CurrentPage.Rectangle(72, 260, 468, 80);
Pdf.CurrentPage.Fill;
end;
给同一个渐变调用传三个分量,得到的就是 DeviceRGB shading,资源遍历会以 shading pattern 的名字拒绝它。注册 Separation 时 alternate 用 'DeviceRGB' 也一样:专色名看着无害,遍历却顺着 alternate 找到底下的 RGB。当阅读器或 RIP 回退到 alternate 时这类专色油墨如何合成,见 在 Delphi 中渲染 Separation 与 DeviceN 专色一文
栅格图像需要同样的纪律。HotPDF 把 Flate 压缩位图钉死在 DeviceRGB 上,而用 icJpeg 嵌入的 JPEG 保留自己的分量数,所以四分量 CMYK JPEG 或灰度 JPEG 没问题,普通 RGB JPEG 不行。让图像在进 PDF 之前,就在你的成像管线里转换到印刷 profile
为什么 /DefaultRGB 救不了 PDF/X-1a 文件?
/DefaultRGB 帮不上 X-1a,因为规则关心的是印刷机收到什么,而不是 RGB 有没有被特征化。PDF/A 那边,HotPDF 允许 RegisterDefaultColorSpace('DefaultRGB', ...) 吸收 DeviceRGB 的使用:一旦 Default 颜色空间把 DeviceRGB 映射到 ICC profile,内容就是设备无关的,PDF/A 一致性检查接受它。经历过那条工作流的开发者往往会在这里试同样的招
EnsurePDFX1aContentColorSpaces 刻意无视那个映射。被特征化的 RGB 仍是 RGB,X-1a 的 OutputIntent 是 CMYK 印刷条件,总得有人去转换。两个档案与印刷 profile 之间若只记一条区别,就记这条:PDF/A 问的是颜色是否无歧义,PDF/X-1a 问的是它是否已经是油墨
绘制顺序,以及不作数的页面初始化
因为检查在 EndDoc 时对已写好的内容运行,你设置各种东西的顺序无关紧要。在赋 PDFXCompliance 之前、或在注册 OutputIntent 之前画的 RGB,照样被抓住。这一点对在共享例程里攒页面、很晚才决定输出 profile 的代码很重要——有时那还是文档写了一半之后才由用户选的设置
Pdf.BeginDoc;
// ... OutputIntent 和 TrimBox 同前 ...
Pdf.CurrentPage.SetRGBFillColor(clRed); // 遗留助手,输出 DeviceRGB
Pdf.CurrentPage.Rectangle(72, 72, 200, 100);
Pdf.CurrentPage.Fill;
Pdf.PDFXCompliance := 'X-1a'; // 绘制之后才决定
Pdf.EndDoc; // 仍然抛出:DeviceRGB 已被记录
如果你要自己写类似的检查,绘制期标志有一个值得知道的坑。每个新页面都通过 SetRGBColor(0) 以黑色图形状态初始化,这会发出一对 0 0 0 rg 和 0 0 0 RG;如果放任这页面初始化样板去置 DeviceRGB 标志,每份文档都会显得被 RGB 污染,这项检查和 PDF/A 颜色一致性检查都会中招。HotPDF 在那次初始化前后保存并恢复标志,所以只有你真正画下的颜色才计入
EndDoc 抛异常时怎么找到 RGB 内容?
读异常消息开头的位置部分,它会告诉你是两条路径中的哪一条发现了问题。消息形如 PDFXCompliance 'X-1a': <where> uses <what>,后跟 ISO 15930-1 引用,以及转成 CMYK 或 Gray、或改用 X-3/X-4 的建议。校验器在第一个违例处抛普通 Exception,所以请按文本匹配,别按异常类匹配
document content uses DeviceRGB (rg/RG operators, RGB images or annotation colours recorded during drawing)意味着绘制期标志触发了。没有页码,因为标志是文档级的,且在遍历之前检查。去绘制代码里搜SetRGB*调用、Flate 压缩位图、RGB JPEG 和TColor注释颜色page 3 image XObject /Im2 uses DeviceRGB content或page 1 shading Pattern /P1 uses DeviceRGB content来自资源遍历,指向一个确切的资源键ColorSpace /...位置后面跟着uses an ICCBased RGB colour space (N=3),典型成因是用RegisterICCProfile注册了 sRGB profile 再经SetFillColorSpace选用——这条路径绘制期标志看不见- 嵌套位置会串成链,比如
page 2 Form XObject /Fm1 ColorSpace /CS1,让你能从页面顺着路径走到携带 RGB 空间的那个资源
try
Pdf.EndDoc;
except
on E: Exception do
begin
if Pos('X-1a', E.Message) > 0 then
LogPrepressFailure(Pdf.FileName, E.Message); // 你自己的日志器
raise;
end;
end;
因为只报告第一个违例,修一个有多个 RGB 来源的任务是迭代式的:修一处、重新构建、读下一条消息。批量任务值得按文件记录完整消息,并维护一张已知惯犯的短清单,比如某个 logo 位图或仍在收 TColor 的图表助手
RGB 门禁在你的 preflight 里的位置
EnsurePDFX1aContentColorSpaces 是一个跑在你构建内部的定向 RGB 门禁,它与完整的 PDF/X-1a preflight 搭配而不是取而代之。它覆盖 RGB 各家族:通过从每页 /Resources 出发的资源遍历,以及 HotPDF 颜色设置器为内联 rg 与 RG 操作符维护的绘制期标志;嵌套颜色空间追到深度 8 以防畸形循环,每个 EndDoc 报告第一个违例。Lab、CalGray 等其他颜色家族属于完整 preflight 的事,所以构建时把成品文件再过一遍外部校验器,才能拿到完整合规结论。合规输出的全貌见 用 HotPDF 输出 PDF/A、PDF/X 与 PDF/UA 指南,把校验器接进无人值守管线则见 在 Delphi 中自动化 PDF preflight 检查
HotPDF 是面向 Delphi 与 C++Builder 的原生 VCL PDF 组件,支持 PDF/X、PDF/A 与 PDF/UA 输出、CMYK 与专色 API,以及 ICC OutputIntent。详情、授权与试用下载见 HotPDF Delphi PDF component 产品页