英文:
compute bounding box for arbitrary PostScript code, from within PostScript
问题
我想要能够运行一些任意的PostScript代码,并找出它的边界框,以便在再次运行时居中和/或缩放该PostScript代码。这需要在PostScript解释器的单个调用内完成。(即在可以发送到打印机的.ps文件内发生)
伪代码:
运行此PostScript代码,但不绘制任何内容
<一些任意的PostScript代码>
获取刚刚运行的代码的边界框
进行一些计算以使其居中,如果它太大则进行缩放
以缩放/平移运行PostScript代码,并实际绘制
<相同的任意的PostScript代码>
任意的PostScript代码可能会绘制多条路径,因此不能简单地调用pathbbox。 (是否有一种方法将任意PostScript代码块合并为单个路径,以便可以使用pathbbox?)
这似乎类似于此问题,不幸的是,该问题自1992年以来一直未得到解答。
不能接受的答案是涉及调用PostScript解释器多次的任何答案。 (例如使用GhostScript bbox设备。)这必须作为普通的PostScript程序运行,在任何()符合PostScript解释器上。 ()可以要求PostScript Level 3,但不能要求特定的实现(例如GhostScript)。
英文:
I want to be able to run some arbitrary PostScript code, and find out what its bounding box is, for the purpose of centering and/or scaling that PostScript code when I run it again. This needs to all happen within a single invocation of the PostScript interpreter. (i. e. happen within a .ps file that I can send to a printer)
Pseudocode:
Run this PostScript code without drawing anything
<some arbitrary PostScript code>
Get the bounding box of the code that just ran
Do some computations to center it, and/or scale it if it's too big
Run the PostScript code with scaling/translation, and actually draw
<the same arbitrary PostScript code>
The arbitrary PostScript code might draw multiple paths, so it's not as simple as just calling pathbbox. (Is there a way to combine an arbitrary chunk of PostScript code into a single path, so that pathbbox could be used?)
This seems similar to this question, which unfortunately has remained unanswered since 1992.
An unacceptable answer is any answer which involves invoking the PostScript interpreter more than once. (Such as using the GhostScript bbox device.) This has to run as a normal PostScript program, on any(*) compliant PostScript interpreter.
(*) It's acceptable to require PostScript Level 3, but it's not acceptable to require a specific implementation (e. g. GhostScript)
答案1
得分: 4
这是一个可能的解决方案,但并不简单,答案很大程度上取决于您对准确性、性能和可靠性的要求,以及您愿意投入项目的时间和您的PostScript编程能力水平。
理论上,您可以重新定义每个PostScript标记运算符(例如stroke、fill、image、所有show变体等),而不是绘制结果,而是确定由该操作标记的页面区域。对于某些运算符(例如rectfill),这很简单,对于其他运算符,可能会更复杂,但仍然可以使用charpath和pathforall来确定任何PostScript操作标记的区域。
现在来讨论您的第一个问题;pathbbox本身是不够的,因为任何PostScript绘图操作都可以通过剪切进行绘制,而剪切不一定是一个简单的矩形。
考虑这个简单的示例,它确实使用了矩形剪切:
%!
100 100 translate
0 0 0 setrgbcolor
-45 rotate
0 0 moveto
0 100 lineto
50 100 lineto
50 0 lineto
closepath
clip
newpath
90 rotate
0 -50 translate
0 1 0 setrgbcolor
0 0 moveto
0 100 lineto
50 100 lineto
50 0 lineto
closepath
fill
showpage
为了帮助可视化发生了什么,让我们将剪切绘制成黑色轮廓。这看起来像这样:
并渲染为:
如果我们将pathbbox应用于填充的路径,它将不应用剪切(并将使用CTM报告其坐标),因此与实际渲染的内容进行比较时,结果将是错误的(执行pathbbox然后撤消CTM并转换为默认用户空间会导致bbox为64.64,64.64,170.71,170.71,而bbox设备以72 dpi返回的实际bbox为100,64,171,136)。
您需要将当前剪切与当前路径相交(使用clippath检索当前剪切和currentpath检索当前路径,如果正在绘制路径),或者与执行像图像运算符之类的内容时的矩形路径相交,然后计算该交集的边界框,以确定渲染的边界框。
还存在其他困难;当线条被描边时,您需要考虑线条宽度以确定所标记的区域,线条连接可以是斜接的,您需要确定斜接的终点在哪里,曲线可以延伸到基于其端点的简单矩形边界框之外。
所有这些问题在渲染时都会被处理,通过将复杂形状转换为一系列填充的矩形。在极限情况下,矩形的高度是一个扫描线(即一个像素高)。剪切只是简单地交叉矩形。
当然,这会导致精度限制,因为矩形位于设备分辨率。以72 dpi扫描转换浅曲线的结果可能与以720 dpi扫描转换的结果不同。这就是Ghostscript bbox设备使用高分辨率的原因。
现在,PostScript是一种编程语言,所以显然您可以在PostScript中执行扫描转换和矩形交叉操作。另一方面,它也是一种解释性语言,所以相对较慢;这就是性能限制的原因。对于复杂的PostScript输入,如果在PostScript中执行扫描转换和交叉矩形列表,可能需要相当长的时间(并且实际上需要大量内存来存储矩形列表)。
您可能可以获取当前剪切路径和当前路径,对它们都运行pathbbox,然后找出它们的交集。这将给出最大的x和y。这对您的目的可能已经足够了,而且不需要分解为设备分辨率。请注意,bbox将在当前用户空间中,因此您需要反转CTM并应用它。
再次注意,描边路径是带有宽度的线,而路径是该线的中心,所以您需要考虑线宽的一半位于路径bbox之外。
最后是可靠性;虽然不常见,但真正任意的PostScript可能会定义自己的序言,直接从systemdict中读取运算符,而不是使用当前的定义(例如/myfill systemdict /fill load def)。这样做的PostScript将规避运算符的重新定义,从而不会运行'bbox'程序,阻止其正常工作。
所以这是一个解决方案;我不声称它是唯一的解决方案,但我不知道有更好的解决方案,可以纯粹在PostScript中工作,并与任何PostScript解释器一起使用。我认为这将是一个相当大的工作。
英文:
This is 'possible' but non-trivial, and the answer depends a great deal on how much accuracy, performance and reliability matter to you, as well as how much time you are prepared to invest in the project and your level of PostScript programming ability.
In theory you could redefine each of the PostScript marking operators (eg stroke, fill, image, all the show variants etc) and instead of drawing the result, determine the area of the page marked by that operation. For some operators (eg rectfill) this is trivial, for others it would be more complex but it is still possible to determine the area marked by any PostScript operation by using charpath and pathforall.
Now to cover your first point; pathbbox itself is not sufficient, because any PostScript drawing operation can be drawn through a clip and a clip is not necessarily a simple rectangle.
Consider this simple example which does use a rectangular clip:
%!
100 100 translate
0 0 0 setrgbcolor
-45 rotate
0 0 moveto
0 100 lineto
50 100 lineto
50 0 lineto
closepath
clip
newpath
90 rotate
0 -50 translate
0 1 0 setrgbcolor
0 0 moveto
0 100 lineto
50 100 lineto
50 0 lineto
closepath
fill
showpage
To help visualise what's going on let's draw the clip as a black stroke. That looks like this:
If we applied pathboox to the path for the fill it will not apply the clip (and will report it's co-ordinates using the CTM), so the result will be wrong when compared to what is actually rendered (executing pathbbox then undoing the CTM and converting to default user space results in a bbox of 64.64, 64.64, 170.71, 170.71, whereas the actual bbox returned by the bbox device at 72 dpi is 100, 64, 171, 136).
You would have to intersect the current clip with the current path (using clippath to retrieve the current clip and currentpath to retrieve the current path if the path is being drawn) or a rectangular path if executing something like the image operator, and then work out the bounding box of that intersection in order to determine the bounding box of what is rendered.
There are other difficulties; when lines are stroked you need to account for the line width in order to determine the area which is marked, line joins can be mitered and you need to determine where the mitre terminates, curves can extend beyond the edges of a simplistic rectangular bounding box based on their endpoints.
All of these problems are dealt with, when rendering, by scan-converting the path. Basically this means converting the complex shape into a series of filled rectangles. In the limiting case the height of the rectangle is one scan line (ie one pixel high). Clipping is then simply a case of intersecting rectangles.
This does, of course, lead to accuracy limits, because the rectangles are at device resolution. The result of scan converting shallow curves at 72 dpi may not be the same as scan-converting at 720 dpi. This is why the Ghostscript bbox device uses a high resolution.
Now PostScript is a programming language so obviously you can do the scan-conversion and rectangle intersection in PostScript. On the other hand, it is also an interpreted language and so comparatively slow; this is where the performance limitation comes in. For a complex PostScript input, scan-converting and intersecting a list of rectangles could take quite a long time (and indeed use up quite a lot of memory to store the lists of rectangles) if performed in PostScript.
You could, probably, get the current clip path and the current path, run pathbbox on them both and then figure out the intersection of them both. That would give you the maximum x and y. It's probably good enough for your purposes and it doesn't involve decomposing to the device resolution. Note that the bbox will be in current user space so you'll need to reverse the CTM and apply it.
Again, be aware that a stroked path is drawn with a line which has a width, and the path is the centre of that line, so you'll need to account for half the line width lying beyond the pathbbox..
Finally reliability; it isn't common but truly arbitrary PostScript could define it's own prologue to read the operators directly from systemdict, rather than using the current definitions (eg /myfill systemdict /fill load def). PostScript which did that would evade the redefinition of the operators, which would then not run the 'bbox' program and prevent it working.
So there's a solution; I don't claim it's the only one but I don't know of a better solution which will work purely in PostScript and with any PostScript interpreter. It will be quite an undertaking to write I should think.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。




评论