| Main index | Placemat: main guide | About author |
Julian D. A. Wiseman
Contents:
General-purpose routines: Concatenate; CurrentFontName; CurrentFontSize; GreatestCommonDivisor; IsNumber; HeapSort; RequireCompliantOutput;
Debug: CountGraphicsStack; ThingToDebugText; OutputLogToPage; Error reporting;
Compound Objects: GlyphPath; GlyphPathMoveto; CharPathRecursive; CharPathRecursiveMoveto; StringHeight; StringMiddleOffset; StringTopOffset; StringWidthRecursive; ShowRecursive; LengthCompoundObject; XcheckRecursive; DeGlyphRecursive;
Paths and Output: PathBBox; SetPaperSize; RepeatClippedWithin; TransformPath.
The author has written a program, in PostScript, to render placemats for glasses at a wine tasting. That program can be found at www.jdawiseman.com/papers/placemat/placemat.ps, and the documentation at www.jdawiseman.com/papers/placemat/placemat.html and www.jdawiseman.com/papers/placemat/placemat-code.html. The code contains several routines that other PostScript programmers might wish to reuse, described here.
However, users are cautioned that this ‘manual’ tends to lag the code.
| string1 string2 | Concatenate | string |
Concatenate concatenates the two strings. An older version, available at www.jdawiseman.com/papers/placemat/concatenate.ps, would work with strings or arrays, and did some error handling.
| - | CurrentFontName | name |
CurrentFontName returns the name of the current font.
| - | CurrentFontSize | num |
CurrentFontSize returns the size of the current font, such that CurrentFontName CurrentFontSize selectfont has no effect.
| int1 int2 | GreatestCommonDivisor | int |
GreatestCommonDivisor returns the greatest common divisor of the two integers. There is a small speed gain if int1 ≥ int2. If it is not known which is larger, it is not worth testing.
| any | IsNumber | bool |
IsNumber returns true if the parameter is an integer or a real, otherwise false.
| array code | HeapSort | - |
The code is a function that takes two of the items in array from the stack, and returns a boolean. HeapSort sorts the array such that for elements i and i+1 the comparison code returns true. E.g., [2 0 4 3 1] dup {lt} HeapSort == outputs [0 1 2 3 4]. This HeapSort was originally converted from Numerical Recipes in C by J Plowes, and then generalised by this author.
| - | RequireCompliantOutput | bool |
This function attempts to assess whether the output must be compliant with PDF standards. (If it is, on-page links are forbidden.) RequireCompliantOutput may well be imperfectly forward compatible: those engaged in a future attempt to rewrite it might wish to read this thread.
Routines, and one piece of code, that simplify debugging and improve the programming environment.
| - | CountGraphicsStack | int |
The code that creates CountGraphicsStack, and initialises it to zero, also redefines gsave (to increment CountGraphicsStack) and grestore (to decrement). Thereafter CountGraphicsStack plays a role analagous to that of countdictstack, checking that gsaves and grestores are balanced. Some help in writing CountGraphicsStack was received here.)
| any | ThingToDebugText | string |
ThingToDebugText creates a text representation of any, working well with code, names, numbers, booleans, dictionaries, or arrays of anything which which it works well.
| - | OutputLogToPage | bool |
OutputLogToPage is user-created boolean, controlling whether output to the log file is also sent to an extra page at the end of the PDF. Users whose only PostScript interpreter does not produce a log file, such as Mac Preview, should use this. The boolean is tested by code at the end of the PostScript program, code that calls a variety of routines and parameters, including SetPaperSize, and the array OutputLog created early in the program (also copy-paste this creation which also makes OutputToLog). Most functions described in this page call OutputToLog: include this code in your program.
The bulk of the PostScript program lies within a piece of code that is exec’d by a stopped command. If this stopped returns true a block of code then calls OutputToLog with much of the contents of the $error dictionary. This output is far more comprehensive than that output by default in Distiller or GhostScript. Anybody writing a large PostScript program is strongly advised to use this block of code, or something similar.
/Left|/Center|/Right /Top|/Middle|/Bottom num1 /Radius|/Diameter|/Height|/Width num2 int1 int2 DrawLinesInside AntiClockwise Star dict

Star appends a star-shaped path to the currentpath.
A dictionary is left on the stack, it having values for LeftX (of the vertices), CenterX (of the enclosing circle), RightX, BottomY, CenterY (circle), TopY, Radius, InnerRadius, and the boolean AntiClockwise.
In the example on the right the columns have int1 from 5 to 10; and rows have num2 being 2, 3 and 4; DrawLinesInside being false.

| /Left|/Center|/Right /Top|/Middle|/Bottom num1 num2 num3 num4 true | AnalogueClock | - |
| /Left|/Center|/Right /Top|/Middle|/Bottom num1 num2 false | AnalogueClock | - |
AnalogueClock’s first two parameters, both names, have the same role as in Star; num1 is the width, num2 is the height. If the last parameter, the boolean, is true, then num3 is the hours (0 ≤ num3 ≤ 12), and num4 the minutes (0 ≤ num4 ≤ 60). If the last parameter is false no hands are shown.
In May 2011 this was removed from the main code, but can still be found at www.jdawiseman.com/papers/placemat/analogueclock.ps.
In many places the PostScript program accepts parameters in the form of a “compound object”. A compound object is any of the following.
There are a number of routines handling compound objects described below.
Note that the wide freedom allowed to code in a compound object causes some problems.
A bug in at least one version of Distiller (Professional 8.1.0 (10/23/06) on Mac OSX) causes paths made partly from charpath and partly from linetos and curvetos to be stroked incompletely (see thread).
It seems natural to surround the size-measurement routines with a gsave nulldevice … grestore. But, alas, nulldevice seems to cause the current path to be stored with integer coordinates, thus causing PathBBox (and indeed pathbbox) to return slightly incorrect values (thread).
| glyphname bool | GlyphPath | - |
GlyphPath computes the path of a glyph, with the charpath-style boolean flag, destroying the currentpoint. Its design was based on suggestions made in this thread.
| glyphname bool | GlyphPathMoveto | - |
GlyphPathMoveto computes the path of a glyph, with the charpath-style boolean flag, moving the currentpoint as if after a show.
| CompoundObject bool | CharPathRecursive | - |
CharPathRecursive computes the path of a compound object, with the charpath-style boolean flag, destroying the currentpoint. This, and several other functions taking a CompoundObject, must work around two different bugs in distiller; one about forming paths with both charpath and lineto; the other about path construction differing after a call of nulldevice. CharPathRecursive calls GlyphPath, CharPathRec, CharPathRecMoveto and XcheckRecursive.
| CompoundObject bool | CharPathRecursiveMoveto | - |
CharPathRecursiveMoveto computes the path of a compound object, with the charpath-style boolean flag, moving the currentpoint as if after a show. Other than not destroying the currentpoint, CharPathRecursiveMoveto closely resembles CharPathRecursive. CharPathRecursiveMoveto calls GlyphPathMoveto, CharPathRecMoveto and XcheckRecursive.
| CompoundObject | StringHeight | num |
StringHeight computes the height of its parameter, that is, the difference between the top and bottom of its bounding box. Calls PathBBox.
| CompoundObject | StringMiddleOffset | num |
StringMiddleOffset computes the offset to the baseline required to centre the parameter vertically. Calls PathBBox.
| CompoundObject | StringTopOffset | num |
StringTopOffset computes the offset to the baseline required to align the top of the parameter vertically against the y value of the currentpoint. Calls PathBBox.
| CompoundObject | StringWidthRecursive | num |
StringWidthRecursive computes the width of the compound object. Unlike the operator stringwidth, it does not compute a vertical offset, as compound objects are always shown horizontally. Other than this no-y computation, StringWidthRecursive and stringwidth are analagous. Calls StringWidthRec.
| CompoundObject | ShowRecursive | - |
ShowRecursive is the compound-object version of show. The compound object is shown starting at the currentpoint, and in the current font, though either of these might be changed by code within the compound object.
| CompoundObject | LengthCompoundObject | - |
LengthCompoundObject computes the width of the parameter in characters, and is hence meant to be analagous to length. Before any code execution, EffectiveNumCharacters is set to zero. After the code execution EffectiveNumCharacters is added to the length. Hence code rendering a glyph or sequence of glyphs can set EffectiveNumCharacters to be the effective number of ‘characters’, whatever that might be wanted to mean.
| CompoundObject | XcheckRecursive | bool |
XcheckRecursive returns true if the parameter contains any code, however deeply within nested arrays, otherwise false.
| CompoundObject | DeGlyphRecursive | string |
DeGlyphRecursive approximates a compound object with a string. PDF pages can be named, but the name is passed to pdfmark as a string. But page names might contain, for example, accents. DeGlyphRecursive strips accents, and approximates other glyphs.
| - | PathBBox | llx lly urx ury |
PathBBox is what the lower-case operator pathbbox should have been. From PLRM3 on pathbbox: “If the user coordinate system is rotated (other than by a multiple of 90 degrees) or skewed, the bounding box returned may be larger than expected.” This is not so for PathBBox which, further, does not need a preceding flattenpath. PathBBox works using pathforall, and for curve pieces solves the quadratic to compute precise turning points.
If the current path contains any line or curves, isolated moves are ignored. If the current path contains only isolated moves, the returned box includes all the moves. If the current path contains nothing, PathBBox invokes emptycurrentpath, which, if not defined by calling code, will cause distillation to terminate with an error.
PathBBox is about a hundred times slower than flattenpath pathbbox, and was discussed in this thread.
| bool1 bool2 name1 name2 | SetPaperSize | - |
The parameter name1 should be one of /A0 though to /A9, or /USL (for 8½″×11″ US Letter), /USL2 (for 11″×17″ US Ledger), /USLegal (for 8½″×14″ US Legal), or /A4_USL (the width of A4, the 11″ height of US Letter). The parameter name2 should be one of /Landscape or /Portrait. If bool1 then setpagedevice is called. If bool2 then the sizes are echoed to the log by calling OutputToLog. SetPaperSize thus sets the paper size. Calls Dimensions.

numx numy CompoundObject numSpaces numLineStep numAngle int code1 code2 RepeatClippedWithin -
RepeatClippedWithin paints repeated copies of a compound object within a path. On calling RepeatClippedWithin there is a current font. The current path is used as the clipping path, and remains the current path on exit. The CompoundObject is the object shown repeatedly. Horizontally, these are separated by numSpaces space widths. Lines are numLineStep apart. The compound object is shown at angle numAngle. The int parameter is the number of outlines shown. The innermost colour, that is the fill colour of the compound object, is set by code1, and the other by code2. The numx and numy values determine a ‘starting point’ for the filling. In the example on the right RepeatClippedWithin has been called twice: the fills line up because the numx and numy values were the same. (Help on insideness testing was received here.)
| {TransformFunction} num | TransformPath | - |
TransformPath replaces the current path with a transformed version. The parameter TransformFunction does most of the work, it taking four parameters x, y, dx, dy (the last two being a vector, or a direction pointing away from x,y), and converts them to x', y', dx', dy'. Thus the first parameter converts a position, and a direction from that position, to a new position and a new direction. The second parameter, a number, is a distance in the transformed space that can be deemed small (with 0.12, or one pixel per inch at 600dpi, being sensible). If TransformPath appears not to work, a likely cause is a failure by the calling routine to pass code that correctly computes dx', dy'. Calls TransformMoveto, TransformLinetoClosepath, TransformLineto, TransformClosepath and TransformCurveto.
In May 2011 this was removed from the main code, but can still be found at www.jdawiseman.com/papers/placemat/transformpath.ps.
Julian D. A. Wiseman
| Main index | Top | About author |