这需要修改示例PDFA_def.ps 脚本。我的回答是基于 Ghostscript 9.27 和 Debian 10 打包的版本,在 /usr/share/ghostscript/9.27/lib/PDFA_def.ps 或 Ghostscript repository 中找到。您可以使用更新版本,它的功能应该类似。我假设您已成功编辑文件以指向颜色配置文件的正确路径(我将使用 ArgyllCMS's sRGB module)
按照现有的 pdfmark 嵌入教程,我们可以相当容易地嵌入任何文件,例如 this email 或 this related question(如您所见)。请注意,命名空间 push/pop 并不重要,所以我将这个 postscript 代码复制到我的 PDFA_def.ps 文件的末尾:
[/_objdef {fstream} /type /stream /OBJ pdfmark % create an object as a stream
[{fstream} << /Type /EmbeddedFile >> /PUT pdfmark % sets the stream's type
% use one of these two options to define the contents of the stream
%[{fstream} (Alternatively, inline text) /PUT pdfmark
[{fstream} MyFileToEmbed (r) file /PUT pdfmark
% define the embedded file fia the /EMBED pdfmark option
[/Name (my.txt)
/FS <<
/Type /Filespec
/F (my.txt)
/EF <<
/F {fstream}
>>
>> /EMBED pdfmark
[{fstream} /CLOSE pdfmark % close streams when done
请注意,我使用了从MyFileToEmbed 读取的文件,该文件必须在gs 命令行上定义为-sMyFileToEmbed=/path/to/my/file.txt。如果您只想要纯文本,请取消注释第一个选项(并删除引用 MyFileToEmbed 的第二行)。间距通常也不重要。
这大概就是您所在的位置,它嵌入了文件,但不是您所说的有效 PDF/A-3。让我们依次看看每个错误:
嵌入文件的文件规范字典应包含 F 和 UF 键
这是最容易处理的,只需将/UF 键添加到/FS 字典即可。
首先,不过是对 PostScript 语法的简要介绍(至少在此答案中使用),因为它相当不寻常,甚至 StackOverflow 也缺少语法突出显示(我在这篇文章中使用了 LaTeX 荧光笔)。
PostScript (PS) 是一种基于堆栈的语言,(粗略地说)从右到左执行。 C 风格的语言将表达为file("r", MyFileToEmbed) 是MyFileToEmbed (r) file。注释以% 开头,字符串不被引用而是在括号中。 /foo 是一个名称,大致相当于 Ruby 中的 :foo 或 lisp 中的 'foo。 << ... >> 是一个字典,就像现代脚本语言中的 { ... } 一样。在这里,{foo} 是一个名为 object1 的 pdfmark,我们将使用的 pdfmark 行以标记开头:[(有关详细信息,请参阅PostScript 或 pdfmark spec)。
考虑到这些知识,我们可以更新/UF (spec page 102) 的代码:
/F (my.txt)
/UF (my.txt) % Add this. Unicode File, defined the spec at Table 44, 7.11.3
/EF <<
运行 veraPDF 显示我们现在已减少到 3 个错误,尽管我们正在复制 (my.txt)。让我们为它创建一个变量,或者通过添加一个 gs 参数-sMyFileName=my.txt,或者在使用之前的任何地方:
/MyFileName (my.txt) def
% snip...
/F MyFileName % update to the varible
/UF MyFileName
% snip...
现在让我们解决下一个错误:
为了能够识别文件规范字典和引用它的内容之间的关系,定义了一个新的(必需的)键,并且它的存在(在字典中)是必需的。
这个也很简单,/FS 需要一个/AFRelationship 键(I can't find the spec, but here's the notes, page 6)。该值可以是:
Source、Data、Alternative、Supplement、EncryptedPayload、FormData、Schema 或未指定。如果这些条目都不合适,则可以使用自定义值。
我将在这里使用 Supplement,但选择最适合您嵌入的内容:
/F MyFileName
/UF MyFileName
/AFRelationship /Supplement % These lines can be in any order, but the key must be before the value
/EF <<
检查 veraPDF,我们发现有 2 个错误。好的!下一个要解决的错误:
嵌入文件或文件子集的 MIME 类型应使用文件规范字典的 Subtype 键指定。如果 MIME 类型未知,则应使用“application/octet-stream”。
这有点棘手。 MIME 类型中有一个/,因为空格在 PS 中并不重要,所以/Type/Filespec 与/Type /Filespec 一样有效。因此,由于 mime 类型必须是名称,而不是字符串,我们不能简单地说 /text/plain。相反,我们需要使用 cvn 函数(规范,第 402 页),它将字符串转换为名称(如 lisps 中的 (quote) 或 Ruby 中的 to_sym)。请注意,此参数位于流对象的字典本身 (spec, page 104, table 45):
[{fstream} << /Type /EmbeddedFile
/Subtype (text/plain) cvn % Our new addition (can be on the same line as above)
>> /PUT pdfmark
% equivalent to the Ruby-syntax: { :Type => :EmbeddedFile, :Subtype => cvn("text/plain") }
到我们最后的 A-3 验证错误,这个有点棘手:
为关联文件提供的附加信息以及关联文件的使用要求表明嵌入文件与 PDF 文档或与其关联的 PDF 文档部分之间的关系。
我们需要从定义中拆分 EMBED,并更新文档 /Catalog 字典以使用 /AF 键 (notes, page 6) 也指向定义。创建一个新的 pdfmark dict 对象,并重构代码:
[/_objdef {myfileinfo} /type /dict /OBJ pdfmark % create new object
% assign the new pdfmark object
[{myfileinfo} <<
/Type /Filespec
/F MyFileName
/UF MyFileName
/AFRelationship /Supplement
/EF <<
/F {fstream}
>>
>> /PUT pdfmark % refactored out of the following line
[/Name MyFileName /FS {myfileinfo} /EMBED pdfmark % updated embed line
% This line was moved from the end of the original PDFA_defs.ps to after our attachment code
[{Catalog} <</OutputIntents [ {OutputIntent_PDFA} ] /AF [{myfileinfo}] >> /PUT pdfmark
现在通过 Ghostscript 运行,我们得到了一个 veraPDF 接受的有效 PDF/A-3B,带有任意文档附件!
为了完整起见,这里是整个修改后的PDFA_def.ps 文件,以及我用来运行它的脚本。注意我已经用变量替换了大多数常量。对于多个附件,您可以添加我们添加的代码的更多副本以及更多 {fstream} 和 {myfileinfo} 对象(显然具有不同的名称)。
我们修改后的 PDFA_def_attach.ps 的最终完整列表:
%!
% This is a modified version of the Ghostscript 9.27 PDFA_def.ps file with
% that creates a PDF/A-3 file with an embedded attachment
% Define entries in the document Info dictionary :
/ICCProfile (/usr/share/color/argyll/ref/sRGB.icm) % Customize
def
[ /Title (My PDF/A-3 with an embedded attachment) /DOCINFO pdfmark % Customize
% Define an ICC profile :
[/_objdef {icc_PDFA} /type /stream /OBJ pdfmark
[{icc_PDFA}
<<
/N currentpagedevice /ProcessColorModel known {
currentpagedevice /ProcessColorModel get dup /DeviceGray eq
{pop 1} {
/DeviceRGB eq
{3}{4} ifelse
} ifelse
} {
(ERROR, unable to determine ProcessColorModel) == flush
} ifelse
>> /PUT pdfmark
[{icc_PDFA} ICCProfile (r) file /PUT pdfmark
% Define the output intent dictionary :
[/_objdef {OutputIntent_PDFA} /type /dict /OBJ pdfmark
[{OutputIntent_PDFA} <<
/Type /OutputIntent % Must be so (the standard requires).
/S /GTS_PDFA1 % Must be so (the standard requires).
/DestOutputProfile {icc_PDFA} % Must be so (see above).
/OutputConditionIdentifier (sRGB) % Customize
>> /PUT pdfmark
% New code starts here.
% If you want to not use Ghostscript command line arguments,
% then uncomment these variable definitions
%/MyFileName (my.txt) def % alternative to -sMyFileName=my.txt
%/MyFileToEmbed (/path/to/my/file.txt) def % alternative to -sMyFileToEmbed=/path/to/my/file.txt
%/MyMimeType (text/plain) def % alternative to -sMyMimeType=text/plain
% Define the embedded file objects
[/_objdef {myfileinfo} /type /dict /OBJ pdfmark
[/_objdef {fstream} /type /stream /OBJ pdfmark
% Load the file to embed
[{fstream} MyFileToEmbed (r) file /PUT pdfmark
% assign the stream information
[{fstream} <<
/Type /EmbeddedFile
/Subtype MyMimeType cvn
% /Params << % Optional, see Table 46, page 104 for options
% /Size 1234 % or use a -dMyVarName flag. -d defines numbers, -s Strings
% /ModDate (D:20211216) % see section 7.9.4, page 87 for full date format
% % etc...
% >>
>> /PUT pdfmark
% assign the file information
[{myfileinfo} <<
/Type /Filespec
% /Desc (My Optional Description) % optional, see page 103, table 44
/F MyFileName
/UF MyFileName
/AFRelationship /Supplement
/EF <<
/F {fstream}
>>
>> /PUT pdfmark
% Embed the stream
[/Name MyFileName /FS {myfileinfo} /EMBED pdfmark
[{fstream} /CLOSE pdfmark
% Updated last line from the original PDFA_defs.ps
[{Catalog} <</OutputIntents [ {OutputIntent_PDFA} ] /AF [{myfileinfo}] >> /PUT pdfmark
命令行(使用 GS 9.27):
gs -dPDFA=3 -sColorConversionStrategy=RGB -sDEVICE=pdfwrite -dPDFACompatibilityPolicy=1 -dPDFSETTINGS=/default -dAutoRotatePages=/All -sMyFileToEmbed=/tmp/test.png -sMyMimeType=image/png -sMyFileName=the_name_you_see_in_reader.png -dNOPAUSE -dBATCH -dQUIET -o output-a3.pdf PDFA_def_attach.ps input.pdf
(请注意,在较新的 Ghostscript 版本中,您需要添加 --permit-file-read=/tmp/test.png)
1 从技术上讲,正如 KenS 在 cmets 中有用地指出的那样,它实际上是以非标准方式使用的 PostScript 过程,但由于此答案不会使用 pdfmark 命名对象之外的过程(不包括PDFA_def.ps 的已编写的部分),我在这里掩盖了该实现细节,以支持 pdfmark 参考手册中使用的名称。