【问题标题】:Using Ghostscript to convert a PDF with an embedded file to PDF/A-3使用 Ghostscript 将带有嵌入文件的 PDF 转换为 PDF/A-3
【发布时间】:2022-01-19 08:19:15
【问题描述】:

我可以设法使用 Ghostscript 的 PDFA_def.ps 文件从普通 PDF 创建 PDF/A-3,但 similar to this answer for per-page embedded files,任何非 PDF 嵌入附件都会被剥离。 我找到了一种在this SO post 中嵌入文件的方法,但它生成的 PDF 无法通过 PDF/A-3 验证。 veraPDF 报以下4个错误:

为关联文件提供的附加信息以及关联文件的使用要求表明嵌入文件与 PDF 文档或与其关联的 PDF 文档部分之间的关​​系。
CosFileSpecification
isAssociatedFile == true

为了能够识别文件规范字典和引用它的内容之间的关系,定义了一个新的(必需的)键,并且它的存在(在字典中)是必需的。
CosFileSpecification
AFRelationship != null

嵌入文件或文件子集的 MIME 类型应使用文件规范字典的 Subtype 键指定。如果 MIME 类型未知,则应使用“application/octet-stream”。
嵌入式文件
子类型 != null && /^[-\w+.]+/[-\w+.]+$/.test(子类型)

嵌入文件的文件规范字典不包含 F 或 EF 键。
CosFileSpecification
EF_size == 0 || (F != null && UF != null)

如何避免剥离或仅重新附加嵌入的整个文档文件以通过 Ghostscript 制作有效的 PDF/A-3B 文件?

【问题讨论】:

    标签: pdf ghostscript postscript pdfa


    【解决方案1】:

    这需要修改示例PDFA_def.ps 脚本。我的回答是基于 Ghostscript 9.27 和 Debian 10 打包的版本,在 /usr/share/ghostscript/9.27/lib/PDFA_def.psGhostscript repository 中找到。您可以使用更新版本,它的功能应该类似。我假设您已成功编辑文件以指向颜色配置文件的正确路径(我将使用 ArgyllCMS's sRGB module

    按照现有的 pdfmark 嵌入教程,我们可以相当容易地嵌入任何文件,例如 this emailthis 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&lt;&lt; ... &gt;&gt; 是一个字典,就像现代脚本语言中的 { ... } 一样。在这里,{foo} 是一个名为 object1 的 pdfmark,我们将使用的 pdfmark 行以标记开头:[(有关详细信息,请参阅PostScriptpdfmark 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 参考手册中使用的名称。

    【讨论】:

    • 这不适用于最新版本的 Ghostscript,因为安全模型不允许对任意文件进行文件操作。您要么需要将 -dNOSAFER 添加到命令行(我不推荐这样做),要么使用 --permit-file-read= 将特定文件添加到可以读取的文件列表中。构造“{foo}”不是“pdfmrk 对象”,它是一个完全正常的 PostScript 程序。 '[' 标记是一个标记对象,是的,它是 pdfmark 名称的来源,但它只是堆栈上的一个标记。 'pdfmark lines' 不必以 '[' 开头。
    • @KenS 很高兴知道。你想编辑这些更正吗?
    • @KenS 我已经添加了一些说明,但我仍然不确定您的某些观点。第 10 页和第 11 页上的 pdfmark spec 称它们为“命名对象”,而不是过程。这是不准确的,无论是在一般情况下,还是在这个答案的用法中?对于行的开头,我简化了 pdfmark 行(第 7 页)语法 start 因为我主要看到 [ 而不是 mark 来启动 pdfmark 行,并且只是对其进行解释,以便可以在此答案中阅读.或者我还有什么遗漏的吗?
    • 重点是你已经在一段描述为 PostScript Primer 的段落中声明了 '{...}' 是一个 pdfmark 特定的命名对象。不是,它是 PostScript 程序。然后,pdfmark 非标准 PostScript 运算符和类似 Distiller 的设备使用它来创建 PDF 命名对象的方式我想说的有点不同。我关于使用 '[' 的观点再次与它在 PostScript 中的使用有关。是的,在所有 pdfmark 示例中都以这种方式使用(行首),但不一定非要如此,在 PS 术语中它是一个标记。您修改后的文本很好。
    • @KenS 确实如此,尽管该部分的目标只是作为“简要入门”,仅足以理解所编写的新代码的语法。因此,我故意这样称呼它,因为实现细节与阅读代码无关。我认为这个通用术语不会帮助那些还不熟悉 PS 的人理解这篇文章。我已经添加了一个脚注来解释这个对孩子的谎言,以供那些希望了解更多 PS 细节的人使用。你觉得合理吗?
    猜你喜欢
    • 2016-06-15
    • 2014-11-20
    • 2020-03-07
    • 2023-04-08
    • 1970-01-01
    • 2010-12-12
    • 2016-03-17
    • 2019-04-02
    • 2017-02-21
    相关资源
    最近更新 更多