PDFlibPas 从 Delphi 生成 PDF/X 印刷文件只需要一个调用:TPDFlib.SetPDFXMode,用来选择 PDF/X-1a:2003、PDF/X-3:2003 或 PDF/X-4。这个调用会写入 XMP pdfxid 标识、添加 GTS_PDFX 输出意图、声明 /Trapped /False、钉住 PDF 版本,并且从那一刻起拒绝所选子标准禁止的绘图调用。保存后的文件再用 CheckFileCompliance 配 test 3 审计
这是简短版回答。长版本之所以值得讲,是因为印刷厂拒收文件不是因为文件丑,而是在预检环节拒收,通常在周五,通常为了一个生成代码本可以抓住的问题:X-1a 任务里夹了一个 RGB logo,字体只引用没嵌入,页面没有成品尺寸。PDF/X 的存在就是让这类失败变成结构问题而不是视觉问题,而库这边的工作,是让你从一开始就产不出这种文件
Delphi 应用应该瞄准哪个 PDF/X 子标准
接收方工作流没有特别要求就选 PDF/X-4,有要求就选 PDF/X-1a。三个子标准的差异恰好集中在三条轴上:颜色、透明度和基础 PDF 版本,而每条轴都对应 PDFlibPas 内部的一道闸门:
- PDF/X-1a:2003(ISO 15930-4):PDF 1.4 语法,只允许 CMYK、灰度和专色,不允许基于 ICC 的颜色,不允许透明度。文件已经分色成印刷色,印刷厂完全不用做颜色转换
- PDF/X-3:2003(ISO 15930-6):仍是 PDF 1.4,仍不允许透明度,但允许色彩管理空间,而且当输出意图本身描述的是 RGB 印刷条件时,设备 RGB 也能被接受
- PDF/X-4(ISO 15930-7):PDF 1.6 语法,支持活透明度和可选内容(图层)。现代 RIP 就是为这个子标准造的,因为老 X-1a 的许多痛苦正来自输出前那一道透明度扁平化
广告交付和老式印刷工作流里 X-1a 仍然随处可见,他们要的是一份不用动脑就能成像的文件。只要印刷厂接受,X-4 就是更好的工程选择:透明度原样保留,颜色决策发生在 RIP 上、用真实的印刷机特性文件,而不是在你的代码里。模式号分别是 1、2 和 3,GetInformation(203) 会回报当前生效的模式。PDFlibPas 还为 PDF 2.0 上的 PDF/X-6 家族定义了更高的模式号,它们遵循不同的版本与输出意图规则,不在本文范围内
SetPDFXMode 会往文件里写什么
TPDFlib.SetPDFXMode 负责处理 PDF/X 阅读器最先检查的标识和样板内容,应用代码只需要提供真实的印刷机特性文件和正文内容。具体来说,一次成功的调用做四件事:
- 把版本字符串以
pdfxid:GTS_PDFXVersion写进 XMP(PDF/X-1a:2003、PDF/X-3:2003或PDF/X-4)。2003 版通过这个字符串就能完整标识子标准,所以不再写单独的一致性属性 - 文档没有输出意图时添加一个,并把子类型设为
/S /GTS_PDFX,也就是 PDF/X 阅读器寻找的那个子类型(ISO 32000-1 §14.11.5 定义了输出意图字典) - 在文档信息字典里设置
/Trapped /False,因为本库从不做陷印,而 PDF/X 要求陷印状态必须声明出来,不能留成未知 - 钉死版本基线:X-1a 和 X-3 是 1.4,X-4 是 1.6
保存时还有两处自动修补。每个没有 TrimBox 的页面会得到一个等于其 MediaBox 的 TrimBox,因为 PDF/X 要求每页声明成品尺寸(ISO 32000-1 §14.11.2 讲的是页面边界);注释也会被加上标准要求的打印标志。TrimBox 兜底是安全网,不是排版决策。出血满版的活儿需要一个位于更大介质框内的真实成品框,PDFlibPas 中 TrimBox、BleedBox 与 CropBox 默认值指南讲了如何设置这些框,以及继承机制会怎样坑你
uses
PDFlibrary;
procedure BuildX1aAd(const OutFile, PressProfile: string);
var
Pdf: TPDFlib;
begin
Pdf := TPDFlib.Create;
try
if Pdf.SetPDFXMode(1) <> 1 then // 1 = PDF/X-1a:2003
raise Exception.CreateFmt('PDF/X mode refused (error %d)',
[Pdf.LastErrorCode]);
// 用真实的印刷条件替换默认特性文件
if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
raise Exception.Create('Could not load the CMYK output profile');
// Embed = 1 嵌入完整字体程序;PDF/X 不接受 0
if Pdf.AddTrueTypeFont('Arial {1252}', 1) = 0 then
raise Exception.Create('Font could not be loaded for embedding');
Pdf.SetTextSize(24);
Pdf.SetFillColorCMYK(0, 1, 1, 0); // 四色红,不用 RGB
Pdf.DrawText(72, 720, 'Spring catalog, page 1');
Pdf.SaveToFile(OutFile);
finally
Pdf.Free;
end;
end;
这段示意代码里的顺序是刻意的。LoadOutputIntentProfile 替换的是一个已存在输出意图的特性文件,所以它必须等 SetPDFXMode 先创建出一个意图再调用。这个调用也没法凭空变出一个意图:文档没有输出意图时它直接返回 0
为什么默认输出意图必须替换掉
SetPDFXMode 为模式 1 到 3 添加的输出意图声明的是 DeviceCMYK,但它背后的 ICC 字节其实是库自带 sRGB 特性文件把颜色分量数改写成 4 的产物。这个默认值是为向后兼容而存在的,足以让文件在结构上成为 PDF/X,可嵌入的特性文件并不描述任何一台印刷机。检查特性文件头的预检工具会看到一个贴着 CMYK 标签的 RGB 特性文件,而靠这个意图打样的印刷厂拿到的就是胡话
所以做真正的印刷活儿时,请把 LoadOutputIntentProfile(FileName, 'DeviceCMYK') 当成必选项,传入印厂为纸张和印刷机指定的 ICC 特性文件。TPDFlib.LoadOutputIntentProfile 会替换 DestOutputProfile 流并更新 /N,意图字典的其余部分保持原样。特别要注意,默认意图的输出条件标识符和描述字符串仍是 sRGB 风格的值,如果你的印厂是按标识符而非嵌入特性文件匹配的,发货前先跟他们确认检查的是什么。这是 2003 年代默认值的真实缺口,不是你的代码写错了
陷阱一:你没选过的版本
新建的 PDFlibPas 文档从写入器默认的 PDF 1.5 起步,这高于 PDF/X-1a 和 PDF/X-3 要求的 1.4 基线。常见情形下 TPDFlib.SetPDFXMode 会静默解决这个问题:只要保存版本还没被锁定,它就把文档版本降到该子标准的基线然后继续。第一版实现选择的却是拒绝,导致新文档上每次 SetPDFXMode(1) 都返回 0,这是一条纸上正确、实践中却没用的规则的好例子
有一种情形拒绝仍然存在,而且拒绝得对。如果之前有代码用高于基线的版本调用了 LockSaveVersion,SetPDFXMode 会返回 0 并把 LastErrorCode 置为 601,因为调用方是刻意锁定版本的,库不会越权推翻。在共享的文档初始化代码里,某个辅助函数出于不相干的原因锁了版本,X-1a 任务恰恰就死在这里:
Pdf := TPDFlib.Create;
try
Pdf.LockSaveVersion('1.7'); // 由通用的“机构默认值”辅助函数设置
if Pdf.SetPDFXMode(1) = 0 then
// LastErrorCode = 601:1.7 已锁定,PDF/X-1a 需要 1.4
// 印刷任务请跳过锁版本。PDF/X-4 也躲不开:
// 它的 1.6 基线同样低于 1.7 锁定值,照样失败
LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
Pdf.Free;
end;
同一道闸门也解释了另一个限制。PDF/X 模式是文档创建期开关:SetPDFXMode 只对新建文档有效,对从文件加载的文档返回 0,因为库没法为自己没写过的内容担保。把既有 PDF 转成 PDF/X 是另一件事,需要检查和修复,不是一个开关能解决的
陷阱二:悄悄拒绝的闸门
PDF/X 模式一旦激活,PDFlibPas 会在你调用的当下就拒绝所选子标准禁止的每个内容调用,而不是等文件之后在预检里失败。被拒绝的范围包括:设备 RGB 填充与描边(X-1a 全部拒绝;X-3 和 X-4 除非输出意图是 RGB)、X-1a 里基于 ICC 的颜色、X-1a 和 X-3 里的透明度与 Blend mode、X-1a 和 X-3 里的可选内容、JavaScript 动作、嵌入式文件附件、LZW 图像滤镜、无法嵌入的 Standard 14 字体,以及公钥加密。通过 Encrypt 做的密码加密不在创建期设闸,但验证器会对任何加密文件亮红牌,所以印刷任务请保持不加密
这些拒绝不共享同一个错误码,这就是陷阱所在。SetFillColor、SetLineColor 里的颜色空间闸门、基于 ICC 的颜色调用和 EncryptPublicKey 会把 LastErrorCode 设为 601,也就是 PDF/A 闸门也在用的那条库一致性错误码。而透明度、JavaScript、附件、LZW 和 AddStandardFont 这几道闸门只返回 0,LastErrorCode 原样不动,也就是说它可能还留着更早某次调用的值。一批绘图调用之后只检查 LastErrorCode 的代码会漏掉一半拒绝,所以每个返回值都要查:
procedure Require(Ok: Integer; const What: string);
begin
if Ok = 0 then
raise EPrintConformance.CreateFmt('%s refused under PDF/X', [What]);
end;
Require(Pdf.SetFillColorCMYK(0, 0, 0, 1), 'CMYK fill');
Require(Pdf.SetTransparency(40), 'transparency'); // X-1a / X-3 下返回 0
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
'app.alert(1)', 0), 'JavaScript link'); // 所有子标准下都返回 0
字体值得单独警告一次。AddStandardFont 在 PDF/X 下返回 0,因为库不带 Standard 14 轮廓,自然无法嵌入。AddTrueTypeFont 不一样:PDF/A 模式下它会替你把 Embed = 0 升级成完整嵌入,PDF/X 模式下不会,于是被引用的系统字体照样进了文件,只有验证器能逮住它。请自己传 1(完整嵌入)或 2(子集嵌入),或者在初始化时调一次 SetEmbedAllFonts(1),让漏网的 0 无处可钻
文件送去印刷厂之前如何验证 PDF/X
对保存后的文件运行 CheckFileCompliance,ComplianceTest = 3。TPDFlib.CheckFileCompliance 在没有记录任何发现时返回 0,否则返回一个字符串列表句柄,条目都是带 PDFX- 前缀的行。PDF/X 审计与PDFlibPas 的 PDF/A 和 PDF/UA 预检共用同一个流式读取器,它检查的正是印刷预检最先查的那批规则:
- 文件未加密,XMP 带有已知的
GTS_PDFXVersion,PDF 版本不超过该子标准的基线 - 恰好存在一个输出意图,子类型为
GTS_PDFX,且带有目标特性文件和输出条件标识符 - 信息字典声明了
/Trapped - 每页都有位于 MediaBox 之内的 TrimBox;BleedBox 若存在,则必须包住 TrimBox
- 页面上用到的每个字体都有嵌入的程序,Type0 字体通过其 descendant font 检查
- 没有违禁注释类型(RichMedia、Screen、3D、Movie、Sound、FileAttachment),没有 JavaScript 打开动作或文档级 JavaScript,没有嵌入式文件,对象图里任何地方都没有 LZWDecode 滤镜
function PreflightPDFX(Pdf: TPDFlib; const FileName: string;
Report: TStrings): Boolean;
var
ListId, I: Integer;
begin
ListId := Pdf.CheckFileCompliance(FileName, '', 3, 0); // 3 = PDF/X
Result := ListId = 0;
if Result then
Exit;
try
for I := 1 to Pdf.GetStringListCount(ListId) do // 条目下标从 1 开始
Report.Add(Pdf.GetStringListItem(ListId, I));
finally
Pdf.ReleaseStringList(ListId);
end;
end;
要清楚这个通过意味着什么。PDFlibPas 验证器审计的是上面列出的结构规则,而它们抓住的正是生成器的失误。它不是颜色精确的预检:不检查总墨量、图像分辨率,也不检查嵌入特性文件是否匹配你家印刷机的实际条件。把它当作构建或导出管线里的闸门,印刷机专属的检查留给印厂的预检特性文件——反正他们横竖都要跑一遍
FAQ
PDFlibPas 支持 PDF/X-4 透明度吗
支持。在 SetPDFXMode(3) 下,PDFlibPas 允许透明度、Blend mode 和可选内容组,并写出 PDF 1.6 文件。同样的调用在 PDF/X-1a 和 PDF/X-3 下返回 0
PDFlibPas 能把既有 PDF 转成 PDF/X 吗
不能。SetPDFXMode 只适用于当前 TPDFlib 会话中创建的文档,对加载的文件返回 0。但你仍然可以用 CheckFileCompliance 配 test 3 审计任何既有 PDF
被拒绝的 PDF/X 调用返回什么错误码
颜色空间和公钥加密类拒绝会把 LastErrorCode 设为 601。其他闸门——比如透明度、JavaScript 和 Standard 14 字体——只返回 0,所以函数返回值永远要查
PDF/X 输出、PDF/X 验证器以及其余印刷与归档工具集都是 PDFlibPas 的一部分——losLab 面向 Delphi 的原生 Object Pascal PDF 库。版本、平台和完整 API 参考见 PDFlibPas Delphi PDF Library 产品页