【问题标题】:What are PostScript dictionaries, and how can they be accessed (via Ghostscript)?什么是 PostScript 字典,如何访问它们(通过 Ghostscript)?
【发布时间】:2012-06-23 15:54:01
【问题描述】:

我通常将ghostscript视为命令行工具;然而,我从未停止对那里存在的大量设置和选项感到惊讶——这是因为ghostscript 是一个成熟的 PostScript 语言解释器(我经常忘记)。

例如,在Querying Ghostscript for the default options/settings of an output device (such as 'pdfwrite' or 'tiffg4');学习如何检索给定输出设备的默认选项。但是,我想知道的是 - 这些选项与所谓的 PostScript 字典有关吗?

或者,换句话说——什么是 PostScript 字典; ghostscript 有哪些工具可以查询(并可能)修改这些数据?

【问题讨论】:

    标签: dictionary stack ghostscript postscript


    【解决方案1】:

    PostScript 中的字典是一个“容器”对象,它们本质上是一个对、一个键和一个值的列表。有关详细信息,请参阅 PostScript 语言参考手册,尤其是第三版中的第 3.3.9 节。

    字典通常用于将一组参数传递给 PostScript 运算符或函数,例如图像运算符可以接受字典参数,但它们同样可以简单地存储。

    字典可以具有访问权限,因此可以具有只读字典,其值可以检查但不能修改,并且字体字典可以是“无权访问”以防止在 PostScript 中提取轮廓数据。

    可以随意修改非只读或无访问权限的字典中的条目。

    【讨论】:

    【解决方案2】:

    用最简单的话来说:在 PostScript 中,字典是 键(名称)+值对的列表。字典允许 PostScript 解释器查找如果键存在并获取其值以在任何过程中使用它。解释器还可以创建键、存储或修改值,甚至创建完整的自定义字典(由 PostScript 代码处理)。键通常是类型名称(但它们也可以是任何其他类型,null 除外)。

    对于 PostScript 解释器的任何实现,必须始终存在其中两个字典:

    • systemdict 这个包含预定义的 PostScript 操作符(以及使它们执行 PostScript 规范所期望的操作的实现)。

    • userdict 这个包含 PostScript 程序的变量和过程(将“过程”视为由语言定义的运算符和程序定义的值和参数组合而成的函数或子例程)。

    关于names的一句话:对于其他编程语言来说,名称是唯一的标识符(它们区分大小写)。这些标识符可以是变量或过程名称。它们可以由 ASCII 的 256 个字符的任意组合组成(但它们不是字符串)。

    您可能知道,PostScript 是一种面向堆栈 的语言。它使用了几个堆栈:

    • 操作数栈 此堆栈保存每个操作数和中间操作的每个结果(将最后一个结果临时转入操作数堆栈的最顶部元素)。

    • 字典堆栈 顾名思义:这个堆栈只包含字典。因此,堆栈定义了任何键/名称查找的当前上下文。

    • 执行堆栈 这个包含 executable 对象,即主要是当前正在执行的 proceduresfiles。如果解释器中断了当前对象的执行,它会将被中断的对象放到这个堆栈中。在一个对象完全执行后,它会从堆栈中移除,并从现在最顶层的那个继续执行。

    • 图形状态堆栈 此堆栈托管当前用于弹出图形元素的上下文:当前线宽设置、当前字体、当前颜色或灰度值、当前路径...可以保存当前图形状态 (gsave)并在稍后恢复 (grestore)。最顶层的图形状态始终是当前图形状态

    所有这些堆栈都是相互独立的。然而,操作数、字典和图形状态堆栈都在 PostScript 程序的控制之下(也就是说,可以被它操纵)。执行堆栈是解释器的唯一属性。

    对于每个堆栈都有一定的限制(如可以存储在其上的元素数量等)。 PostScript 知道可以操作堆栈的运算符:将新元素放入堆栈,删除最顶部的元素 (pop),复制最顶部的元素 (dup strong>),打乱堆栈中元素的顺序(roll),交换两个最上面的元素(exch),还有更多(来自 Adob​​e 的 'Bluebook' 是 PostScript 编程的一个很好的介绍)。

    正如我已经说过的,字典有自己的堆栈,其中包含 PostScript 解释器可能使用的所有字典。

    在那个堆栈上可能有一个单独的字体字典,或者 PostScript 程序想要创建(使用 dict 关键字)并私下使用的任意数量的字典,或者一些字典特定于某个 P​​ostScript 解释器,例如 Ghostscript。

    systemdict 总是最底层的;上面是userdict。这两个不能从字典堆栈中删除,而所有其他的都可以受制于任何堆栈操作运算符(例如 pop,它会从堆栈中删除最顶部的元素)。

    每当解释器查找一个名称时,它都会在字典中搜索该名称,从最顶层的字典开始。因此userdictsystemdict 之前搜索。一旦找到名称(键),解释器就会停止搜索并使用该键(或者更确切地说,它保存的值)。这种架构的结果是 PostScript 程序员可以用他自己的变体覆盖在 systemdict 中预定义的任何 PostScript 运算符。

    另外,一些字典可以是用于 PS 程序的“私有”(不可访问,例如字体字典)或“只读”。


    更新 -- 更多答案:

    【讨论】:

    • 非常感谢@pipitas 的详细回答!如果/当您有时间时,您是否还可以添加一个简短的 ghostscript 终端示例,“解释器 [] 查找名称”(也可以显示“userdictsystemdict" 之前搜索,并可能覆盖运算符)?再次非常感谢 - 干杯!
    • @sdaau:我的另一个答案向您展示了如何在字典.distillersettings 中查找名称/键/screen:就像.distillersettings /screen get 一样简单。 get 运算符将键的值(如果找到)放入操作数堆栈(如果未找到,将打印 undefined 错误)。现在键的值在堆栈上,其余的代码 sn-p 只是在那里获取它,对其进行格式化以使其看起来更漂亮并打印出它的内容......
    • 小修正。 pop 不适用于字典堆栈。 begin 将字典推入字典堆栈。 end 从字典堆栈中弹出顶部字典。
    【解决方案3】:

    其他答案已经涵盖了您问题的“什么是字典?”部分。现在让我们转向“Ghostscript 如何访问它们?”

    也许问题应该是:“我(高级用户、开发人员、极客......)如何访问它们?”

    您可以通过编写一个简单的 PostScript 程序单行代码来打印出 PostScript 解释器(可能是 Ghostscript)已知的任何可访问字典的内容 - 或者只需调用解释器 (Ghostscript) 并传递程序代码在命令行上 (-c ...)。

    您只需要知道相应词典的名称即可。

    让我们看一个有趣的内部 Ghostscript 字典,称为 .distillersettings

    gs \
     -dNODISPLAY \
     -c ".distillersettings {exch ==only ( ) print ==} forall quit"
    

    结果:

    /default -dict-
    /prepress -dict-
    /PSL2Printer -dict-
    /ebook -dict-
    /screen -dict-
    /printer -dict-
    

    乍一看,这可能不会告诉您太多信息。但是您可能会认出该字典中的一些键名:/prepress/printer/screen/ebook...

    当您想要-sDEVICE=pdfwrite 的输出时,您可以在 Ghostscript 命令行上使用所有这些来请求一组预定义的设置(Ghostscript 'Distiller' 类似的功能)。要请求这样一组设置,只需在命令行中添加 -dPDFSETTINGS=/printer

    现在再看一眼,您会发现.distillersettings 字典的内容本质上是一组另外 6 个字典。它是一本“字典中的字典”。

    默认情况下不打印字典内容(不使用上面的 PostScript 代码)。但是,如果您需要它们,您可以使用名为 === 的 Ghostscript 特定过程,而不是上述命令中的标准 PostScript 语言运算符 ==。此过程的行为与== execpt 相同,它还扩展字典并打印其中包含的所有键:值对。

    小心=== 程序:您尝试扩展的-dict- 可能非常长,可能会导致您失去视力。 :-)

    在我们目前的情况下,它仍然是可管理的:

    gs \
     -dNODISPLAY \
     -c ".distillersettings {exch ==only ( ) print ===} forall quit"
    

    现在的输出是:

     /default << /Optimize false /DoThumbnails false /PreserveEPSInfo true /ColorConversionStrategy /LeaveColorUnchanged /DownsampleMonoImages false /EmbedAllFonts true /CannotEmbedFontPolicy /Warning /PreserveOPIComments true /GrayACSImageDict << /HSamples [2 1 1 2] /VSamples [2 1 1 2] /QFactor 0.9 /Blend 1 >> /DownsampleColorImages false /PreserveOverprintSettings true /CreateJobTicket false /AutoRotatePages /PageByPage /NeverEmbed [/Courier /Courier-Bold /Courier-Oblique /Courier-BoldOblique /Helvetica /Helvetica-Bold /Helvetica-Oblique /Helvetica-BoldOblique /Times-Roman /Times-Bold /Times-Italic /Times-BoldItalic /Symbol /ZapfDingbats] /ColorACSImageDict << /HSamples [2 1 1 2] /VSamples [2 1 1 2] /QFactor 0.9 /Blend 1 >> /DownsampleGrayImages false /UCRandBGInfo /Preserve >>
     /prepress << /DoThumbnails true /MonoImageResolution 1200 /ColorImageDownsampleType /Bicubic /PreserveEPSInfo true /ColorConversionStrategy /LeaveColorUnchanged /GrayImageDownsampleType /Bicubic /EmbedAllFonts true /CannotEmbedFontPolicy /Error /PreserveOPIComments true /GrayImageResolution 300 /GrayACSImageDict << /ColorTransform 1 /QFactor 0.15 /Blend 1 /HSamples [1 1 1 1] /VSamples [1 1 1 1] >> /ColorImageResolution 300 /PreserveOverprintSettings true /CreateJobTicket true /AutoRotatePages /None /MonoImageDownsampleType /Bicubic /NeverEmbed [] /ColorACSImageDict << /ColorTransform 1 /QFactor 0.15 /Blend 1 /HSamples [1 1 1 1] /VSamples [1 1 1 1] >> /CompatibilityLevel 1.4 /UCRandBGInfo /Preserve >>
     /PSL2Printer << /DoThumbnails false /CompatibilityLevel 1.2 /TransferFunctionInfo /Preserve /MonoImageResolution 1200 /PreserveEPSInfo true /CompressFonts true /ColorImageDownsampleType /Bicubic /GrayImageDownsampleType /Bicubic /ColorConversionStrategy /LeaveColorUnchanged /EmbedAllFonts true /ColorACSImageDict << /ColorTransform 1 /QFactor 0.15 /Blend 1 /HSamples [1 1 1 1] /VSamples [1 1 1 1] >> /CannotEmbedFontPolicy /Error /PreserveOPIComments true /CompressPages true /GrayImageResolution 600 /GrayACSImageDict << /ColorTransform 1 /QFactor 0.15 /Blend 1 /HSamples [1 1 1 1] /VSamples [1 1 1 1] >> /ColorImageResolution 600 /PreserveOverprintSettings true /AutoRotatePages /None /MonoImageDownsampleType /Bicubic /ASCII85EncodePages true /MaxViewerMemorySize 8000000 /NeverEmbed [] /PreserveHalftoneInfo true /UCRandBGInfo /Preserve >>
     /ebook << /DoThumbnails false /MonoImageResolution 300 /ColorImageDownsampleType /Bicubic /PreserveEPSInfo false /ColorConversionStrategy /sRGB /GrayImageDownsampleType /Bicubic /EmbedAllFonts true /CannotEmbedFontPolicy /Warning /PreserveOPIComments false /GrayImageResolution 150 /GrayACSImageDict << /ColorTransform 1 /QFactor 0.76 /Blend 1 /HSamples [2 1 1 2] /VSamples [2 1 1 2] >> /ColorImageResolution 150 /PreserveOverprintSettings false /CreateJobTicket false /AutoRotatePages /All /MonoImageDownsampleType /Bicubic /NeverEmbed [/Courier /Courier-Bold /Courier-Oblique /Courier-BoldOblique /Helvetica /Helvetica-Bold /Helvetica-Oblique /Helvetica-BoldOblique /Times-Roman /Times-Bold /Times-Italic /Times-BoldItalic /Symbol /ZapfDingbats] /ColorACSImageDict << /ColorTransform 1 /QFactor 0.76 /Blend 1 /HSamples [2 1 1 2] /VSamples [2 1 1 2] >> /CompatibilityLevel 1.4 /UCRandBGInfo /Remove >>
     /screen << /DoThumbnails false /MonoImageResolution 300 /ColorImageDownsampleType /Average /PreserveEPSInfo false /ColorConversionStrategy /sRGB /GrayImageDownsampleType /Average /EmbedAllFonts true /CannotEmbedFontPolicy /Warning /PreserveOPIComments false /GrayImageResolution 72 /GrayACSImageDict << /ColorTransform 1 /QFactor 0.76 /Blend 1 /HSamples [2 1 1 2] /VSamples [2 1 1 2] >> /ColorImageResolution 72 /PreserveOverprintSettings false /CreateJobTicket false /AutoRotatePages /PageByPage /MonoImageDownsampleType /Average /NeverEmbed [/Courier /Courier-Bold /Courier-Oblique /Courier-BoldOblique /Helvetica /Helvetica-Bold /Helvetica-Oblique /Helvetica-BoldOblique /Times-Roman /Times-Bold /Times-Italic /Times-BoldItalic /Symbol /ZapfDingbats] /ColorACSImageDict << /ColorTransform 1 /QFactor 0.76 /Blend 1 /HSamples [2 1 1 2] /VSamples [2 1 1 2] >> /CompatibilityLevel 1.3 /UCRandBGInfo /Remove >>
     /printer << /DoThumbnails false /MonoImageResolution 1200 /ColorImageDownsampleType /Bicubic /PreserveEPSInfo true /ColorConversionStrategy /UseDeviceIndependentColor /GrayImageDownsampleType /Bicubic /EmbedAllFonts true /CannotEmbedFontPolicy /Warning /PreserveOPIComments true /GrayImageResolution 300 /GrayACSImageDict << /ColorTransform 1 /QFactor 0.4 /Blend 1 /HSamples [1 1 1 1] /VSamples [1 1 1 1] >> /ColorImageResolution 300 /PreserveOverprintSettings true /CreateJobTicket true /AutoRotatePages /None /MonoImageDownsampleType /Bicubic /NeverEmbed [] /ColorACSImageDict << /ColorTransform 1 /QFactor 0.4 /Blend 1 /HSamples [1 1 1 1] /VSamples [1 1 1 1] >> /CompatibilityLevel 1.4 /UCRandBGInfo /Preserve >>
    

    还是不太好。所以让我们试着让它变得更好。我们可以这样做的方法是修改我们的 PostScript 代码:我们现在告诉它访问 .distillersettings 字典并从中获取其中一个键的值(让我们使用 /screen)。由于我们知道该值是另一个字典,因此我们知道我们将获得另一组 key:value 对,我们将能够像以前一样格式化它们:

    gs \
     -q \
     -dNODISPLAY \
     -c ".distillersettings /screen get {exch ==only ( ) print ===} forall quit"
    

    现在看起来更好了,不是吗?看看你自己:

    /DoThumbnails false
    /MonoImageResolution 300
    /ColorImageDownsampleType /Average
    /PreserveEPSInfo false
    /ColorConversionStrategy /sRGB
    /GrayImageDownsampleType /Average
    /EmbedAllFonts true
    /CannotEmbedFontPolicy /Warning
    /PreserveOPIComments false
    /GrayImageResolution 72
    /GrayACSImageDict -dict-
    /ColorImageResolution 72
    /PreserveOverprintSettings false
    /CreateJobTicket false
    /AutoRotatePages /PageByPage
    /MonoImageDownsampleType /Average
    /NeverEmbed [/Courier /Courier-Bold /Courier-Oblique /Courier-BoldOblique /Helvetica     /Helvetica-Bold /Helvetica-Oblique /Helvetica-BoldOblique /Times-Roman /Times-Bold /Times-Italic /Times-BoldItalic /Symbol /ZapfDingbats]
    /ColorACSImageDict -dict-
    /CompatibilityLevel 1.3
    /UCRandBGInfo /Remove
    

    正如您敏锐的眼光可能已经发现的那样:一些关键值又是字典。您可以再次使用上述命令,这次用=== 代替第二个== 来解决/GrayACSImageDict -dict- 等可能隐藏的谜团...

    无论如何,现在您只需使用-dPDFSETTINGS=/screen 而不是枚举嵌入在此/screen 字典中的所有单个参数,您就知道在键入时节省了什么...

    如果您想要一般的'screen' 质量输出,您还知道需要覆盖哪个单个值,但区别在于嵌入所有 字体:

    gs \
     -o out.pdf \
     -sDEVICE=pdfwrite \
     -dPDFSETTINGS=/screen \
     -c "<</NeverEmbed [ ] /AlwaysEmbed [/Courier /Courier-Bold /Courier-Oblique /Courier-BoldOblique /Helvetica /Helvetica-Bold /Helvetica-Oblique /Helvetica-BoldOblique /Times-Roman /Times-Bold /Times-Italic /Times-BoldItalic /Symbol /ZapfDingbats]>> setdistillerparams" \
     -f input.pdf
    

    只要您知道它使用的字典的名称,您就可以通过这种方式探索很多关于 Ghostscript 内部的有趣事物。 :-)

    【讨论】:

    • 太棒了,@pipitas - 现在 this 是我很久以来就想知道的;特别是== 是什么意思,以及如何使用它;关于长字典的警告也很受欢迎 - 好吧,整个帖子都是!已编辑的标题 - 如果您可以在接受的答案末尾链接此答案,那就太好了!非常非常感谢 - 干杯!
    【解决方案4】:

    如果您想获取 systemdictuserdict 字典中包含的其他字典的列表,只需运行:

    for _dict in userdict systemdict; \
       do \
       gs \
         -dNODISPLAY \
         -c "${_dict} {exch ==only ( ) print ==} forall quit"; \
    done \
    | awk '{print $1, $2}' \
    | grep -- -dict- \
    | sort
    

    这将生成一个字典名称的排序列表,您可以调查这些字典名称是否可能“有趣”的名称。

    你会发现这样的名字,如@9​​87654324@、localdictAdobeGlyphListuserparams.eexec_param_dict.substitutefamiliesEncodingDirectorycolorspacedict.distillerparamkeys、@98765433 .symbol_list, ...

    使用这些名称中的每一个,您都可以通过运行 f.e. 来查找有关 Ghostscript 内部的或多或少有趣的信息和花絮:

    gs \
      -q \
      -dNODISPLAY \
      -c "Fontmap {exch ==only ( ) print ==} forall quit"
    

    如您所见,即使是 Ghostscript 使用的 Fontmap 也存储在字典中。我在本地的结果摘录如下:

    [....]
    /Arial [/ArialMT]
    /Arial,Bold [/Arial-BoldMT]
    /AvantGarde-Book [/URWGothicL-Book]
    /Bookman-Demi [/URWBookmanL-DemiBold]
    /Calligraphic-Hiragana [(fhirw.gsf)]
    /Calligraphic-Katakana [(fkarw.gsf)]
    /Charter-Bold [/CharterBT-Bold]
    /CharterBT-Bold [(bchb.pfa)]
    /Courier [/NimbusMonL-Regu]
    /Courier-Bold [/NimbusMonL-Bold]
    /Courier-BoldOblique [/NimbusMonL-BoldObli]
    /Courier-Oblique [/NimbusMonL-ReguObli]
    /Helvetica [/NimbusSanL-Regu]
    /Helvetica-Bold [/NimbusSanL-Bold]
    /NewCenturySchlbk-Bold [/CenturySchL-Bold]
    /Palatino-Roman [/URWPalladioL-Roma]
    /Symbol [/StandardSymL]
    /Times-Bold [/NimbusRomNo9L-Medi]
    /TimesNewRoman,Bold [/TimesNewRomanPS-BoldMT]
    /Utopia-Regular [(putr.pfa)]
    /ZapfDingbats [/Dingbats]
    [....]
    

    注意事项:上述文件格式实际上并不是您在操作 Fontmap 文件时必须使用的文件格式> Ghostscript 应该使用(一般来说,或用于特定工作)。对于该格式,请阅读 Ghostscript 提供的示例 Fontmap 文件中的 cmets。上面的列表是 Ghostscript 存储在其内部 dictionary 中的字体图表示。

    【讨论】:

      【解决方案5】:

      已经有很多好的答案了,但没有人提到这个:

      调用 ghostscript 时,-d-s 选项会在 systemdict 中创建初始定义。这允许您执行parameterized invocation of your postscript program

      使用 -dname[=token] 将值设置为 null 或数字(或任何其他单个 postscript 标记)。使用-sname=string 设置字符串值(在大多数情况下,它与名称一样有效)。

      并且您可以使用正确的运算符在某种程度上操纵所有堆栈。

      • token 从字符串或文件推送到操作数堆栈(这是解释器循环用来消耗程序流的内容,因此无论是通过文件输入代码还是直接从键盘输入代码,这都是您使用的内容)
      • pop 从操作数堆栈中丢弃
      • begin 推送到字典堆栈
      • end 从字典堆栈弹出
      • run, exec, %procedure-invocation 推送到执行堆栈
      • exit, stop 弹出或清除执行堆栈
      • gsave 将 gstate 推送到图形堆栈上
      • grestore弹出图形栈
      • save 推送所有 VM 内容的副本(所有字典和数组,但不是字符串)
      • restore 将内存回退到保存状态(将所有字典和数组恢复到以前的状态)

      作为复合对象的字典继承了所有复合对象共有的许多运算符。

      • -typename- 创建对象,例如dict
      • length 报告对象大小
      • put插入一个元素
      • get 检索元素
      • copy 用另一个对象的内容填充一个对象
      • forall 对每个元素做一些事情
      • *load 备用检索元素(对于字典,load 使用 whereget 执行搜索;对于数组,aload 将数组的全部内容溢出到操作数堆栈上)
      • *store 备用插入元素(对于字典,store 使用 where 执行搜索,如果找到,则使用 put,否则 def;对于数组,astore 从堆栈上的对象填充数组)

      字典添加到这个套件

      • def 放入当前字典(字典栈顶)
      • known 查询一个元素的字典
      • where 查询所有字典中的元素
      • maxlength 在 PS Level 2 添加自动扩展字典和 gc 后不再有趣
      • dictstack 将 dictstack 复制到一个数组中(也许你想自下而上搜索,你可以!)
      • 前面没有斜杠 / 的名称会自动被 loaded,如果可执行,则执行
      • //token 正在构造一个后记对象时,任何以双斜杠开头的名称都将被loaded 并替换到过程数组中。这非常强大,因为您可以模仿 Lisp 宏。

      编辑:还有一件事。创建字典时,当您选择字典的大小时,需要权衡时间/空间。字典几乎肯定是作为哈希表实现的(除了最简单的解释器),大多数哈希函数可以在表大约半满时避免冲突(经验法则:使用双倍大小的字典来提高速度)。当然,由于 level-2,当你添加 size+1 个元素时,字典会自动增长,大概是通过分配一个新的 k*size 字典(其中 k 可能是 1.5 或 2);但是手动控制尺寸可以提高速度。在级别 1 中,如果您没有多次引用您的字典,则可以在 errordict 中安装 dictfull 的替代品以增加字典并重新执行 put(或 def 或其他)。由于 level-2 在内部执行此操作,因此它可以替换所有引用。

      【讨论】:

      • 太棒了,@luserdroog - 非常感谢您的澄清!干杯!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-25
      • 1970-01-01
      • 1970-01-01
      • 2016-12-02
      • 2020-07-02
      • 1970-01-01
      • 2015-10-24
      相关资源
      最近更新 更多