【问题标题】:Determine page orientation in Postscript在 Postscript 中确定页面方向
【发布时间】:2020-07-26 05:28:38
【问题描述】:

我需要使用 Ghostscript 在 PDF 文档每一页的左下角添加一个白色矩形和一些文本。为此,我创建了以下 Postscript 脚本:

<<
   /EndPage
   {
     2 eq { pop false }
     {
        newpath
        0 0 moveto
        0 20 lineto
        200 20 lineto
        200 0 lineto
        closepath
        %%gsave
        1 setgray
        fill
        %%grestore
        1 setlinewidth
        0 setgray
        stroke

        gsave
        /Times-Roman 9 selectfont              
        30 5 moveto                            
        (My text) show

        grestore
        true
     } ifelse
   } bind
>> setpagedevice

与 Ghostscript 命令结合使用时效果很好:

gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=output.pdf my_script.ps input.pdf

但是,如果 input.pdf 处于横向模式,则白框和文本将打印在左上角而不是左下角。我可以通过添加:

90 rotate 0 -595 translate

但我无法确定页面何时处于横向模式与纵向模式。我可以获得页面宽度和高度,但即使对于横向模式页面,宽度也小于高度。我尝试了以下方法,但失败了:

/orient currentpagedevice /Orientation get def

我已经被这个问题困扰了一段时间。非常感谢任何帮助!

(Ghostscript 版本为 9.25)

[更新]

为了说明横向模式下页面的宽度如何小于高度,这是我正在使用的 script.ps:https://gist.github.com/irinkaa/9faadf30b3a5a381a0b621d72b712020

这里是input.pdfoutput.pdf。如您所见,612.0 - 792.0 打印在输出文件中,显示宽度 (612)

当我在输出文件上重新运行相同的命令时,它会打印相同的宽度和高度值,但该框随后会正确放置在左下角。

当我在脚本中添加以下内容时:

/orient currentpagedevice /Orientation get def

我收到一条提示未设置方向的错误消息(如果我理解正确的话):

Error: /undefined in --get--
Operand stack:
   orient   --dict:212/312(ro)(L)--   Orientation
Execution stack:
   %interp_exit   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--   --nostringval--   --nostringval--   false   1   %stopped_push   1999   1   3   %oparray_pop   1998   1   3   %oparray_pop   1982   1   3   %oparray_pop   1868   1   3   %oparray_pop   --nostringval--   %errorexec_pop   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--
Dictionary stack:
   --dict:977/1684(ro)(G)--   --dict:0/20(G)--   --dict:80/200(L)--
Current allocation mode is local
Current file position is 151
GPL Ghostscript 9.25: Unrecoverable error, exit code 1

【问题讨论】:

    标签: pdf ghostscript postscript


    【解决方案1】:

    首先您应该升级您的 Ghostscript 版本。 9.25 太旧了,存在安全漏洞。

    其次,您需要查看页面设备字典中的 /Orientation /PageSize 条目。不仅如此,您还应该使用 PageSize 来确定您用于“调整”的翻译。除非您处于固定的工作流程中(如果您收到混合方向的文件,这似乎不太可能),否则您不应该假设媒体是 A4。

    Ghostscript PDF 解释器查看 PDF 文件每一页上的 MediaBox,并重置页面设备字典中的 /PageSize 以匹配页面的 MediaBox。它(IIRC)永远不会设置 /Orientation,如果 PDF 页面有 /Rotate 条目,那么它将应用于 MediaBox 和页面的内容。

    所以你真的只需要查看所请求媒体的宽度和高度,这是由页面设备字典中的 /PageSize 数组给出的。

    话虽如此......

    您说“即使对于横向模式页面,宽度也小于高度”。这对我来说似乎不太可能,但在没有例子的情况下很难说。这也让任何人都很难提供任何建议。

    我建议你在某处上传一个示例,并在此处发布 URL,以便我们查看文件。

    哦,我真的建议您不要将输出文件发送到标准输出。这对您来说可能很方便,但 pdfwrite 设备已经有某些功能,如果您这样做(它们要求输出文件可搜索),这些功能将无法正常工作,并且将来可能会有更多情况。

    编辑

    您的问题是执行顺序。 script.ps 中的程序在解释 PDF 文件之前运行,然后再解释 PDF 文件。

    当您的程序所做的只是在页面设备字典中设置一个 EndPage 过程这不是问题时,对页面设备字典的更改是保守的,除非特别覆盖,否则它们会累积。

    因此,在解释 PDF 文件的过程中,页面设备字典发生变化这一事实并不重要(除非以某种方式改变了 EndPage 过程)。

    但是在您的程序运行时,页面设备字典 /PageSize 键有一个关联值,该值是一个包含 默认 媒体大小的数组(因为尚未发生任何更改)。在 PDF 文件被解释之前,不会更改 PageSize 条目。这意味着无论您的 PDF 文件使用什么尺寸的媒体,您的程序都将始终返回默认媒体尺寸。

    您需要在执行 EndPage 过程时知道实际的 PageSize。因此,您需要调查 当前 PageSize 作为 EndPage 过程的一部分。

    类似:

    <<
       /EndPage
       {
         2 eq { pop false }
         {
           % Get the current page device dictionary and extract the PageSize
           currentpagedevice /PageSize get
    
           % Load the values from the array onto the stack
           % and discard the array copy returned by the aload operator
           aload pop
    
           % If width < height (or equal, square page)
           le {
             % Handle a portrait page
           } {
             % Handle a landscape page
           } ifelse
        }ifelse
      } bind
    >> setpagedevice
    

    请注意,这避免了创建字典条目来保存页面宽度和高度。这样做有几个原因;

    首先,每个页面的宽度和高度可能不同(尤其是在 PDF 文件中)。

    其次,您不会(在您的程序中)创建自己的字典来存储这些键/值对,这意味着您正在使用当时处于活动状态的任何字典。虽然这在您目前拥有它的方式上是可以接受的,因为 userdict 将在程序开始时处于活动状态,当调用 EndPage 时,您无法知道哪个字典位于字典堆栈的顶部。因此,将值插入恰好位于顶部的任何字典中是不安全的,您最终可能会覆盖具有相同名称的键,这将导致不可预测的副作用。同样(根据下面的方向),如果当前字典不包含这些键,您将收到未定义的错误。所以你现在靠运气侥幸逃脱了。

    第三,在 PostScript 中通常认为更好的做法是使用堆栈进行临时存储,而不是在字典中创建键/值对。

    出于后两个原因,我强烈建议不要在程序开始时在字典堆栈顶部的任何字典中创建一个名为 stringholder 的键(如您的程序当前所做的那样),并假设它将在 EndPage 过程中可用,您应该改为使用 10 string 创建一个临时字符串。

    例如:

    /Times-Roman 9 selectfont              
    30 5 moveto                            
    pagewidth
    stringHolder cvs
    show
    

    会变成:

    /Times-Roman 9 selectfont              
    30 5 moveto                            
    currentpagedevice /PageSize get 0 get
    256 string cvs
    show
    

    10 位可能有点小,对于任何人来说 256 位应该都足够了,并且字符串将被垃圾收集,因此不会像您泄漏内存或其他任何东西。

    关于方向;是的,你是对的,正如我最初所说的那样,PDF 解释器没有在页面设备字典中设置方向。如果您尝试 get 字典中不包含该键的键,则会收到未定义的错误。如果您不确定某个键是否存在于字典中,您应该首先使用known 运算符检查它。

    编辑 2

    如下面的 cmets 所述,可以使用 transform 运算符和单位向量来测试 CTM 的方向。如果 transform 产生的一个或两个坐标为负数,则 CTM 涉及旋转,通过检查每个坐标的符号,我们可以确定旋转最终进入哪个象限。

    对于 PDF 中的 /Rotate 标志来说就足够了,因为它只能以 90 度增量指定。这是一个确定旋转的示例函数,以及一个简单的 PostScript 来执行它:

    %!PS
    
    /R {
    1 1 transform
    
    0 ge {
      0 ge {
        (no rotation\n) print
      } {
        (90 degree ccw rotation\n) print
      } ifelse
    } {
      0 ge {
        (270 ccw rotation\n) print
      } {
        (180 ccw rotation\n) print
      } ifelse
    } ifelse
    } bind def
    
    R
    gsave
    90 rotate R
    grestore
    gsave
    180 rotate R
    grestore
    gsave
    270 rotate R
    grestore
    gsave
    360 rotate R
    grestore
    

    可以使用这种技术来确定原始文件是否已旋转,然后选择让 EndPage 过程表现不同。

    【讨论】:

    • 非常感谢您的回答!是的,调整需要使用 PageSize,我也会应用其他建议。不幸的是,我无法控制 Ghostscript 升级,这是一个大型遗留项目。先前的升级尝试显示了重大更改并已被推迟。我已经更新了输出到文件而不是标准输出的答案,并上传了示例输入、输出和脚本版本,该版本显示 input.pdf 中横向页面的宽度小于高度。如果您能提供任何进一步的帮助,那就太好了!
    • 肯,非常感谢你!通过将这个脚本放在一起,我已经让它为 input.pdf 工作:gist.github.com/irinkaa/0950046a18fd2aa39de33fcf894130bb。在第一次运行 gs 命令时,它会在左下角正确打印横向页面的框。但是,如果我使用 output.pdf(从第一次运行)作为输入运行相同的命令,它会在右下角打印第二个框,而不是与现有的框重叠。好像 gs 重置了坐标系的位置,尽管页面宽度和高度保持不变。你知道这里发生了什么吗?再次,非常感谢!
    • 您的原始 PDF 文件在 Page 字典中有 /Rotate 90。 Ghostscript 通过将当前转换矩阵旋转 90 度 (ccw) 并交换媒体请求的宽度和高度来处理这个问题。您的原始 PDF 文件请求 A4 媒体,595x842,GS 将其转换为 842x595 请求。但是 pdfwrite 设备不知道 Rotate 所以输出文件没有它。所以输出文件不请求旋转为 90 的 A4,它直接请求 A4 横向。这个问题没有简单的答案,您需要通过检查 CTM 来检测旋转。
    • 我在这里的意思是原始 PDF 文件不是完全横向的,它是一个纵向 595x842 请求,并且内容被绘制以适应它,然后页面旋转了 90 度。而 pdfwrite 输出是真正的横向页面。
    • 非常感谢!我将尝试查看 CTM。
    【解决方案2】:

    “但我无法确定页面何时处于横向模式与纵向模式”

    $ gs -sDEVICE=bbox -dNOPAUSE -dBATCH input.pdf | grep %B 
    %%BoundingBox: -1 0 842 596
    %%HiResBoundingBox: -0.008930 0.018000 841.988998 595.223982
    %%BoundingBox: -1 0 842 596
    %%HiResBoundingBox: -0.008930 0.018000 841.988998 595.223982
    

    然后你可以有一个 script-portrait.ps 和一个 script-landscape.ps 视情况而定。

    编辑:我同意 KenS 的观点。 ghostscript pdfwrite 输出创建的布局与 Acrobat Distiller 10.1.1 (Windows) 创建的原始 pdf 不同。即使不包括 EndPage 脚本,我也发现了这种差异。

    【讨论】:

    • 谢谢,我去看看边界框!这不适用于具有混合页面方向的文档,对吗?
    • 不要使用 bbox 设备。这不会返回媒体大小,它会返回包含页面上标记的框。在您的情况下,内容涵盖了整个页面,但通常情况并非如此。它确实适用于具有混合方向和媒体尺寸的文档,为每个页面返回不同的值。但是您必须将其作为一个单独的过程运行,以查找每个页面上内容的边界框,然后收集信息并重新使用它,以便生成一个添加了附加内容的新 PDF 文件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-28
    • 1970-01-01
    相关资源
    最近更新 更多