Escolar Documentos
Profissional Documentos
Cultura Documentos
Thinking in
PostScrıpt
Thinking in
PostScrıpt
Glenn C. Reid
All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or
transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise,
without the prior written permission of the publisher. Printed in the United States of America. Published
simultaneously in Canada.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book and Addison-Wesley was aware of a trademark
claim, the designations have been printed in initial capital letters.
The name PostScript® is a registered trademark of Adobe Systems Incorporated (Adobe). All instances
of the name PostScript in the text are references to the PostScript language as defined by Adobe unless
otherwise stated. The name PostScript also is used as a product trademark for Adobe’s implementation of
the PostScript language interpreter.
Any references to a PostScript printer, a PostScript file, or a PostScript driver refer to printers, files, and
driver programs (respectively) that are written in or that support the PostScript language.
ABCDEFGHIJ-MW-943210
First printing, September, 1990
vi :
Table of Contents
Preface......................................................................... xi
Chapter 1
PostScript as a Programming Language .................. 1
DESIGN FEATURES............................................................................2
STRUCTURED PROGRAMMING TECHNIQUES ...............................3
PROGRAMMING TASKS.....................................................................4
WINDOW SYSTEMS, COMMUNICATIONS, AND DISPLAYS............4
DATA STRUCTURES AND ALGORITHMS .........................................6
CONCLUDING THOUGHTS ................................................................6
EXERCISES .............................................................................. 7
Chapter 2
PostScript is Not Like C.............................................. 9
COMPARISON OF LANGUAGE MECHANISMS...............................11
EXPRESSING AN ALGORITHM AS A PROGRAM ...........................12
THE UNIX SHELL AND OPERATING SYSTEM................................13
INPUT, OUTPUT, AND THROUGHPUT ............................................14
v
CONCLUDING THOUGHTS ..............................................................15
EXERCISES............................................................................. 16
Chapter 3
Foundations............................................................... 17
POSTSCRIPT LANGUAGE SYNTAX ................................................19
SIMPLE PROGRAM STRUCTURE....................................................20
SETTING UP TEMPLATES................................................................24
DECLARING AND USING VARIABLES.............................................26
ALLOCATING MEMORY....................................................................29
GETTING MEMORY BACK................................................................31
OPENING AND CLOSING FILES ......................................................31
COMPARISONS AND EQUALITY OF OBJECTS..............................32
CONCLUDING THOUGHTS ..............................................................34
EXERCISES............................................................................. 34
Chapter 4
Some Typical Programs ........................................... 37
A TYPICAL PAGE DESCRIPTION PROGRAM .................................38
FONT PROGRAMS............................................................................40
PROGRAMS THAT READ DATA.......................................................42
QUERY PROGRAMS.........................................................................43
ENCAPSULATED POSTSCRIPT PROGRAMS.................................43
PERSISTENTLY RESIDENT PROGRAMS .......................................46
CONCLUDING THOUGHTS ..............................................................49
EXERCISES............................................................................. 49
Chapter 5
Understanding the Stack .......................................... 51
A QUICK OVERVIEW OF DATA TYPES ...........................................51
vi Table of Contents
NAME LOOKUP .................................................................................53
HOW OPERATORS USE THE STACK..............................................53
GROUPING AND VISUAL CHUNKING .............................................56
THINKING BACKWARD AND SIDEWAYS ........................................58
COMPOSITE OBJECTS ....................................................................61
THE OTHER STACKS .......................................................................62
CONCLUDING THOUGHTS ..............................................................63
EXERCISES ............................................................................ 64
Chapter 6
Trusting the Stack ..................................................... 67
SAFETY OF DATA ON THE STACK .................................................68
WHERE ARE THE DATA GOING? ....................................................69
REARRANGING THE STACK............................................................71
CONDITIONALS AND LOOPS...........................................................73
RECURSION AND LOCAL VARIABLES............................................76
CONCLUDING THOUGHTS ..............................................................77
EXERCISES ............................................................................ 78
Chapter 7
Building Conditional Statements ............................. 79
SIMPLE CONDITIONALS ..................................................................80
SETTING UP THE CONDITION.........................................................81
CONDITIONALS ARE NOT MAGIC...................................................86
NESTED CONDITIONALS AND ELSE CLAUSES ............................87
COMPOUND CONDITIONALS ..........................................................88
CONCLUDING THOUGHTS ..............................................................90
EXERCISES ............................................................................ 91
Chapter 9
Procedures .............................................................. 105
WHAT EXACTLY IS A PROCEDURE?............................................106
PARAMETER PASSING ..................................................................108
CONSTRUCTING GOOD PROCEDURES ......................................111
SELF-MODIFYING PROCEDURES.................................................114
CONCLUDING THOUGHTS ............................................................116
EXERCISES........................................................................... 117
Chapter 10
Using Dictionaries................................................... 119
DICTIONARIES FOR NAME SCOPING ..........................................120
LOCAL DICTIONARIES ...................................................................121
GLOBAL DICTIONARIES OF PROCEDURES ................................123
MAINTAINING THE DICTIONARY STACK......................................124
INTO AND OUT OF DICTIONARIES ...............................................126
LOOKING INTO DICTIONARIES .....................................................127
REDEFINING OPERATORS............................................................130
CONCLUDING THOUGHTS ............................................................132
EXERCISES........................................................................... 133
Chapter 12
Storing and Using Data........................................... 145
CLASSICAL DATA STRUCTURES..................................................151
CONCLUDING THOUGHTS ............................................................156
EXERCISES .......................................................................... 156
Chapter 13
Program Data and Instructions .............................. 157
TURNING DATA INTO INSTRUCTIONS .........................................159
TURNING INSTRUCTIONS INTO DATA .........................................161
DATA CONVERSIONS ....................................................................163
CONCLUDING THOUGHTS ............................................................165
EXERCISES .......................................................................... 165
Chapter 14
Using Files and Input/Output Techniques ............ 167
OPENING AND CLOSING FILES ....................................................170
READING AND WRITING FILES .....................................................172
Table of Contents ix
WRITING FORMATTED DATA TO FILES .......................................175
FILE STATUS INFORMATION.........................................................178
RANDOM VERSUS SEQUENTIAL ACCESS ..................................179
CONCLUDING THOUGHTS ............................................................180
EXERCISES........................................................................... 180
Appendix
Answers to Exercises ............................................. 185
x Table of Contents
Preface
xi
language. If you think of yourself as an expert PostScript programmer,
you will still find some useful techniques in this book, and it is a very
worthwhile addition to your library of PostScript books, since it overlaps
very little with existing material.
The most fundamental level of a program is the set of techniques used in
the development of the program itself, regardless of what the program
does. This book addresses that area of PostScript programming in a way
rooted partly in computer science theory, encompassing algorithms and
data structures, but it is very practically oriented toward real-world
programming and programmers.
Each chapter contains a section of Exercises. You should find these
problems easy to solve if you have understood the concepts in the chapter.
Many concepts do not gel fully without some direct application to a
problem. In these cases, the exercises should stimulate you to think about
the issues that have been presented and give you a chance to confront a
situation that requires you to apply your knowledge. The appendix
contains answers to the exercises.
To give you a feel for what this book is about and to whet your appetite,
the following code segment illustrates a common (though advanced)
technique that is used in many PostScript programs. If you instantly
recognize what it does and could have written it yourself, then you may
use this book to discover other techniques that you may not have yet
encountered. If this example seems mysterious or you have to spend a lot
of time analyzing it, then you need this book to help you start thinking this
way.
% print out all the elements in an array or procedure
dup type /arraytype eq 1 index type /packedarraytype eq or { %ifelse
0 exch { %forall
exch dup (XXX) cvs print (: ) print 1 add exch ==
} forall pop
}{ pop } ifelse
xii Preface
In order to get the maximum benefit from this book, you probably should
have written at least one program—from scratch—longer than 100 lines
of code (and gotten it to work correctly). This program might have been
written in virtually any programming language. If you then modified the
program for an additional 100 hours after it first started to work, if you
understand variables, arrays, and strings—and if you want to become a
very good PostScript programmer—then you are an ideal candidate for
this book.
Preface xiii
xiv Preface
Chapter 1
PostScript as a
Programming Language
1
Every programming language was designed with a specific purpose, even
if that purpose was to be a “general-purpose programming language.” To
use a language effectively, you should understand its design and learn its
strengths and weaknesses. PostScript is a full-fledged programming
language, and it is possible to accomplish almost any task with it. It
includes many high-level language constructs and data structures. The
PostScript language is a tool kit filled with hundreds of special-purpose
tools. Mastery of the language teaches you to select just the right
instrument, swiftly and instinctively, and to use it effectively.
DESIGN FEATURES
If you have had any formal training in programming, you are familiar
with the various structured programming techniques, up through and
including object-oriented programming. These techniques vary, but are
largely based on the concept of a procedure, or a modular functional unit
(sometimes called a black box). A black box is typically any set of
operations that has a specific input and output and performs a single task.
The mechanics of the operation are hidden from one who uses the black
box. This is the notion behind writing procedures, creating software
libraries, object-oriented programming, block-structured code; it is a
widely recognized technique in computer science.
To start out, PostScript simply isn’t well suited to structured
programming, and if you try too hard to formalize the interfaces, declare
local variables, and observe many tried and true programming practices,
you may end up with a complicated, inefficient, and ill-designed program.
On the other hand, if you ignore structured programming techniques and
write code off the top of your head, you will probably have something
worse. The trick is to borrow enough of the useful concepts from
structured programming to make programs readable, easy to maintain,
easily debugged, and efficient, while using the expressive power of the
language to your advantage.
There is no concept of goto in the PostScript language, or even explicit
control transfer, so you don’t have to worry about that particular dogmatic
concern. You do have to be careful about procedure-calling interfaces,
where they exist, since all program checking is done at run-time.
Many of the concepts in structured programming can be used quite
effectively in PostScript. You can define procedures to help you share
similar code across parts of your program. You can use the operand stack
for your temporary data rather than having many global variables. You
can design interfaces between components of your program and pass
parametric information on the stack. You can even define functions that
return results onto the stack for use by other procedures.
CONCLUDING THOUGHTS
The next several chapters help you build some solid approaches to
PostScript data structures and help you master the language elements
thoroughly so that you will feel comfortable implementing your favorite
algorithms. In particular, the data types unique to PostScript are explored
(dictionaries and names) as well as traditional data types such as strings,
arrays, and numbers. Other chapters are devoted entirely to seemingly
simple things like the ifelse operator or looping. However, since
PostScript was not originally designed for heavy programming, these
traditional programming techniques have never been thoroughly
addressed. And you simply can’t write a serious PostScript program
without using ifelse or writing a procedure or two.
Each chapter in this book comes with some exercises at the end of it, to
help you to test your understanding of the concepts presented. Since this
first chapter has taken a philosophical overview of PostScript as a
programming language, the exercises are more like essay questions than
problems.
1. Why do you want to write a PostScript program? (This is a serious
question, and one which should be given some thought; reflection on
why may provide you with good horse sense throughout your pro-
gramming experience.)
2. Why is PostScript a “backwards” language, in which the operators
come after the operands?
3. Why is PostScript an interpreted language?
4. Why is it silly to write fractal programs in PostScript?
9
understand the differences rather than to take the posture that it’s just
another language.
In a procedural language, the interface between components of a program
is formalized in the notion of a procedure call. (For more information
about procedures, see Chapter 9). The program is typically written as a
series of modular procedures or functions, with a well-defined calling
mechanism for transferring control and passing data between the
modules. This calling mechanism is artificial, and is apparent only at the
programming language level. When compiled, the data (or pointers to the
data) are in fact placed on the operand stack of the hardware processor,
and an instruction to jump to a subroutine (JSR) is issued to transfer
control to the other procedure, which then uses the data on the stack. The
notion of type checking is carried out at the language level by requiring
that the data be declared to be of an explicit type as the data are being
passed to or from a procedure or function.
When writing C programs, the programmer designs the modules, the
interfaces, and the interaction between them. The data structures are
designed to work cleanly within the modular design of the program, and
the parameters to the various procedures are set up to pass data
appropriately between the modules or procedures. Data types and
structures should be appropriate for the task at hand; some of them are
made globally visible (and therefore can be used throughout the entire
program); others are local to a particular procedure (and can be used only
within that procedure). A fairly high percentage of coding time is spent
just selecting names for data types, structure elements, procedures,
variables, and modules. The names given to these program elements
contribute heavily to the readability and maintainability of the code.
The PostScript language is an interesting hybrid between high-level and
low-level languages. It is high level in the sense that there are individual
operators that encompass very powerful functionality (the show operator,
for example), yet the mechanics of the language are less structured than
other high-level languages, and the manipulation of objects on the
operand stack more closely resembles assembly-language programming.
TIP Here’s a trick that you may find very helpful in assimilating the large
space of operators in the PostScript language. (It evolved from my mech-
anism for learning about UNIX.) If you are having a difficult time with a
particular part of your program or you think to yourself that there must be
a better way, the chances are that the designers of the language encoun-
tered the same situation, and designed an operator that exactly solves the
problem. Quickly review a summary list of operators (see the Appendix)
to see if there is some other operator that could make short work of your
task.
A program is usually only as good as the data you put through it. Most
programs do not come with data already in them, you have to provide the
data, and wait for the program to provide output based on the input. This
is also true with PostScript, although the input and output may depend on
the environment in which the program operates. For example, a
PostScript program is often just a vehicle for printing or displaying
information. In this sense, the data are provided en masse from a
document, and the output of the program is simply the display or printing
of the data in the document.
The input and output of a program depend greatly upon the environment
in which the program is running. If your PostScript program is running on
an interpreter built into your laser printer, there are few choices for input
and output. The input can either come from the cable leading into the laser
printer (which is also where the program comes from, typically) or it can
come from an internal disk system built into the printer. The choices of
output are the same, although the output can also be the printed page itself
CONCLUDING THOUGHTS
EXERCISES
Foundations
17
seem a little light-hearted in places, but is an attempt to accurately
identify the learning curve and problem areas that real people encounter.
18 Chapter 3: FOUNDATIONS
14. Debug the new feature thoroughly, getting it to work well with
the existing program.
15. Go back to Step 10 and repeat through Step 15 indefinitely
Steps 1 through 9 in this list represent the learning curve for a new
language or system, before you have become comfortable with the
process. Steps 10 through 15 comprise one view of the basic software
cycle for a single-person programming project (without a lot of advance
planning, which is typical of most programming projects).
The software cycle is very important to consider because it very much
affects the quality of the programs you write. You may adopt techniques
during your learning phase that lead to very poor quality programs, but
you may not even know it. You may introduce fundamentally bad design
or deeply-rooted bugs during the development process that could have
been prevented had you designed the entire program at the beginning, or
if your techniques had been sound enough to prevent cancer in the core of
your software.
Good programming skills will make your PostScript programming task
much more successful and enjoyable. Some of the techniques presented in
this and subsequent chapters should strike a chord with you and help you
to develop a sound methodology of your own.
Chapter 3: FOUNDATIONS 19
be all you need to avoid syntactic problems when you are writing
PostScript programs.
Here are some general rules of thumb to help you with PostScript
language syntax. These rules should help you avoid common pitfalls.
• Make sure all your delimiters match; for instance, { }, ( ), and [ ].
Using good indentation style will help you avoid the problems of
mismatched or missing delimiters.
• Use parentheses for string delimiters (rather than quotation
marks, as used in other languages). If your string contains
parentheses or backslashes, quote them with a backslash, as in the
following string:
( this string contains two parens \) and \( a backslash \\ )
• Always use a slash that leans to the right for literal names, as in
/Palatino-Roman or /MyEncoding. There is nothing special
about the slash, it just keeps the name from getting executed
immediately. Instead, it lets the name be put on the operand stack.
• Remember that the operators always come after the operands
(postfix notation). The order of the operands on the stack makes
the rightmost operand the one at the top of the stack (closest to
the operator itself).
• Spaces, tabs, and newlines are all treated as white space. Use
them wisely to make your program layout more readable.
• Anything following a % (through the end of the line) is treated as
a comment, unless the % is inside a string body (in parentheses).
In practice, you will probably not encounter very many syntax errors. It is
much more likely that the execution of your program will go astray with a
typecheck or stackunderflow error, indicating that you gave the wrong
arguments to some operator. If you can look at the contents of the operand
stack when the error occurs, you will usually be able to find the problem
quickly.
20 Chapter 3: FOUNDATIONS
you don’t have to declare procedures in any particular order. The only
checking is done when you run the program. If it works, it works. If it
doesn’t, you will get an error message explaining why it doesn’t work.
However, since most people who read your program (including you, most
likely) are used to seeing some structure in code, there is a strong case for
structuring your program carefully. If nothing else, it will help you to
maintain the program, to update it and revise it as necessary.
Chapter 3: FOUNDATIONS 21
definitions at the beginning of the program. An exception to this rule is
that if a procedure requires some data structures for its operation but they
must be defined outside the procedure, it is often best to group them with
the procedure, to make the program easier to maintain (see Example 3.3).
Indentation Style
The single most important step you can take to make your program
readable is to adopt a good indentation style. The PostScript language is
very unstructured, and without proper indentation, it can be made quite
impossible to read or maintain.
There are a few basic rules of indentation that you should observe, based
on the structure of your program:
• Wherever one part of your program is contained within some
clear beginning and ending constructs (such as gsave and
grestore, begin and end, or curly braces), you should indent all
of the included program text an equal amount.
22 Chapter 3: FOUNDATIONS
• Use indentation to make it easy to find the beginning and ending
of any matched construct visually.
• Be careful that adding and deleting lines from inside a structural
element does not disturb the structure or readability of the
program.
The indentation style used in the examples in this book is one developed
over several years of maintaining programs, and it works well. There are
other approaches, of course. The most common alternative to the method
used in this book is to move the opening delimiters to a new line; this
provides a much better balance, visually, to the delimiters, but it costs an
extra line in the program (see Example 3.4). Extra lines don’t consume
much space, but editing a program is much easier when you can see more
of the program at once in the screen of your text editor.
It is also a good idea to indent other program elements that have distinct
beginning and endings, such as begin and end with dictionaries, save and
restore, gsave and grestore, [ and ]. Example 3.5 shows some of these
indentation styles in a dummy program.
Chapter 3: FOUNDATIONS 23
Example 3.5: Indentation Style for Dictionaries and Structure
/mydict 24 dict def
mydict begin
/draft % - draft -
{ %def
/saveobj save def
500 600 moveto 10 rotate
/Helvetica 24 selectfont
0.5 setgray
gsave
(DRAFT) show
grestore
0 setgray 1 setlinewidth
(DRAFT) false charpath stroke
saveobj restore
} bind def
end %mydict
SETTING UP TEMPLATES
24 Chapter 3: FOUNDATIONS
in place, you can then come back to fill it in. The blank lines in the
templates are where you add code later.
end %mydict
% procedure template:
/newline % - newline -
{ %def
% this procedure will simulate “newline”
% on a line printer
} bind def
% conditional template:
currentgray 0.5 gt { %ifelse
}{ %else
} ifelse
% loop template:
0 10 360 { %for
} for
% combination template:
/mydict 24 dict def
mydict begin
/newline % - newline -
{ %def
currentpoint pop 550 gt { %ifelse
}{ %else
0 10 360 { %for
} for
} ifelse
} bind def
end
Chapter 3: FOUNDATIONS 25
Templates aren’t just a learning tool, they are sound practice. The very
best programmers use this technique every day, whether they are writing a
simple example program for a book on PostScript or developing a quick
hack to turn off the start page on the printer. Using templates will save
you enormous amounts of time in the long run, and it is a highly
recommended technique. If you learn nothing else from this book, please
develop a habit of laying out templates as you program. Close those curly
braces right after you open them, then go back and fill them in. Or, if you
hate to use templates, at least develop your own techniques for laying out
and maintaining the structure and indentation of the program source.
26 Chapter 3: FOUNDATIONS
Example 3.7: Declaring Variables
/LeftMargin 72 def
/TopMargin 792 72 sub def
/DocumentName (Thinking in PostScript) def
/ScaleFactor 300 72 div def
/DefaultFont /Courier findfont 12 scalefont def
In order to reference a variable, you simply use its name (without the
slash). Whenever you use the name, the value will be looked up and
executed. As long as the variable you have defined is not a procedure, the
value of the variable will placed on the operand stack. In Example 3.8 you
can see all the variables from Example 3.7 being used in some typical
situations.
Chapter 3: FOUNDATIONS 27
Example 3.10: Arithmetic in PostScript
/grade test1 test2 add test3 add final add 4 div def
/counter counter 1 add def
28 Chapter 3: FOUNDATIONS
Notice that all of the variables for margins and leading are used with the
double slash notation inside the procedure body. The names are there for
you to look at when you are editing the program, but when it is executed,
they are replaced by their numeric values inside the body of the procedure
before the procedure is ever encountered. In fact, if you look at the
procedure body on the stack right before bind is executed, you will see a
procedure that looks quite a bit different than the one in your source
program. (For instance, compare Example 3.11 and Example 3.12).
NOTE: The // syntax for immediate name lookup is not available in the
very first implementations of a PostScript interpreter, which are those
with version numbers below 25.0 from Adobe Systems Incorporated.
ALLOCATING MEMORY
There are two ways that memory gets allocated in a PostScript program. It
is either allocated implicitly when an object is created or it is allocated
explicitly by using one of a handful of operators that create empty data
structures of a particular size. See Table 3.1 for a list of operators that
explicitly allocate memory for data storage.
Chapter 3: FOUNDATIONS 29
Table 3.1: Operators that Explicitly Allocate Memory
Arguments Operator Action
int array array create array of length int
int dict dict create empty dictionary with
capacity for int elements
± matrix matrix create identity matrix
any0 ... anyn-1 int packedarray packarr
create packed array consisting
of specified int elements
int string string create empty string of length int
The operators in Table 3.1, with the exception of matrix, all take an
integer argument specifying exactly how large a data structure to allocate.
The matrix operator always creates an array with six elements, and is
equivalent to the code sequence [ 1 0 0 1 0 0 ].
There are also a number of ways to allocate memory implicitly,
sometimes without even realizing it. In general, whenever a composite
object is created, memory must be allocated to store its contents. There
are a few operators that create composite objects implicitly, and a few bits
of PostScript language syntax that will cause a composite object to be
created. These operations are summarized in Table 3.2.
The most common of these methods for implicitly allocating memory are
procedure bodies and strings. The latter would literal strings, if they are
created by the PostScript interpreter when it recognizes a string
represented with the ( ) notation.
A composite object, whether created explicitly or implicitly, uses memory
in proportion to its size. For example, when you create a dictionary object,
a certain amount of memory is allocated for each entry in the dictionary,
and the total amount allocated depends on how many empty slots are in
the dictionary to be created. Similarly, a string object will require more
30 Chapter 3: FOUNDATIONS
memory if the string is a long one. In either case, the memory allocated is
exact, and should be predictable. A string that is thirty bytes long requires
exactly ten bytes more storage than a string that is twenty bytes long.
This discussion does not cover the precise details of memory allocation,
but it does provide a pretty good framework for understanding how
memory is consumed in a PostScript program. If you are concerned about
gaining precise control over memory allocation, it is best to get specific
documentation for the interpreter that you are using.
Once you have allocated memory in your program, either by creating data
structures or by executing some code that uses memory, there are only
two ways to reclaim that space:
• Use the save and restore operators to reclaim all memory used
since the last save was executed.
• Use the undef, undefinefont, and vmreclaim operators available
in the Display PostScript extensions to the PostScript language
(not available in all products).
When using save and restore, you have to plan carefully where you want
to restore the contents of memory, because the save/restore mechanism
also affects other aspects of the interpreter, including the current graphic
state, the current device, and so on. In a typical page description, save and
restore may be done at each page boundary or around each page element
(like a block of text or a photographic image).
Chapter 3: FOUNDATIONS 31
COMPARISONS AND EQUALITY OF OBJECTS
PostScript has several distinct data types and is fairly strict about type-
checking. However, you can easily convert one type to another, assuming
that the type conversion makes some sense, and you can then compare
them. (See Data Conversions in Chapter 13 for a discussion of type
conversions.) In many instances, the PostScript comparison operators will
even do this for you as a convenience. Strings and names, for example,
are both simply composed of bytes, so they can easily be compared, as
shown in Example 3.13.
32 Chapter 3: FOUNDATIONS
/show where { %if
systemdict eq % true (if “show” has not been redefined)
} if
5 dict 5 dict eq % false (dictionaries created independently)
As you may have noticed upon careful reading of Example 3.13, some
composite objects (dictionaries, procedures, and arrays) get a little bit
tricky when you compare them. In particular, when you use, say, the eq
operator, you are comparing the objects themselves, not the structure to
which they point. Composite objects represent a structure that is stored in
memory somewhere. If you create two different structures, even if they
happen to contain the same elements, the structures themselves are
independent, and therefore the objects that represent them are not
considered to be equal. However, if you have two copies of the same
composite object, the eq operator will confirm that they are in fact equal.
Chapter 3: FOUNDATIONS 33
If you really need to find out if two independently formed composite
objects are equivalent, you can compare their contents. If their contents
are exactly equal and the lengths of the two composite objects are equal,
then you can consider the two composite objects to be equivalent, if not
equal. In Example 3.14 is a procedure definition called arrayeq that will
compare two arrays, returning true if their contents and lengths are equal.
CONCLUDING THOUGHTS
EXERCISES
1. Syntax errors arise very infrequently in PostScript, but when they do,
it can be difficult to track them down.
a.Find the syntax error in the following program segment:
0 1 20 { ( Hello world (part 1) == flush } for
34 Chapter 3: FOUNDATIONS
2. Create a simple procedure named line that takes four operands, the
x, y location for the beginning of the line and the x, y location for the
end of the line. The procedure should perform the necessary moveto,
lineto, and stroke operations. Use your procedure once or twice to
create some sample lines.
3. Create a template for a procedure that contains an ifelse statement.
4. Design a procedure called ++ that will increment a variable. The pro-
cedure should be able to increment any variable that contains an inte-
ger or a real value. Design it so that the following sample will add 1 to
the value of the variable named counter each time it is called:
/counter 0 def
/counter ++
/counter ++
counter == % should be 2
Chapter 3: FOUNDATIONS 35
36 Chapter 3: FOUNDATIONS
Chapter 4
37
problems faced may differ dramatically between, say, a font program and
an Encapsulated PostScript program (which is explained later in this
chapter).
output page
There are several things that affect the programming style in page
descriptions. First, there is usually a great deal of structure in the program,
and in fact the whole program can be thought of as a document containing
pages, illustrations, and so on. In Example 4.1, the structure is delineated
according to document structuring conventions, where comments like
%%EndProlog mark the boundaries between the elements of the
document. In this case, there are really only two major components,
known as the prologue and the script. The prologue is simply a few
PostScript language definitions that permit more powerful and compact
representations to be made in the document. The script is the body of the
document, and contains mostly data from the document processing
system such as fonts, locations on the page, and bits of text. The prologue
is usually the same for all documents, and is hand-written, whereas the
script is generated by the document processing program.
FONT PROGRAMS
output page
QUERY PROGRAMS
Since query programs are intended to write results to the standard output
communications channel (terminology borrowed from UNIX and C),
their behavior depends to a great degree upon the environment in which
they are executed. Some printers do not have two-way communications
channels; in such cases the results will be lost. Some operating
environments think that anything coming back from a printer must be an
error message, so the results may be written to an error log file
somewhere, and they may even have extra text added to them by the
printer control program.
output page
ed
p s ulat
a
Enc cript
tS
Pos
Yet another type of PostScript program is one that makes some persistent
definitions that are visible to all subsequent programs. This provides a
mechanism for redefining operators, making fonts semipermanent in the
interpreter, or defining some procedure definitions once in such a way
that they are usable to all subsequent programs.
On most printer implementations, this can be accomplished using the
exitserver operator. This operator permits the program to exit the job
server loop’s save/restore context, causing any definitions made to stay
resident until the printer is rebooted. Example 4.6 shows the use of
exitserver, in this case to make a font definition semipermanent.
shareddict
private
virtual
memory
process 1 process 3
private
virtual process 2 private
private memory stacks
stacks
private
virtual
private memory
stacks
The examples in this chapter should provide a feeling for the different
kinds of programs that can be written, and the different techniques that
might be required for each. As you learn more about the details of writing
PostScript programs, it is helpful to keep a bit of perspective about the
kind of program you are constructing, so you can make your code
readable where it should be, efficient where it counts, and well-behaved.
The next two chapters take a hard look at the operand stack, since it is the
principle mechanism for all program execution.
EXERCISES
All PostScript data types are represented by a single object. There are
three varieties: simple objects like integers, reals, marks, and booleans;
composite objects (like arrays and strings) that will not fit into an object;
and some special types that behave like simple objects but which have
some internal structure that you can’t see (these include save objects and
FontID objects). For the purpose of this discussion, only simple and
composite objects will be covered.
51
Data types are represented in the interpreter as objects, which are a fixed
size and are easily manipulated by the interpreter, placed on the operand
stack, or stored into arrays or dictionaries. For the most part, it is not
necessary to know that data types are represented as objects, but it does
affect some things. For a more complete discussion of PostScript
language objects and data types, please refer to the PostScript Language
Reference Manual.
Probably the most important distinction to understand between the
various data types is the difference between a simple object and a
composite object. Composite objects can be thought of as pointers to the
real data, whereas simple objects really are data themselves. Figure 5.1
shows the various data types available in PostScript.
(string body)
real
string
integer
mark array
boolean
dict
name
/name characters
PostScript objects are created in one of two ways: they are either created
directly by the language interpreter when it reads the program from the
input file, or they are created directly by the executing program itself
through an explicit allocation. Some data types (the real number is an
example) cannot be created explicitly by a program except by converting
another data type (an integer). It must be created by the scanner, as it
reads a number from the input stream (for instance, 103.46). Other data
types cannot be created at all by the scanner. For example, the data types
NAME LOOKUP
The standard method for retrieving something that has been stored into a
dictionary is to use the name lookup mechanism. An executable name
encountered by the interpreter is looked up in the context of the current
dictionary stack, and if there is an entry corresponding to that name, the
value will be placed on the operand stack—with one notable exception. If
you happen to retrieve a procedure body (an executable array object)
under the name you look up, that procedure will be placed on the
execution stack and immediately executed, rather than being placed on
the operand stack and treated as data.
TIP If you need to retrieve a procedure body that you have stored into a dictio-
nary, but don’t want to execute it just yet, you can get a copy of it onto the
operand stack using the load operator, using the name of your procedure
(with the leading slash, to make it a literal name) as the argument to load.
When a name is looked up, the dictionary stack is searched from the top
down, and the first instance of the key that is encountered is the one that is
used. That enables you to redefine a name that is already built into the
PostScript language, although you should do so with great care (see
Redefining Operators in Chapter 10 for further discussion of this topic).
This code segment looks like a definition of two variables, Xlocation and
Ylocation, which are then used as the coordinates for the moveto
operator. From the point of view of stack manipulation, the def operator
simply takes two objects from the stack and puts them in the current
dictionary, and the subsequent name lookup of, say, Xlocation, retrieves
the object associated with that key from the current dictionary. As shown
in Figure 5.2, by the time you get to moveto, there are no variables or
anything else left; there are just two numbers on the operand stack, which
is what is required by the moveto operator. So in effect, the numbers 100
and 200 start out on the operand stack, are stored into a dictionary,
retrieved from that dictionary back onto the operand stack, and used by
the moveto instruction. A variable is a name used to reference data.
TIP When you define a variable in your PostScript program, you are actually
taking something off the operand stack and putting it into a dictionary. In
order to use the variable, you must always recall it from the dictionary
back onto the operand stack. Be aware that using “variables” is always
somewhat slower than using the operand stack directly.
200
/Ylocation /Ylocation
/Ylocation 200 def
200
100 100
Xlocation Ylocation moveto
(text sample)
(text sample) show
One of the best ways to learn to read and write PostScript programs is to
be able to “chunk” together sequences of instructions into a block that has
a well-understood input and output. In many programming languages,
you can usually read a single line of the source code and make sense of it.
In fact, usually one line of the program represents a single statement in
that language, which may be an assignment statement or a procedure call.
But in the PostScript language, the statements can get fairly complex, and
each operator and its arguments represents one phrase of the statement.
The programs that are the most difficult to read are those that use one
operator to manufacture the arguments for the next operator.
Let’s look quickly at two examples that illustrate the concept of one
operator leaving behind results that become arguments for the subsequent
operation. The first case (Example 5.2) shows a series of code samples of
gradually increasing complexity, culminating in some that may look a
little bit confusing at first glance. Take a moment to mentally execute the
program to see what it does.
% save leaves a value for restore (“3 exch” is just there to confuse you)
save 3 exch restore
output page
Since stacks are last-in, first-out data structures, the last object on a line is
at the very top of the operand stack. In this example, this can be seen most
clearly by looking at the procedure call itself; that’s why the first line in
the procedure grabs the last argument on the line, since it is now on the
top of the operand stack (see Figure 5.5).
You can start to see the relationship in this example between the left-to-
right distribution of objects in the source program and the top-to-bottom
result on the operand stack. Notice the way the arguments to the
procedure line up with the exch def lines in the procedure definition itself.
TIP When reading a PostScript program, find the executable names and use
them as reference points. If the name is a procedure call, look to the left of
the name in the body of the program to follow along as you read down
through the procedure definition (as in Figure 5.5).
COMPOSITE OBJECTS
The values associated with composite objects do not exist on the operand
stack. There is an object on the stack that represents the composite object
data, and it behaves, for the most part, as though the whole object were on
the stack. The only time it is confusing is during copying and
manipulation of the internal structure of a composite object, when you
have to realize that you really have a pointer to the object, not a self-
contained object. For example, if you dup a string object and then change
one of the letters in the copied object, the original string changes, too,
since there really is only one string and you have only copied the pointer
to it, not the string itself.
string object
somewhere in memory
PostScript
length: 10
string object
length: 10
(PostScript) dup
dup 0 (Pre-) putinterval
somewhere in memory
string object
Pre-Script
length: 10
string object
length: 10
There are three other stacks used by the interpreter during the execution
of your program in addition to the operand stack, which you have been
studying: the dictionary stack, which helps with name lookup and in
making definitions; the execution stack, which holds partially executed
procedure bodies, file objects, and other executable objects; and the
graphics state stack, which keeps track of your gsave and grestore
operations.
These stacks will be described briefly so that you’ll know about them. For
more detailed information, refer to the books PostScript Language
Reference Manual and PostScript Language Program Design, both by
Adobe Systems, available from Addison-Wesley Publishing Company.
CONCLUDING THOUGHTS
This chapter provided you with a brief overview of data types and some
examples of how operators use the operand stack. The stack and its
EXERCISES
1. What is left on the operand stack after the following code is executed?
/Times-Roman 24 selectfont
100 dup moveto
(Vocabulary) stringwidth 2 div neg rmoveto (Vocabulary) show
d. save
/Optima 12 selectfont 20 40 moveto
(Save me!) dup show
restore
3. Rewrite the following code using just stack operators, without storing
anything into a dictionary.
67
SAFETY OF DATA ON THE STACK
In truth, the safest place for a piece of data is on the operand stack, with a
few small caveats.
• If your program has bugs, you may inadvertently remove
something from the operand stack that you needed, or leave
something extra that will cause some later operation to trip over
it. Sometimes these stack alignment programs persist for a long
time, unless you consistently test all the paths through your
program.
• Stack-based manipulations of more than a few operations or more
than a few operands can be tricky to read and maintain, leading to
the bugs just mentioned.
• If you transfer control temporarily to some other program, as you
might do with an embedded illustration, for example, you should
not be surprised if something you left on the operand stack is no
longer there.
In general, the cleanest, fastest and best programs are those that make
judicious use of the operand stack. As a rough rule of thumb, in designing
a PostScript program you should spend 65 percent of your time thinking
about the order of operands on the stack, whether or not you should divide
all of your coordinate numbers by 1,000 before sending them to the
interpreter, and whether you can get away without sending the X
coordinate every time you plot a vertical line. You should spend 20
percent of your time adjusting the procedures you have defined to match
the decisions you have made about operand order and use. (The remaining
15 percent is for debugging and getting distracted.)
Of course, these principles of careful use of the operand stack are derived
primarily from printer drivers, which are inherently batch programs. If
you are writing an interactive application in PostScript, or if you are
writing a utility program that is entirely self-contained, you may have
different goals—but the basic ideas still apply. The data must be on the
operand stack in order for any PostScript operator to use it, so you might
as well take advantage of that premise and try not to take things off the
stack and put them on too many times unnecessarily.
The first few times you try to read or write PostScript programs, it may be
difficult to understand what is going on in a statement like the one in
Example 6.1. What is exch doing exactly where you would expect to see
the value part of the definition?
As you know, the def operator requires its key and value in a particular
order on the stack. If the value—the object you want to store—got put on
the operand stack before you had a chance to put the key—the name under
which you store that value—on the stack, then you would have them in
the wrong order for def. In this case, you need to call exch to get them in
is equivalent to
/A (a) def
(without the exch), which is why it works. (See also Thinking Backward
and Sideways in Chapter 5.)
Once you have collected the operands into the current dictionary, you
simply call them by name in order to use them. Each time you supply the
name in your program, it will be looked up in the current dictionary (or
dictionary stack) and the value will be placed on the operand stack again.
If you want to use the operand stack directly in your program, the chances
are you will need to perform some stack manipulation. It’s almost
unavoidable. There are a few operators that you will find indispensable
for stack manipulation. There are also some techniques worth learning to
help you keep bugs out of your stack exercises. Table 6.1 contains a
summary of the stack operators in the PostScript language for quick
reference. Don’t worry if you don’t understand how to use all the
operators in the table; it will become more clear as you read on.
The most important operators for stack manipulation are dup, exch,
index, and roll. Of these four operators, dup and exch are fairly easy to
understand, since they apply only to the topmost item or items on the
stack, but index and roll can be a bit more confusing.
/A /B /C /D /E /F /G
4 -1 roll
roll amount
roll group
One helpful way to conceptualize the roll operator is to verbalize it. For
the example shown in Figure 6.1, one might say “Take the top four
elements on the stack and roll it negative one times.” The top four
elements are easy enough to understand, but what does it mean to roll
something “negative one times?” The sign of the number (negative or
positive) indicates the direction to roll, and the magnitude of the number
indicates the number of elements to be rolled. The easiest way to think of
the direction of the roll is like this:
• If the roll amount is negative, that means you take the elements
from the bottom of the roll group and bring them to the top.
• If the roll amount is positive, you take the elements from the top
of the stack and roll them to the bottom of the roll group.
Figure 6.2 and Figure 6.3 show the effects of rolling with a positive and a
negative roll amount.
/A /B /C /D /E /F /G 6 2 roll
roll amount 2
roll direction “to bottom of roll group”
/A /B /C /D /E /F /G
6 2 roll
/A /F /G /B /C /D /E
result of 6 2 roll
roll group of 6
/A /B /C /D /E /F /G 6 -2 roll
roll amount -2
roll direction “to top of roll group”
/A /B /C /D /E /F /G
6 -2 roll
/A /D /E /F /G /B /C
result of 6 -2 roll
Unfortunately, the where operator returns the dictionary in which the key
is found in the event that it does find the key, but returns nothing other
than the boolean if it does not find the key. Example 6.3 shows a simple
fix to correct this problem. The if statement is changed to ifelse, and the
extra dictionary is popped from the stack if the where operator returns
true.
Table 6.2 provides a list of operators that can cause you some trouble by
returning sometimes unexpected results on the operand stack. If you get a
typecheck error that you have trouble finding, this might be the cause.
2 recurse_proc
There are several ways to accomplish recursion, as you have seen. None
of them is perfect, due partly to the lack of true local variables in the
PostScript language. It is easy enough to implement recursion, though,
and you need only exercise a reasonable amount of care to keep out of
trouble and to avoid execstackoverflow, dictstackoverflow, or
stackoverflow errors.
CONCLUDING THOUGHTS
The operand stack is an integral part of the PostScript language, and you
cannot avoid using it. Once you start to understand it and to trust it, you
will be able to write programs that make very effective use of the stack. A
stack-based language seems to make you think backward and upside-
down, but in fact it only makes you think in chunks, where each chunk is
the result of one operation. Every operator is grouped with its operands,
EXERCISES
2. What are the contents of the operand stack after executing the follow-
ing program segment?
clear
/A /B /C /D /E /a /b /c /d /e
2 copy 6 index 6 index 12 -4 roll exch 5 3 roll
79
complex program. You should learn inside and out how if and ifelse work
in the PostScript language.
SIMPLE CONDITIONALS
}{ %else
} ifelse
Table 7.1 shows the most useful PostScript operators that return booleans.
An ifelse or if statement can follow any of these operators. In fact, the
operators were designed to be followed by a conditional.
TIP It is important to test both the true and false clauses of your conditional
statements, especially as you gradually change your code and evolve the
contents or the type of items on the stack before going into the condition-
al. You can “rig” this testing by redefining the ifelse operator to reverse
the sense of all your conditionals (see Example 7.8), just to give a dry run
through them, or you can test each one by hand.
As you gain more experience with the language, you will become expert
in the use of the ifelse statement, which is a fundamental and useful
operator in any PostScript language program.
Another interesting example that illustrates the fact that ifelse is just an
ordinary operator is Example 7.11, which redefines the ifelse operator to
get a little extra information while it is executing, for debugging purposes.
TIP If you use ifelse rather than if, make sure you supply both procedures, and
that you think carefully about what is on the operand stack for each of
them.
COMPOUND CONDITIONALS
CONCLUDING THOUGHTS
The most important thing to remember about conditionals and the ifelse
operator is that the whole construction is interpreted each time it is
encountered. The condition (true or false) is computed each time, the two
procedure bodies are loaded onto the operand stack, and the ifelse
operator pushes one or the other of them onto the operand stack. There’s
nothing really magical about the way it works, and the procedure bodies it
uses are interchangeable with all other procedures in the PostScript
language.
EXERCISES
1. Design a case operator for the PostScript language, and design a pro-
cedure that will implement it. Think about the design trade-offs be-
tween ease of use (once your case operator exists) versus ease of
implementation for you. Supply some simple documentation (com-
ments in your program are good enough) explaining how to use your
new case operator.
2. The following procedure emulates the setcmykcolor language exten-
sion by using setrgbcolor and doing a simple conversion. Design a
conditional statement with the where operator that will define this
procedure only if the setcmykcolor operator does not already exist.
/setcmykcolor { %def
1 sub 4 1 roll
3 { %repeat
3 index add neg dup 0 lt {pop 0} if 3 1 roll
} repeat setrgbcolor pop
} bind def
3. The “standard” procedure used with the image operator uses the
readhexstring operator to get a line of data from the input file. The
trouble is, it ignores the boolean returned by readhexstring, which is
not really a good idea. Please rewrite this procedure body to check for
the end-of-file condition reported by the readhexstring operator.
(The second line of hex data is incomplete in this example, causing an
error in the execution of the program as it stands. Fill out the line of
data to see what the program is supposed to do.)
/picstr 16 string def
100 100 translate 100 900 scale
16 2 8 [ 16 0 0 16 0 0 ]
{ currentfile picstr readhexstring pop } image
00FF00FF00FF00FF00FF00FF00FF00FF
00FF00FF00FF00FF
93
you’ll see later in this chapter). For example, a routine to draw a polygon
with hundreds of sides could benefit from being put into a loop.
LOOP BASICS
output page
Looping operators are useful because they let a task be performed more
than once. They can be even more useful when you keep track of the loop
index and use it effectively.
Example 8.2 sets up the loop index to give 10-degree increments around a
circle, but actually does not use the loop index explicitly within the loop.
The results are shown in Figure 8.2.
output page
In Example 8.3 is a short program that uses the loop index to set the line
weight of a series of stroked lines and to control their spacing; its results
are shown in Figure 8.3.
output page
When using a loop index, make sure you either pop the unwanted data off
the stack or use it within the loop body, or you will leave unwanted data
on the operand stack which may affect other portions of the program.
Several looping operators push data onto the stack each time around the
loop. In particular, the forall operator pushes each element of an array or
string onto the stack; it pushes both the key and value for each dictionary
entry onto the stack. Table 8.2 provides a summary of each looping
operator and the data that are pushed onto the stack for each iteration of
the loop.
Example 8.4 shows both a forall loop and a filenameforall loop being
used to find out all the font names defined in FontDirectory and on the
disk of a printer. Notice the way that the data are used inside the loop
bodies.
As you can see, there are a lot of ways to use the looping operators
effectively, especially if you take advantage of the operators that push
data onto the operand stack during execution of the loop body.
Since the proc passed to all of the looping operators is just an ordinary
procedure body, you can perform any of the standard operations on it.
One often-overlooked but very important detail is to apply bind to the
procedure body before executing the loop instruction, so that the loop will
execute more quickly (see Example 8.5). Be careful, however, not to
apply bind to a loop that is inside a procedure body that has also been
bound; it doesn’t accomplish anything further (since bind applies to all
nested procedure bodies recursively) and may degrade performance
slightly if bind is executed each time the procedure is called.
You can also take advantage of the fact that a loop body is an array (all
procedures are arrays) and actually put objects directly into it to save
time. Example 8.6 shows this technique applied to creating an Encoding
array for a font, where you have to supply the array object itself 255 times
during the execution of the loop. This saves the interpreter name lookup
overhead each time around the loop.
This example is a bit tricky, but it has a simple underlying concept. The
trick is to put a dummy object into the procedure body (in this case, the
executable name ENCODING_HERE, but it could be any object), then,
before the procedure is ever executed, to replace that dummy object with
the actual encoding array itself, instead of a name. The advantage to this
approach is that you don’t have to look up the name all 256 times around
the loop; the array object itself is in the procedure body, which saves you
256 name lookups and a lot of time.
The procedure in this call to image now contains four objects, none of
which are executable names, thanks to bind and the way we constructed
the procedure. (The procedure contains a file object, a string object, and
two operator objects.)
If you’re having difficulty following the way the procedure body was
constructed in Example 8.7, you might skip ahead and read Constructing
an Array in Chapter 11.
output page
Sometimes you have to construct a loop that may run for an indeterminate
length of time. For example, you might loop until an EOF condition is
met, or until no more spaces are found in a string, etc.
Sometimes you have to construct a loop that may run for an indeterminate
amount of time. For example, you might loop until an end-of-file
condition is met, or until no more spaces are found in a string. The best
way to construct a loop of this type is to use the loop and exit operators.
The exit operator will simply cause the innermost looping context to be
broken, allowing exit from the loop at any point.
Here is the basic loop construct:
{ %loop
exit_condition { exit } if
} loop
CONCLUDING THOUGHTS
In this chapter you have seen many different situations for loops, some of
which use data, some of which use instructions, and some of which are
simply executed until it is determined that they are done. It is up to you to
decide which of the looping operators is best suited for your needs, and to
use it appropriately. In the next chapter you will see how to construct and
use procedures in your programs.
EXERCISES
3. Write a loop that takes a single string on the operand stack, counts the
number of spaces in the string, and returns the string and an integer
count of spaces back on the operand stack when finished. For this ex-
ercise, use the search, loop, and exit operators.
4. Rewrite the above exercise using the forall operator instead of search
and loop.
5. Find the bugs in the following program (there are at least two or three
of them). The program should print a grid of 20 lines by 10 lines.
% draw a grid of 20 x 10 lines
save
0.1 setlinewidth
20 20 translate
0 10 200 { %for
dup 0 moveto 0 100 rlineto
} for
stroke
0 10 100 { %for
0 1 index moveto 200 0 rlineto
} for
stroke
restore
Procedures
105
function that had three operands passed to it and returned a real number
(their average). In most traditional procedural programming languages,
you need to specify what kind of value will be returned by the function
while you are setting up the function itself.
PostScript procedures can act as functions quite simply by leaving
something behind on the operand stack when they exit. In fact, since there
is no compile-time checking of your program, a procedure might return a
value inadvertently. Furthermore, a PostScript procedure acting as a
function can return a value of any type, which is both good and bad.
Although there are explicit data types in the PostScript language, the lack
of a compile cycle forces run-time type checking, which, although it does
a good job of checking types, often does so a bit too late.
Let’s look at a typical procedure definition and its use (Example 9.1).
All of the definitions shown in Example 9.2 have exactly the same effect.
They are not different procedure definitions, they are the same procedure
definition, accomplished in various ways. This illustrates that procedures
are not magic; they are simply a collection of instructions in an executable
array.
You can associate a procedure body with a name if you like, by creating a
definition in a dictionary. This can be done in various ways (including use
of put as shown as the third alternative in Example 9.2), but the simplest
of them is the method that just uses the def operator. The def operator is
very simplistic. All it does is take two objects and make an association
between them in the current dictionary. It does not help you write correct
In this case, of course, if you tried to use the S procedure, it would tell you
it was undefined, because it has no dictionary entry with S as the key.
Now let’s look at the procedure call itself, which requires three values to
be on the operand stack.
(some text) 100 200 S
Of course, the string (some text) and the numbers 100 and 200 are literal
objects, so they are simply pushed onto the operand stack. When the S is
encountered, looked up, and the procedure is executed, the correct
arguments just happen to be on the operand stack.
PARAMETER PASSING
The procedure S in Example 9.3 has three parameters, a string body and
the x, y coordinates. These are supplied on the operand stack in the order
required. That is, since moveto must be executed before show, the
parameters used by moveto appear on the top of the stack. Note that this
requires them to be actually written after the string, so that they will be on
top (last in, first out). Let’s rearrange this example a little to see what is
going on (Example 9.4).
This is a little unusual, because the data and instructions are not
interleaved in a comfortable and readable fashion, as in Example 9.6.
In a procedure body, you don’t have the luxury of interleaving data and
instructions, so you need to rearrange slightly the way data are presented
to the procedure. You must pile all of the data up on the operand stack and
use it piece by piece from within the procedure. Or you can simulate local
X Y moveto
text show
} def
100 200 (some text) S
TIP A good rule of thumb is that if the operators within a procedure use each
parameter just once or twice, it is better not to use local names for the pa-
rameters. Conversely, in a complicated procedure, it is usually better to
choose sensible names for the parameters to heighten readability and to
make the program more robust.
Also, for comparison, Example 9.10 presents the same program without
any local names defined for the arguments:
The purpose of this procedure is much more clear now, since the native
PostScript operators can be seen easily, and since the names chosen for
SELF-MODIFYING PROCEDURES
This is a simple function that inverts the sense of black and white. Let’s
imagine that you want to set another transfer function that makes a single
exception of black and makes it 50 percent gray instead (see Example
9.12).
To concatenate the two procedures, you would want a result in which both
procedures are executed in order (see Example 9.13).
But in order to do this, you must know the size of the existing transfer
function, allocate a new array, and copy the contents of both the old and
the new functions into the third array. This is both time-consuming and
uses storage space inefficiently.
Let’s look at an alternative that is predicated on modifying a procedure
body as if it were just an array. Strictly speaking, this is not a self-
modifying procedure, but shares the notion of creating new procedure
bodies from existing ones (Example 9.14).
Example 9.14: Invoking the Old Function from the New One
{ OLD exec dup 0 eq { pop 0.5 } if }
dup currenttransfer exch 0 exch put
settransfer
This is a bit tricky, but it has a simple underlying concept. You want the
new procedure simply to execute the old one before it starts, which could
be done with the exec operator if you had the object to exec. The trick is
that the new transfer function is created with a dummy element as the
argument to exec. The name OLD is not a name that is defined anywhere,
but since the procedure has not yet been executed, it is okay to put any
name into it. The trick is to replace that name with the actual body of the
existing transfer function. The exec that immediately follows will then
execute it. The advantage to this approach is that you don’t have to
allocate a new array, and since all procedures are represented by a single
PostScript object, you can always replace the name OLD with the object
that represents the entire previous transfer function.
In Example 9.14, the currenttransfer operator produces the existing
procedure object on the operand stack. The fancy dup currenttransfer
exch 0 exch put code just inserts that procedure object into location 0 of
the new procedure array, and carefully leaves a copy of the procedure
body on the stack when done. (That’s what the instances of dup
accomplish.) The net result, in memory in the interpreter, looks something
like Figure 9.1.
{ 1 exch sub }
CONCLUDING THOUGHTS
Procedures are executable arrays. This gives you the power to execute
array operations on them occasionally, to copy them, to add elements, or
to replace dummy objects with real ones. Procedure bodies are also used
as arguments to many PostScript operators. Since the procedure bodies
used by the ifelse operator are no different than ones you might define for
EXERCISES
Using Dictionaries
119
Example 10.1: Sample Dictionary Entry
/redraw % strokegray fillgray redraw -
{ %def
gsave
setgray myuserpath ufill
setgray myuserpath ustroke
grestore
} def
In this case, the object that is stored as the value is the procedure body
{ gsave setgray myuserpath ufill setgray myuserpath ustroke
grestore }, and the key under which it is stored is the name redraw.
TIP The memory used for storage of PostScript objects is allocated when the
object is created, not when it is stored into a dictionary. The dictionary
must have room for the definition in it already; only the two objects repre-
senting the key and the value are actually saved in the dictionary. In Ex-
ample 10.1, the procedure body consumes about 72 bytes of memory
when it is created, but no additional memory is used when it is stored into
the dictionary with def.
LOCAL DICTIONARIES
In Example 10.3, the LOCAL dictionary stores only the data, and the
procedures themselves are simply written into the current dictionary.
The only time you really need local dictionaries is for re-entrant code or
recursion. For most other situations, a good approach is to create one
larger dictionary to hold both procedures and their data, as long as you are
careful about name conflicts (Example 10.3). They are not really “local
variables,” but if you need to be careful about storage space but are not
worried about recursion or name conflict within your own code, this is
much simpler and more efficient. This technique also relieves each
procedure of having to begin and end the dictionary, which makes it
easier to maintain and faster at the same time.
TIP Most PostScript drivers and any program with more than a small handful
of procedures should make sure to build its own dictionary for storing the
procedures, to avoid the dictfull error that might result if you trust the exe-
cution environment to have enough room in, say, the userdict dictionary
for all your definitions.
Example 10.3 shows the use of a single dictionary for all definitions made
by the program. This technique can be a little bit riskier, since there is still
a chance that another dictionary might be left on top of ProductDict
(perhaps by an included illustration); this would make all subsequent
instances of def write into the wrong dictionary. However, you can make
sure to check the dictionary stack whenever you include an illustration.
This technique is the best general approach for storing definitions in a
private dictionary.
ProductDict begin
/Text % (string) Xloc Yloc Text -
{ %def
/X exch def
/Y exch def
/text exch def
X Y moveto text show
} bind def
%%BeginSetup
% make sure “ProductDict” is available at run time:
ProductDict begin
%%EndSetup
%%Trailer
% make sure to remove “ProductDict” when done:
end %ProductDict
%%EOF
A currentdict end B
B A
The dictionary stack holds dictionary objects. There are built-in operators
in the PostScript language for putting things into or retrieving things from
the current dictionary. There are also several other ways to manipulate the
contents of dictionaries directly.
A dictionary is a normal PostScript object and can exist on the operand
stack as well as on the dictionary stack. The put operator lets you put an
entry directly into a dictionary that is on the operand stack, and get lets
you retrieve from it. (See Example 10.6.)
If your needs are simple, you may be able to avoid having to place a local
dictionary on the operand stack at all. You can use the get and put
operators to explicitly reference a dictionary without ever placing it on the
dictionary stack. In Example 10.7, the save object that results from
executing save is stored away into a dictionary, and is retrieved later for
restore. Since there is only a single entry to be written into the dictionary,
it is a little bit simpler (although perhaps no clearer) to use put rather than
begin, def, and end.
The entries will not come out in a particularly sensible order. Instead, they
are in hash table order, in which the location in the dictionary is
determined by deriving an index directly from the key you used. (To
digress slightly, an example hash function might be to add up the byte
values of all the characters in a name object and use that number as an
index into an array.) To make the forall operator a little more useful, you
can use the type operator to take a look at each value in the dictionary,
and take different measures depending on the data type. This will help to
look into other dictionaries and arrays that might be lurking inside the
In this case the /C is left on the operand stack while where is executed to
determine whether or not the simulation is needed for setcmykcolor. If
the name setcmykcolor is found, the current definition of it is loaded onto
the operand stack. In either case, the name /C will have equivalent
functionality to the setcmykcolor operator, and can be used throughout
the program with the same arguments that setcmykcolor requires,
without having to adjust the rest of the program according to the color
model or the interpreter used.
There are very often instances in which you want to change the behavior
of your program, even just temporarily, by redefining some of the names
in it before the program executes. This has many possible uses, for
debugging, page accounting, distilling documents into another form, or
adding to the functionality of an existing program.
As you have learned, name lookup is done in the context of the dictionary
stack. There are correspondingly two basic ways to redefine a name non-
destructively.
• Make a simple redefinition in the current dictionary with def.
• Push a special dictionary onto the dictionary stack into which the
redefinitions are placed.
CONCLUDING THOUGHTS
Dictionaries are flexible and powerful. The main uses for them are as
storage for items that you don’t want to leave on the operand stack and as
a name lookup mechanism for scoping program execution or local
variables. You have learned several ways to manipulate dictionaries, store
and retrieve entries from dictionaries, and look at their contents.
Dictionaries are not heavily used in most PostScript programs other than
to store simple definitions, but they can be exploited for some interesting
purposes when the need arises, as you have seen. In the next chapter,
dictionaries are contrasted to arrays and various methods of creating and
manipulating data are presented.
1. What value is left on the operand stack after executing the following
short program segment?
/variable 7 def
5 dict dup begin
/variable 9 def
end
/variable dup load 3 1 roll get add
3. Name three operators in the PostScript language that use the dictio-
nary stack in some way.
4. Write a program that shows (with the == operator) the names of all
the fonts stored in the dictionary called FontDirectory.
The two most basic types of data structures are arrays and strings.
PostScript arrays can contain any other type of PostScript objects,
including composite objects. Strings contain individual characters, as in
most languages.
CONSTRUCTING AN ARRAY
135
This array contains ten elements:
/one /one
/one (four)
/five 6
/seven { 8 (the whole procedure) }
9 10
Notice that the second and third instances of /one were created by the
execution of dup and the 6 was constructed by the add operator before
the ] was executed. The elements between the [ and ] operators are all
executed, unlike the way procedure bodies are declared.
If you want to construct a literal array (the kind with [ ] brackets) that
contains an executable name like dup or add, obviously you have to be
careful, or the operators will actually execute instead of landing in your
array. We could have created the array first as a procedure, taking
advantage of the fact that the execution of objects inside the procedure is
deferred, and then converted it to a literal array after it has been
constructed (see Example 11.2).
CONSTRUCTING A STRING
Strings can be constructed in much the same way as arrays. You can
create one at run-time in the original input program by using the notation
found in Example 11.4 for simple strings or hexadecimal strings (also
called hex strings).
Hexadecimal strings are scanned two bytes at a time to create the single
byte represented by the 8-bit number. (One byte of hexadecimal, since it
is base 16, provides only 4 bits of data; it requires two bytes of
hexadecimal to represent a full 8 bits.) Hex strings are generally used only
for image data, strings for Kanji character sets, or other places where full
8-bit string data must be used. The string operator can also be used to
create an empty string body, and you can use put or putinterval to put
bytes into the string.
Strings and arrays are composite data structures. They are both
represented by single objects, but really they are composed of collections
of objects. There are a few different ways to read and write the contents of
these composite data structures, included in Table 11.1. The fundamental
mechanism is the family of put and get operators. We’ll start with the
simplest ones.
The put operator requires three arguments: the thing you want data put
into, the key or position in that composite data structure where you want
the data put, and finally the piece of data itself that is to be added (see
Example 11.5).
The putinterval operator is similar to put, but the piece of data you
supply needs to be of the same type as the data structure into which you
are putting it. The data gets copied from the supplied data into the
destination, rather than putting just one piece of data (see Example 11.6).
TIP If you have difficulty remembering the order of operands to the put and
get operators, remember that they work in the same order that def and load
take their operands: the key comes before the value.
Arrays and strings have a fixed length. You cannot dynamically extend
them. In order to tack two of them together, you must allocate a third
array or string that is big enough to hold the other two, then copy them
both into it. There is no easier way to do it, unfortunately.
Example 11.7 sets forth a procedure called concatenate. This
concatenate procedure tacks together two string bodies into a third string
that is left on the operand stack as the result of the procedure call. The
procedure works with arrays in precisely the same way. If you copy this
example, be careful not to call it concat, since that is the name of another
PostScript operator for concatenating matrices.
The roll operator is used in the body of this procedure to avoid having to
make any dictionary entries. This is intentional, since a procedure used as
an operator-equivalent (as concatenate is) should have as few side effects
as possible.
Note the use of putinterval in this procedure. It is called once to copy the
body of the first argument into the destination at position 0, then it is
called a second time with the position in the destination equal to the
length of the first argument, so the second argument lands exactly where
the first part left off. Note the use of length to determine this position, and
the careful use of dup and index to keep copies of the various strings or
arrays until we are done with them. The length, putinterval, and type
operators all consume their operands, so you must take care to operate on
a copy of the original arguments until they are no longer needed.
There are several differences between arrays and dictionaries that make
them more (or less) appropriate for a given task. The most important
differences are:
• Arrays can be accessed sequentially, but dictionaries cannot be
accessed sequentially (the forall operator uses hash table order).
ADVANCED TECHNIQUES
PostScript programs that read data directly from a file sometimes have to
perform extensive string manipulation to use the data. An interesting trick
for parsing data is to use the token operator. If your data are ASCII and
can be interpreted somehow as PostScript-like objects, your parsing task
can be greatly simplified.
Consider the program in Example 11.10 that reads pairs of points from the
input file and constructs line segments from them. The output of this
example is shown in Figure 11.1.
output page
CONCLUDING THOUGHTS
In this chapter, you’ve seen how to construct arrays and strings and how
to manipulate them with put and get. You have also seen how to
EXERCISES
1. What are the contents of the array constructed by the following code?
[ 0 0 moveto 100 100 lineto currentpoint ]
There are various kinds of data that get used by computer programs. Some
programs “crunch” data, bent on achieving a particular result based on its
input. Other programs allow users to create, edit, or rearrange data; the
result is up to the user’s imagination. Still others use input data only to
guide them through labyrinthine paths, producing no results at all.
When writing your own program, it is worth considering just what the
numbers and text will be used for before you decide on your data
structures. For example, if your program takes a sequence of numbers and
plots them, you probably don’t want to store the numbers in a permanent
place. In fact, the sooner you get rid of them (plot them), the better.
However, many bits of data are in fact used internally by the program
itself, to establish state, and are never intended as part of any output.
(These bits of data may include, for example, the current page margins,
the point size of the font you are using, or maybe a string buffer needed
for reading from a file object.)
145
Data and the Operand Stack
Remember that all PostScript operators require their operands to be on the
stack. Even if you store something into a dictionary, it must eventually get
put back on the operand stack before it can be used for anything.
Of course, the operand stack is also the first place any data land on the
way into your program. Assembling these ideas in our minds, we realize
that maybe, under some circumstances, the data should just stay on the
operand stack until they are used. That is the first principle of data storage
and use in PostScript programming.
The exaggerated instance shown in Example 12.1 is a procedure that
requires several operands, all of which are eventually used by some
PostScript operators internally. In this example, these operands are given
names within the procedure body, ostensibly so that the procedure will be
easier to maintain. But notice that the values are no sooner stored into a
dictionary than they are recalled onto the operand stack, which is
unnecessarily slow.
In this example, variables are created to hold data that are inherently
transitory. The data really represent the box itself, and are not used by the
program in any other way. To give them names and make them into
variables has no purpose other than to make the program a bit more
/Times-Bold 24 selectfont
underlineON
(Hamlet, Act II) 200 650 TEXT
underlineOFF
/Times-Roman 12 selectfont
(This should be the first line from Hamlet.) 72 600 TEXT
(And this, of course, the second.) 72 570 TEXT
This example, unfortunately, won’t draw any underlines yet, but it shows
the use of the underlineON and underlineOFF procedures.
In our example driver, we don’t want to have to change our use of the
TEXT procedure to use underlining, we just want to be able to turn
underlining on or off whenever the command is issued from the word
processor. Of course, somewhere along the way the underlining has to
actually happen. In this case, the TEXT procedure must check the
underline flag to see if it is supposed to underline the text, and it must also
calculate the appropriate parameters and location for the text itself
(Example 12.4).
So far, this doesn’t seem too bad, since only a single test has been added
to our TEXT procedure. Of course, it still doesn’t do any underlining,
because the other half of the ifelse statement is still empty. Let’s expand
/Times-Bold 24 selectfont
underlineON
(Hamlet, Act II) 200 650 TEXT
underlineOFF
/Times-Roman 12 selectfont
(This should be the first line from Hamlet.) 72 600 TEXT
(And this, of course, the second.) 72 570 TEXT
This is quite a bit more involved than the version without underlining, but
most of it is fluff (ifelse statements, curly braces, known, and so on).
Notice that the underline position below the baseline and the thickness of
the underline are borrowed from the FontInfo dictionary in the current
font, if it exists; if it does not, the values are hard-wired to 12 and 1. This
data actually exist (or should exist) upstream at the word processor.
/Times-Roman 12 selectfont
(This should be the first line from Hamlet.) 72 600 TEXT
(And this, of course, the second.) 72 570 TEXT
The resulting program has a nice balance of data use, passing some of it
from the host word processor, storing some of it, and reducing the
complexity of the program itself in the way it relies on the data. Notice
that the only change to the way we wanted to set the program up was to
add two operands to the underlineON procedure call. The rest of it stayed
the same.
There are some classic data structures in the field of computer science that
you might want to construct in your PostScript program. For instance, you
might want to make use of doubly-linked lists, queues, stacks, trees, and
so forth. (Stacks are generally free in PostScript because of the inherent
stack-based model, but sometimes you need to create your own.)
Linked Lists
Linked lists can be very useful data structures. A linked list is basically a
sequential list of chunks of data that you can traverse either forward or
backward (if it is doubly-linked). The main advantage to linked lists in
traditional programming languages is that you can allocate memory as
you need it, and it doesn’t have to be contiguous. In each data block, you
store a pointer to the next block, wherever it may be. In PostScript, the
need for these lists diminishes greatly, since you don’t need to worry
about the way memory is allocated in order to access data. PostScript
language arrays are arrays of other PostScript objects, each of which is
self-contained (and very likely occupies some memory that is not adjacent
to the next element in the array). But, readjusting the terminology very
slightly, lists are still extremely useful, even if you don’t have to spend
much time worrying about the links between the elements.
What might be the best way to implement a composite data structure like
a struct in PostScript? You have only a few choices.
• You could use an array that contains all of the pieces of data.
• You might use dictionaries, creating one for each chunk of data.
• You might simply accumulate piles of data on the operand stack,
keeping track of what was where.
Notice that the names have disappeared, since arrays contain just the data.
But how would you create a link to the next chunk of data? Since a
PostScript array object is already a pointer to a composite data structure,
we could use it directly, including an array object within the array that
represents the next item in the list. It looks as though the arrays are nested
when you look at them syntactically, but in effect one array simply
contains a pointer to another. An array might even contain a circular
reference by including a copy of itself as one of its elements, but we will
try to avoid that in our examples. Notice that the original array (the one
containing the name Julius Caesar) has been reproduced, but is written a
little more compactly to save some space on the page (Example 12.9).
The actual structure of these arrays looks more like Figure 12.1 when it is
represented internally in the PostScript interpreter.
This method is very compact and efficient, but it does present one
difficulty: the elements of the structure cannot be referred to by name, but
only by their position within the array (although you could write
procedures to help you with this). Arrays could be troublesome to
maintain effectively and to debug, but are much more space-efficient than
dictionaries.
Notice that the next pointer in the first chunk of data has been updated to
be a reference to the newly-created list element, which is now stored as
the tail of the list. The only elements in the list that have names are the
head and tail entries in the list. The rest are anonymous, and can only be
referenced by following the next pointers from the other dictionaries in
the list.
Although the data structures we have discussed in this chapter are very
sound principles in computer science, they may not be the most
appropriate storage paradigm in your PostScript program. Think very
carefully about the relationships between your data elements and the way
that they will be created, used, and destroyed. Consider the benefits and
drawbacks of arrays, dictionaries, and the operand stack before deciding
on the final representation for your data structures. Keep these points in
mind as you read about program data and instructions in the next chapter.
EXERCISES
1. When drawing a vertical line, you only need the x coordinate once,
since it is the same for both endpoints of the line. Design a procedure
called V that draws only vertical lines. Your procedure should be able
to draw a line at any thickness, at any x,y location, and of any length.
2. In order to underline some text, you must determine the position and
thickness to draw the line.
a.What are the disadvantages to performing this calculation in your
PostScript code?
b.What are the disadvantages to performing the underlining calcula-
tion in your word processor application?
3. Create a data structure that could serve as a stack (you can restrict its
size to 50 elements). Write two procedures called stackpush and
stackget that will let you place objects on your stack and then retrieve
them back again. The stackpush procedure should take just one argu-
ment—the object to be pushed onto the stack data structure. The
stackget procedure should return one value onto the operand
stack—the value retrieved from the stack data structure. Don’t worry
about stack overflow or underflow error conditions.
Programs generally consist of instructions and data in the same sense that
an arithmetic problem does: there are operations (such as addition or
multiplication) and there are numbers or other data that are fed to those
operations. However, in the same sense that you can’t add without having
a few numbers, many PostScript programs require data just to make them
programs. Example 13.1 presents a very simple program to show the
distinction between data and instructions in some typical code.
157
The dichotomy between the data and instructions in Example 13.1 can be
made more clear by a slight rearrangement of the instructions into a
procedure, which is then called with all of the data on the operand stack,
as in Example 13.2.
But in another context, the font used and the position on the page might be
part of the algorithm, and not really just data used by the algorithm. For
instance, in a simple text formatter, the page number might always appear
in a fixed location in a particular font, but the page number itself would be
passed as data (see Example 13.3).
3 % data
pagenum % instruction
The instructions in your program are the fixed part, and the data are the
elements that can be different each time you run the program. But there
are circumstances in which data may actually be executed as instructions.
Let’s look at an example. If you were to write an emulator in PostScript
that made your printer pretend to be a line printer, then you may want to
make it recognize some simple line printer commands among the lines of
text to be printed. To simplify this somewhat, let’s say that you want to
recognize the form feed primitive that is often used in line printers. In
particular, the ASCII character for form feed is octal 013. Example 13.4 is
a very simple line printer emulator, written in PostScript, that will print
any lines of text it gets, and will eject the page to start a new one
whenever it encounters the form feed character. The form feed character
has been changed to (X) instead of (\013) in this example so you can see
the form feeds.
There are many other possibilities for turning data into instructions.
Occasionally instructions must be read from a file rather than being
included in the program itself, in which case the instructions are data
while they are being read from the file, and before being executed.
Example 13.5 shows a simple looping program that reads lines from a file
and executes them (assuming that they are in fact PostScript instructions).
This simplistic program won’t work with all data files, since it attempts to
execute exactly one line of the program at a time. If any delimiters such as
parentheses or braces are opened on one line but not closed until a
subsequent line, the code will not execute correctly.
output page
%!
/datafile (/usr/local/lib/sample.ps) (r) file def
/buffer 1024 string def
{ %loop
datafile buffer readline { %ifelse
cvx exec % execute the line from the file
}{ %else
datafile closefile exit
} ifelse
} bind loop
DATA CONVERSIONS
Quite a few PostScript operators convert one data type to another. Once
you know that they exist, they are easy enough to use. There are also
some fancy ways to accomplish data conversion in the next few sections.
But first, some examples of common data conversions using the
appropriate PostScript operators (see Example 13.7).
The program segments shown in Example 13.9 show how to convert back
and forth between name and string data types, which are very similar in
content. Remember that the slash (/) character often seen in programs is
not really part of the name, but just the syntax used to express a literal
name to the interpreter.
The cvs operator converts arbitrary data types to strings, which can be
useful for printing out data. However, the cvs operator cannot convert
composite objects like dictionaries or arrays to a string representation. An
example of this simple kind of conversion is given in Example 13.11,
with a workaround for composite objects. A better approach would be to
use the type operator to explicitly determine the type of the object, then
take some appropriate action to convert it to a string. Exercise 3 at the end
of this chapter explores this concept more fully.
CONCLUDING THOUGHTS
In this chapter, you’ve learned about data and instructions and how they
can be converted from one to the other. You’ve also seen how various
data types can be converted into other data types. These techniques will
come in handy in the next chapter as you begin using files.
EXERCISES
1. Take the following code segment and rewrite it so that all the data are
on the operand stack and all the instructions are inside a procedure.
Since the example has two sets of data, please call your procedure
twice, once with each set of data.
100 200 moveto 0.5 setgray
/Times-Roman findfont 96 scalefont setfont
(G) show
143 171 moveto 0.7 setgray
/Times-Roman findfont 96 scalefont setfont
(R) show
2. Write a procedure called cvhex that will turn an integer into a hexa-
decimal string and leave that string on the operand stack.
3. Write a procedure called tabshow that shows a string of text in the
current font, but treats tab characters specially by moving the current
point to the right by some fixed amount. (HINT: the ASCII code for a
tab is 9.)
File Objects
A file is always represented by a file object in a program. This is even true
in other languages, such as C, where a file descriptor is used in all file
references once you have opened the file. There is a bona fide PostScript
object for file objects, and all of the file-related PostScript operators
require the use of it. The file operator and the currentfile operator
provide the two standard ways to obtain a file object.
167
Streams and Files
A stream is a special case of a file object that can only be accessed
sequentially. You have no real way to know, given an arbitrary file object,
whether or not you can read or write arbitrary parts of the file. It is best
always to assume that the file is a stream, and only to read sequentially
from it (or write sequentially to it). For example, the currentfile operator
will often return the file stream representing the serial port on the printer,
although it might represent a real disk file in an environment where there
is a file system available to the interpreter. As long as you use the
setfileposition operator with some care, the distinction between a stream
and a file probably won’t matter.
TIP Some file operations will work on virtually any PostScript interpreter,
such as reading from the current file or writing to the standard output
stream. Other file operations, such as explicitly opening a file by name,
checking the status of a file, or using a very specific operator like set-
fileposition, depend heavily on the existence of a file system supported
by the interpreter. It is very important to make sure that the interpreter
supports these operations before attempting them, or you may get an er-
ror. You can use the known or where operators to check for the existence
of certain operators, or you can consult the manual for your interpreter to
find out how to test for the presence of a file system.
As a rule of thumb, any printer equipped with a hard disk or any worksta-
tion running a PostScript interpreter will have access to the full range of
disk operations described in this chapter.
To open a file in PostScript, you use the file operator (which you can
think of as an “open” operator, for all practical purposes). Opening a file
gives you a file object that you can then use for reading from or writing to
that file. The only tricky part is to make sure that there really is a file to
open.
The file operator requires a file name and a mode or permission string.
The file name is obvious. The mode is how you specify that you want to
open the file for reading, writing, or (in some implementations)
appending. If you open a file for writing (not appending), it will instantly
create a file of zero length, destroying any other file that might have
existed with that name. Because of this you should be especially careful
when you open files for writing the first few times.
Let’s look at the syntax of the file operator a little more closely.
filename mode file file_object
The exact syntax of the filename operand depends, to some extent, on the
environment in which the interpreter is running (see Table 14.2).
You will have to find some documentation for your system that details the
syntax for file names.
The best way to make sure that the file name you are about to use is valid
is to use the status operator first (see Example 14.8 later in this chapter).
Example 14.1 shows some examples of the file operator in use, to give
you the flavor of some file names and modes:
Once your file is open, you can read from it (if you opened it with a mode
for reading) or write to it, as detailed in the next section. Don’t forget to
close it properly (with closefile) when you’re done (Example 14.2).
Once you have a valid file object, either from the file operator or the
currentfile operator, you can read from it or write to it any number of
times (or at least until you run out of data to read or until you fill up the
file system).
The read operator (and its variants) returns an exit status boolean to let
you know whether or not it succeeded. This lends itself nicely to an ifelse
statement for error checking, unless you are very sure that the operation
will succeed and you want the code to execute as fast as it can (which is,
actually, fairly often the case).
If you run out of data before the buffer is full, the operator you were using
to read from the file will tell you (by leaving false on the stack). There is
no general way to make sure that your use of readline won’t overflow
your buffer. If you’re worried about it, you can certainly use a very large
buffer (some implementations limit string sizes to about 64 kilobytes).
Example 14.4 illustrates use of the readstring operator to read data from
a file and write it to a new file.
Writing to a File
Writing to a file is very similar to reading from a file, with a couple of
exceptions. First of all, the writing operators do not return a boolean
If you need to write out a data file or create a file with formatted data in it,
you’ll need to become very familiar with the whole family of file writing
operators, as well as the data conversion operators (discussed in Chapter
13). Let’s look at some useful techniques.
FontDirectory { %forall
pop Wname
fd (\n) writestring
} forall
fd closefile
Notice that the Wnum procedure in Example 14.6 doesn’t pay much
attention to the type of its operand, so you could use it for either an integer
or a real number. The beauty of the cvs operator (used inside the Wnum
procedure to convert the number to a string) is that it is polymorphic; it
doesn’t matter what type of object you present to it, as long as there is a
reasonable string equivalent. The Wname procedure is very much the
same as the Wnum procedure, except that it prints a leading slash if the
name presented to it is a literal name. Note that the slash is not part of the
name itself. The slash syntax helps you to create a literal name when your
program is parsed for the first time, but it just sets the literal flag on the
name object, and does not otherwise differ from an executable name.
% these procedures all depend on “fd” being a valid output file descriptor
/Wname % /name Wname -
{ %def
dup type /nametype ne { %ifelse
fd (% invalid name\n) writestring pop
}{ %else
dup xcheck not { fd (/) writestring } if
scratch cvs fd exch writestring
} ifelse
} bind def
/Wstring { fd exch writestring } bind def
/Wline % (string) Wline -
{ %def
fd exch writestring return
} bind def
/space % - space -
{ %def
fd ( ) writestring
} bind def
/return % - return -
{ %def
fd (\n) writestring
} bind def
/tab % - tab -
{ %def
fd (\t) writestring
} bind def
fd closefile
One of the most useful bits of information about a file is the number of
bytes it contains. On most PostScript interpreters with file systems
(including printers with a hard disk), there are two ways to determine this.
The first is the status operator, to which you pass a file name. The status
operator actually provides several kinds of information about the file,
including how many pages of disk space it occupies (a page is typically
1,024 bytes), when it was last accessed, when it was written, and how
many bytes it contains. Here is the syntax of the status operator.
filename_string status
pages bytes referenced created true
or
false
The status operator returns a boolean which is false if the file exists, true
otherwise. If the file exists, the status operator also returns the other
fields shown in the syntax of the status operator. Example 14.8 shows the
status operator in use.
CONCLUDING THOUGHTS
EXERCISES
1. Write a procedure that finds all the fonts on the local hard disk speci-
fied as (fonts/FontName) and write just the font names to the stan-
dard output file. (HINT: use the filenameforall operator.)
2. Adapt the program from Exercise 1 to write both the font names and
the byte sizes of the font files to the standard output file.
3. Write a program that will open a file called prog.ps and execute the
file one token at a time (using the token and exec operators), writing
APPENDIX 183
184 APPENDIX
Appendix
Answers to Exercises
CHAPTER 1
1. Your answers to the question Why do you want to write a PostScript
program? will be varied, and there is no answer to this exercise, of
course. Here are some potential responses that might stimulate some
thought.
• You want to write a driver to control a laser printer.
• You are writing a graphics application for a computer system that
uses PostScript for display graphics.
• You want to modify the behavior of an existing PostScript
program, or to add functionality to (or fix bugs in) an existing
piece of application software you have been using.
• You want to include graphics in a text document you are
preparing.
• You are interested in PostScript as a programming language, and
want to write any program you can.
• You want to write fractal programs to draw fancy pictures on
your display or on your printer.
CHAPTER 2
1. Notice that no variables or names are required to implement this fac-
torial algorithm. There are, of course, many other ways to accomplish
this task.
1 % leave on stack
10 -1 0 { mul } for
(10 factorial is ) print ==
3. Here are several language constructs that are available in both the C
and PostScript languages, of which you may have come up with four.
There may be others, as well.
• while loops
• if statements
• the ability to define and call procedures
• built-in mathematical operations for addition, subtraction,
multiplication, division, exponentiation, etc.
• boolean operations for and, not, or, and related bit-wise
operations
• array and string data types.
4. Here are some capabilities of the PostScript language that are not
available in other languages (you may have come up with any three
from this list, or perhaps others).
• built-in typographic operators for typesetting text (the show
operator or the findfont operator, for example)
• the dictionary data type
• the name data type
• the “path and paint” graphics model for constructing shapes and
filling or stroking them with color
• device-independent halftoning for shades of gray or color
• the ability to convert literal data types into executable program
fragments (for example, you can create an array of objects and
then make it an executable procedure body with one instruction).
2. Here is a simple procedure named line that takes four operands, the
x, y location for the beginning of the line and the x, y location for the
end of the line.
/line % X1 Y1 X2 Y2 line -
{ %def
4 -2 roll moveto
lineto stroke
} bind def
100 150 300 600 line
650 75 50 500 line
CHAPTER 4
1. In a page description, the prologue of procedure definitions should be
made as readable as possible, but the script that is generated by the
application should be made compact and efficient, since the prologue
is only executed once per document, but each procedure may be
called many times in the script.
2. Here are the kinds of PostScript programs that you might expect to
consume memory.
• downloaded font programs
• programs that load procedures into memory
• documents with string data in the body
• any program that stores data.
output page
test underlined text
2. Here is the program segment again. The results left on the operand
stack after its execution follow.
clear
/A /B /C /D /E /a /b /c /d /e
2 copy 6 index 6 index 12 -4 roll exch 5 3 roll
standardfonts
CHAPTER 7
1. Here is a sample program that implements a case operator. It is cer-
tainly not the best nor the only way to implement it. Compare this
version to the one you came up with.
/case % mark proc bool0 proc0 ... booln procn case -
{ %def
% requires a pair of operands on
% the stack for each option: the
% matching case item and a procedure.
% You must have a mark at the bottom
% of your case pairs, and you must
% supply the thing to be tested on
% the top of the operand stack:
% mark { default }
% case3 { proc3 }
% case2 { proc2 }
% case1 { proc1 }
% item_to_test case
CHAPTER 8
1. The loop index is used directly as provided by the for operator on the
operand stack. Notice that neither the loop index nor the factorial
variable are stored into a dictionary. They are used directly from the
operand stack.
1 % leave 1 on stack
10 -1 1 { mul } for
(10 factorial is ) print ==
2. The only trick to this is to rearrange the data in the opposite order on
the operand stack and to provide the correct number for the loop
constraint.
0 0 moveto
600 600
500 600
400 400
300 400
200 200
100 200
6 { lineto } repeat closepath fill
3. Notice the way the loop in the following segment of code takes
advantage of the way the search operator works, and uses the
arguments in a very natural way.
5. There were two bugs in the program, each of which alone would
result in a typecheck error on restore when executing the program.
In both cases, the problem was that the loop index was left on the
operand stack. In the first loop, the dup operator should not have been
used, since it caused the loop index to be left on the stack after each
iteration. In the second loop, the 1 index had the same effect, and
should have been simply exch.
CHAPTER 9
1. The secret is to rearrange the order in which the data are provided on
the operand stack to suit the order in which the operators in the proce-
dure require data.
/TEXT % (string) ptsize /fontname X Y TEXT -
{ %def
moveto
findfont exch scalefont setfont
show
} bind def
(This is an example of Times-Bold)
24 /Times-Bold 100 100 TEXT
CHAPTER 10
1. The value left on the operand stack after executing the following short
program segments is 16. The following walk-through of the program
segment shows how that answer was derived.
/variable 7 def % define /variable in current dict
5 dict dup begin % new dict on op-stack and dict-stack
/variable 9 def % /variable is 9 in new dict (both places)
end % remove one copy of new dict from dict-stack
/variable dup load % loads from original (current) dict => 7
3 1 roll get % “get” from new dict copy still on op-stack (9)
add % 7 + 9 = 16
CHAPTER 11
1. The contents of the array constructed by the code
[ 0 0 moveto 100 100 lineto currentpoint ]
2. The calculation of where to put the underline and how thick to make it
depends on several things. First of all, the position of the underline
depends on the text being underlined—both its position on the page
and its point size (the underline should be further away from the text
baseline for larger text). This font information is available in the
PostScript interpreter as well as in Font Metric files or other tables in
the host environment.
a. The main disadvantage of performing this calculation in your Post-
Script program is performance; it will take some time to execute in
PostScript. Also, it will increase the complexity of your PostScript
code a fair amount. The other disadvantage is that you don’t know
anything else about the page on which you’re underlining text. For
example, if you underline three words in a row, each of which is in
a different font, you would prefer not to have the underline change
thickness beneath the second or third word, and there is no reason-
able way to know about this at the PostScript level.
(Testing) stackpush
/Times-Roman 24 stackpush stackpush
72 600 stackpush stackpush
2. Here is a procedure called cvhex that takes an integer and turns it into
a hexadecimal string. It just uses the cvrs operator.
/scratch 128 string def
/cvhex % int cvhex <hex_string>
{
16 scratch cvrs
} bind def
1245 cvhex
3. Here is a program that will open a file called prog.ps and execute the
file one token at a time, writing each token to an output file.
% open prog.ps and execute one token at a time, writing each token
% to trace.ps as you go
209
210 INDEX OF EXAMPLE PROGRAMS
Index of
Example Programs
Chapter 1: PostScript as a
Programming Language ............................................ 1
Example 1.1: Repetitive Algorithms ........................................................ 5
211
Example 3.13: Comparison of Different Types of Objects .................... 32
Example 3.14: Procedure to Compare Array Contents........................... 33
Chapter 3: FOUNDATIONS 17
A C structures 152
algorithms 6, 12, 158 carriage return 160, 173, 177
allocating memory 29 case statement 79
argument passing 76 closefile operator 31, 169
arithmetic 27 closing a file 170
array object 51 comments 20
arrays 135 comparison of objects 32
concatenating 139 composite objects 30, 51, 61
construction 135 concatenating arrays 139
converting to procedures 164 concatenating strings 139
for linked lists 152 conditionals 73, 79
versus dictionaries 141 compound 88
ASCII 2 constructing 80
nesting 87
B templates for 80
backslash 20 true and false 81
backspace 173 currentfile operator 167, 168, 169, 172
basic skills 17 cvs operator 164
begin 22
begin operator 126 D
booleans 82 data
braces 20, 22 converting types 163
bytesavailable operator 169, 179 storing and using 145
versus instructions 157
C data on operand stack 69
C programming language 9 data storage in dictionaries 119
217
data structures 6, 69, 141, 151, 155 files
data types 51 copying and renaming 174
def operator 54, 126 file naming 171
definitions 21 open modes 171
deletefile operator 169 opening and closing 31, 167, 170
delimiters 20 random access 179
reading and writing 168
dictfull error 123
reading instructions from 160
dictionaries 119
sequential access 179
for linked lists 154
findfont operator 57
global 123 flow charts 12
local 121 font names 98
versus arrays 141 font programs 40
dictionary stack 62, 63, 75 for loops 93
disk (see files) 167 forall operator 127
diskstatus operator 178 form feed 159
displays 4 functions 76, 105
Document Structuring Conventions 21
dup operator 71 G
get operator 126
E goto 3
emulator, line printer 159, 161 graphics state stack 62, 63
Encapsulated PostScript 43 grouping 56
end operator 126
EPSF 43 H
equality 32 hard disk (see files) 167
hard disk status 178
error, syntax 19
/etc/passwd 174
example programs 37 I
exch def 61, 70 ifelse operator (see also conditionals) 79
image operator 100
exch operator 71
immediate name lookup 29
executable names 60
indentation 20, 22
execution model 54 index operator 71
execution stack 62, 63 input/output 14
exitserver operator 46 instructions versus data 157
intuition 56
F ioerror 180
file objects 167
file operator 167, 169 K
filenameforall operator 169 known operator 129