在 Postscript 中确定页面方向

Determine page orientation in Postscript

我需要使用 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.pdf and the output.pdf。如您所见,612.0 - 792.0 打印在输出文件中,显示宽度 (612) < 高度 (792)。

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

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

/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

首先你应该升级你的 Ghostscript 版本。 9.25 老了,有安全漏洞。

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

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

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

话虽如此....

你说'even for landscape mode pages the width is smaller than the height'。这对我来说似乎不太可能,但在没有例子的情况下很难说。这也让任何人都很难提供任何建议。

我建议您在某处上传一个示例,post 此处 URL 以便我们查看该文件。

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

编辑

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

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

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

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

您需要知道执行 EndPage 过程时的实际 PageSize。因此,您需要调查 current 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 文件中)。

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

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

出于后两个原因,我非常强烈建议不要在开始时在字典堆栈顶部的任何字典中创建一个名为 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

如以下评论所述,可以使用 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 过程以不同的方式运行。

"but I can't determine when the pages are in landscape mode vs. portrait mode"

$ 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

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

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