Skip to main content
Skip table of contents

(v13) Tips for writing PostScript-language code emitted by a filter

This page applies to Harlequin v13.1r0 and later; both Harlequin Core and Harlequin MultiRIP.

When writing your own filter for some other language or file format, it is our experience that it is much easier to maintain the state of the page being prepared in the PostScript-language VM rather than in the filter implementation’s memory, and to keep the filter C code as simple as possible.

For example, consider a fictitious language that models the way lines are drawn with the concept of a “pen” selected from a carousel of four pens. This model perhaps reflects the origins of the language in a plotter for which the language was originally developed, which did indeed have such a carousel. Different pens would be represented in PostScript language by altering various settings in the graphics state, such as line width and color.

The language might contain a cryptic “select next pen” command: for example, “<ESC>PN~ ”, which conceptually rotates the carousel by one position and picks the pen up from it. It is easier to remember which pen is in use and what the characteristics of the pens are in PostScript dictionary entries, rather than in data structures in the filter itself.

Then the easy translation of “<ESC>PN~ ” is a simple bit of PostScript language such as SelectNextPen (with suitable delimiting white space). The definition of SelectNextPen could consult the “current pen number” stored in some dictionary, increment it, look up the definition of the new pen in some other dictionary, and set the appropriate graphics state properties from it.

Another example of keeping state out of the filter is deciding when to execute a showpage operator. Many simple printer languages require a line of ASCII text to be printed and then the position moved down one line. When the end of the page is reached the printer throws to the next page automatically.

This requires a showpage in PostScript language terms. The position of the “next line” may be influenced by the size of font and other factors. The technique in this case would be to emit each line of text as a string (suitably escaped for necessary characters) with a procedure name following, for example:

(some text \(which was in the original input\)) ShowLine

That procedure would then keep track of the position on the page and execute showpage when necessary, as well as displaying the characters in the appropriate size and font.

The definitions of SelectNextPen and ShowLine could be emitted by the filter near the beginning. However, this means that the filter implementation contains a large body of constant strings which is simply copied to the filter’s output buffer on the first read from the filter;  this requires added complexity in the state machine implementing the filter to cope with the case when all that text does not fit in the buffer). This can be simplified significantly by wrapping up the definitions in a separately edited procset resource. If this is called PenLanguage, then all the constant filter has to emit at the beginning is something like this:

/PenLanguage /ProcSet findresource begin

to access the resource. A procset resource is a read-only dictionary. Therefore, the above is not sufficient if the filter requires to emit def operators or similar, or if any of the procedures in the procset need to def. There are many ways to handle this: one is to copy the procedures from the procset to another dictionary that is used to store the other values emitted by the filter, as in:

100 dict begin /PenLanguage /ProcSet findresource begin { def } forall

Another is to put a second dictionary onto the dictionary stack:

/PenLanguage /ProcSet findresource begin 100 dict begin

Where the procedures in the procset maintain state themselves, as is suggested here, it is most elegant to hide their internal data in a dictionary or dictionaries created within the procset for the purpose. Here is an abbreviated version of a procset for this example. Some notes on the implementation follow:


TEXT
            %!PS-Adobe-3.0
            %%Title: (PenLanguage)
            %%Creator: Harlequin
            %%CreationDate: ...
            %%For: ...
            %%EndComments
            %%BeginResource: procset PenLanguage currentglobal false setglobal
            10 dict begin
            /globalness exch def
            /max-pen 4 def
            /workspace <<
              /current-pen 1
            >> def
              /pens << 1 [
                1.0 % linewidth (points)
                0.0 0.0 0.0 % rgb color (a black pen)
              ]
              2 [ 1.0 1.0 0.0 0.0 ] % a red pen
              3 [ 5.0 0.0 0.0 0.0 ] % a thick black pen
              4 [ 1.0 1.0 1.0 1.0 ] % an "erasing" pen
            >> def
            ...
            /SelectNextPen {
              //workspace begin
                % increment the current pen number
                /current-pen current-pen 1 add
                  % wrap round to the first pen if necessary dup //max-pen gt { pop 1 } if def
                % set the graphics state according to new current pen
              //pens current-pen get aload pop setrgbcolor setlinewidth end
            } bind def
            /ShowLine {
              //workspace begin
                ...
              end
            } bind def
            ...
            [ /workspace /pens /max-pen ]
            { currentdict exch undef } forall currentdict
            end
            /PenLanguage exch /ProcSet defineresource % returns read-only copy
            /globalness get setglobal
            %%EndResource


The comments at the top and tail of the file are included so that where necessary comment parsing can recognize the file (see (v13) Comment parsing).

In general, it is impossible to predict whether memory is global or local when we execute the procset definition. Therefore, we take care to ensure it is local while we define the procset and reset afterwards. This is the purpose of globalness.

workspace, max-pen, and pens are only used by the procedures of the procset. They are removed by an undef at the end: note the technique of using forall on an array, which means that changes are easily accommodated. The use of //workspace embeds a reference to the dictionary inside the procedures SelectNextPen and ShowLine so the name is only used for convenience during the definition.

In this simple example, the pens are defined in the dictionary pens. In use, it may be more appropriate to define them along with the filter, as filter parameters. Of course, it is also possible that the language being interpreted contains “define pen” commands.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.