Meta Postscript
Volume Number: | | 9
|
Issue Number: | | 4
|
Column Tag: | | Cover Story
|
Metaprogramming Postscript
A system for arbitrary diagnostic analysis
By Gregor Koomey, Albany, New York
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
Metaprogramming is the programming of programming; specifically, in this case, involving the manipulation of a language environment to alter the effect of a previously defined block of functionality (a program). The techniques described herein define an aspect of the PostScript (PS) programming language that should be both interesting and useful to anyone who needs to examine/troubleshoot PS programs beyond a superficial level and, hopefully, to anyone interested in the general appreciation of programming language order and heuristic. The substance involves the arbitrary manipulation of dictionary data structures in order to enhance the diagnostic capabilities of any given PostScript language environment.
Definition of Framework
This section is a brief description, for those who dont know about, or are only vaguely familiar with, the PostScript programming language. Some of the following is later described in more detail.
I will use several abbreviations such as the following: PostScript <-> PS, procedure <-> proc, dictionary <-> dict, etc... Synonyms are employed to keep the text shorter and more readable than it might have been otherwise.
PostScript is an interpreted page description language (developed, marketed and trademarked by Adobe Systems, Inc.) whose inception initiated the series of events now referred to as the desktop publishing revolution, and is the defacto standard format (in the U.S., anyway) for complex DTP graphics. Initially it was implemented in laser printers and high-end imagesetters, allowing for a resolution-independent, graphic description format; PostScript text and graphics are easily proofed in house on a low resolution laser printer for later output at a service bureau on high resolution imagesetters. These days PS can be had in the form of low cost third party clone interpreters, available on all major computing platforms, as well as various forms of Display PostScript implemented on high- and low-end graphic workstations; while the focus of this article is on the embedded printer driving model, the techniques described herein should be adaptable to any PostScript language environment.
The language itself is an interpreted, free-binding, postfix, derivative/enhancement of the LISP concept. Interpretation is optimised through use of postfix notation (run-time syntactic analysis is minimized). Since the format is interpreted, most generated PS is in the form of ASCII text and is thus easy to examine for analysis/alteration (An aside: this may change if/when the PS level 2 binary formats are fully accepted by the marketplace; in any event, the text based format should always be available).
The dictionary data structure is central to the flexibility of the binding system. Like dictionary data structures in other environments, a PS dict is basically a grouping of key/value pairs; when a dict is accessed using a valid key, the appropriate value is returned for processing. A value can be anything recognised by the interpreter, from a number to an executable procedure body.
Dictionaries are represented to the system through a high-level stack hierarchy; during normal language operation the dict stack is searched from top to bottom (the most recently defined dict laying on the top) for every PS token identified as an executable name.
A procedure body is an array of executable objects with an executable attribute. The atomic functional objects are called operators; they are, in essence, pointers to machine code appropriate to carry out the function of the invoked operator.
Upon initialization, two dictionaries are available on the dict stack, systemdict and userdict. Systemdict contains all the pointers to the operator objects, as well as entries for a number of other internal PS objects (including an entry for itself). Userdict is storage supplied for user defined procedures and variables.
The PostScript language has not been widely marketed as a general purpose programming environment, though within the limitations of the standard job-oriented printer-embedded execution model, it is quite capable. Applications (app) that support PS generally define an app specific dictionary, to be laid over systemdict and userdict, so as to create an optimal PS printing environment for the logical model of the given application; this is further complicated by the popularity of comprehensive computing environments, such as Macintosh and Windows, which include their own custom dictionary as a channel into each respective high-level print mechanism. The intended area of focus of this article is the PS programming activity of analysis/design of a custom application printing environment through the creation/manipulation of an application dictionary.
The Problem
Assume that there is some previously defined PostScript code, in the format of an in house app-dictionary designed long ago by someone who left the company before you were hired; assume further that you have no documentation describing the various procedure definitions and, since there are hundreds of them, you dont have time to decode and memorize each one. A glitch has arisen in translating your applications graphics into PostScript (every other print format works fine), but you need coherent understanding of the questionable code in order to figure out whats going wrong. Your boss is having fits because the latest program update is two weeks late and yours is the only section not finished...
The general problem addressed by the techniques presented here is the analysis, by an arbitrary designer of PostScript output, of PostScript structures defined by complex or encrypted code for which no documentation is available. While straight PS is fairly obvious in function, including commands such as moveto, lineto and curveto, most application generated PostScript looks as if it might have been created through typing randomly.
The reason for this is simple; one of the difficulties in dealing with an interpreted language is the manipulation of copious amounts of ASCII text. If your system setup involves a computer hooked through a cable to an embedded interpreter on a laser printer (the generally accepted paradigm of PS printing), code transmission alone probably accounts for most of your print time.
To minimize transmission, PS allows for simple, efficient definition of composite functional structures, the previously mentioned procedure. A procedure body in PostScript is simply an array (or packedarray) of executable objects with an executable attribute. The app designer can easily build an optimised, frequently used, arbitrarily complex proc, send the definition down to a PS dictionary structure at the beginning of the job and later invoke it with one or more letters, significantly reducing the transmitted code (assuming of course that the proc is used multiple times - there is always give and take in the PS system design process).
While this helps cut down transmitted code, it does nothing to improve readability, damning a PostScript analyst to a fairly nightmarish experience of printing out dictionary definition code and looking up, by hand, the definitions and redefinitions of various executable objects.
A similar problem is the result of encryption; some PS dictionary definitions are scrambled by their creators in order to discourage people from looking at them. The Adobe Type 1 Font format is an example of a PS dict definition whose substance was initially encrypted for purposes of security; since the specification has been published, security is no longer a problem, but the encryption format remains since the software base recognises it as the valid data format; this includes such widely used software utilities as ATM, in addition to the internal PS type 1 character building procedure.
With the problem of redefined code, the option of spending time with printouts looking up definitions by hand, while unpleasant, is at least an option; unless access to the appropriate key and decryptor is available, no such option exists for encrypted PostScript code. Hopefully, the techniques herein can solve any problems that might arise.
General Solution
The solution lies in using the flexibility of binding and procedure definition of the PostScript environment to redefine arbitrary dictionary entities, in this case all functional entries in systemdict, to include, in each entry, new code, in addition to the previously existing value, which tracks the functional activity of the PS engine.
This flexibility is largely due to the treatment of most internally representable data, especially procedure bodies, as first class objects, a strategy shared by one of the currently popular LISPs, a language called Scheme.
One of the most interesting facets of the various forms of LISP is the ability to treat procedure definitions, basically lists of operations, as data, so that the program environment can engage in on-the-fly procedure definition/alteration.
While PostScript does not share the list data structure with LISP (and so cannot really be considered part of the LISP family), the PS procedure data structure and the typeless nature of data objects allows for a similar enough ontology that it too offers functional code altering capability; in fact, within the limitations of the PostScript environment, the self alterability of arbitrary PS procedures is more pronounced than most LISP environments because the internals of procedures can be manipulated even after parsing, without reference to any form of the defining source code. (This is obviously an ambitious claim which overtly ignores a significant discussion of the differences between the LISP interpreter/compiler and the internal PS source-code-to-machine-action facility; its still, however, quite true...)
The technique is based on simple theory. Since all key and value entries in a dictionary are first class objects, they can be extracted, after definition but before functional application to the program code, adding instructions to the value to write the dictionary operator name (the key) and a newline, upon invocation, either to a disk file or to the standard output stream. In this case, the code is added only to operators and procedures, defining a simple PS compiler which allows for the arbitrary analysis of the use of the functionality built into the primitive PostScript environment. Building on this foundation, any given redefinition of the PS environment can be tracked and observed.
By the way, the term compiler is used in its loosest sense (a program that translates data of one format to another) and does not suggest that linkable machine language object code is herein created. A possible source of confusion is that the names of the primary functions in the example code are compile1 and compile2; three levels of compilation are herein defined: altering the systemdict to write the names of the functions (compile1), altering the altered systemdict to also write the operands of the functions (compile2), and finally (the either simple or complex virtual compiler) to transform the functional activity of the PS interpreter into a readable format. The source data, to be acted upon by our virtual compiler, does not yet exist when the compiler is defined, since the compiler acts upon the internal PS dict activity, instead of a text file of source code; the text source to be analysed is parsed by the PS RIP and transformed into the source format for our compiler; the PS source code should be thought of as the potentiality of the activity which serves as the data input format for our compiler.
The standard PS execution model relies upon a number of internal data structures, including the standard input file (%stdin), the operand stack, the dictionary stack, the dictionary and the execution stack. An ASCII stream is received through %stdin from which PostScript tokens are extracted, of the various PS data types, including numbers, other literal objects, executable names, and a number of notational operators (such as { and }). Numbers and other literal objects are pushed directly on the operand stack to be used as data. Upon encounter of an executable name, the interpreter searches the dictionary stack, from top to bottom, to find a key that matches the name. If a key is found, the appropriate dict is queried and the corresponding value is returned; if executable, the action is immediately invoked by pushing the value on the execution stack; if the value is literal, it is merely pushed on the operand stack, again to serve as data.
A number of problems arise. /systemdict, in most clones and certainly in all Adobe implementations of PostScript, is a read-only dictionary structure; it cannot itself be arbitrarily changed. In addition, many applications build their dictionaries by circumventing the standard PostScript execution model in the definition of their procedures using code of the following form:
... systemdict -name- get ...
where -name- is some key in systemdict; this code directly references systemdict, extracting the value corresponding to the supplied key, accessing even executable code as data, to be manipulated by dictionary and procedure construction operators. The advantage of this approach, to a PS system designer, is speed, since procedures can be built using the value in systemdict, a pointer to the machine code, which directly executes instead of going through the usual (time-consuming) dictionary lookup. The problem is that the read-only nature of systemdict added to non-standard procedure definition creates obstacles for our intended strategy.
The first step is to create a dictionary the same size as systemdict, then copy all the key/value pairs into it, placing it on top of the dict stack (userdict cannot be removed); the value for the /systemdict entry is then replaced with a pointer to the new dictionary; a call to systemdict will be intercepted, by the new systemdict entry. A copy of (pointer to) userdict is then placed on top of the dict stack so as to completely emulate the initial environment.
When systemdict is invoked under the standard execution model, the new dictionary, with whatever redefinitions we have defined, will be invoked in place of systemdict; in addition, the above mentioned non-standard use of systemdict will also result in placement of our redefined code. (Note that the technique described herein doesnt require the explicit copying of a dict unless it is read-only, as are systemdict and the built-in font dictionaries in a printer; generally, a data structure defined in ROM on an embedded system is read-only)
Two problems exist regarding this particular solution.
First, PostScript memory, the size of the new dictionary structure, is lost through explicit creation of the new systemdict; this shouldnt be a significant problem unless the code to be analysed is particularly memory inefficient. Its mentioned here in the name of completeness.
Second, this strategy doesnt entirely solve the problem of code circumventing the standard execution model; the operators get and load are sometimes used to extract objects of operatortype for procedure construction, sometimes not; its an unfortunately gray area. If procedure bodies are being replaced by larger procedure bodies, as would be the case in redefining an application dictionary, the technique works without a hitch. If, however, operator objects are replaced by procedure bodies, as occurs in the new systemdict, there is a problem. If a procedure body is positioned in the construction of another procedure, in the place of an operator object. the result, upon execution of the new proc, will be the original proc, left on the stack without execution, possibly invoking a typecheck error and certainly destroying the intended functionality of the constructed procedure. To get around this, two extra definitions, for load and get are included, but commented out. If the problem arises, try uncommenting the appropriate proc. If that doesnt work, access thesysdict, the utilitydict entry for the original sysdict, as necessary to obtain the original operator objects, tracking the specific problem procedures in some other way.
Specific Solution
The technique is defined in two stages, the first of which involves a simple name writing operation, which redefines each functional (either procedure or operator) entry in a given dict into a procedure body including code to write the appropriate key to the output file. The second stage adds the ability to include the operands as well, so that the subsequent compiler can produce more useful information; since a newline is appended to each name in the dict, this defines the format of our output. Using this technique, every operator in any dictionary, even those involving encrypted definition, is open for examination.
A note with caution: the second stage is flexible enough to produce directly runnable code for a functional subset of PS commands, such as the graphics commands; this technique has been used to unwrap PS code that uses complex graphical calculation or extensive looping, so that a final print job, free of calculation, can be fed to disk either for transport to a high-res imagesetter, or for transformation into the Adobe Illustrator 88 file format (or something similar) as an EPS file.
Unfortunately, the code presented here is not quite robust enough to produce a runnable representation of all objects encountered in the interpreter. The cvs, or convert to string, operator is limited in function to simple objects; if it gets even mildly confused, it responds with -nostringval- or something equally unpalatable. The /writeobj procedure is designed to write out an object, if directly accessible through cvs, or a representation of the object type, for more complex things; further, it serves as a central location for modification of the representational code. The /dictdump procedure is an example of one way to modify the code in order to write out a more complex data object. Redefine /writeobj as necessary.
A further problem involves the fairly common situation where the results of a computation are offered to an operator as operand:
...variable variable add 15 moveto...
The output, if both add and moveto are being tracked, would be:
...num num add
(num+num) 15 moveto...
If an output file including this code were run as a PS file, the result of the add operation would be left on the stack, without being consumed; since many such cases are likely to exist, in any given print job, the result would be either duplicate data passed to operators or, eventually, an operand stack overflow. The strategy must, therefore, be to think of the output of this technique as data for analysis.
The Code
The simple compiler model is defined in listing one for systemdict. In order to overcome the read-only nature of systemdict, additional, clearly marked code to copy the dict is included; this should be removed if these techniques are to be applied to any other dictionary that can be directly modified (ie. is not read-only).
A utility dictionary is defined to hold all technique specific bindings. It is represented internally with the first binding, /utility.
The /outfile is a general convention in my PS designs to define a generic disk output file name; %stdout or any possible disk file can be set up as /outfile. In this case the output file is on a Macintosh hard drive, accessed from within the Freedom of Press PS emulation software; the main advantage, beside cost, to using a software PS emulator is direct access to a hard disk, which is otherwise available only with the more expensive printers and of course on Display PostScript equipped workstations.
Following the initialization of outfile is code to alter the default errorhandler to close outfile if anything goes wrong. Uncomment this if the relevent disk i/o system is not robust enough to do it for you. This is an area of limitation of the clones, such as the above mentioned Freedom of the Press which closes the file but does not then execute the appropriate error code (%stdout is partially emulated through Macintosh specific dialog boxes, and the implementation is not robust enough to absorb this minor fiddling and continue to work properly; fortunately, the implementation of the FOP file object seems to be strong enough to close the file on its own, without reference to the errorhandler fix).
Workstring is a string variable that should be long enough to accommodate any object representations likely to come along, yet not long enough to take up significant memory resources. The only limitations on the function of this variable are described below in the description of the writeobj procedure.
The comment procedure is included to allow for the binding of writestring before redefinition so it wont be repeated in the output everytime a comment is placed. Comments are primary to the utility of the technique, serving the role of transparent breakpoints in the printout.
Dostack is a destructive stack dump procedure, so that the stack can be transmitted to disk at the appropriate place in the output. Remember that it is destructive; the stack is cleared by this proc.
Makestring and writeobj are defined here for easy customization of the code.
Makestring minimizes VM consumption by creating string constants of the appropriate size (and no larger) for each key to be written to disk. The cvs operator takes an object and a string returning the substring taken up by the representation of the object. If a constant string size is used, the VM corresponding to the total difference between the sum of the strings used and the sum of the substrings is lost. By using the global variable workstring in this way, no extra memory is lost.
Writeobj is the generic write object to disk function. It is separate from the main forall loop in order to be easily modified. The structure of this procedure uses the dictionary data structure to perform the role that in a more static language would be implemented with a switch...case structure; so long as the switch can be made to revolve around name objects, returned from some PS function, the dictionary data structure is very useful for this purpose. Note that the get operator is used for name lookup instead of the standard execution model; given the generality of the subject matter of this particular program (its designed to work on the environment itself), the typename based switch mechanism might be useful for some additional purpose, so it seems best to leave that option open.
As previously mentioned, the program defined herein directly writes only simple objects, returning either a string describing the object type, if known, or -nostringval- for anything more complex. One simple object type that will not directly work with the workstring variable is a string of a size larger than the variable; the solution is to recognise strings directly, writing them to disk without conversion. Another string problem is that the character definitions for Truetype and type 1 fonts use data objects that are inaccessible to direct reading, for security purposes; in order to avoid errors, the example program checks for strings with the read-lock attribute, by using the rcheck operator, indicating them in the output stream with a string object that does not allow read access string. Both of these modifications, again, are good examples of where and how to insert code to enhance flexibility of the general technique.
/writename is a chunk of code manipulated by /compile1 to add the appropriate operations to write the arbitrary name to disk. It is never to be directly invoked and is included here as a data object that happens to be an executable procedure.
The /combineprocs procedure is similar in utility to the /concatprocs procedure defined in the green book (see bibliography); it takes two procedure bodies, returning a single synthesis including the functionality of the first proc, then the second. The main difference is that this procedure does not use the putinterval operator, allowing for a more efficient procedure definition resulting from use of the packedarray data structure, instead of the simple array; packedarray uses less memory.
The two compile procedures, /compile1 and /compile2, are the procs for use in the forall loops at the heart of the technique.
Listing 1
To create the simple virtual compiler, the forall operator, which performs some procedure for each key/value pair in a dictionary, is passed the dictionary and the /compile1 procedure which tests whether the value is an array and then whether it is executable; if both of these are the case, or if the object turns out to be of type operatortype, the object is passed to a procedure that constructs a new procedure including code to write a string of the key and a newline to /outfile, either the original procedure body or just the constituent parts and, if necessary, the /exec operator, which is required to execute a procedure embedded within another procedure. This new procedure is then stored in the new dictionary, under the old key.
The new dictionary is then stored in the utility dictionary under the name dict2. Upon completion of the forall loop, dict2 is fully capable of writing all the keys to disk, according to the outfile specification. If more information is needed, the next forall loop must be included.
Listing 2
For the second stage, to build the complex virtual compiler, code is included that takes into account the n-ary operand property of each entry in the given dictionary. For our purposes, this information is encoded in yet another dictionary structure, dict3, which is a listing of almost all the PostScript level one systemdict keys that take operands, with the corresponding number of operands defined as name objects in the corresponding value slot. This particular step required a bit of drudgery, as a listing had to be made of how many operands each dict entry takes; a number of problematic operators were left off this list, particularly those whose operand list is variable in length, since these would require custom coding for each such operator. This list can be contracted or expanded, according to need.
The utility dictionary includes the code corresponding to each of the possible operand number values, listed using the numbers themselves as keys.
In normal operation, the PS interpreter immediately checks the type of all tokens before looking anything up; if the token is a number, it is merely left on the operand stack without any reference whatsoever to the dict stack; numbers can be used as keys only if the normal execution context is circumvented using explicit dictionary manipulation operators. In this case the get operator allows numbers to be used as keys into the dictionary; note that the procedures stored as values for the number keys are not meant to be executed directly, but again are data, intended to be attached to other procedure bodies.
The second forall loop loads whatever utility dict procedure corresponds to the dict3 number name, copies the key, saves it at the bottom of the stack, then checks to see if the key is available in dict2. If so, a new array is created into which both procedure bodies are combined, so that each will be executed upon invocation. The packedarray is made executable, then stored in dict2 using the key previously saved. If the key is not known in dict2 (a circumstance which should not occur, since the list was defined by hand) an error message is sent to outfile.
One thing to note in the complex compiler building loop is that no check is made for procedure-hood, allowing this code to avoid the nested conditionals of the first forall loop. The reason for this is simple; this code depends on the data dictionary, which only contains entries for objects which, after compile1, have all been transformed into the appropriate procedure bodies.
Application of Technique
The result of all this is a redefined PS execution environment through which interpreter activity can be easily tracked. Using the flexibility designed into the language allows for analysis of arbitrary PostScript at the level of the logical engine, from which few things can hide.
First, insert the listing 1 code. Create a PostScript file by funnelling the application output intended for the printer to a disk file. Open that file with a text editor (a word processor will do, but the file must be saved as plain text or ASCII) and copy the code from listing 1 to the beginning of the PostScript file (somewhere before the definition of the application dictionary; I usually put everything at the very beginning of the file which shouldnt be a problem unless a print spooler is involved, in which case the code should be inserted after the initial round of %%comments); choose an outfile, according to your platform file protocol (if the \ character is used (as in DOS, or on the Atari ST), it must be doubled in the file naming string, \\, because the backslash is used to denote octal character definition in PS strings), filling the appropriate information into the file invoking string. If the simple compiler is all that is desired (ie. a printout of systemdict operators, without any reference to the operands) this is all the dictionary manipulation that is required; in order to create the more complex compiler model, the code from listing two, which further alters the dictionary, must also be included.
Once the appropriate compiler model is defined in the file, the appropriate commands must be included to begin dict2 and userdict (as indicated).
Finally, insert comments in the body of the PS file, in order to define reference points in the output file, and finally include the line outfile closefile in order to properly close the file. When run, this will create an outfile which includes a history of all systemdict activity in the interpreter. The appropriate comment strategy should allow anyone to analyse the innermost structure of any previously defined PostScript application dictionary.
%%
%Metaprogramming PostScript:
%A System for Arbitrary Diagnostic Analysis
%This text is Copyright 1992 Gregory Koomey.
%All rights are reserved;
%nothing herein shall be used without
%written consent from the author.
%Listing 1
%%begin utility dict definition
20 dict dup begin
/utility exch def %internal identity of dict
/outfile (Horatio:Programming:Qued/M:quark outfile) (w)
file def
%in case of error, the following code closes outfile
%/*handleerror errordict /handleerror get def
%errordict begin
%/handleerror {
%outfile closefile
%*handleerror
%} bind def
%end %%errordict
%%end of error handling code
%to keep original systemdict available
/thesysdict systemdict def
/workstring 100 string def
%some useful procedures
/comment
{outfile (Comment: ) writestring
outfile exch writestring
outfile (\r) writestring} bind def
/makestring
{workstring cvs dup length string copy} bind def
/dostack %inverted destructive stack print
{outfile (dostack: ) writestring
count
{writeobj outfile ( ) writestring}
repeat
outfile (\r) writestring} bind def
%%The following are included for the complex compiler model
/writeobj % depends on the following dictionary definition
{dup type dup writeobjdict exch known
{writeobjdict exch get exec} %
{workstring cvs outfile exch writestring pop} ifelse
} bind def
14 dict dup /writeobjdict exch def
begin
/arraytype{xcheck{ outfile (-executable-arraytype- ) writestring}
{ outfile (-arraytype- ) writestring}ifelse
} bind def
/booleantype{workstring cvs outfile exch writestring
outfile ( ) writestring} bind def
/dicttype {pop outfile (-dicttype- ) writestring} bind def
/filetype {pop outfile (-filetype- ) writestring} bind def
/fonttype {pop outfile (-fonttype- ) writestring} bind def
/integertype{workstring cvs outfile exch writestring
outfile ( ) writestring} bind def
/marktype {pop outfile (-marktype- ) writestring} bind def
/nametype {outfile (/) writestring
workstring cvs outfile exch writestring
outfile ( ) writestring} bind def
/nulltype {pop outfile (-nulltype- ) writestring} bind def
/operatortype {pop outfile (-operatortype- ) writestring} bind def
/packedarraytype {xcheck {outfile (-executable-packedarraytype- ) writestring}
{outfile (-packedarraytype- ) writestring} ifelse
} bind def
/realtype {workstring cvs outfile exch writestring
outfile ( ) writestring} bind def
/savetype {pop outfile (-savetype- ) writestring} bind def
/stringtype {dup rcheck not
{pop outfile (\() writestring
outfile (-string-with-no-read-access- ) writestring
outfile (\)) writestring
outfile ( ) writestring}
{outfile (\() writestring
outfile exch writestring
outfile (\)) writestring
outfile ( ) writestring } ifelse
} bind def
end%writeobjdict
/dictdump
{
exch writeobj writeobj
outfile (\r) writestring
} bind def
/writename
{
outfile /dummy writestring
outfile (\r) writestring
} bind def
/combineprocs %takes two procs, returns combined proc
{
mark
counttomark 2 add index
aload pop
counttomark 1 add index
aload pop counttomark packedarray cvx
4 1 roll pop pop pop
} bind def
/compile1
{
dup
type dup /packedarraytype ne
{/arraytype eq}
{pop true} ifelse
{
dup xcheck
{
dup rcheck
{
exch dup 3 1 roll makestring
/writename load 1
3 2 roll put
/writename load exch combineprocs
2 index 4 1 roll put
}
{
/outfile load
3 -1 roll dup 4 1 roll makestring
/writestring load
/outfile load
(\r)
/writestring load
7 -1 roll
thesysdict /exec get
8 packedarray
cvx
3 -1 roll dup 4 2 roll
put
} ifelse
}
{pop pop} ifelse
}
{
dup type /operatortype eq
{
/outfile load
3 -1 roll dup 4 1 roll makestring
/writestring load
/outfile load
(\r)
/writestring load
7 -1 roll
7 packedarray %
cvx
3 -1 roll dup 4 2 roll
put
}
{pop pop}
ifelse
}
ifelse
} bind def
/compile2 {
numberdict exch get exch
dup 3 1 roll %copy of key at bottom of stack
dup dict2 exch known
{
dict2 exch get
combineprocs
dict2 3 1 roll put
}
{
workstring cvs outfile exch writestring
outfile ( ... is not known in dict\r) writestring
pop
pop
}
ifelse
} bind def
%%end of utility dict definition
(end of systemdict listing) comment
%%beginning of code specifically for duplication of systemdict
systemdict length dict dup /dict2 exch def%the arbitrary name used here
for our dict is /dict2
systemdict {3 -1 roll dup 4 2 roll put} forall %
dup
dup /systemdict exch put %redefine systemdict entry
%two special operation definitions which may or may not be necessary
%dup
%/load %offered a key as operand
%{dup thesysdict exch known
%{thesysdict exch dup 3 1 roll get
%type /operatortype ne
%{load} if
%}
%{load} ifelse
%} bind put
%dup
%/get %offered a dict and key as operand
%{ exch dup 3 1 roll
%systemdict eq
%{thesysdict exch dup 3 1 roll get
%type /operatortype eq
%{exch pop}
%{get} ifelse
%}
%{get} ifelse
%} bind put
%%end of code specifically for duplication of systemdict
dup
{compile1}
forall
pop
% code to list dict2 in outfile as /name object/type
(the following is a listing of dict2) comment
systemdict
{dictdump}
forall
(end of listing of dict2) comment
6 dict dup /numberdict exch def begin
/1 {
dup writeobj outfile ( ) writestring
} bind def
/2 {
2 copy exch writeobj outfile ( ) writestring
writeobj outfile ( ) writestring
} bind def
/3 {
3 copy 3 -1 roll writeobj outfile ( ) writestring
exch writeobj outfile ( ) writestring
writeobj outfile ( ) writestring
} bind def
/4 {
4 copy 4 -1 roll writeobj outfile ( ) writestring
3 -1 roll writeobj outfile ( ) writestring
exch writeobj outfile ( ) writestring
writeobj outfile ( ) writestring
} bind def
/5 {
5 copy 5 -1 roll writeobj outfile ( ) writestring
4 -1 roll writeobj outfile ( ) writestring
3 -1 roll writeobj outfile ( ) writestring
exch writeobj outfile ( ) writestring
writeobj outfile ( ) writestring
} bind def
/6 {
6 copy 6 -1 roll writeobj outfile ( ) writestring
5 -1 roll writeobj outfile ( ) writestring
4 -1 roll writeobj outfile ( ) writestring
3 -1 roll writeobj outfile ( ) writestring
exch writeobj outfile ( ) writestring
writeobj outfile ( ) writestring
} bind def
end%numberdict
%Listing 2
165 dict dup
/dict3 exch def
begin
%math ops
/add /2def
/div /2def
/idiv /2def
/mod /2def
/mul /2def
/sub /2def
/abs /1def
/neg /1def
/ceiling/1def
/floor /1def
/round /1def
/truncate /1def
/sqrt /1def
/atan /2def
/cos /1def
/sin /1def
/exp /2def
/ln/1 def
/log /1def
/srand /1def
%array ops
/array /1def
/length /1def
/get /2def
/put /3def
/getinterval/3 def
/putinterval/3 def
/astore /1def
/aload /1def
/copy /2def
/forall /2def
%packedarray ops
/packedarray/1 def
/setpacking /1 def
%Dict ops
/dict /1def
/maxlength/1def
/begin /1def
/load /1def
/known /2def
/where /1def
/copy /2def
/dictstack/1def
%string ops
/string /1def
/anchorsearch /2def
/search /2def
/token /1def
%boolean ops
/eq/2 def
/ne/2 def
/ge/2 def
/gt/2 def
/le/2 def
/lt/2 def
/and /2def
/not /1def
/or/2 def
/xor /2def
/bitshift /2def
%control ops
/exec /1def
/if/2 def
/ifelse /3def
/for /4def
/repeat /2def
/loop /1def
/stopped/1def
/execstack/1def
%type ops
/type /1def
/cvlit /1def
/cvx /1def
/xcheck /1def
/executeonly/1 def
/noaccess /1def
/readonly /1def
/rcheck /1def
/wcheck /1def
/cvi /1def
/cvn /1def
/cvr /1def
/cvrs /3def
/cvs /2def
%file ops
/file /2def
/closefile/1def
/read /1def
/write /2def
/readhexstring /2def
/writehexstring /2def
/readstring /2 def
%/writestring /2def %might duplicate output
/readline /2def
/token /1def
/bytesavailable /1def
/flushfile/1def
/resetfile/1def
/status /1def
/run /1def
/print /1def %might duplicate output, depending on setup...
%VM ops
/restore/1def
%gstate ops
/setlinewidth /1def
/setlinecap /1 def
/setlinejoin/1 def
/setmiterlimit /1def
/setdash/2def
/setflat/1def
/setgray/1def
/sethsbcolor/3 def
/setrgbcolor/3 def
/setscreen/3def
/settransfer/1 def
%coord system and matrix ops
/identmatrix/1 def
/defaultmatrix /1def
/currentmatrix /1def
/setmatrix/1def
/translate/2def %sometimes /3
/scale /2def %sometimes /3
/rotate /1def
/concat /1def
/concatmatrix /3def
/transform/2def%sometimes /3
/dtransform /2 def%sometimes /3
/itransform /2 def%sometimes /3
/idtransform/2 def%sometimes /3
/invertmatrix /2def
%path construction
/moveto /2def
/rmoveto/2def
/lineto /2def
/rlineto/2def
/arc /5def
/arcn /5def
/arcto /5def
/curveto/6def
/rcurveto /6def
/charpath /2def
/pathforall /4 def
%paint
/image /5def
/imagemask/5def
%device setup and output
/banddevice /4 def
/framedevice/4 def
/renderbands/1 def
%character and font
/definefont /2 def
/findfont /1def
/scalefont/2def
/makefont /2def
/setfont/1def
/show /1def
/ashow /3def
/widthshow/4def
/awidthshow /6 def
/kshow /2def
/stringwidth/1 def
%font cache ops
/setcachedevice /6def
/setcharwidth /2def
/setcachelimit /1def
/setcacheparams /3def
%stack manipulation ops
/pop /1def
/exch /2def
/dup /1def
/copy /2def
/index /1def
/roll /2def
%needs to be at end of definition of dict
/bind /1def
/def /2def
end %dict is in utility dict under dict3
%
(end of dict3 definition\r) comment
dict3 %procedure requires that dict to be modified
%is in utility as dict2
{compile2}
forall
(this is the end of the dict redefinition...\r) comment
%Systemdict Finish
dict2 begin
userdict begin
(the following is a listing of dict2, after n-ary stuff added) comment
systemdict
{dictdump}
forall
(end of listing of dict2, after n-ary stuff added) comment
(this is the beginning of redefined environment...\r) comment
%%
Book list:
PostScript Language Reference Manual, second edition Adobe Systems Inc., Addison-Wesley, 1985. The Red Book is the standard reference material for all implementations up to and including Level 2 and Display PostScript.
PostScript Language Tutorial and CookBook, Adobe Systems Inc., Addison-Wesley, 1985. The Blue Book is the official Adobe tutorial.
PostScript Language Program Design, Adobe Systems Inc., Addison-Wesley, 1988. The Green Book is an advanced technique manual geared toward the design of PS printer drivers.
Real World PostScript, ed. Stephen F. Roth, Addison-Wesley, 1988. This is the most interesting of the lot, consisting of essays and code by various (non-Adobe) PS professionals; particularly interesting is the essay PostScript as a Programming Language, by Bill Woodruff.
Programming the LaserWriter, David Holzgang, Addison-Wesley, 1991. Custom program the Macintosh LaserWriter driver with Think C object system.
Inside PostScript, Frank Braswell, Systems of Merritt & Peachpit Press, 1989. In depth reference describing QMS-PS 800 true Adobe PostScript environment; Nuts and bolts.
Encapsulated PostScript - Application Guide for Macintosh and PCs, Peter Vollenweider, Prentice-Hall UK, 1990. Fairly useful PS/EPS interchange information regarding specific applications.
Online resources:
Adobe forum on Compuserve (go Adobe) - If you have complex questions, ask them here.
PostScript roundtable on Genie (psrt) - This is somewhat hobbyist oriented, but includes an extensive file library of programming examples.
Glossary:
Apple Computer - if you dont know what this is you shouldnt be reading this magazine
Adobe, Inc. - Company that created and markets the PostScript language and related software products
Display PostScript - screen display oriented dialect of PostScript found in NeXT and Sun workstations
Epson - printer manufacturer
DTP - desktop publishing
ASCII text - plain text without any control codes that comes out of programmers editors
postfix notation - operands precede operator: 5 10 mul results in 50
stack - last in, first out data structure
operand stack - primary stack of PostScript environment, upon which operators recieve and return data
dictionary - collection of key/value pairs
dictionary stack - dictionary objects are activated by placing them on this stack
array - random access data structure
procedure body - array of objects with executable attribute corresponding to stream of code
font - dictionary of procedures specifically oriented toward the setting of text
type 1 font - font using efficient low level details of Adobe PS environment
type 3 font - font built around standard PostScript model
truetype font - font designed for use in Mac and Windows environments for both screen and printer
systemdict - dictionary in PS environment from which all built in operators are invoked; read-only and irremovable
userdict - writeable dictionary in default PS environment
EPS file - encapsulated PostScript code that is written within certain limitations, sometimes with an embedded preview graphic