Postscript Language Program Design GREEN BOOK
Postscript Language Program Design GREEN BOOK
Copyright 1988 by Adobe Systems Incorporated. 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. PostScript is a registered trademark of Adobe Systems Incorporated. The information in this book is furnished for informational use only, is subject to change without notice, and should not be construed as a commitment by Adobe Systems Incorporated. Adobe Systems Incorporated assumes no responsibility or liability for any errors or inaccuracies that may appear in this book. The software described in this book is furnished under license and may only be used or copied in accordance with the terms of such license. ISBN 0-201-14396-8 ABCDEFGHIJ-HA-898 First printing: February 1988
iv
CONTENTS
PREFACE.................................................................................. xi chapter 1 THE POSTSCRIPT LANGUAGE: OVERVIEW ............................1
1.1 INTRODUCTION...................................................................................1 1.2 THE LANGUAGE MODEL.......................................................................2 DICTIONARIES AND DATA STRUCTURES...............................................4 STACKS ...........................................................................................4 BUILT-IN POSTSCRIPT LANGUAGE OPERATORS .....................................5 1.3 THE IMAGING MODEL..........................................................................5 COORDINATE SYSTEMS .....................................................................6 PATHS AND PAINT ............................................................................7 FONTS ............................................................................................7 1.4 ELECTRONIC PUBLISHING AND PRINTING ...........................................7 1.5 PROGRAM DESIGN GUIDELINES ...........................................................9
2.8 THE EXECUTION STACK ..................................................................... 32 2.9 THE SERVER LOOP .............................................................................. 34
vi
vii
viii
ix
PREFACE
ABOUT THIS BOOK
PostScript Language Program Design is intended to provide a solid background for developing software in the PostScript languagenot just getting a program to work, but thoroughly designing it from top to bottom. The book is organized into fteen chapters, each of which addresses a specic aspect of program design or problem-solving. The ideas in this book are targeted for sophisticated software development, although they should be useful at any level. The goal of this book is to teach the fundamentals of designing PostScript programs and to show how the language works, so that your programs will be fast, well-behaved, easy to understand, and portable. The PostScript language is somewhat unusual in the realm of programming languages in that it is interpreted and stack-based. Learning to program effectively in the PostScript language is not as easy as just learning another procedural high-level language. This book should help you learn how to think in the language as well as provide pragmatic advice and examples for accomplishing specic tasks. The sample programs contained in each chapter are intended to be directly usable. They are examples, but they are not trivial ones. Each program has been carefully designed and debugged and should provide an excellent foundation for an industrialstrength printer driver. Feel free to use the codethat is why it is there. This volume is a companion volume to two other books by Adobe Systems Incorporated: PostScript Language Reference Manual and PostScript Language Tutorial and Cookbook, both published by and available from the Addison-Wesley Publishing Company, Inc. We recommend that you have at least the Reference Manual handy when writing programs. If you have not
xi
been exposed to the language before, the Tutorial and Cookbook is a good place to start.
PRODUCTION NOTE
Glenn Reid, of Adobe Systems Incorporated, is the principal author of this book. Glenn conceived and executed the text, examples, and illustrations, with feedback from many people at Adobe. John Deubert helped with preliminary outlines of the book and supplied the rst draft of Chapter 8. Doug Brotz, Ed Taft, Ann Robinson, Ming Lau, and many others proofread the manuscript, found bugs, and offered valuable advice. Thanks to Carole Alden of Addison-Wesley as editor for providing guidance and encouragement. Special thanks to Pat Marriott, who was a paragon of patience and wisdom throughout the project, and to Linda Gass, who made it all possible. The book was produced using Frame Maker 1.1 from Frame Technologies, Inc., San Jose, California. It was composed and written on a Sun Microsystems 3/50 workstation running Sun Unix. All illustrations were produced in the PostScript language directly with the Adobe Illustrator software package on an Apple Macintosh Plus computer and imported into Frame Maker for nal printing. Proofs were printed on a Digital Equipment Corporation ScriptPrinter and nal camera-ready copy was set on a Linotype L-300 typesetter at Adobe Systems.
xii
maintainable as possible without requiring too much white space, which tends to make the listings long and more difcult to read.
PROCEDURE BODIES
Procedure bodies are probably the most common construct in any program written in the PostScript language. Procedure bodies are delimited by braces, also frequently called curly braces. It is important to be able to determine at a glance where the beginning and end of a procedure body are found. PostScript procedures can be used as arguments for many different operators, including loop, forall, kshow, image, ifelse, def, and others. The notation used for procedures in this book uses a simple PostScript language comment to indicate the operator for which the procedure body is an operand: usertime dup 1000 add { %loop dup usertime lt { pop exit } if } bind loop Short procedure bodies that t entirely on one line do not use this comment convention, since their use is more obvious.
INDENTATION
In general, any construct that has a clear beginning and end has its contents indented, including gsave and grestore, begin and end, procedure brace delimiters, and so on: /$arithdict 12 dict def $arithdict begin /avg { %def add 2 div } bind def /graph { %def gsave 0 0 moveto 1 3 avg lineto 2 setlinecap stroke grestore } bind def end % $arithdict
xiii
The horizontal alignment (indentation) of the beginning of the line containing an open-procedure brace delimiter matches the horizontal alignment of the close brace: currentpoint exch 500 gt { %ifelse 72 exch 12 sub moveto }{ %else pop } ifelse The slightly unusual technique of placing brace delimiters on a line by themselves is to make it easier to edit. You can add lines to or subtract lines from procedure bodies without worrying about the delimiters becoming unbalanced. There are, of course, other styles that work well. This one has been adopted throughout this text so that it is at least consistent, which is the last of the three goals.
xiv
1.1 INTRODUCTION
PostScript is the name of the computer programming language developed originally by Adobe Systems Incorporated to communicate high-level graphic information to digital laser printers. It is a exible, compact, and powerful language both for expressing graphic images and for performing general programming tasks. As is true with many programming languages, the PostScript language has been designed for a specic purposeto express complex digital graphics in a device-independent manner. Powerful typesetting features are built into the language for sophisticated handling of letterforms as graphics. The PostScript programming language is an easy one to learn, and graphics programs may be written by hand to produce high quality text and images. However, the language is intended for machine generation. That is, PostScript language programs are generally produced by other software rather than by programmers. This chapter is an overview of some of the more important features of the PostScript language. It is intended to provide a basis for understanding the why behind the programming examples given in the rest of the text. The full specication of the PostScript programming language can be found in a companion volume, PostScript Language Reference Manual (Addison-Wesley).
It is assumed that the reader of this book is a programmer or has some experience with programming languages. Previous experience with the PostScript language is recommended, but not required. The book is targeted at the specic tasks that need to be performed to implement a driver for any device containing a PostScript interpreter. The examples in the book are intended to be cut and paste routines. They are optimized for efciency wherever possible, and should be appropriate for direct inclusion in a product-level driver. Rather than making the examples simple and relying on the text to illustrate the point, the examples show what you should do, and the text attempts to explain why. In theory, at least, it should be possible to paste all the examples in the book together without necessarily understanding how they work and have a very functional program when you are through. In practice, of course, it is best to understand why an example is written the way it is, so that you can adapt the programming style to your own implementation. Much of the material contained in this book is useful to developers of drivers for devices containing PostScript interpreters. A printer driver is typically the part of a software application that produces output specically intended for a printer. A printer driver for a device with a PostScript interpreter simply produces a PostScript language page description as output, rather than supplying escape sequences for a particular printing device. These page descriptions are partly generated by the application software and partially written by hand. This book is dedicated to the process of design which must go into producing high-quality PostScript language software.
input stream, which means that no memory restriction is placed on a PostScript language program other than memory allocated by the program itself. The PostScript language is based heavily on context, or state. This context dependency is very powerfulby modifying the coordinate system transformation, any page can be easily scaled, rotated, or translated. The program executes in the context established for it beforehand. By redening the context of the dictionary stack mechanism, built-in PostScript operators can easily be replaced by procedures with additional functionality. Correctly using the context provided by the PostScript imaging model is a challenging aspect of learning the language, since it behaves much differently than most other programming languages in this respect. The PostScript language is designed to be interpreted, not compiled. User programs do not execute directly on the CPUthey are read and interpreted by another program, the PostScript interpreter. The PostScript language is a high-level programming language and it can be used to implement algorithms and process data, but a careful understanding is necessary to use it effectively. The difference in execution speed between a poorly written C program and well written one may be a factor of two or three at best, if the code is improved dramatically. More often, improvements of 10 percent are considered quite good. In general, speed improvements to software written in compiled languages come from improving the algorithms used, not from changing ones programming style. Simply improving the style of a poorly written PostScript language program can yield substantial execution speed improvements, frequently by a factor of ten or more. The nature of the interpreted language model and the execution semantics require more attention to detail than most compiled languages. Learning to use the PostScript language well can have an immediate and dramatic effect on the efciency and reliability of your software.
STACKS
The PostScript language is based on stacks. A stack is a xed data structure in which objects are placed temporarily, one on top of another. By nature, stacks are last in, rst out data structures. If you put three items on a stack, the third one is on the top, (and is the rst item available). Operators in the PostScript language are preceded by their operands. The operands are placed on the operand stack as they are encountered by the interpreter, and when the operator itself is encountered and executed, its arguments are found on the stack. This is often referred to as post-x notation.
There are several stacks used by the PostScript interpreteran operand stack, an execution stack, a dictionary stack, and a graphics state stack. The operand stack is in constant use, while the others are more specialized. The operand stack is frequently referred to simply as the stack. Programming in the PostScript language is primarily an exercise in stack manipulation. The most common PostScript errors result from incorrect manipulation of the stack, and many optimization strategies consist of using the stack as efciently as possible. Further discussion of stack mechanics is provided in Chapter 2. It is worthwhile to become quite familiar with the mechanics of stack operations in the PostScript language.
tion for raster imaging that frees application software from having to make device-specic rendering decisions. An important aspect of using the PostScript language correctly is to make proper use of the imaging model. The purpose of the PostScript language is to provide a uniform way to represent visual elements on any raster device. An application program typically needs to communicate visually with the user in terms of representing text, graphics, and aspects of the user interface. Whatever internal representation or data structure is used, some imaging model is necessary when presenting the information visually. When printing on a device with a PostScript interpreter, some translation is usually necessary between the applications internal representation for graphics and the PostScript languages imaging model, unless the screen display is also driven by a PostScript interpreter. The PostScript language provides both an execution model and an imaging model to applications programs. An important consideration is to adapt naturally to the imaging model when producing output for the PostScript interpreter, rather than using the programming power of the language to emulate another graphic imaging model.
COORDINATE SYSTEMS
The user coordinate system in the PostScript imaging model provides a consistent and predictable environment for constructing graphics. This coordinate system, referred to as user space, is the same for all printers with PostScript interpretersit is only the mapping from user space to device space that is different from one raster device to another, depending on the resolution and orientation of the device. To produce truly device-independent programs with the PostScript language, it is important to think only in terms of the user space abstraction, and never about any particular output device. Similarly, page layout and sizes should be expressed only in terms of where they fall in user space. The coordinate system transformation from user space into device space can be modied easily to scale an entire page when it is actually ren-
dered, and it is good not to make any specic assumptions at page composition time about the size of the nal page.
FONTS
The PostScript language has a completely integrated model for quality typesetting. PostScript fonts are executable programs that draw the character shapes using the same path construction and painting mechanism as all other graphics. The text can be thought of as integrated graphics, and can be transformed in any way that graphics can be. There is extensive support in the PostScript language (beyond the standard graphics commands) for manipulating font programs and for setting text characters. Fonts may be created from existing graphic shapes. Once a program or procedure has been devised that renders a particular shape, that code can be packaged into a font dictionary and placed on the page through the text-handling mechanisms. It is the representation of typefaces as graphic descriptions that realizes the full integration of text and graphics on a single page. It is not that the text alone may be scaled and rotated that is signicantit is that the entire page is completely integrated and may be treated as a single graphic entity, and the page itself may be printed half-size or rotated to any angle.
Computers are now used extensively to simplify the editing, composition, and typesetting of books and documents. The PostScript language is a system solution to the perplexing problem of composing, proong, and printing documents. It is intended chiey for high quality typesetting, graphic design, and book production. However it is also used for more standard computer printing, including everything from program listings to business correspondence using typewriter-like typefaces. There are many, many different levels at which a computer environment might support PostScript technology. A PostScript printer may be used to get hard copy of something on the computer screen, it may be used to print text in much the same way that a line printer does, or the PostScript language typesetting and graphic models may be used fully in the preparation of camera-ready output for major book production. There is a great difference between printed output intended as a nal product and simply a hard copy of some information that already exists in some other form. When designing software, it is useful to consider what the nal product will be. If the program is a word processor, for instance, is the nal product considered to be the words themselves, their arrangement, the typeface that they are set in, or the book in which they will eventually appear? To enter even slightly into the level of presentation means a heavy commitment to the nal form. For example, if the arrangement of words is part of the product, then it becomes a visual product, and most likely a printed visual product. When designing software for printing and publishing, the nalform technology must be considered. The graphic imaging model used by the printing technology, the available typefaces, the units of measure, and the available paper sizes are all part of the nal product. A common problem in bridging from traditional computer printing into the realm of electronic publishing is to muddy the waters between composing the document and printing it. For example, when printing on PostScript devices, one must compose text using the character widths of the fonts that will ulti-
mately be used on the printer. This requires some advance knowledge of the printing technology. But it is the abstraction that is used, and not a particular output device. The resolution and printing characteristics of the device should not be propagated into the document designonly the ideals of the layout.
2.1 INTRODUCTION
The manner in which the PostScript interpreter executes a program has a profound effect on the efciency and design of software. This chapter takes a close look at the execution semantics and details of the PostScript interpreter, with the idea that a full understanding of how a program is being executed will provide a solid foundation for good programming skills. Probably the most important concept to glean from this chapter is the notion of PostScript language objects and how they are stored and manipulated. Understanding the mechanics of the scanner and interpreter will also help shed light on issues of efciency.
11
application invokes a particular section of code usually known as a printer driver to produce the output for the printing device. This page description must then be transmitted to the printer as a batch job, usually by an intermediate communications filter. The print job is then scanned and interpreted by the PostScript interpreter for that printer, and the resulting pages are printed. This model of execution is mostly invisible when one creates and executes PostScript language programs. It is presented here as a framework of reference, since it is the outermost level of interpretation in a device with a PostScript interpreter. A PostScript interpreter (in batch mode) executes a single job from the time it receives the initial byte over a communications channel through the next end-of-le (EOF) indication. All legal PostScript language sequences are interpreted and executed by the interpreter until the EOF is reached. At the end of each job, the interpreter resets the state which existed at the beginning of the job. This is actually accomplished by the save and restore operators. The interpreter then returns to an idle state until the next job is received. This behavior is discussed in more detail in Section 2.9, THE SERVER LOOP. This job execution model has two important consequences: The PostScript interpreter is completely restored to its original state after each job nishes execution. Each print job must be a contiguous stream of bytes. Anything that is to be part of a particular document must be present in the print le. The main advantage of this mechanism is that it guarantees certain initial state conditions to all print jobs, regardless of whether the job is the rst or the forty-rst one interpreted since the machine was powered on. A print job should always assume that the initial state of the PostScript interpreter is the default state, which is dened in Section 6.3, MODULARITY AND PAGE STRUCTURE. This initial state is guaranteed by the job server loop. For reasons that will become apparent, it is important to trust the initial state of the interpreter. A common practice is to initialize the interpreter at the beginning of each job, in an
12
attempt to guarantee some execution state for the job. In most instances, this accomplishes nothing. (The state has already been initialized by the server loop). In instances where the state is not the default state, there is likely to be a good reason for it. For example, the transformation matrix may have been modied to print the page as a small illustration rather than as a full-size page. Initializing the matrix will reverse this effect. For a more involved discussion of this principle, see Section 6.3 and most of Chapter 11.
13
To understand the distinction between a simple and a composite object, consider what happens when the dup operator is executed. The dup operator makes a copy of (duplicates) the top entry on the operand stack. Since the operand stack contains only objects, the result of executing dup is to duplicate the object itself. If the topmost object on the operand stack is a simple object, dup produces a new object with a copy of the original value, since the object is completely self-contained. With a composite object, however, the new object produced by dup contains a pointer to the same value as the original. The value of the object is stored elsewhere in memory, and is not duplicated. This means that any changes made to the value of the duplicated copy are also made to the original. You can obtain a new, distinct copy by allocating a new composite object (with the dict, string, or array operators, for example) and using the copy operator to copy the original objects value to the new one. Another way to think of it is that the value stored with an object either ts into the object (simple) or it doesnt. If it doesnt t, then a pointer to the value is stored instead (composite). See Figure 2.1.
gure 2.1
duplicating a simple object: 9 dup duplicating a composite object: (PostScript) dup
()
PostScript
9 operand stack
()
operand stack
14
Each object has several attributes associated with it. These include a literal or executable ag, an access attribute, which controls read-write-execute permissions for the object, and a length attribute (used only with composite objects). There is also a type associated with every object as one of its attributes. Some of these attributes are easily changed with the following operators: Attributes literal/executable access Operators cvlit, cvx readonly executeonly noaccess cvi, cvr, cvrs cvs, cvn
type
For the most part, there is no need to convert objects from one type to another or to modify their attributes. However, it is good to remember that PostScript language objects are typed and maintained internally with this representation.
15
consider the two (equivalent) PostScript language fragments given in listing 2-1; each takes the same values on the operand stack, but each in a different order. Compare the procedures and how they use the data.
listing 2-1
%!PS-Adobe-2.0 %%Title: stack example /procGOOD { %def findfont exch scalefont setfont } bind def /procOK { %def exch findfont exch scalefont setfont } bind def /procBAD { %def /ptsize exch def /fontname exch def fontname findfont ptsize scalefont setfont } bind def %%EndProlog 12 /StoneSerif procGOOD /StoneSerif 12 procOK /StoneSerif 12 procBAD %%Trailer
The procedure calls themselves look much the same. The data passed is identical in each case. The difference between the procGOOD and procOK procedures is only a single exch instruction. However, if the data is supplied in the natural order, the exch is unnecessary. The only difference between procGOOD and procBAD is that the operand stack is used naturally in the good example, whereas the operands are dened into a dictionary (and subsequently retrieved from the dictionary and put back on the operand stack) in the bad example.
16
occur. It can be manipulated directly, but it is best to think of it as an environment, not a data structure. The dictionary stack contains objects, just as the operand stack does. However, the only type of object which can be placed on the dictionary stack is, naturally, a dictionary object.
DICTIONARY OBJECTS
A dictionary is a composite object, much like an array. It is represented by a dictionary object, and it is always referenced through that object. The object can be placed onto one of the stacks, or it can be stored in a dictionary, just like any other object. However, certain special operations can be performed only with dictionaries. In particular, entries in a dictionary are ordered pairs. Each entry has a key and a value (actually an object, not to be confused with the value of the object itself). Either of these objects can itself be a composite object, including a dictionary. Dictionaries are the foundation of the storage and retrieval mechanism in the PostScript language. Data can actually exist in only one of two places: On the operand stack (or as a component of a composite object on the operand stack) In a dictionary (or in a component of a dictionary, such as an array). The reason is that unless an object is on one of the stacks, the only way to call it up is by name. If you dont store something explicitly into a dictionary (with def, for instance) and the object is removed from the operand stack, it cannot be recovered. That alone is not quite reason enough to place something in a dictionary, but it is something to be aware of. Dictionary objects can be created only by the dict operator. The dict operator requires an integer value as its argument, and it creates an empty dictionary object with that number of elements allocated for later use. The dictionary object is returned on the operand stack. For example, the dictionary created by the sequence 3 dict is just a composite object. It is not the current dictionary, nor does it have a name. It is just an empty data
17
structure on the operand stack, which (in this instance) can contain up to three entries.
18
the dictionary object (the one currently on top of the dictionary stack) and places the copy on the operand stack. Notice that the dictionary object that was initially created was also on the operand stack until begin moved it to the dictionary stack. The end operator simply pops one element off the dictionary stack (and discards it). If currentdict had not been executed before end, the dictionary (along with its three entries) would have been lost. This is because the object created by the 3 dict statement was never stored into a dictionaryit was simply used by the begin operator. Here is an equivalent (though substantively different) PostScript language fragment: 3 dict dup /proc1 { pop } put dup /two 2 put dup /three (trois) put This examples uses put instead of def. It never places the dictionary onto the dictionary stack, and it makes each entry explicitly by presenting the dictionary object to the put operator each time. (The dup is used to avoid losing the object when put is performed.) However, the resulting dictionary on the operand stack is identical to the one created in the previous example. The dictionary stack is an environment. It is most important as a context for name lookup, which is discussed in depth in the next section. The previous discussion, although cumbersome, is meant to illustrate two concepts: Dictionaries are represented as objects, which can either be left on one of the stacks or given a name and dened as an entry in another dictionary. Dictionary objects do not necessarily have names and are much like arrays in their use. They are special mostly in the context of name lookup.
19
which these operators are stored are the names by which we think of them: moveto, showpage, or add, for example. The names themselves are not special in any way. They are used simply as keys for dictionary entries. (For the most part, they are keys in systemdict.) The actual execution is carried out after these names have been looked up and an executable object has been found in systemdict. This mechanism is known as automatic name lookup. Name lookup is invoked whenever the PostScript interpreter encounters an executable name object. The difference between a literal name and an executable name in the PostScript language is the presence (or absence) of a leading slash character: /moveto moveto % literal name % executable name
When the PostScript interpreter encounters an executable name, it looks up the name in the current dictionary context and interprets whatever it nds as a result of that name lookup.
gure 2.2
3 dict begin
userdict
systemdict
dictionary stack
Name lookup is done from the top down, in the context of the current dictionary stack. When a name is encountered
20
(whether it is in the original input le or found in a procedure body executed later) it is looked up in all dictionaries that are currently on the dictionary stack. (See gure 2.2.) If the interpreter nds an executable object (like a procedure body) or a built-in PostScript language operator, it executes it directly, rather than placing it on the operand stack. This is how both operators and procedures are executed: They are just looked up in a dictionary as names, and the object found in the dictionary is interpreted further.
21
(as distinguished from an executable name). It means that the name objects have been removed and replaced by operator objects. The array as it is stored in memory actually looks like gure 2.3:
gure 2.3
value: findfont type: nametype executable: executable value: --exch-type: operatortype executable: executable value: --scalefont-type: operatortype executable: executable value: --setfont-type: operatortype executable: executable
value: type: arraytype executable: executable length: 4 value: F type: nametype executable: literal
operand stack
The procedure body is actually an array object with its executable ag set, and the contents of the array are, in turn, other objects. After bind is applied, the original name objects are replaced by their respective operator objects, as you can see in the gure. (See Section 2.7 for more detailed information about procedure bodies and how they are constructed.) It is useful to execute bind on any procedure body that will be used many times. The result of binding a procedure body is that the name-lookup operations can be distilled out, requiring less time to execute the procedure. The semantics of bind dictate that if an operator has been redened prior to being used in a procedure, then bind leaves the name in the procedure body and will not replace it with the operator object. /showpage { } def /ENDPAGE { %def gsave showpage % name is not replaced grestore } bind def
22
/ENDPAGE { %def gsave showpage % name is replaced by operator grestore } bind def /showpage { } def % has no effect in ENDPAGE proc Because bind replaces only those items that are found to be of type operatortype, it will not replace an object that is of type arraytype (which is the case when an operator name is redened with a user procedure). This means simply that it is all right to bind procedures even if some of their elements might be redened, as long as the denitions are done before the procedures are called.
23
the scanner will recognize and replace the backslash notation with the appropriate characters and it will produce a string object as follows. The parentheses are omitted because they are not part of the string, but indications to the scanner of where the string starts and end (although this string contains some parentheses as data, as you can see): ABCD \(ABCD) (balanced) The scanner also is responsible for issuing the PostScript language syntaxerror, which can only result from mismatched (unbalanced) string delimiters and unexpected ends-of-le. There are no other syntax errors possible in the PostScript language! The PostScript interpreter invokes the scanner each time it needs a token from the object currently being executed. The scanner reads from a le or string with exactly the same semantics as the token operator (see the PostScript Language Reference Manual), returning exactly one object to the interpreter.
RECOGNITION OF OBJECTS
It is the scanners responsibility to recognize objects. There are only four kinds of objects that it knows how to recognize: Literal strings: any string object delimited by either < > or ( ). The scanner watches for \ notation and attempts to nd the closing delimiter. Mismatched delimiters result in syntaxerror. Numbers: the scanner tries rst to make a number out of a token. This includes reals, integers, and radix notation. Names: anything that cannot be interpreted as one of the above is returned as a name object. The literal/executable ag is set depending on the presence of a leading slash (/). Procedure bodies: Procedure bodies delimited by curly braces { } are recognized by the scanner and returned to the interpreter as executable array objects.
24
There are a few special cases. For instance, the leading slash (indicating a literal name) overrides the scanners ability to recognize a token as a number: /1000 type == nametype It is worth noting that any type of object can be used as a key in a dictionary. Names are used most often, but they are not required. The following is a perfectly legal, although somewhat confusing, PostScript language sequence (shown as though typed in interactive mode): PS> 13 (thirteen) def PS> 13 load == (thirteen) PS> It is necessary to use load in this example because the token 13 is recognized rst as an integer, not as a name. Therefore, automatic name lookup is not invoked. The load operator explicitly invokes key lookup for any object, in the context of the current dictionary stack. Note: When a name is encountered in a program, the scanner returns a name object. Name objects require a fixed amount of overhead, regardless of the number of bytes in the name. However, time required to recognize a name is proportional to its length. With this in mind, it is always best to use short procedure names if the procedure will be invoked many times (as in the script, but not in the prologuesee Section 6.2).
2.7 PROCEDURES
A PostScript language procedure is simply an executable array of PostScript objects. Normally, procedures are given a name and stored in a dictionary, so that they may be called up by presenting the appropriate executable name to the PostScript interpreter. However, procedures can exist without having names at all (as can any object until it is stored into a dictionary).
25
A procedure that exists (perhaps on the operand stack) but is not referenced by name is typically referred to as a procedure body. Procedure bodies are usually stored in a dictionary under a particular name and used in the same manner as the built-in operators, although they are also required (or permitted) as arguments to several operators, including image, imagemask, setscreen, settransfer, if, ifelse, loop, and forall. The PostScript languages mechanism for building and dening a procedure is worth examining more closely: /F { %def findfont exch scalefont setfont } bind def This is a typical procedure denition. It builds a procedure body (in this case consisting mostly of operators), executes bind to distill out the name lookup, and then denes the procedure in the current dictionary under the name F. Lets look at this sequence as the PostScript interpreter would, token by token: /F The scanner digests this as a literal name object and hands it to the interpreter. The PostScript interpreter places all literal objects directly on the operand stack and moves on. { This is a special PostScript language delimiter (informally known as a curly brace, or open-curly). It marks the beginning of a procedure body. The scanner enters deferred execution mode when this character is encountered. All objects through the closing } token are scanned but not executed, regardless of their type. When the closing delimiter is found, an executable array is created containing all the collected objects between the { } delimiters. In most implementations, the scanner uses the operand stack to construct procedure bodies, placing a mark on the stack and then placing each object on the operand stack until the closing
26
brace is found, at which point the array is constructed from the objects on the stack. This places a practical limitation on the number of objects (which may be composite objects) which can be put into a procedure. In most implementations, this number is 499, which is the depth of the operand stack less 1 for the mark (listing 2-3 contains a method for building much larger procedure bodies when necessary). Here is the state of the stack just before the } character is encountered, which causes the objects on the stack (down through the mark) to be collapsed into a single array object. setfont scalefont exch findfont --mark-/F
These are all objects of type /nametype, with the exception of the mark, which is of /marktype. The scanner performs no name lookups during the scanning of a procedure body. When the scanner recognizes the } delimiter it performs several functions. First, it decrements the level counter that keeps track of the nesting of curly braces, and if the level is back to 0, the interpreter reverts back to immediate execution mode (which is the normal state of the PostScript interpreter). Next, the scanner performs the equivalent of counttomark array astore: It allocates an array of the appropriate length, lls it with the objects on the stack down through the mark, removes the mark, and leaves a freshly created array object on the stack. The nal step is to make the new array an executable array. (It does this by performing the equivalent of cvx.) Here is the state of the operand stack right after the } character is processed:
27
--executable array-/F
The name /F is still on the bottom of the operand stack, where it was at the beginning of this example. The entire procedure body is now stored in memory as a sequence of objects represented as an executable array object on the operand stack. Note that there is still no procedure named F. There are merely two objects on the operand stack. They could be eliminated very easily by executing the clear or pop operators, for example. It is not until the def operator is nally executed that this array object is safely stored in the current dictionary under the name F. The remaining part of the last line of the program is bind def These are two more executable names. Each of these is looked up and executed (since the interpreter is now in immediate execution mode). The effect of executing bind is to perform early name binding on any elements of the array object that may be executable operator names. (See Section 2.5, OPERATORS AND NAME LOOKUP, for a discussion of bind.) The bind operator expects to find an executable array on the operand stack, and it leaves one there (possibly containing different objects) when it is through. The effect of executing def, of course, is to take the top two elements from the operand stack and make a key-value pair association in the current dictionary. The F procedure is now dened. It can be used like any of the built-in operators in the PostScript language, as long as the dictionary in which it is stored is available on the dictionary stack.
listing 2-2
%!PS-Adobe-2.0 /F { %def findfont exch scalefont setfont } bind def %%EndProlog
28
Listing 2-2 would result in something like this being printed on the page:
Green Book
VERY LARGE PROCEDURE BODIES
Occasionally one needs to dene a very large procedure body, perhaps as a character description in a font or as part of a logotype. The operand stack depth limit is typically about 500 elements, which limits the total size of a procedure created while in deferred execution mode, since all the elements in the procedure are rst placed on the operand stack. There are a few ways to cope with this problem. The easiest way, if the host application can keep track of how many elements are being generated, is to break out pieces of the procedure and make them separate procedures, invoked by name within the large procedure body: /small1 { one two three four } def /small2 { five six seven eight } def /small3 { nine ten eleven twelve } def /large { small1 small2 small3 } def If the procedure is being generated automatically, rather than by hand, this may not be a feasible approach. For one thing, the number of new (unique) names required cannot be predicted easily. In listing 2-3 is a method for collapsing a large procedure into many small component pieces.
29
listing 2-3
%!PS-Adobe-2.0 %%Title: collapseproc.ps %%EndComments /EXEC { /exec load } bind def /mainproc [ %def { gsave 400 36 moveto } EXEC { /Times-Italic findfont 24 } EXEC { scalefont setfont (from Listing 2-3) } EXEC { show grestore } EXEC ] cvx bind def %%EndProlog mainproc showpage %%Trailer
Notice the use of exec in this example. It is necessary since the component procedure bodies will not be immediately executed when encountered directly (they are simply placed on the operand stack). Also, the relationship between building square bracket arrays and curly-brace procedure bodies is interesting (and crucial to this example). Since the outermost brackets are actually square brackets, all the intervening PostScript code is executed, not deferred. For this reason, in order to place the exec operator in the procedure body, its value must be obtained with load (see the EXEC procedure in the example). Here is what the /mainproc in listing 2-3 looks like after it is built (a little interactive dialog is shown to show the length of the array, too): PS> /mainproc load dup == { { gsave 400 36 moveto } --exec-- { /Times-Italic findfont 24 } --exec-- { scalefont setfont (from Listing 2-3) } --exec-- { show grestore } --exec-- } PS> length == 8 PS>
30
Each procedure body within the square brackets is built individually, and when the } token is encountered, each one is collapsed into a single (executable) array object on the operand stack. This way the operand stack does not overflow, since it is effectively segmented into smaller pieces. An array of arrays is built, and each sub-array is explicitly executed by exec. The outermost array is simply made executable and can be used like any other procedure.
listing 2-4
%!PS-Adobe-2.0 %%Title: addproc.ps %%EndComments /F { findfont exch scalefont setfont } bind def /S { moveto show } bind def /addprocs { %def dup length 2 add array cvx dup 3 -1 roll 2 exch putinterval dup 0 4 -1 roll put dup 1 /exec load put } bind def %%EndProlog gsave currenttransfer { 1 exch sub } addprocs settransfer 36 /Helvetica-Oblique F 1 setgray % normally white... (Black) 36 670 S % but the inverse transfer (Words) 36 634 S % function yields black grestore showpage %%Trailer
Another application of this technique is a space-efcient way to concatenate two procedure bodies, as can be seen in listing 2-4. It denes a procedure called addprocs which concatenates two existing procedure bodies. It creates one new procedure body that is the length of the second procedure plus two. The array object representing the first procedure is inserted into the 0th position in the new array, the exec operator is placed in the sec-
31
ond slot, and the second procedure is copied into the rest of the new procedure body, resulting in a new procedure like this: { one two three } { four five six } addprocs => { { one two three } exec four five six } This preserves at least the first of the procedures without having to copy it (it is simply executed with exec).
32
%!PS-Adobe-2.0 /F { %def findfont exch scalefont setfont } bind def %%EndProlog 24 /Helvetica F 0 0 moveto (Red Book) show showpage This program is represented by a le object on the execution stack that the scanner reads and interprets token by token. We have seen the execution of the scanner; now lets look at what happens when the procedure is executed. When the F token is scanned and interpreted from the le object on top of the execution stack, it is seen to be an executable name. This name is looked up in the current dictionary stack, and an executable array (procedure body) is found. This procedure body is then pushed onto the execution stack:
Remember that the array is represented by an array object, even when it is on the execution stack. Figure 2.4 shows what this array object looks like on the execution stack. The PostScript interpreter executes array objects one element at a time, leaving the unused part of the array on the execution stack. The interpreter always checks the remainder of the procedure body before it executes the rst element. If the procedure body is empty, the interpreter discards it. This permits innitely deep tail recursion, since the empty procedure bodies will not accumulate on the execution stack. (Tail recursion means that the recursive procedure call is the very last element (the tail) of the procedure body.)
33
gure 2.4
value: --findfont-type: operatortype executable: executable value: --exch-type: operatortype executable: executable value: --scalefont-type: operatortype executable: executable value: --setfont-type: operatortype executable: executable
value: type: arraytype executable: executable length: 4 value: --filestream-type: filetype executable: executable
execution stack
34
In listing 2-5 is an idea of what the server loop looks like. A few pieces of pseudo-code are included to show some functionality without providing all the nuts and bolts. These are represented in italics. This example does not match any real server loop code, but it provides the theoretical model for how it operates. The server loop procedures for any given interpreter can be seen by simply executing this code: 128 array execstack ==
listing 2-5
/server save def % save the initial state { %loop server restore % when entering loop { %loop any port active { %if exit } if } loop % until new job is encountered /server save def % before each job (%stdin) (r) file cvx stopped { % execute the file object errordict /handleerror get exec currentfile flushfile } if } bind loop % main server loop
35
3.1 INTRODUCTION
The PostScript language imaging model is the metaphor through which graphics are rendered on output devices. The imaging model consists of specic rules and mechanisms by which a picture is described, and its behavior is exactly predictable. The imaging model for silk-screening, for example, might be loosely stated like this: Ink is forced through a mask of lm that adheres to a stretched piece of silk. Ink ows through the mask only where it has been cut away. The imaging model for the PostScript language might be stated: Algorithmic paths are created to dene an area or a series of lines, and ink is applied to those paths through one of several methods, including stroking, lling, halftoning, text rendering, and clipping. In the world of computers, all graphics application programs have a graphic imaging model, whether it is explicitly stated or simply assumed. Even text editors have a paradigm for the lines of text being edited. Although video screens are two-dimensional, the model used by a drawing program may permit objects to overlap on the screen, as though there were a third dimension. There is no correct imaging modelit is just a consistent way of thinking about graphics that is useful to a computer program. Writing a driver for an application to print through a PostScript interpreter is almost entirely the task of adapting one graphic imaging model to another. The act of printing a page is a translation of some computer graphics representation into a printed
37
reality. If the software permits the user to draw a circle, for example, the circle must be stored in some way (the internal representation), and ultimately printed with whatever means are available.
38
gure 3.1
User Space
Physical Page
gure 3.1
Path Construction
Painting (Rendering)
eofill imagemask ashow kshow fill stroke image show widthshow erasepage
Printing
showpage copypage
39
moveto lineto curveto arc, arcto closepath show widthshow charpath clip clippath strokepath
rmoveto rlineto rcurveto arcn reversepath ashow kshow flattenpath eoclip initclip newpath
Each of these adds to (or creates, or modies) the current path. The newpath, initgraphics, restore, and grestoreall operators can be thought of as path construction operators, even though they are really path destruction operators. The variations on the show operator are path construction operators only in that they affect the current point, which is part of the current path. The current path is part of the standard graphics state, and it is preserved (and restored or overwritten) by both the gsave and grestore operators and the save and restore operators. The clip operator uses the current path (appending it to the current clip path) but does not destroy it. This is often the cause of strange program behavior when using clip. Using the newpath operator after the clip usually fixes these problems. Construction of a path alone is not sufcient to cause marks to be made on the page. A path must be painted in order to see it.
40
in this example, and scale is invoked once. What does this path look like on the nal page, after it has been stroked?
listing 3-1
%!PS-Adobe-2.0 %%Title: pathconstruct.ps %%EndComments /F { findfont exch scalefont setfont } bind def %%EndProlog gsave 400 300 moveto 100 0 rlineto % horizontal line 2 setlinewidth 4 4 scale 0 100 rlineto % vertical line 1 setlinewidth gsave % make the picture more interesting... 14 /Times-Roman F (A) show 2 -12 rmoveto -20 rotate (A) show grestore stroke grestore showpage %%Trailer
To answer this, one must look more carefully at the graphics state (and at figure 3.2). Here are the elements of the graphics state: Current point and current path Current transformation matrix (CTM) Current font Current color (or gray level) Line weight, line cap, and line join Line miter limit and dash pattern Current raster device Current clipping region All halftone parameters The entries set in bold are the only elements of the graphics state that affect path construction directly. The others affect
41
only the painting of the path. In listing 3-1, the 4 4 scale does affect the subsequent execution of rlineto, causing the line to be (effectively) four times as long when it is appended to the current path. The path construction operators use the current transformation as the path is being constructed, but once an element of the current path exists, it is not affected by further changes to the coordinate system.
gure 3.2
Another way to think of it is that changes to the current transformation matrix and the current font affect future elements of the path, but not existing elements. Changes to the line width or the current color take effect only when stroke or fill is executed, and they apply to the entire current path.
42
that some part of the path is within the clipping region, of course). Here is a list of painting operators: fill image show ashow eofill imagemask widthshow kshow stroke
When marks are made in device space by one of these painting operators, they stay there. Even the showpage, erasepage, and framedevice operators, which cause device space to be cleared, operate by painting the entire page with white. In the steps for path construction and painting outlined in Section 3.2, the use of save and restore guarantees the functional independence of each page element, although the marks on the page persist until showpage is nally executed.
RECTANGLES
A common task performed by a graphics application is to draw a rectangle, often one that is shaded and/or outlined with a border. There are many ways to represent a rectangle. The goal, when writing a PostScript driver, is to minimize the computation required and the data transmitted. For example, if rectangles are always oriented either vertically or horizontally (never at an angle), they can be represented by two points (for example, the lower left and upper right corners), or by one corner
44
and the width and height of the rectangle. However, if the rectangle may be rendered at an angle, four points are necessary, or two points and an angle (although that would require more computation). A rectangle procedure may require several painting attributes (like a gray level and a line weight) as well as the geometric information to construct the rectangle. If so, this data should be passed to the procedure on the operand stack. Listing 3-2 provides a good rectangle procedure.
listing 3-2
%!PS-Adobe-2.0 %%Title: rectangle.ps %%EndComments % "R" takes the following arguments: % lineweight linegray fillgray % width height LLx LLy /R { %def gsave moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath gsave setgray fill grestore setgray setlinewidth stroke grestore } bind def %%EndProlog 3 .5 .9 200 300 100 100 R showpage %%Trailer
This example draws a rectangle that is 200 points wide, 300 points tall, is lled with 10 percent gray (0.9) and stroked with a 3-point line at 50 percent gray (0.5). Notice that the procedure uses the values directly from the stack (rather than dening them into a dictionary and calling them up again), since they are only needed once.
45
listing 3-3
%!PS-Adobe-2.0 %%Title: circle.ps %%EndComments % "C" takes the following arguments: % linewidth linegray fillgray % X Y r ang1 ang2 /C { %def gsave newpath 0 360 arc gsave setgray fill grestore setgray setlinewidth stroke grestore } bind def %%EndProlog 3 .5 .9 300 500 200 C showpage %%Trailer
Listing 3-3 draws a circle of radius 200 at the point 300, 500, lls it with 10 percent gray (0.9), and strokes it with a 3-point line at 50 percent gray.
ARROWHEADS
Lines terminating with arrow heads are commonplace in many graphics applications. In listing 3-4 is a procedure that will draw an arrow head at the current point, at a specied orientation and line width. (The line width is used only as a size referencethe arrow head is scaled to an appropriate size for lines of the specied width.) The orientation is supplied by a previous point. The arrow head is drawn parallel to the line implied by
46
the previous point and the current point, which should be the tangent of the line at the end point. See Listing 3-4.
listing 3-4
%!PS-Adobe-2.0 %%Title: arrowhead.ps %%EndComments %%BeginProcSet: arrows 1.0 0 % "arrowhead" takes these arguments: % lineweight prevX prevY /arrowhead { %def gsave currentpoint 4 2 roll exch 4 -1 roll exch sub 3 1 roll sub exch atan rotate dup scale -1 2 rlineto 7 -2 rlineto -7 -2 rlineto closepath fill grestore newpath } bind def /l^ { %def % lineto-arrow currentlinewidth currentpoint 5 3 roll lineto currentpoint stroke moveto arrowhead } bind def /rl^ { %def % rlineto-arrow currentlinewidth currentpoint 5 3 roll rlineto currentpoint stroke moveto arrowhead } bind def /arc^ { %def % arc-arrow 5 copy arc currentpoint stroke moveto % stroke arc % getting the correct orientation for the arrowhead % is tricky. This procedure uses the arguments to % "arc" to determine the tangent of the curve at the % endpoint, and it orients the arrowhead along that % tangent line. It leaves an X-Y point that is just % behind the arrowhead along the tangent.
47
% newX = X + radius * cos(endAngle-1) % newY = Y + radius * sin(endAngle-1) exch pop 1 sub % endAngle - 1 degree dup cos 2 index mul 4 index add % arrowX exch sin 2 index mul 3 index add % arrowY currentlinewidth 2 add 3 1 roll % thickness arrowhead pop pop pop % draw -> } bind def %%EndProcSet: arrows 1.0 0 %%EndProlog %%Page: 1 1 % line sample: 200 600 moveto 0 10 360 { currentlinewidth .1 add setlinewidth gsave dup cos 100 mul exch sin 100 mul rl^ grestore } bind for newpath % curve sample: /radius 10 def .1 setlinewidth 0 30 360 { /radius radius 10 add def 280 230 radius 0 5 -1 roll arc^ } bind for showpage %%Trailer
Listing 3-4 contains a fairly sophisticated set of procedures. There is one procedure to draw an arrowhead at the current point, and there are three other procedures (rl^, l^, and arc^) that work like rlineto, lineto, and arc, respectively, except that each draws an arrowhead at the end of the line segment (or arc) using the arrowhead procedure. In the arc^ procedure, the tangent to the curve is computed and a point is passed to the arrowhead procedure for orienting it properly. It is more likely that the host application would provide this simple x-y location to orient the arrowhead, to avoid this largely unnecessary calculation. There are also other possible orientation schemes.
48
Note: Keep track of the current path, and use it naturally. Avoid unnecessary path operations or safeguards that may seem at rst like good, defensive programming. The language is designed to work the way you probably need it to work, and it is best to use it that way. The fill, eofill, and stroke operators effectively use up the current path, destroying it when they are through (by executing the equivalent of newpath). The image and imagemask operators do not disturb the current path in any way. The show operator and its siblings make changes only to the current point (corresponding to the width of the string being shown), and otherwise do not disturb the current path.
49
listing 3-5
%!PS-Adobe-2.0 %%Title: text examples %%EndComments /F { findfont exch scalefont setfont } bind def /S1 { moveto show } bind def /S21 { moveto gsave show grestore } bind def /S22 { %def 0 exch rmoveto gsave show grestore } bind def %%EndProlog % method 1: 10 /Times-Roman F (method 1:) 72 512 S1 (Viva Nuestra Senora de Guadelupe!) 72 500 S1 (Viva la Independencia!) 72 488 S1 % method 2: 10 /Times-Roman F (method 2:) 72 412 S21 ( There was a young lady named Bright,) -12 S22 ( Whose speed was far faster than light;) -12 S22 ( She set out one day) -12 S22 ( In a relative way) -12 S22 ( And returned home the previous night.) -12 S22 ( -Arthur Buller) -12 S22 showpage %%Trailer
The rst method in listing 3-5 (labeled method 1 in the comments) has a very simple procedure (called S1) which does a moveto and a show. The x,y location for the text is passed on the stack along with the string, and the current font is understood already to exist. The second example (called method 2) uses a different approach. The rst line uses an x,y pair, as does method 1, except that it uses gsave and grestore to preserve the current point after show is nished. Subsequent lines of text are set with just a relative quantity (an amount to move down before
50
printing the line). This enables the driver to generate and transmit less data, but there is a greater computation cost in doing the gsave, grestore, exch, and rmoveto for each line. The two examples above are inherently based on the behavior of the show operator: It always leaves the current point at an adjusted position (usually to the right of the text shown) based on the character widths. In method 2, the second line (and subsequent lines) of text are dependent on the rst, in the sense that the position is relative to the rst line. Another approach might be to have a procedure for setting an entire paragraph, where the inter-line spacing is constant for the whole paragraph, and the moveto can be generated easily from within the code. The rest of this book is dedicated to the decision-making process. The PostScript language execution and imaging models provide great freedom in setting text, but in any environment there are constraints or advantages that may dictate using one method over another. Understanding the workings of the mechanism will help you to make these decisions.
CHARACTER WIDTHS
Character widths are stored within PostScript language font programs as displacements in coordinate space. The best way to think of text setting is to view each character as an origin, a shape, and a width. The relationship between the shape and the origin is xedthe origin is the 0,0 point in character space (described later) and the character draws itself relative to that location. The width is essentially an implicit rmoveto that occurs after the character shape is rendered. (See gure 3.3). The character width is not related to the actual execution of the character shape (the shape may even be in the font cache, and not get executed at all). This is not important unless you are building font characters yourself. (See also Section 9.3). Remember that characters widths have both an x and a y component. A common bug is to call stringwidth but to forget that there are two values returned. Typically, the extra number stays on the stack until a typecheck occurs later.
51
gure 3.3
character width (displacement after show) origin (current point when show executes)
The important thing to remember is to use the way The PostScript language renders text instead of ghting it. Below are some aspects of the execution and imaging models that commonly present difculty. Each of these is a symptom of not having used the language effectively: When you try to use save and restore at the page boundaries, you discover that it destroys the current font at the bottom of the page, and it needs to be explicitly re-set at the beginning of the next page. You find that changing fonts in the middle of a line of text makes it hard to justify the line, since the character widths are different for the two fonts. You are using stringwidth to determine where text is going to be placed. (The application should always control placement of text, and should know the character widths beforehand.) When you relocate or remove graphics from a le, the text does not print in the correct position on the page, since it relies on the current point being in the right place.
52
Some thought is required to build a complex program, and using the PostScript language is no exception. Learning the characteristics of each text operator and remembering the semantics of the imaging model will help enormously in designing a simple and efcient program.
3.8 CLIPPING
Clipping is a mechanism used to limit the area in which paint can be applied by the PostScript language painting operators. It behaves like a stencil. There is always a clipping path in the graphics state, which by default is the current page boundary. The clipping path may have further restrictions appended to it or removed from it (with grestore, for example)but it cant be replaced entirely. The clip operator appends the current path to the current clipping path (but does not destroy the current path).
53
3.9 RASTERIZATION
Rasterization is a term for the process of converting the original high-level PostScript language description into rasters, or plain old bits of black or white (or plain old 16 bits of color or plain old 8 bits of grayscale, or whatever is the appropriate fodder for the output device). In particular, rasterization is the end result of all the painting operators such as stroke, fill, and show. The real device independence of the PostScript language lies in the fact that the interpreter performs the rasterization at the resolution of the output device. Only high-level descriptions of graphics are sent to the printer. (Even scanned images are scalable, and not resolution-dependent.) In many devices, the PostScript interpreter can rasterize in parallel with the interpretation of the program, and may be deferred when the original painting operator is executed. This is especially true in high-speed printing devices, where some of the nal rasterization may be done on the y during showpage. Most programs need not be concerned with the rasterization process itselfthe purpose of the PostScript language is that the user program is removed from the level of rasterization to ensure true device independence.
54
of all others. (See Chapter 6). The graphics state stack should naturally parallel the execution of your program. Simply stated, save and restore (they are always used in pairs) provide a mechanism for saving the current state of the world and then returning to it. This mechanism affects everything (except the contents of the stacks and the marks on the current page). It saves the contents of dictionaries, the current line weight, the current halftone screen, the current font, the path, the clipping region, the existence of string bodies, array objects, and all objects in memory. There is nothing special required to use save and restore effectively. They should be designed in from the beginning into any procedures that use memory. If memory conservation is not an issue, gsave and grestore will probably work just as well, and are more efcient. In general, every procedure that affects the graphics state in some way should contain either gsave and grestore or save and restore. Chapter 6 provides more detail on how to structure procedures and programs to take full advantage of these operators. Section 13.3 discusses the use of save and restore for memory management.
55
Characters are removed from the font cache on a least-recentlyused basis whenever the available space in the cache is exhausted. The font cache occupies a xed amount of space in most implementations (although not all of them), and characters always are added to the cache if they qualify. If there is not enough room, the least recently used characters are removed. Entries are made into the font cache for a single character, and are not pre-cached for an entire font. The entry is made based on the current font, the size and orientation of the character (based on the current transformation matrix), the character name, and its width.
56
4.1 INTRODUCTION
The PostScript language can be thought of in two distinct ways: it provides an imaging model for describing and printing complex text and graphics, and it is a complete and general programming language. A printer emulator is software that uses the PostScript programming language to implement some other imaging model. The simplest example of this might be a line printer emulator, in which the ASCII text sent to the printer is simply listed out on the page rather than being interpreted. A print format translator is a host-based program which converts a le created for another printing device (and its accompanying imaging model) into a PostScript le. For instance, the program might take line printer input les and produce PostScript output les which may then be transmitted to a device for printing. There are many problems which can arise in translating other print les or emulating other printers, most of which generally have to do with fonts. When setting text, the widths of the individual characters are very important in making decisions about line breaks and for centering text. If text has been formatted for a different printer (perhaps with different character widths for its fonts) the translation process becomes more difcult. In addition, many print les may have been decomposed from their
57
original editable form into an output format appropriate for a particular printer, and what used to be text or labels might be vectors or bit images by the time the print le is produced. At that stage, there is no reasonable way to reverse-engineer the print le to produce the clean typography which the PostScript language provides. Whenever possible, it is best to compose pages originally with PostScript devices in mind, and to use font width tables that correspond to the fonts that will be used on the PostScript printer. It is also best to consider the PostScript imaging model fully, and to adapt the editable representation to the PostScript language model when producing the print le, rather than translating it after the fact, or programming the interpreter to emulate another printer. This chapter outlines some of the best methods for writing emulators and translators when it is not possible to modify the software which produces the print les. Chapter 5 is devoted to the subject of creating PostScript language page descriptions directly from the composing application.
58
and prints it, quitting only when the readstring operator detects an end-of-le indication. Note that no parsing of the string is attempted, no control codes are recognized, and there is no capability to change fonts. In fact, with this example even line breaks (carriage return characters) are ignored.
listing 4-1
%!PS-Adobe-2.0 %%Title: emulate1 /buff 128 string def /emulate1 { %def { %loop currentfile buff readstring exch show not { exit } if } loop showpage } bind def %%EndProlog 72 750 moveto /Courier findfont 10 scalefont setfont emulate1 So spake the Fiend, and with necessity, The tyrants plea, excused his devilish deeds. -Milton
The readline operator may be used to automatically recognize newline characters, but with even this simple change the program becomes much more complex. For instance, there is immediately the possibility of trying to read in a line that is longer than the allocated buffer size (this is not a problem with the readstring operator, since it does not look for newline characters, it simply lls the buffer). Additionally, depending on the communications channel, newlines may be handled differently in different environments and by different PostScript interpreters. In the purest sense, it is better not to use readline, but instead to use search to parse for various escape sequences and newline characters explicitly. Listing 4-2 is a revised form of the previous example. It searches for linefeed characters.
59
listing 4-2
%!PS-Adobe-2.0 %%Title: simple line printer emulator /buff 5000 string def /leftmargin 72 def /topmargin 72 def /bottom 72 def /top 792 topmargin sub def /ptsize 10 def /lead 10 def /EOLchar (\n) def % line feed /F { findfont exch scalefont setfont } bind def /newline { %def currentpoint exch pop lead sub dup bottom lt { %if showpage pop top } if leftmargin exch moveto } bind def /emulate { %def { %loop currentfile buff readstring exch EOLchar { %loop (innermost) search { %ifelse show newline }{ %else show exit } ifelse } loop not { %if readstring found EOF exit } if } loop showpage leftmargin top moveto } bind def %%EndProlog %%BeginSetup ptsize /Courier F leftmargin top moveto %%EndSetup emulate I wasted time, and now doth time waste me; For now hath time made me his numbering clock; My thoughts are minutes. - William Shakespeare
60
There are several items of note in this program. The innermost loop, for instance, takes advantage of the syntax of the search operator. After calling search, there is a boolean on the top of the operand stack which indicates the success or failure of the search. This boolean is used immediately by the ifelse statement, and one of the two procedure bodies is conditionally executed. If the search failed, the original string is still on the stack, and it is merely printed (show) and the loop is exited. If the search succeeded, there are three strings on the stack. The topmost one is the portion of the string just before the newline (the newline was the match string provided to search), which is then printed (with show). The remaining two strings are already in the correct position for another call to the search operator, so it is okay to just drop through the loop and re-enter at the top. The search operator was designed very carefully to return on the stack the information that is most likely to be needed, in an appropriate order, and it is easy to write a program that takes advantage of this design. This use of search can easily be extended to search for other characters as well as the newline character, although the inner loop would become more complicated. For instance, the tab character could be caught, and perhaps the escape character, in order to interpret control sequences.
61
rent point is modied if the given string were printed in the current font. Here is an example of its use (a session from an interactive PostScript interpreter): PS> 0 0 moveto PS> /Times-Roman findfont PS> 12 scalefont setfont PS> (TRANSLATING EXISTING FILE FORMATS) PS> dup stringwidth exch == == 230.316 0 PS> The stringwidth operator can take almost as long to execute as show, since it does the same work of imaging the characters into the font cache and computing the offset to the current point that would occur. It is not too unreasonable to say that printing time doubles when the width of a string must be computed before it is printed. Although the characters are already in the font cache when show is executed, the overhead in interpretation and execution time required to compute the strings width and to adjust the current point tend to offset this. Whenever possible, an emulator written in the PostScript language should avoid computing the width of a character string. For instance, if line breaks have already been determined (as is usually the case in line printer output) and the text is not justied, there is no reason to measure the string.
62
printers. It takes ASCII input data already broken into lines (it uses readline, in fact) and prints it with a variety of possible fonts. It is designed to use a default font (which you can specify) and to search for any of several escape sequences which permit changing fonts and going into or out of justication mode. The following are the recognized escape sequences: ESC Sequence ESC Y ESC N ESC 0 ESC 1 ESC 2 ESC 3 Function justication on justication off set font 0 set font 1 set font 2 set font 3 Result
The escape character and the individual commands are soft. The fonts mapped to the font codes are also soft. The 0, 1, 2, and 3 in the font codes are, in this example, actually the ASCII versions of those numbers (48, 49, and so on) so that the example program is readable. Similarly, the escape character is set to the caret character (^). In a real emulator, these would probably be changed. This emulator prints at roughly 17 pages per minute on current implementations, with a good mix of fonts and in full justication mode.
63
listing 4-3
%!PS-Adobe-2.0 %%Title: justify.ps %%Creator: Glenn Reid %%EndComments %%BeginProcSet: general 1.0 0 /FS { findfont exch scalefont } bind def %%EndProcSet: general 1.0 0 %%BeginProcSet: justify 1.0 0 /spacecount { %def 0 exch (\040) { %loop (space) search { %ifelse pop 3 -1 roll 1 add 3 1 roll }{ pop exit } ifelse } loop } bind def /justify { %def % mark (text) <font> (text) <font> /spaces 0 def /currentwidth 0 def /currlength 0 def counttomark /num exch def 2 2 num { %for % roll through stack and count -2 roll setfont dup spacecount spaces add /spaces exch def dup stringwidth pop currentwidth add /currentwidth exch def dup length currlength add /currlength exch def currentfont } for currlength 1 le { setfont show } { %ifelse /adjust %def columnwidth currentwidth sub spaces 0 eq { %ifelse currlength 1 sub }{ %else spaces } ifelse div def num 2 idiv %repeat spaces 0 eq { %ifelse { %repeat setfont adjust 0 3 -1 roll ashow } }{ %else
64
{ %repeat setfont adjust 0 32 4 -1 roll widthshow } } ifelse repeat } ifelse } bind def %%EndProcSet: justify 1.0 0 %%BeginProcSet: linemulator 1.1 0 /buff 1000 string def % longest line for "readline % for recognizing escape sequences to change fonts % and enter/leave "justify" mode: /ESC (^) def % escape character /justON (Y) 0 get def % ESC-Y /justOFF (N) 0 get def % ESC-N /QUIT (Q) 0 get def % ESC-Q /zero (0) 0 get def % for subtracting ASCII offset /newline { %def currentpoint exch pop lead sub dup bottom lt { %if showpage pop top } if leftmargin exch moveto } bind def /linetoolong { %def newline (%%[Error: line too long.]%%) show showpage } bind def /printline { %def JUST { %ifelse counttomark 2 ge { justify } if }{ counttomark 1 ge { show } if } ifelse newline } bind def /emulate { %def % read currentfile one line at a time. Search for % ESC sequences. If not found, just "show" with % current font and move on. If justification is % turned on, then LEAVE all the strings and the % correct font dictionaries on the stack, and call % "justify" at the end of the line: /fmax fonts length def /top 792 topmargin sub def /JUST justification def leftmargin top moveto
65
/sv save def { %loop mark currentfile buff { readline } stopped { linetoolong stop } if not /done exch def % use the boolean later { %loop % handle escape sequences ESC search { %ifelse JUST { %ifelse exch pop exch currentfont exch }{ show pop } ifelse dup length 0 eq { pop ESC JUST { %ifelse exch currentfont exch } if printline exit } if dup 0 get % escape argument true exch % look for ESC-fontchange? dup justON eq { /JUST true def exch pop false exch } if dup justOFF eq { /JUST false def exch pop false exch } if dup QUIT eq { /done true def exit } if exch { %ifelse zero sub % subtract ASCII offset dup dup fmax le exch 0 ge and { %ifelse fonts exch get setfont dup length 1 sub 1 exch getinterval }{ %else pop ESC JUST { %ifelse exch currentfont exch }{ show } ifelse } ifelse }{ %else pop dup length 1 sub 1 exch getinterval } ifelse }{ %else
66
JUST { %ifelse dup length 0 ne { %ifelse currentfont printline }{ pop printline } ifelse }{ %else dup length 0 ne { %ifelse printline }{ %else pop newline } ifelse } ifelse exit } ifelse } loop cleartomark done { exit } if currentpoint /JUST JUST sv restore /sv save def def moveto } loop showpage sv restore } bind def %%EndProcSet: linemulator 1.1 0 %%EndProlog %%BeginSetup /columnwidth 432 def /justification false def /leftmargin 108 def /topmargin 72 def /bottom 72 def /lead 14 def /fonts [ %def 12 /Times-Roman FS % ESC-0 12 /Times-Bold FS % ESC-1 12 /Times-Italic FS % ESC-2 12 /Times-BoldItalic FS % ESC-3 ] def fonts 0 get setfont %%EndSetup emulate (turn on justification after this line)^Y Here is ^1bold^0, here is ^2italic^0, and here is ^3BoldItalic^0. ^NNo other justification is necessary.^Q
67
UNITS
One of the rst issues that must be addressed when translating a print le intended for another printer is to determine what units or measurements were used in the original le, and to convert or scale them into an appropriate PostScript coordinate system. Conversion between number systems can result in signicant round-off error in computation. When setting text and producing graphics, exact tolerances are needed to preserve the quality of the page image. It is best to study the relationship between the host units used by the host application and the default PostScript coordinate system and to perform the simplest possible conversion. One way to map an applications coordinate system into the units that the PostScript language uses is simply to use the scale operator. However, the following issues should be considered if this technique is chosen: Fonts are scaled separately from the user coordinate system, but they are rendered through the current transformation matrix. If the user-space transformation is modied, then the numbers provided to scalefont must be adjusted accordingly. The implicit mapping performed by the current transformation matrix represents a calculation. This calculation is performed whether or not the mapping is used as a means to reconcile coordinate system differences; taking advantage of this mechanism is essentially free.
68
showpage performs an implicit initgraphics. It will be necessary to use gsave and grestore to preserve the coordinate system across pages, or to set it explicitly for each page. For example, if the base coordinate system in a layout application uses inches as its unit of measurement, it is much more efcient to scale the PostScript coordinate system to match this than it is to programmatically convert numbers into points: /in. { 72 mul } def 1 in. 2 in. moveto This requires three name lookups, the execution of a procedure body, and two multiplication operations (as well as 6 more bytes of information to be received and parsed by the PostScript interpreter) to position the current point. Compare this to the following bit of code, which has the same function and more than twice as fast: 72 72 scale 1 2 moveto
FONTS
The most basic problem encountered in translating other print formats into the PostScript language is incompatibility among font libraries. If a xed-pitch font is used (one in which all character widths are the same) the problem is simplied quite a bit, but in most cases involving proportional fonts, there will be incompatibilities. Deciding on a philosophical approach to these kinds of differences is perhaps the most difcult part of writing a good emulator or translator.
69
When the fonts available on the original printer do not match the fonts available on the PostScript device in question, the rst step is to introduce a font substitution strategy. Essentially, a table is built that maps the generic Roman font to Times-Roman on the PostScript printer, and so on for all the possible fonts. Some of these substitutions will be better than others (depending on the similarities or differences between the original and substituted fonts), but they provide a good starting point. A renement to this approach is to dene additional fonts in the PostScript language that may more closely match the fonts on the original printer, rather than trying to use the existing PostScript fonts. In either case, when a font is encountered in the original print le, some PostScript font must be used. At the second level, a decision must reached as to where compromises can be made. For instance, for text which is not justied, it may be best to allow the normal letter spacing of the PostScript font to prevail, as long as the lines will be approximately the correct length. However, with justied text (like the text you are reading, with the margins aligned on both sides) the difference in line lengths between the original line of text (set in one font) and the line set in Times-Roman on a PostScript printer must be accounted for. The widthshow and awidthshow operators can be used to stretch a line of text to any arbitrary size, although the result may not necessarily look like the original text. Typographers usually prefer to modify the word spacing rather than the letter spacing (this means using widthshow and stretching only the space character). If the text is not justied, then the line can simply be printed with show and the result can be quite good, depending on the match of the substituted font. (Frequently the results are much better than the original printer may have been able to produce, if the font style is not particularly important.) When the decisions have been made as to roughly how to deal with the original imaging model and font library, there are implementation decisions to be made. For printing text, switching from one font to another tends to be the most difcult task, especially when the text is justied. Depending on the informa-
70
tion provided in the original print le, there are two schools of thought: Print all instances of any given font at once, for any particular page, regardless of their positions on the page. Proceed left to right, top to bottom, switching fonts as necessary. If the print format you are translating has location information for each word (or letter, or string) independently, the rst approach may be preferable, especially if there are many font changes. If the existing print format relies on the current position of one piece of text in order to set the next, this is probably not feasible. Sometimes this kind of positioning information can be computed from the original print le, even if it is not inherent in the original format. For instance, the positions of the lines of text (or words of text, given many font changes) must be determined at some point in order to print them. Unfortunately, they are often computed in the printer simply because it seems easier. However, by keeping a width table for all characters of a font, it is fairly easy to compute the position of any word on a page simply by adding together the character widths of preceding characters. The resulting PostScript program can be enough faster to make the rst of these approaches worth considering when the translator is being designed.
71
RENDERING
The physical properties of a display or marking device heavily inuence the way in which graphics are rendered. PostScript interpreters are currently designed to drive raster devices. In particular, the nal page image is typically represented as a large pixel array. Other devices, notably pen plotters or vector displays, may require an input le containing only the equivalent of moveto and lineto operations. The capabilities of the target device often factor heavily into the graphic imaging assumptions in the print le. For further discussion on adapting to the PostScript imaging model, read Chapter 3.
72
This represents movement rst in the y direction, then in the x direction. By recognizing this sequence in the original print le, it can easily be optimized by combining the motion into a single command: 300 150 rmoveto This is quite a bit shorter than the original, and it will therefore transmit faster and be interpreted faster. The key is to recognize sequences or patterns in the original le that might be represented more compactly in the PostScript language. Another important optimization relates to setting text. Strings should be printed in as long a chunk as is practical; entire lines at a time are best. As an example, consider two approaches to setting a line of text in which the word spacing needs to be modied slightly, as in the following example: % first example: 0 800 moveto (Entire) show 4 0 rmoveto (line) show 4 0 rmoveto (at) show 4 0 rmoveto (a) show 4 0 rmoveto (time.) show % second example: 0 800 moveto 4 0 32 (Entire line at a time) widthshow Even if the rst of these is optimized by putting the rmoveto operation into a short procedure, it is still much slower to reposition between words than to print the whole line, modifying the width of the space using widthshow, as is done in the second part of the example. Chapter 7 contains more detailed information on text setting algorithms.
73
exible, but it is designed as a nal-form output representation, not as a layout or computation tool. Making decisions at the PostScript interpreter level can also be quite expensive. As usual, it is best to make the decisions on the host computer if possible. Consider the mechanism presented in listing 4-4 for performing line wrapping. This is not a recommended approach, given the amount of computation performed.
listing 4-4
%!PS-Adobe-2.0 %%Title: example of what not do to /SH { %def dup stringwidth pop currentpoint 3 1 roll add 300 gt { %if 72 exch 48 sub dup 36 lt { %if showpage pop 700 } if moveto }{ %else pop } ifelse show } bind def %%EndProlog 72 700 moveto /Times-Roman findfont 48 scalefont setfont (As ) SH (usual, ) SH (it ) SH (is ) SH (best ) SH (to ) SH (make ) SH (the ) SH (decisions ) SH (on ) SH (the ) SH (host ) SH (computer ) SH (if ) SH (possible.) SH showpage %%Trailer
This program takes input text one word at a time and places each string on the page. Right before printing each word, its
74
width is computed and added to the current point on the page. If the resulting position would exceed the right margin, that word is moved to the next line. If that new line is below the bottom margin (36), then showpage is rst executed for the current page, and the current point is then moved to the top of the next page. There is a great deal of computation involved in doing this, and it may cause a PostScript printer to run at less than one fourth the speed which it might otherwise achieve if the same page were composed carefully on the host system.
75
5.1 INTRODUCTION
An important aspect of designing printer driver software is to adapt the applications graphic data structures to the PostScript language imaging model. If the application has a representation for text, exactly how is it maintained? How are graphics edited, stored, and represented? What might be the relationship between lled regions in the PostScript language and the graphic texturing in the applications own representation? As is the case with any output device, these internal graphic imaging models must be translated into a format appropriate for the printer: the PostScript language. The subject of this chapter is the task of designing the page description as a software application in its own right.
77
Frequently, one of the rst questions that arises in the early stages of laying out a PostScript language driver is: Where is the top of the page? Many imaging models work from the top of the page with positive y coordinates growing downward, rather than using the lower left corner as the origin. The way to think about this in the PostScript imaging model is to answer: There is no page at the composition stage, only an innite real number coordinate system. A good way to think about page composition is to imagine the desired page at any size: 11 inches by 18 inches, 36 inches by 48 inches, or 8-1/2 inches by 11 inches, it doesnt matter. What matters is the relative positioning of page elements, the overall proportions of the page, and its design. The page can easily be scaled to print at any size and orientation, and on any kind of paper (or on many sheets instead of one) without much trouble. It is most important to consider how the page elements will interact with one another. In fact, the individual page elements should probably interact very little. That is, each page element should be self-contained, and be placed and executed individually. Columns of text (or perhaps even individual lines of text) might be considered to be independent page elements. In this context, independent means that there should be no ripple effects from, say, changing the location of one page element, or selecting a thicker line weight or a different font. This kind of context should be isolated from one page element to another.
78
of this in Chapter 6, but it is mentioned here because it affects page layout design directly. All programs in the PostScript language should be written as if they were subroutines rather than the main program. You should avoid PostScript language operators which are absolute (for instance, initgraphics, grestoreall, defaultmatrix, and so on). For example, a document that is composed to print on standard letter-sized paper should be written in such a way that it can easily be printed 2-up (two half-size page images on a single sheet of paper, side by side). In order to accomplish this, the coordinate system must be scaled to half its original size. If the document executes initgraphics, this effect is nullied, since the default transformation matrix is installed. Even simply printing a page in landscape mode will not work if the le contains a call to initgraphics or grestoreall. There is further discussion of this in Chapter 6. Note: Never initialize or replace the existing state of the interpreter. All changes should be modications to the existing state, including changes to the transformation matrix, the halftone mechanism, the transfer function, and redenitions for operator names. Always concatenate the new procedures or matrices with the existing ones.
79
Identify the aspects of this page element that can be considered data, and the order in which these data are needed at the PostScript level. Perform any calculations necessary to convert data from the programs internal representation into the desired PostScript language representation. Decide whether to generate native PostScript code at this stage, or to generate a procedure call to a procedure dened in the prologue. The machine-generated part of the data stream (the script) should be as compact, modular, and efcient as possible. The script segment of a PostScript program is tightly linked to procedure denitions in the prologue, and a large part of the design process for a PostScript language program is to decide what the distribution of labor should be between the prologue and the script. Note: Remember that the PostScript language is interpreted, and that it is a programming language only as one aspect of its design. It is best not to defer calculation (such as division problems, computing the diameter of a circle, or guring the length of some text) to the interpreter. Instead, perform these calculations on the host system as the script is being generated, providing the data to the procedures in the format expected by the PostScript language and the individual operators used.
80
Essentially, there are two possible approaches in a digital device such as a laser printer: 1. A moveto or lineto instruction falls on the nearest pixel boundary in device space. 2. A path operation (like moveto) is maintained as a real number. Either of these methods could be adopted. The rst method would help ensure uniformity of line weights, for example, but it would prove far less accurate for constructing curves and other complex shapes (especially character descriptions in fonts). The second method is the one chosen for the PostScript language. One side effect of this model is that stroked lines (or lled shapes) may vary by as much as one device pixel, depending on where the paths fall in device space. There is an easy way to coerce the path construction operators into behaving like the rst example, if desired. (See Section 9.3.) Be careful not to make any assumptions about the resolution or orientation of device space when you use the PostScript language, or your program may not be device-independent.
5.5 EFFICIENCY
The overall efciency of a PostScript program is affected primarily by the following three factors: 1. Data transmission (and tokenizing) time 2. Computation overhead 3. Interpretation time All three of these are of roughly equal importance, although one or the other of them may become more signicant in a particular application or environment. For instance, at low baud rates across a serial connection, data transmission time is likely to be the primary bottleneck, whereas across a high bandwidth connection, it is often less signicant. However, reducing the
81
time the scanner takes to tokenize the input stream is benecial at any data transfer rate.
82
The second example is actually longer than the rst, but the initial procedure denitions (the prologue) are only executed once. For a very short document like this, the rst example is faster. However, it would not take very many more boxes to change the balance in favor of the second example, since the amount of information transmitted and scanned for each box is minimized. Notice that, in the previous example, each time a box is drawn a very predictable data stream is produced. In particular, there is always the sequence of moveto lineto lineto lineto closepath fill. An extremely useful approach in program design is to look for patterns like this and to factor out repeated procedure calls from the script. The following fragment denes a procedure called B that combines the functions of m, l, and cf in the previous example: %!PS-Adobe-2.0 /B { %def gsave moveto lineto lineto lineto closepath setgray fill grestore } bind def /S /showpage load def %%EndProlog 0.5 10 10 20 10 20 20 10 20 B S %%Trailer
COMPUTATION
Consider the following illustration of computation overhead. The task at hand is to draw an arc of a circle, but the stored representation for this arc maintained by the composition software is much different than the model used by the native arc operator in the PostScript language. In the original representation, an arc is represented by storing two corners of the bounding rectangle of the arc, and two angles representing the starting and ending points for the arc. (See gure 5.1).
83
gure 5.1
300, 300 90 0
100, 100
The arc is stored in the host application simply as these six integers This representation must be converted to the syntax required by the arc operator, which uses the center of the arc, its radius, and the beginning and ending angles to represent the arc segment. Listing 5-1 is a procedure that draws this arc merely by passing in the original six numbers as parameters. Incidentally, this listing is presented as an example of what it is best not to do.
listing 5-1
%! example of how not to draw an arc: /origmtx matrix def /drawarc { %def /EndAngle exch def /StartAngle exch def /URy exch def /URx exch def /LLy exch def /LLx exch def gsave LLx URx add 2 div LLy URy add 2 div translate
84
newpath URx LLx sub URy LLy sub origmtx currentmatrix 3 1 roll scale newpath 0 0 0.5 StartAngle EndAngle arc setmatrix stroke grestore } bind def % end of prologue 100 100 300 300 0 90 drawarc 35 10 translate % as opposed to this: 300 200 100 0 90 arc stroke showpage
Contrast this to a simple call to the built-in PostScript arc operator, without any of the attendant computation. The resulting mark on the page is the same: 300 200 100 0 90 arc stroke Refer to Section 3.6 for a good procedure to construct and paint a circle with the arc operator.
INTERPRETATION TIME
Interpretation time is the time spent in the PostScript interpreter when no work is being done directly. For example, the scan time, stack manipulations and name lookup required to execute 0 0 moveto are interpretation time necessary for establishing a current point. Minimizing interpretation time is probably the most effective way to improve the performance of a PostScript program. The shorter the procedure is, and the fewer PostScript operators executed, the more efcient the program will be. This can make an order of magnitude difference in execution time under some circumstances, given the interpreted nature of the language.
85
There are several ways to go about improving your PostScript programs efciency, especially the interpreter overhead: Study the listings in this book. The examples throughout the text have been carefully designed to be efcient. Look carefully at the balance between the data passed in the script and the procedures dened in the prologue. Study your script. After you have gotten your program working, take a critical look at the script. Forget the constraints that you are working under, and forget the prologue procedures themselves. Are there any patterns in the output? Can you factor anything out of the script? There are many clues to the performance of your driver locked up in the script for an average document. Look at your prologue procedures. As a rule of thumb, if they are more than a few lines long, they are too complicated. If there are more than four or ve instances of roll, dup, exch, or index, you may want to look more carefully at the data itself. Can it be rearranged? Can a division be performed by the host application? On the other hand, if there are a lot of instances of /variable exch def, look carefully to see if these dictionary entries need to be made. If the data is completely used up by the procedure, there is usually no reason to store the data in a dictionary, and it tends to be much slower to do so. Do a timing test. It is an extremely rare page that should take more than about ve seconds to print. Without exception. This is true even when writing emulators. If your PostScript program is taking longer than that to print a page with text and graphics on it, start simplifying things. The usertime operator can be used for precise timings. Implement special cases. For the most part, procedures that are generalized to handle several possibilities are a bad idea in PostScript programming. It is better to make several simple, slightly different variations and invoke the appropriate one.
86
6.1 INTRODUCTION
A PostScript language program contains several levels of structure. The program le conventionally consists of a prologue, a script, and a trailer. The prologue may be composed of several procedure sets, each of which is an independent package of procedures appropriate for a specic task. The script is divided into pages, and each page may contain many page elements. Within the execution of page elements, there are many procedure calls, and there is a structured interaction between these procedures and the PostScript language operators. The trailer section often is empty, but it can be used to restore the environment to its original state at the end of a document. The program structure is important for several reasons. First, the structure affects the execution, as is true with most programming languages. The performance and robustness of the program are, in turn, linked to its execution. PostScript les also should be well-formed, according to specic structuring conventions (see Section 6.4) to cooperate effectively with print spoolers, page reversal programs, and applications wishing to import PostScript les as illustrations. There is a thorough discussion in Chapter 11 of the issues of embedding a complete PostScript document le within another PostScript le.
87
prologue consists of procedure denitions and constants, and should contain only denitions (it should not, for instance, contain any modications to the graphics state). The script is program-generated data along with procedure invocations (calls either to native PostScript language operators or to procedures dened in the prologue). A software developer typically writes the prologue by hand, but the script is generated by software and is usually different for each document printed. The PostScript interpreter does not require the clean break in functionality found in the prologueand-script model, but it is easier to maintain the software, and, more importantly, many kinds of pre- or post-processing of PostScript les depend on this structure. For instance, if the script can be broken cleanly into pages then host software can reverse the pages even after the application has produced a nal print le. (See Section 6.3, MODULARITY AND PAGE STRUCTURE.) Here is a simple example of a simple program written in the PostScript language that has an inherent prologue and script: /F { findfont exch scalefont setfont } bind def 12 /Optima-Oblique F 10 10 moveto (Fairly Oblique) show showpage There is only a single procedure denition (the F procedure), and it must, of course, be dened before it can be executed. It is signicant, however, that the denition precedes all of the executable code. For instance, if the 10 10 moveto were performed rst, the le would still execute correctly, but the clean distinction between the prologue denitions and the executable script would be lost. What follows is the same program with PostScript language comments included to indicate the structure of the le. These are known as structure comments. (See section 6.4.) These comments
88
are ignored by the PostScript interpreter, but may be parsed by other software to determine the programs structure:
listing 6-1
%!PS-Adobe-2.0 %%Title: example %%Pages: 1 %%BeginProcSet: TextProcs 1.0 0 /F { findfont exch scalefont setfont } bind def %%EndProcSet %%EndProlog %%Page: one 1 12 /Optima-Oblique F 100 100 moveto (Fairly Oblique) show showpage %%Trailer
These structure comments may be used, for example, as guidelines to reverse the pages or to print only one page of many. The last section in this chapter is devoted to these structure comment conventions, but their use here points out the implicit prologue/script structure in the original example program. Adding the comments identies the program as a conforming PostScript language program. These comments should not be included unless the le adheres carefully to the prologue and script model and conforms to the PostScript Document Structuring Conventions. The version number to which it conforms is included in the very rst line as part of the %!PS-Adobe- comment; in this case, the version is 2.0.
89
nature of the execution model can be reduced to the following operations, which are typically performed in sequence: Set (or add to) the state of the interpreter. This could consist of setting the current font, adding to the current path, or making a dictionary entry. Execute a graphic operation. This includes, for instance, using show to print characters, applying fill to the current path, or executing showpage to print the current page. Note that the execution of PostScript operators depends to a large extent on the current state of the interpreter (in particular, the graphics state). This is an extremely powerful mechanism, but one which must be understood and used carefully. It is not necessary to program defensively, but it is usually good to isolate the interdependency between page elements as much as possible. Modularity is an important aspect of program design. It means isolating pieces of code and making them perform independently, with a rigorously dened interface between them. In the PostScript language, this might seem to be hard to accomplish, given the strong dependency upon context. However, by carefully looking at the ground state of the interpreter, the contextdependency can be used to advantage.
GROUND STATE
The ground state in a PostScript interpreter is the default context in which all jobs are executed. The ground state of the interpreter is guaranteed to have the following properties: The coordinate system (user space) is set up with printers points as the default measure (72 points to a linear inch), with the origin of the coordinate system placed in the lower left hand of the current page. There are no marks on the current page (it need not be erased).
90
There is no current path. (This means that there is no current point, either.) There is no current font. The dictionary stack contains userdict and systemdict (userdict is on the top of the stack). All elements of the graphics state have some default setting (for example, butted line end caps, mitered line joins, and black as the current color). The default graphics state is enumerated fully in the PostScript Language Reference Manual. The current transformation matrix is in its default state (it is machine-dependent). This context is the starting place for all programs executing on the PostScript interpreter. In order to cooperate effectively with the execution environment, the ground state should be taken to be inviolable. That is, since the page is blank, it should not be erased. Since the graphic state is in its default state, one should not execute initgraphics. Only those elements of the interpreters state which need to differ from the ground state should explicitly be set. There are several reasons for this: the programs are simpler; there is usually nothing accomplished by unnecessarily setting default states; if the state of the machine differs from the documented default, it is probably done deliberately. For example, to print pages 2-up requires modication to the current transformation matrix, which initgraphics would reset. Some parts of the graphics state are restored naturally to their default state, and gsave and grestore may not be necessary. For example, the current point is destroyed naturally by the path painting operators. If a procedure constructs a path and paints it without modifying any other aspects of the ground state, then it need not use gsave and grestore. Modularity is best achieved by carefully redening the ground state and returning to it between elements (modules). That way, every page element, every document page, and every proce-
91
dure can expect the same initial state. It may be convenient in many cases to use the default state of the PostScript interpreter as the ground state for an application, although a slightly different state can be dened at the beginning of the document and reestablished with save and restore.
92
The second kind of failure mode above, which has been termed Incorrect graphic interdependency, is typically a conceptual error on the part of the programmer, and can be paraphrased: It printed, but it did not print as I intended. This is usually the result of a kind of ripple effect that occurs when some aspects of the graphics state carry over from one graphic element to another. For example, if the caption under a drawing is linked to the current point after the drawing is executed, the caption may end up in the wrong place if the drawing is changed. Similarly, if the current font at the end of one paragraph is bold and it is not explicitly re-set to the roman face at the beginning of the next paragraph, the following text may incorrectly print in the bold font. Unfortunately, many programs which are poorly designed only get worse as bugs like this get xed. The resulting program, after debugging, is typically much more complex than it should be, and is often characterized by unnecessary calls to gsave and grestore or explicitly executing 0 setgray at the beginning of every procedure. These kinds of problems usually can be traced to a single procedure that failed to restore the state correctly when it nished executing. This second kind of graphic modularity is perhaps the more important one in terms of designing a driver for an application. Because the order of evaluation for page elements is not enforced when building a page in the PostScript language, it is the programmers responsibility to determine how (and in what order) to execute the various graphic elements on a page. This typically depends upon the composition softwares imaging model, but it must be carefully considered in order to write a modular PostScript language program.
93
to be isolated from other page elements by the save/restore mechanism. Since save and restore are somewhat more expensive than gsave and grestore, they should only be used where the contents of VM need to be restored as well as the graphics state. They are appropriate at a fairly coarse level, either at each page boundary, or between page elements that consume memory (like text blocks, since the strings require storage space). There is further information on save and restore in Section 13.3 and in Section 3.10.
94
of them calls the fill operator, which makes it implicitly dependent upon the current color. In the example, it can be seen that setting the gray value affects all subsequently drawn circles or squares.
listing 6-2
%!PS-Adobe-2.0 %%Title: graphic independence %%EndComments /circle { %def 0 360 arc fill } bind def /square { %def moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def %%EndProlog 0.5 setgray 72 100 200 circle 200 300 100 100 square 0 setgray 200 300 300 300 square showpage %%Trailer
Listing 6-3 shows the same two procedures, slightly rewritten to have the color (gray value) of each shape passed as one of its arguments. This effectively removes the interdependency between the objects. If the shapes are likely to be painted with various gray levels, it is appropriate to pass the shade of gray as one of the attributes for those page elements. This also ensures that the ground state is preserved between page elements, and other procedures can rely on the current color to be as originally dened. For each graphic element on a page, for each module of the PostScript language driver, you must decide exactly how the ele-
95
ments will behave with respect to one another. A ground state can be dened that is appropriate for all the various elements on a page, and each element can provide any alternate properties that are appropriate.
listing 6-3
%!PS-Adobe-2.0 %%Title: graphic dependence %%EndComments /circle { %def gsave arc setgray fill grestore } bind def /square { %def gsave moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath setgray fill grestore } bind def %%EndProlog 0.5 72 100 200 0 360 circle 0.5 200 300 100 100 square 0 200 300 300 300 square showpage %%Trailer
96
le permits greater exibility to adapt the needs of the document to a particular printer. The structuring conventions have a tangible representation in the form of PostScript language comments that are embedded in the program le, which delineate its structure. These comments are intended to be parsed and understood by software, rather than humans. The format of the comments should be strictly followed for this reason. It is also understood that a le which claims to be a conforming le (by having the appropriate comments embedded) should indeed conform to the structuring conventions. The PostScript document structuring conventions are fully documented in a separate document available from Adobe Systems entitled PostScript Document Structuring Conventions. They can also be seen in context in most of the program listings in this book, although their use here is minimal, to keep the listings from being lengthy and hard to read. The purpose of the comments is to convey information about the program to parsing software that may be interested in determining its structure (or its resource requirements, such as downloadable fonts). The structuring rules, including page modularity, are required for parallel processing, page reversal, and other document handling needs. Please refer to Chapter 10 for further information on PostScript le interchange standards.
97
7.1 INTRODUCTION
Setting text is one of the most common operations performed by many printing applications, and one of the most difcult. There are many aspects of typography and graphic design that can be difcult to accommodate in a computer program. For example, a typesetter may expect justied text, even when it contains different fonts or point sizes, kerning of the text based both on the point size and on specic pairs of letters, and all at the full rated speed of the printer. Setting non-Roman text or using font characters as symbols presents another class of problems but uses the same mechanisms in the PostScript language. Many of the requirements of setting text are in fact document formatting issues. For instance, establishing margins, laying out lines, or choosing a text point size are decisions that are typically made by a word processing application or some other layout software. Writing a PostScript language driver is the task of converting the document formatters representation of text into a printable form. This chapter is devoted to the mechanics of setting text in the PostScript language. A primary focus is efciency: how to get the job done as fast as possible. Most of the topics are specic to proportional fonts and traditional typography, although the concepts should apply as well to monospaced fonts and special requirements.
99
Unfortunately, optimizing the text-setting portion of a driver depends to some extent on the nature of the text being set. It may be that an algorithm that works well for setting many lines of text in the same font becomes too slow when fonts are changed frequently, or that a method that assumes line spacing is uniform may be difcult to adapt to display typography. Several different approaches and algorithms are presented in this chapter, and the software designer must choose which ones are appropriate for a particular task. In many instances, it is best to build in several mechanisms, and to invoke one or the other of them depending on the text being set. Note: There is one principle to keep in mind when deciding upon an algorithm for setting text. The longer the string presented to one of the show operators, the more efcient the system is likely to be. This is because the PostScript language built-in operators, such as show, widthshow, and ashow, operate essentially at compiled speed once they have been invoked. Each moveto or div operation performed must rst be interpreted, which is signicantly slower.
100
host composition software. All a word processor needs to be able to use a new font is a table with the widths of all the characters in the font. In a screen display system, a set of screen fonts may be required, as well, although the pixel widths of the screen characters may or may not be accurate enough for careful typesetting. Usually both width tables and screen fonts are required. Adobe Systems typefaces are made available with separate metrics les in a le format designed to be parsed by application software. These les are known as AFM les (for Adobe Font Metrics). A complete description of the format of these les is found in a separate document available from Adobe Systems, entitled AFM Files: An Interchange Format for POSTSCRIPT Font Metrics. These les also contained detailed kerning and ligature information. (Their use is discussed in Section 7.6.) The width of a character is dened to be the amount by which the current point is modied after printing the character. For Roman character sets, the width typically includes the left and right sidebearings, which are a comfortable amount of space on each side of the character for good letter spacing. Figure 7.1 is an illustration of four characters (including a space character) from the font StoneSerif (which is also the font you are reading), that shows the character widths and sidebearings.
gure 7.1
character bounding box
w at
sidebearings
101
Notice that the physical width of the two characters is not quite the same as the optical width of the characters, due to the side-
bearings. This is often ignored in ordinary typography, although it can be handled by obtaining the bounding box of the characters at each end of the string to determine what the side-bearings are. The bounding box provides the corners of an imaginary rectangle that just touches all the extremes of the character shape (hence bounding). This information is given as an offset from the character origin, and therefore provides the left side-bearing directly, and can be subtracted from the width of the character to determine the right side-bearing. The bounding box of each character is also provided in the AFM les.
102
gure 7.2
an excerpt from
Then this ebony bird beguiling my sad fancy into smiling, By the grave and stern decorum of the countenance it wore, Though thy crest be shorn and shaven, thou, I said, art sure no craven, Ghastly grim and ancient Raven wandering from the Nightly shore Tell me what thy lordly name is on the Nights Plutonian shore! Quoth the Raven Nevermore.
The Raven
by Edgar Allan Poe February, 1845
Much I marvelled this ungainly fowl to hear discourse so plainly, Though its answer little meaning little relevancy bore; For we cannot help agreeing that no sublunary being Ever yet was blessed with seeing bird above his chamber door Bird or beast upon the sculptured bust above his chamber door, With such name as Nevermore.
But the Raven, sitting lonely on the placid bust, spoke only, That one word, as if his soul in that one word he did outpour. Nothing farther then he utterednot a feather then he uttered Till I scarcely more than muttered Other friends have own before On the morrow he will leave me, as my Hopes have own before. Quoth the Raven Nevermore.
In listing 7-1 some procedures are dened that implement rightshow and centershow. These procedures do a fair amount of calculation, and are only appropriate if the widths of the strings cannot be determined at the host (for instance, they might be used in a printer emulator). Generally, the host application will already have determined precisely where the text should be placed, and can simply use moveto and show for all of the above styles. These procedures combine the moveto and show operations. Both the x, y values of the location on the page and the string to be printed are passed on the operand stack. This is faster than doing separate name lookup and execution on both moveto and show. Notice that the margins are maintained by the host application, and an explicit location on the page is passed for each string.
103
listing 7-1
%!PS-Adobe-2.0 %%Title: margin procedures %%BeginProcSet: text-procs 1.0 0 % moveto-show /SH { %def moveto show } bind def % rightshow /RS { %def moveto dup stringwidth neg exch neg exch rmoveto show } bind def %centershow /CS { %def moveto dup stringwidth 2 div neg exch 2 div neg exch rmoveto show } bind def %%EndProcSet: text-procs 1.0 0 %%EndProlog /Times-Roman findfont 14 scalefont setfont (Centered about + current point) 306 140 CS (+Flush Left at current point) 72 120 SH (Flush Right at current point+) 560 120 RS showpage %%Trailer
JUSTIFICATION
For standard text-setting, the natural letter spacing and word spacing should be used. Letters may be kerned to improve their visual spacing (see section 7.6), but the lines of text should usually not be stretched in any way unless it is necessary to justify the text. There are two levels of renement for justifying a column of text:
104
Provide careful hyphenation and word breaking to get the lines of text as close as possible to the correct length. Modify the word spacing as necessary to stretch the text to t the necessary width of the line. Under normal circumstances, the inter-letter spacing should not be modied at all. The legibility of text tends to suffer greatly when letter spacing is compromised. If hyphenation is not available, or when it cannot be avoided, both word and letter spacing can be modied simultaneously with the awidthshow operator. The appropriate way to modify word spacing is to use the widthshow operator, which was designed with this purpose in mind. widthshow prints text just as show does, except that it modies the character width of a particular character by the specied amount while it is printing it. By supplying a small amount by which to change the width of the space character, word spacing can be modied quite efciently and easily. Listing 7-2 contains an example of its use.
listing 7-2
%!PS-Adobe-2.0 %%Title: widthshow example /F { findfont exch scalefont setfont } bind def /W /widthshow load def %%EndProlog %%BeginSetup /sp 32 def % ASCII space %%EndSetup 36 745 moveto 24 /Times-Italic F 6 0 sp (PostScript Language Program Design) W showpage %%Trailer
105
The computation that needs to be performed is to determine the amount by which to modify the width of the space character. For justied text, the space modier (SpaceMod) is the intended line width (LineWidth) less the width of the existing string (StringWidth) divided by the number of spaces in the string (Spaces). This computation can easily be performed by the host application if the character widths are known. SpaceMod = (LineWidth - StringWidth) Spaces This SpaceMod quantity is passed directly to the widthshow operator each time it is called. Notice that this value will be different for every line of text, since there are likely to be different numbers of spaces (and different string widths) for each line.
listing 7-3
%!PS-Adobe-2.0 %%Title: mixed-font text setting /FS { %def findfont exch scalefont } bind def /SF { %def Fonts exch get setfont } bind def /MS { %multiple "show" counttomark 2 idiv { %repeat 0 moveto show } repeat pop mark } bind def /LINE { %def gsave translate mark % mark is for MS procedure } bind def /S { %def moveto show } bind def /END { %def pop grestore } bind def %%EndProlog %%BeginSetup /Fonts [ %def 12 /Times-Roman FS 12 /Times-Bold FS 12 /Times-Italic FS ] def %%EndSetup %%BeginObject: text_block 0 SF save % text block ground state % first line of text 72 312 LINE % first line start (Here is a line with ) 0
107
(words and ) 140.315 (words.) 238.991 MS 2 SF (two ) 192.971 (italic ) 112.644 MS 1 SF (bold ) 213.311 (two ) 90.984 MS END % lines 2, 3, and 4 in the text: (You shall see them on a beautiful quarto) 72 288 S (page, where a neat rivulet of text shall) 72 276 S (meander through a meadow of margin.) 72 264 S % credit for quotation: 160 240 LINE 2 SF (Richard Brinsley Sheridan) 0 MS END restore % to ground state %%EndObject showpage %%Trailer
gure 7.3 Here is a line with two italic words and two bold words. You shall see them on a beautiful quarto page, where a neat rivulet of text shall meander through a meadow of margin. Richard Brinsley Sheridan
108
gure 7.4
capital height
Hjx
descender
point size
x-height
109
gure 7.5
AWAY A AY W
110
Kerning, at its simplest, involves an adjustment of the current point after the rst character of a pair. This is effectively the same as temporarily changing the character width, depending on the following character. The second character is set normally, although it may itself be a candidate for beginning another kern pair. The rmoveto operator is suited to adjusting the current point, as in the following example. However, it should be worked carefully into whatever text-setting algorithm is used.
%!PS-Adobe-2.0 %%EndComments /S /show load def /r /rmoveto load def %%EndProlog /StoneSerif findfont 48 scalefont setfont 100 100 moveto (A) S -5.5 0 r (W) S -6.8 0 r (A) S -5 0 r (Y) S showpage There is a bit of unnecessary interpreter overhead for each character printed, in this example. This can be tightened up a bit, using the kshow operator; notice that the values for rmoveto are based on the user-space coordinate system. If the text were set at a different point size, these values would be different, as in the following example: %!PS-Adobe-2.0 %%EndComments /K { %def { pop pop 0 rmoveto } exch kshow } bind def %%EndProlog /StoneSerif findfont 48 scalefont setfont 100 100 moveto -5 -6.8 -5.5 (AWAY) K showpage This procedure minimizes some of the interpreter overhead, since fewer name lookups are performed, and since bind is performed on the procedure body. The main drawback of this approach is that kshow will always execute the procedure between every pair of characters in the string. If no kerning is desired, 0 must still be supplied on the stack (resulting in the unnecessary execution of 0 0 rmoveto). The viability of this approach depends on the amount of kerning that needs to be done. If only an occasional pair of characters is kerned, this is not a good approach. However, if every letter is positioned individually, the K procedure above might be a good choice.
111
Another approach to kerning is to use a negative width space character, and actually put the character in between the pair of letters to be kerned. The show operator will then automatically adjust the position based on the width of the kern character. The only difculty with this is that in order to provide enough exibility in kern values, many space characters of varying widths are required. It is also more difcult for the composing application to insert the extra space characters wherever kerning is required. Listing 7-4 contains a good hybrid approach. A line of text is set by breaking it into pieces that are as long as possible, and placing all the strings on the operand stack along with some extra information. There are three variations on the theme: stackshow, which prints several strings on the stack, each with a different font (supplied on the stack); kernstackshow, which performs the same operation but permits a value for rmoveto between strings; kernshow, which permits kerning values but not font changes. Each of them provides for a fast approach to setting text, where the component strings are made as long as possible, depending on the lines of text in the host application.
listing 7-4
%!PS-Adobe-2.0 %%Title: text-procs.ps %%Creator: Glenn Reid, Adobe Systems %%EndComments %%BeginProcSet: general 1.0 0 /F { findfont exch scalefont setfont } bind def /Fdef { findfont exch scalefont def } bind def /M /moveto load def %%EndProcSet: general 1.0 0 %%BeginProcSet: text-procs 1.0 0 % mark (text) fontdict (text) fontdict ... stackshow /stackshow { %def % reverse stack order first: 2 2 counttomark 2 sub { -2 roll } for counttomark 2 idiv { %repeat setfont show } repeat pop } bind def
112
% mark (Txt) font rX rY ... kernstackshow /kernstackshow { %def 4 4 counttomark 2 sub { -4 roll } for counttomark 4 idiv { %repeat rmoveto setfont show } repeat pop } bind def % "kernshow" is like "stackshow" except that it does % not use a font dictionary for each string. It % should be used when the kerned text is all set in % the same font. % mark (text) rX rY (text) rX rY .... kernshow /kernshow { %def 3 3 counttomark 2 sub { -3 roll } for counttomark 3 idiv { %repeat rmoveto show } repeat pop } bind def /MM { moveto mark } bind def %%EndProcSet: text-procs 1.0 0 %%EndProlog %%BeginSetup /F1 50 /StoneSerif Fdef /F2 50 /StoneSerif-Semibold Fdef /F3 50 /StoneSerif-Italic Fdef %%EndSetup %%Page: 1 1 F1 setfont 36 430 MM (S) 0 0 (W) -4 0 (EPT A) -4 0 (W) -8.0 0 (A) -9.25 0 (Y) -6.75 0 kernshow 36 380 MM (S) F1 0 0 (W) F1 -4 0 (EPT ) F1 -4 0 (A) F3 -4 0 (W) F3 -8.0 0 (A) F3 -9.25 0 (Y) F3 -6.75 0 kernstackshow showpage %%Trailer
113
Ligatures are also used to improve the visual quality of text. These are specially designed characters that comprise two (or even three) sequential letterforms. For instance, the two letters f and i, when set together, may be combined into a single shape: . This can affect composition software since the width of the ligature is usually different than the sum of the widths of the f and i characters. (See gure 7.6; the font is Stone Serif-Italic.) Ligatures cannot usually be added to a fontthey are part of the original font design. However, the application should recognize and replace sequential instances of, say, f and i, with the single ligature character. Spelling checkers, hyphenation tables, and justication algorithms need to know about them, as well.
gure 7.6
fig. ve
7.7 ENCODING AND CHARACTER SETS
A typical PostScript language font program is a collection of procedures, each with a particular name, stored into a dictionary. Each of these procedures, when executed, draws one character shape. There are other entries in a font dictionary, but all of them are used to set up or execute one of the character-drawing procedures. The collection of character procedures that are stored in a font is known as the character set of that font. There can be any number of these character procedures stored in the font dictionary. There is no limitation (other than available memory) on the total number of characters. There is, however, a limitation on how many of them can be accessed at any particular moment.
114
Access to font characters is controlled through the encoding mechanism. The characters in a font currently must be accessed by character code. A string presented to the show operator can be thought of simply as a sequence of ASCII bytes, each of which is actually just an index into the encoding vector for the current font. The encoding vector is an array of 256 name objects, each of which should be the name of a procedure stored in the font dictionary. The show operator actually presents the integer character code to the BuildChar procedure in each font, and it is up to that procedure to invoke the correct character description. This mechanism was deliberately set up to provide an easy way to change the mapping between the codes in the string and the characters that are actually selected from the font. Each font dictionary is required (by definefont) to have an array named /Encoding as one of its top-level entries. To reencode a font, copy the font dictionary, put in a different Encoding vector, and rename the font. Listing 7-5 contains a good reencoding algorithm. It is designed to avoid having to provide redundant data. A block of sequentially encoded names can be specied by providing only the initial encoding valuethe others are assigned in sequence.
listing 7-5
%!PS-Adobe-2.0 %%Title: reencode.ps %%EndComments /F { %def findfont exch scalefont setfont } bind def %%BeginProcSet: reencode 1.0 0 % This file defines a procedure called "R" which % reencodes a font. It expects three objects on the % stack: % % [ array ] /NewName /OldName % % The array should contain pairs of % <number> <name>, % like "32 /space", each of which defines a slot in the % encoding and the name to put in that slot. Only
115
% those names that are needed to over-ride the % existing ones should be specified. An encoding % value (number) may be specified followed by more % than one name, like "128 /name1 /name2". % In this case, the names will be sequentially stored % in the encoding starting at the initial number % given (128). /RE { %def findfont begin currentdict dup length dict begin { %forall 1 index /FID ne {def} {pop pop} ifelse } forall /FontName exch def dup length 0 ne { %if /Encoding Encoding 256 array copy def 0 exch { %forall dup type /nametype eq { %ifelse Encoding 2 index 2 index put pop 1 add }{ %else exch pop } ifelse } forall } if pop currentdict dup end end /FontName get exch definefont pop } bind def %%EndProcSet: reencode 1.0 0 %%EndProlog %%BeginSetup /stdencoding [ 39/quotesingle 96/grave 128/Adieresis/Aring/Ccedilla/Eacute/Ntilde /Odieresis/Udieresis/aacute/agrave/acircumflex /adieresis/atilde/aring/ccedilla/eacute/egrave/ecircumflex /edieresis/iacute/igrave/icircumflex/idieresis/ntilde/oacute /ograve/ocircumflex/odieresis/otilde/uacute/ugrave /ucircumflex/udieresis/dagger/.notdef/cent/sterling/section /bullet/paragraph/germandbls/registered/copyright /trademark/acute/dieresis/.notdef/AE/Oslash/.notdef /.notdef/.notdef/.notdef/yen/.notdef/.notdef/.notdef/.notdef /.notdef/.notdef/ordfeminine/ordmasculine/.notdef/ae/oslash /questiondown/exclamdown/logicalnot/.notdef/florin /.notdef/.notdef/guillemotleft/guillemotright/ellipsis/.notdef /Agrave/Atilde/Otilde/OE/oe/endash/emdash/quotedblleft /quotedblright/quoteleft/quoteright/.notdef/.notdef /ydieresis/Ydieresis/fraction/currency/guilsinglleft /guilsinglright/fi/fl/daggerdbl/periodcentered /quotesinglbase/quotedblbase/perthousand/Acircumflex /Ecircumflex/Aacute/Edieresis/Egrave/Iacute/Icircumflex /Idieresis/Igrave/Oacute/Ocircumflex/.notdef/Ograve /Uacute/Ucircumflex/Ugrave/dotlessi/circumflex/tilde
116
/macron/breve/dotaccent/ring/cedilla/hungarumlaut /ogonek/caron ] def stdencoding /_StoneSerif /StoneSerif RE stdencoding /_StoneSerif-Italic /StoneSerif-Italic RE %%EndSetup 90 /_StoneSerif-Italic F 50 220 moveto (fig. \336ve) show showpage %%Trailer
117
118
gure 7.7
\ k \k
upstem sxflagup quarternotehead sixteenth note When the three characters are set together, a sixteenth note results. Using one additional character (extendflagup) provides the capability of building arbitrarily complicated notes. In this case, the current point is adjusted between characters, but is restricted to vertical movement. The alignment of the characters is guaranteed by their design. (See gure 7.8.) Working with text characters which are in fact used more as graphic symbols than as conventional text requires some careful thought. The same design guidelines apply for using setfont and show with Sonata as they would with StoneSerif. The fewer operations (and the more characters that can be printed at once with a single call to show), the better the performance will be. There are applications for which specialized fonts can be a big advantage. For instance, CAD/CAM applications may build a library of symbols that may be used within a diagram. If they are used as font characters, then the speed of the font cache can dramatically decrease printing time for diagrams with many symbols. The font should be carefully designed so that it can be used with maximum efciency, and the character widths and origins planned around where the current point should be.
119
gure 7.8
\ k
extendflagup composite sixty-fourth note
Arranging characters on a page is the realm of word-processing or page composition software. In order to work with non-Latin character sets, many of the built-in assumptions about text must be relaxed. For example, word wrapping, line spacing, and hyphenation are very closely tied to languages which read left to right, which set characters on a common baseline, and which have very little variation in vertical placement of characters. Setting text in Arabic, Kanji, Urdu, Mathematics, or other languages require much more sophisticated placement algorithms and judgement. Working with these languages in the PostScript language can be reduced to two characteristic issues: Knowing the origin of the characters is crucial to placing it in the correct position. The character width affects the placement of the following character. Adjusting the current point slightly after printing a character is called kerning when setting most languages with the Roman alphabet. Kerning is often considered optional and it is a renement to typesetting. In other languages, the current point may need to be adjusted between every two characters on the page, depending on the context, and it is not considered optional. This is not really a kerning problem, but an issue of placement. But the same principles hold as with all typesetting
120
done with the PostScript languageif more than one character can be passed to show at once, there will be a signicant performance gain over individually placing each character. Some of the burden for this lies on the type designer, and some on the careful placement algorithms used by the layout software.
121
8.1 INTRODUCTION
The image operator is the mechanism in the PostScript language for printing sampled images of any size in up to 256 shades of gray. These images may be printed at any size and orientation with a minimum of calculation. The PostScript language also has a built-in digital halftoning system that permits full control of the rendering of gray shades with halftone screens. This chapter takes an in-depth look at the image operator and the halftone mechanism.
123
The width of the image, in samples. For an 8-bit deep image, this number is the same as the number of bytes in one row of source data. The height of the image, in samples (or scan lines). The number of bits per sample in the original data. A black-and-white scanned image has 1 bit per sample, a grayscale image may have 2, 4, or 8 bits per sample. The image matrix, which is a transformation matrix that maps user space onto the coordinate system implied by the scanning process in the original data. The data acquisition procedure, which fetches sample data and leaves it on the operand stack as a string object.
HOW IT WORKS
The image operator multiplies width times height times bits per sample to determine how many bits of data are needed to render the image. It then executes the data acquisition procedure as many times as necessary, imaging each string directly into device space as it is produced by the procedure. The mapping provided by the image matrix is applied as the data is being imaged.
124
between the region in user space the nal printed image should occupy and the region in sample space that the image originally occupied. Since either the user space transformation or the image matrix may be modied independently, there are an innite number of combinations that will produce the desired image. It is best to hold one of the matrices as a constant entity, and adjust the other one based on the image being rendered. Here are some steps that will simplify use of the image matrix, and provide a generally correct mapping for any image data into the user coordinate space. Use translate, rotate, and scale to alter user space so that a one-unit square at the origin would occupy a space on the current page exactly where you want the image to be printed. That is, if the lower-left corner of the image is to be 3 inches to the right and 10 inches above the current user space origin, and the image is to occupy a space 1 inch wide and 2 inches tall, precede the call to the image operator with the lines save 216 720 translate 72 144 scale Invoke the image operator, supplying an image matrix that prints the sampled image in the one-unit square at the origin. This matrix is a very easy one to calculate. Images scanned bottom-up and top-down require the following image matrices, respectively: [ w 0 0 h 0 0 ] if scanned from the bottom [ w 0 0 -h 0 h ] if scanned from the top where w and h are the width and height of the sample data in samples (not points). For a more complete discussion of the image matrix, see Section 4.7 of the PostScript Language Reference Manual. Provide a data acquisition procedure. This is covered more fully in the next section.
125
126
The width and height of the image are handed directly to the image operator on the operand stack. The datastring, however, must be of the appropriate length to match the dimensions of the image. Strings contain 8-bit bytes. To represent 10 x 8 x 4 bits of image data, 320 bits of information are needed, which requires a 40 byte string to contain the 320 bits.
127
currentfile DataString readhexstring pop } bind image f6b94f... This program fragment prints an image consisting of 512 columns of 340 rows each, where each image pixel has a gray scale specied by an 8-bit value. The image matrix indicates that the data represents an image scanned from the top down. The readhexstring operator takes a string object and a le object (in this case the current le, which is the le object that is currently being read by the PostScript interpreter). It reads ASCII hexadecimal data from the le and places the data into the string until either the string is full or the end of le has been reached. The readhexstring operator returns a string object on the operand stack containing data read from the le, and a boolean object which is false if the end of le was reached before the string was lled and true otherwise. In the previous example, the boolean value returned by readhexstring was popped from the stack for efciency, since the procedure will be called many times by the image operator. For debugging purposes, or for better error recovery, it is best to interrogate the resulting boolean and provide some sort of error message if the end-of-le indication was reached prematurely. The readhexstring operator takes the incoming data two characters at a time, interpreting each pair and producing a single byte in the resulting the data string. It ignores all characters that are not valid hexadecimal digits, so that the data may include tabs, spaces, newline characters, and so forth. The ASCII hex representation of the sample data should immediately follow the call to the image operator, as in our example above. The data acquisition procedure is called repeatedly, taking characters from the input stream until the entire 512-by340-by-8-bit image has been processed. At that point, the input stream will be passed back to the PostScript interpreter and the original program can resume.
128
SYNTHETIC DATA
The data handed to the image operator can be generated directly by the program. For example, listing 8-1 supplies a synthetic data string to the image operator each time the data acquisition procedure is called; in this case, the string is the same each time and represents a gray-scale fountain with 256 possible gray levels (although not all of them will be attainable on low-resolution devices). This somewhat unusual approach builds a string of 256 bytes, each of which ranges from 0 to 255 sequentially (the for loop accomplishes this, although cryptically). When the image operator uses this string, each image sample is an 8-bit value, and the values range continuously from 0 to 255 each of these image samples is interpreted as a gray value which is then rendered with the halftone mechanism. The actual number of gray levels achieved in the fountain will depend on the constraints of the current halftone screen frequency and the resolution of the output device, but the transition between them will be as smooth as possible.
129
listing 8-1
%!PS-Adobe-2.0 %%EndComments /DataString 256 string def /IM { %def gsave translate scale image grestore } bind def %%EndProlog 0 1 255 { DataString exch dup put } bind for 1 256 8 [1 0 0 256 0 0] { DataString } 72 72 144 36 IM showpage %%Trailer
The translate and scale operations in the IM procedure locate the image and stretch it to the desired size. The image is rendered as a single sample in width, with a height of 256 samples. The image matrix provided maps this into the unit square in user space, and the scale stretches it to be exactly a 1-inch square on the nal page. (See gure 8.1.)
gure 8.1
Note that, although this image is 1 sample wide and 256 samples high, the printed size of the image is 1 inch square. Remember that the size of the printed image is determined by the arguments passed to the scale operator, not by the number of rows and columns in the sampled data.
130
131
The PostScript interpreter prints shades of gray with a digital approximation of true halftoning. The dots that a PostScript printer produces are grouped into a square called a halftone cell. On a 300 dot-per-inch printer using a 60-line halftone screen and an angle of 0 degrees, this cell is 5 device pixels on a side. Each gray level is rendered by turning more pixels on in the halftone cell, and a gray area is provided by tiling the area with these halftone cells. Most PostScript interpreters by default print black within the halftone cells in such a way that each halftone cell looks like a single dot that expands as the gray gets darker, as in the fountain in gure 8.1. This is similar to the results of traditional halftoning; it is known informally as a dot pattern.
132
listing 8-2
%!PS-Adobe-2.0 %%EndComments /F { findfont exch scalefont setfont } bind def /setF { %def currentscreen 4 -2 roll pop % remove existing frequency 3 1 roll setscreen } bind def %%EndProlog gsave 20 setF .4 setgray 250 90 moveto 300 /StoneSerif-Italic F (&) show grestore showpage %%Trailer
Figure 8.2 shows a gray character rendered the screen frequency was set to 20 as in listing 8-2.
133
gure 8.2
&
0,1 -1,0 1,0 0,-1
The setscreen operator considers the halftone cell to be centered on a set of coordinate axes, with the cell extending for one unit in each direction. (See gure 8.3.) The spot function supplied to the setscreen operator must calculate a priority for each location within the halftone cell.
gure 8.3
134
The spot function is handed the x and y coordinates on the operand stack that represent the location of the center of each device pixel found in the halftone cell. The function must calculate a priority value in the range -1 to 1 for each position. The halftone mechanism will turn on spots within the halftone cell in order from high to low priority. For example, the following call to setscreen causes the PostScript interpreter to render gray levels with a line screen, instead of a dot screen: %!PS-Adobe-2.0 %%EndProlog /DataString 256 string def 0 1 255 { DataString exch dup put } bind for 40 45 { pop } setscreen 72 72 scale 1 256 8 [1 0 0 256 0 0] {DataString} image showpage The spot function in this case consists only of a pop operation. The function is called with the stack holding the x and y coordinates (with y on top) of a position within the halftone cell. The pop removes the y coordinate from the stack so that the priority of any position in the halftone cell is its x coordinate. That is, the farther to the right a position is within the halftone cell, the higher its x coordinate and the sooner the printer will print a spot at that position.
gure 8.4
-.8 -.4 -.8 -.4 -.8 -.4 -.8 -.4 -.8 -.4
0 0 0 0 0
.4 .4 .4 .4 .4
.8 .8 .8 .8 .8
135
The resulting priorities for a 5-by-5 halftone cell (the default in many PostScript interpreters) are shown in gure 8.4. These priorities render gray areas as a series of lines of varying thicknesses. The gradual blackening of the halftone cell is performed pixel by pixel by the PostScript interpreter, with pixels of highest priority (as determined by this spot function) being blackened rst as the gray level is varied from white to black.
136
9.1 INTRODUCTION
This chapter focuses on some of the more complex graphic imaging problems that may be encountered when implementing a driver for a graphics application. Some of the topics covered in this chapter involve graphic operations that are not indigenous to the PostScript imaging model. Nonetheless, there are graphics applications that need to render these effects on PostScript printers. Emphasis is placed on the most efcient methods for solving these graphics problems with the standard PostScript language imaging model.
137
(by calling the spot function as many times as necessary to build each possible halftone cell). This makes it fairly expensive to change the halftone screen. Halftone screens are also very device-dependent by nature, and do not rotate or scale with the current transformation matrix. Also, the screens are oriented in device space, and they probably will not work the same way on different devices. Another approach to pattern lls is to use a pattern font. A character (or a number of characters) in a font may be dened to represent the ll pattern, and a region may be tiled by showing text within the boundaries of the region. Unfortunately, under most circumstances this requires use of the clip operator to establish the boundaries of the region to be lled before the pattern text is painted. This presents two difculties: Clipping can be quite slow if the clipping region is complex (the region to be lled with a pattern, in this case). The available path space can overow when clip is used on a complex path (yielding a limitcheck error). However, the pattern font provides a general solution to deviceindependent pattern lls, and is a recommended approach. In listing 9-1 are some sample PostScript procedures which implement pattern lling algorithms. The approach is to dene a font with pattern characters in it, then select one of those characters for tiling. The actual ll process uses clip to establish a clipping region, then uses show with rows of pattern characters to tile the area. Care is taken to assure that the pattern will lock into device space. This keeps the pattern from scaling or rotating, and helps guarantee that there will be no stitching problems between rows of the pattern.
listing 9-1
%!PS-Adobe-2.0 %%Title: patternfill.ps %%EndComments %%BeginProcSet: patternfill 1.0 0
138
% width height matrix proc key cache % definepattern -> font /definepattern { %def 7 dict begin /FontDict 9 dict def FontDict begin /cache exch def /key exch def /proc exch cvx def /mtx exch matrix invertmatrix def /height exch def /width exch def /ctm matrix currentmatrix def /ptm matrix identmatrix def /str (12345678901234567890123456789012) def end /FontBBox [ %def 0 0 FontDict /width get FontDict /height get ] def /FontMatrix FontDict /mtx get def /Encoding StandardEncoding def /FontType 3 def /BuildChar { %def pop begin FontDict begin width 0 cache { %ifelse 0 0 width height setcachedevice }{ %else setcharwidth } ifelse 0 0 moveto width 0 lineto width height lineto 0 height lineto closepath clip newpath gsave proc grestore end end } def FontDict /key get currentdict definefont end } bind def
139
% dict patternpath % dict matrix patternpath /patternpath { %def dup type /dicttype eq { %ifelse begin FontDict /ctm get setmatrix }{ %else exch begin FontDict /ctm get setmatrix concat } ifelse currentdict setfont FontDict begin FontMatrix concat width 0 dtransform round width div exch round width div exch 0 height dtransform round height div exch round height div exch 0 0 transform round exch round exch ptm astore setmatrix pathbbox height div ceiling height mul 4 1 roll width div ceiling width mul 4 1 roll height div floor height mul 4 1 roll width div floor width mul 4 1 roll 2 index sub height div ceiling cvi exch 3 index sub width div ceiling cvi exch 4 2 roll moveto FontMatrix ptm invertmatrix pop { %repeat gsave ptm concat dup str length idiv { %repeat str show } repeat dup str length mod str exch 0 exch getinterval show grestore 0 height rmoveto } repeat pop end end } bind def
140
% dict patternfill % dict matrix patternfill /patternfill { %def gsave clip patternpath grestore newpath } bind def % dict patterneofill % dict matrix patterneofill /patterneofill { %def gsave eoclip patternpath grestore newpath } bind def % dict patternstroke % dict matrix patternstroke /patternstroke { %def gsave strokepath clip patternpath grestore newpath } bind def % dict ax ay string patternashow % dict matrix ax ay string patternashow /patternashow { %def (0) exch { %forall 2 copy 0 exch put pop dup false charpath currentpoint 5 index type /dicttype eq { %ifelse 5 index patternfill }{ %else 6 index 6 index patternfill } ifelse moveto 3 copy pop rmoveto } forall pop pop pop dup type /dicttype ne { pop } if pop } bind def
141
% dict string patternshow % dict matrix string patternshow /patternshow { %def 0 exch 0 exch patternashow } bind def /opaquepatternfill { %def gsave 1 setgray fill grestore patternfill } bind def /square { %def gsave moveto dup 0 rlineto dup 0 exch rlineto neg 0 rlineto closepath findfont % a pattern font patternfill grestore } bind def %%EndProcSet %%EndProlog %%BeginSetup 15 15 [300 72 div 0 0 300 72 div 0 0] { %definepattern 2 setlinecap 7.5 0 moveto 15 7.5 lineto 0 7.5 moveto 7.5 15 lineto 2 setlinewidth stroke } bind /RIGHTdiagonal true definepattern pop 15 15 [300 72 div 0 0 300 72 div 0 0] { %definepattern 2 setlinecap 7.5 0 moveto 0 7.5 lineto 15 7.5 moveto 7.5 15 lineto 2 setlinewidth stroke } bind /LEFTdiagonal true definepattern pop
142
30 30 [300 72 div 0 0 300 72 div 0 0] { %definepattern 2 2 scale 2 setlinecap 7.5 0 moveto 15 7.5 lineto 0 7.5 moveto 7.5 15 lineto 7.5 0 moveto 0 7.5 lineto 15 7.5 moveto 7.5 15 lineto 0.5 setlinewidth stroke } bind /crosshatch true definepattern pop %%EndSetup %%Page: 1 1 /RIGHTdiagonal 72 460 100 square /LEFTdiagonal 72 480 120 square /crosshatch 72 500 140 square showpage %%Trailer
143
listing 9-2
%!PS-Adobe-1.0 %%Title: logo procedure %%EndComments /logo { %def % draw at current point gsave /Times-Roman findfont 48 scalefont setfont (G) show -13.5 -14.5 rmoveto (R) show grestore -4 -8 rmoveto /Helvetica findfont 11 scalefont setfont (glenn) show 22 3 rmoveto (reid) show } bind def %%EndProlog gsave 100 100 moveto logo grestore showpage %%Trailer
The copypage operator is designed to print the current page without disturbing the contents of the frame buffer into which the page has been imaged. This permits reusing the image that already exists on the page. A form of electronic mask white may be used from one page to the next to erase the elds on the form and print new data in those areas. (This can be accomplished simply by setting the current color to white and using fill.) The only difculty with using copypage is that it doesnt allow fast PostScript interpreters to take advantage of parallel processing. In most implementations of the PostScript interpreter there is a provision for writing bits into one part of the frame buffer while the other end is being imaged onto paper. In extremely fast printers there may be many different frame buffers and a high degree of parallel processing. If the user program forces reuse of the same frame buffer by using copypage,
144
this parallelism may be defeated (and the printer will run much slower than its rated capacity). There is a trade-off between using copypage and executing the procedure on each page. The use of copypage is discouraged in all but the most unusual situations. The third approach that can be used to create a reusable graphic image is to make it a character in a user-dened font. The font may contain only a single character, or perhaps many such images. Each image is simply a procedure that is invoked by the font machinery as if to draw a character shape. The advantage of this method is that if the image is an appropriate size to t into the font cache, then it will be extremely fast. In listing 9-3 is the logo procedure from the listing 9-2, placed into font format and invoked by a single-character show operation:
listing 9-3
%!PS-Adobe-2.0 %%Title: logo font example %%EndComments /F { findfont exch scalefont setfont } bind def %%EndProlog %%BeginSetup %%BeginFont: Logo-Font 1.0 0 12 dict begin % leave one empty slot for FID /FontName /Logo-Font def /FontMatrix [ 1 0 0 1 0 0 ] def /FontType 3 def /FontBBox [ -5 -12 85 50 ] def /BuildChar { %def exch begin 70 0 -5 -12 85 50 setcachedevice Encoding exch get load exec end } bind def /Encoding 256 array %def dup 0 1 255 { /.notdef put dup } for pop dup 71 /logo put % "G" character def /logo { %def % draw at current point 20 0 moveto
145
gsave /Times-Roman findfont 48 scalefont setfont (G) show -13.5 -14.5 rmoveto (R) show grestore -4 -8 rmoveto /Helvetica findfont 11 scalefont setfont (glenn) show 22 3 rmoveto (reid) show } bind def currentdict end dup /FontName get exch definefont pop %%EndFont: Logo-Font %%EndSetup %%Page: 1 1 gsave 100 100 moveto 1.5 /Logo-Font F (G) show grestore showpage %%Trailer
GRIDS
Producing a grid of lines can result in some slightly unexpected results, due to the rounding of line widths as lines are stroked. (See also Section 5.4.) The following sequence produces a thin line on the page: .1 setlinewidth 72 72 moveto 572 72 lineto stroke However, the exact thickness of the line depends on where the moveto and lineto coordinates fall in device space. PostScript maintains the full precision of coordinates all the way through to device space, and it is not until the execution of stroke or fill that decisions are made for painting individual device pixels. Figure 9.1 is an illustration with a moveto lineto stroke sequence for a short line segment. It shows the interior of the stroked path and the pixels that are blackened by stroke.
146
gure 9.1
actual line on paper stroke path current path device space
72 72 moveto
72 90 lineto stroke
Two procedures are dened in listing 9-4 (redenitions of the moveto and lineto operators) that provide lines of uniform thickness, although their spacing cannot also be guaranteed. The crux of these procedures is the use of the transform and itransform operators. The coordinates for moveto and lineto are actually transformed into device space and rounded off to the nearest device pixel boundary. The coordinates are then inverse-transformed back into user space, and the subsequent moveto or lineto is executed. The points are then guaranteed to land on pixel boundaries, which will cause stroke to produce a line of a uniform thickness regardless of where on the page the lines are located. Notice that the coordinates in device space are not inspected in any way; they are merely rounded off and returned to user space. This keeps the code from becoming device-dependent, since no assumptions are made as to the resolution. Figure 9.2 shows the line that results when the M and L procedures are used instead of moveto and lineto. Since the locations are rounded to the nearest device pixel, the stroke operation always encompasses the same number of pixels to either side of the path, resulting in an even line.
147
listing 9-4
%!PS-Adobe-2.0 %%Title: line thickness example %%EndComments /M { %def transform round exch round exch itransform moveto } bind def /L { %def transform round exch round exch itransform lineto } bind def %%EndProlog gsave .1 setlinewidth 10 10 800 { %for pop 0 10 translate 550 0 M 612 0 L stroke } bind for grestore showpage %%Trailer
148
gure 9.2
actual line on paper stroke path current path device space
72 72 moveto
72 90 lineto stroke
149
100 200 translate 2 2 scale [ 2 0 0 2 100 200 ] concat The concat operator allows you to supply a complete matrix which is to be concatenated with the existing transformation matrix. Each of the scale and translate operations in the previous example is easily represented by specifying the appropriate matrix and using concat. The scale, rotate, and translate operators are provided for convenience, but are not strictly necessary. Rotation of the coordinate space is less straightforward. The entries in the matrix are actually derived by taking the sin or cos of the desired angle of rotation: [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] The following is an example of the combined effects of translation and rotation. In order to set landscape mode, the coordinate axes must be rotated 90 degrees and translated so that the origin remains in the lower left-hand corner of the page. Both of the following PostScript segments will place a standard letter sized paper in landscape mode: 90 rotate 0 -612 translate 612 0 translate 90 rotate Notice that in both cases the sequence 90 rotate is used, but the translation component is not the same. Note: These coordinate system transformations are not commutative. That is, if the order of evaluation is changed, different results may be achieved. By convention, landscape pages should always be effected by rst rotating (positive angle) then translating (negative Y value): 90 rotate 0 -612 translate
150
As a convention, it is best always to accomplish rotation of a page into a landscape orientation in this manner (positive 90 degrees for the rotation component, then a negative translation component along the Y axis). This is so that if the page is nested in another page or scaled, the landscape orientation can be preserved easily.
151
ator) or the red, green, blue color model (using the setrgbcolor operator). For color printing, the reectance model is generally used, with cyan, magenta, yellow, and black as the component colors. Color PostScript printers provide direct support for this color model through the setcmykcolor operator and through related operators to control four separate halftone screens and transfer functions. A colorimage operator is also provided for rendering full color images. Once a color has been specied in the user program, it is rendered through a PostScript language abstraction similar to the black-and-white halftoning mechanism. A current color is established in the graphics state, and all the painting operators use the current color as they apply paint to the current page. The PostScript language interpreter is responsible for producing that color on a given output device. If the device is a screen, the color must be displayed through available screen phosphors. With color printing devices, the color may be produced by successive passes of cyan, magenta, yellow, and black, where each pass uses the appropriate percentage of color as specied by the user program.
COLOR SEPARATIONS
If a PostScript program contains color specication, it can be printed on any PostScript interpreter, and the device will render the color to the best of its ability. However, for full process color presses, you may wish to separate the image into its four components of cyan, magenta, yellow, and black. This can be done by producing four photographic masters (camera-ready plates), one for each process color. These plates are actually printed in black and white using the standard halftoning mechanism, and each plate is then overprinted with the appropriate process color ink in the nal press run. Color separations may be obtained simply by redening the color operators to essentially throw away all but a single process color. The le must then be printed in four passes, where each pass sifts out a different process color. (See listing 9-5.)
152
listing 9-5
%!PS-Adobe-2.0 %%Title: separation.ps /separations 24 dict def separations begin /cmykprocs [ %def % cyan { pop pop pop 1 exch sub setgray } % magenta { pop pop exch pop 1 exch sub setgray } % yellow { pop 3 1 roll pop pop 1 exch sub setgray } % black { 4 1 roll pop pop pop 1 exch sub setgray } ] def /screenangles [ %def 105 % cyan 75 % magenta 0 % yellow 45 % black ] def end % separations % setupcolor takes 0, 1, 2, or 3 as its argument, % for cyan, magenta, yellow, and black. /CYAN 0 def /MAGENTA 1 def /YELLOW 2 def /BLACK 3 def /setupcolor { %def userdict begin dup separations /cmykprocs get exch get /setcmykcolor exch def separations /screenangles get exch get currentscreen exch pop 3 -1 roll exch setscreen /setscreen {} def end } bind def %%EndProlog %%BeginSetup CYAN setupcolor %%EndSetup %%BeginDocument: originalfile.ps % entire file is placed here. %%EndDocument %%Trailer
153
The halftone screen angles used are extremely important to good color separations. Each process color component should be printed with a different screen angle to minimize color interference in the nal press run. The exact angles used are devicedependent and should be determined separately for each different PostScript device. The goal is to provide placement of color spots that will not interfere with one another, and which will avoid moir patterns as the screens are overlaid.
SPOT COLOR
Spot color is much different than four-color process printing. If you imagine real printing presses, the kind you put ink into instead of toner, you can imagine using a colored ink instead of using black. That is a good model of spot color. It is a single color of ink, rather than a color that is composed as some mix of the four process colors cyan, magenta, yellow, and black. There are two sides of spot color: specifying the color and actually printing it. If and when there are devices which permit printing with two or three (or more) individual colors on a single device, then those colors must be specied by the user program in much the same way that shades of gray are specied with the setgray operator. More commonly, spot color will be mastered on a black-and-white device and printed on a high-speed press with colored inks. From the PostScript interpreters point of view, it will be printing only in shades of black, although the masters may eventually be printed with a colored ink when taken to press. Probably the simplest way to specify spot color is to be able to selectively print components of a document. For example, if a user decides that all chapter headings in a book are to be printed in a particular color of green, and the rest of the book is to be printed in black, he or she needs some way to print the document in two individual passes: the green pass and the black pass. To produce the green pass, only those objects in the document which are specied as green should be printed at all (the black material is simply not printed). An application program can permit tagging of objects to be one color or another, and provide a mechanism for printing only selected objects by naming them. In this way the user can
154
selectively print some part of the document to obtain a separation. The PostScript interpreter, of course, is simply interpreting and printing graphics in some shade of black or white, and nothing at all is required at the PostScript level. If the application produces PostScript code that is sufciently modular, each graphic element on the page can be bracketed by the special comments %%BeginObject and %%EndObject. These comments simply provide a way to name the graphic elements on the page. A post-processing program could then selectively extract certain elements of the page for printing. (See the PostScript Document Structuring Conventions document from Adobe Systems for more information on these comments). The PostScript imaging model, however, can complicate the real world of spot color. For instance, if a black rectangle is drawn and lled and a green circle is printed on top of it, the PostScript imaging model completely overwrites the black with the green. However, if the page is printed in two passes, with one pass containing only the black elements and the other pass only the green ones, the black rectangle will not have a white circle cut out of it where the green will go. When green ink is actually printed on top of the black ink on a regular press, some muddying of the color will result. To be done correctly, the black pass should have color removed wherever another color would be overlaid on top of it. This is a much more difcult thing to accomplish, and cannot be done by simply printing individual page elements selectively. This kind of separation problem can also occur in four-color process printing, if the overlaid colors are printed without rst removing any colors which may precede them. This nuance of color printing can be accommodated at the PostScript language level simply by redening some of the color specication operators. Rather than extracting and printing individual objects on a page, all of them may be sent to the printer. To print a separation, the color operators are redened to print black wherever, say, green is specied, and to print with white if any other color is called for. Then, in any place where the green would be obscured by another color above it, that second color would be printed in white, effectively erasing that part of the green image. The technique for this is much like that
155
found in listing 9-5. The difference is the selection processrather than selecting only the cyan pass, some method of selection by name must be implemented in order to specify the spot color. The full details of this specication and separation technique are beyond the scope of this book. For more detailed information, please contact someone at Adobe Systems.
156
10.1 INTRODUCTION
PostScript language print les that are intended to migrate between different environments should observe standard structuring conventions, and they should be device independent. The standardized structure permits document managers to incorporate the documents into other systems and to print them in the appropriate environment. It also makes parallel processing possible and provides a mechanism for ensuring that fonts and other resources are supplied as needed.
157
ment that marks a le as a conforming le (including a version number). Structure: The structure of the le should be in conformance with the le structuring guidelines. This includes page modularity (pages should be functionally independent) and the avoidance (or careful use) of system-level PostScript language operators such as initgraphics and erasepage. The rules for conformance are not particularly limiting. Conforming les are, for the most part, ordinary PostScript language program les. The notion of conformance is introduced as a guarantee to applications software that the le plays by the rules.
158
%%BeginPaperSize: Ledger statusdict begin ledgertray end %%EndPaperSize This PostScript language sequence correctly invokes the ledgertray operator on any printer that has this operator in statusdict. If a document manager wishes to send the le to a different printer than was originally intended (that may have a slightly different method for invoking the Ledger paper size), it should remove the code between these comments, and use the keyword Ledger to nd the appropriate PostScript language segment from the printer description le, with which it will replace the previous code sequence. Even if it happens to be sent to another printer for which ledgertray is the appropriate operator, it doesnt hurt to replace it.
159
listing 10-1
%!PS-Adobe-2.0 %%BoundingBox: 0 0 1224 792 % % <width> <height> <llX> <llY> <thick> box /box { %def gsave setlinewidth moveto dup 0 exch rlineto exch 0 rlineto 0 exch neg rlineto closepath stroke grestore } bind def %%EndProlog 1152 720 36 36 3 box showpage %%Trailer
This program prints a rectangular border (with a line thickness of 3 points) one half inch in each direction from the edges of an 11-by-17 inch page. The rectangle is 10 inches high and 16 inches long. The coordinates in this le represent a decision to lay out the box on a ledger-sized sheet of paper, in a sense. The second decision is to print the document. Notice the distinction between the composition of the document and the act of printing it. At this stage, a particular printer is chosen. It is typical to inspect a proof of the document on the screen rst, then perhaps on a medium-resolution printer, and use a high resolution image-setter for nal output. Each device may have a different capability for rendering a page size as large as 11 x 17 (including, perhaps, not being able to support it at all). The actual invocation of a specic paper size should be done at print time. In listing 10-2 is the same example with specic printing instructions added. In particular, the ledgertray operator is invoked to print on 11-by-17 inch paper. This code is put between a %%BeginPaperSize and %%EndPaperSize comment since the code is (by necessity) device dependent.
160
listing 10-2
%!PS-Adobe-2.0 %%BoundingBox: 0 0 1224 792 %%DocumentPaperSizes: Ledger %%EndComments % <width> <height> <llX> <llY> <thick> box /box { %def gsave setlinewidth moveto dup 0 exch rlineto exch 0 rlineto 0 exch neg rlineto closepath stroke grestore } bind def %%EndProlog %%BeginSetup %%BeginPaperSize: Ledger statusdict begin /ledgertray where { %ifelse pop ledgertray }{ %else 90 rotate 0 -612 translate 792 1224 div dup scale % scale down for proofing on letter paper } ifelse end %%EndPaperSize %%EndSetup %%Page: 1 1 1152 720 36 36 3 box showpage %%Trailer
Notice that the PaperSize code checks for the existence of the ledgertray operator (using known) and if it is not found, the page is printed much smaller to t on letter-size paper. This technique can help provide fallbacks for devices which may contain unknown features (see also Section 10.6, CONDITIONAL EXECUTION).
161
162
This is so that software reading the comments can quickly ascertain that it is a query job. See the PostScript Document Structuring Conventions for more detail on this.
listing 10-3
%!PS-Adobe-2.0 Query %%Title: sample query job %%?BeginFontQuery: Palatino-Roman /Palatino-Roman dup FontDirectory exch known { %ifelse 1 % yes }{ 0 % no } ifelse == flush pop %%?EndFontQuery %%?BeginVMStatus vmstatus exch sub == flush pop %%?EndVMStatus %%EOF
listing 10-4
%!PS-Adobe-2.0 %%EndComments %%EndProlog %%BeginSetup %%BeginPaperSize: Ledger statusdict /ledgertray known { %if
163
statusdict begin ledgertray end }{ %else (Ledger not found. Scaling...\n) print 792 1224 div dup scale } ifelse %%EndPaperSize %%EndSetup %%Page: 1 1 showpage %%Trailer
164
is done). The %%DocumentNeededFonts comment is used to indicate that there is explicit use of the %%IncludeFont comment. It is an indication that the le should be parsed to satisfy a font request embedded in the le. The %%DocumentSuppliedFonts comment is used where an entire font description is contained within the body of the document le (as a downloadable font). The font itself is delimited by %%BeginFont and %%EndFont comments, as in the example. Again, the header comment is provided for information about what is contained in the body of the le. When a le is imported, either from another system or as an illustration in a document, these comments should be parsed to determine the font needs of the document being imported. When appropriate, the importing application must locate the needed fonts and place them in-line in the le, and/or update its own structure comments to reect the aggregate font needs of its own document le and the imported one.
165
comment if you are issuing a query, and dont put in the %%Page comments if the pages arent functionally independent of one another. If you include PostScript code that is not clearly documented as being a standard part of the language, then mark this code with some kind of %%Begin and %%End comments, according to the comment conventions. If the entire PostScript job is somehow special (a downloaded font, a query, or an exitserver job, for example), mark it at the beginning of the job according to the conventions. If you are unsure of whether your le is conforming, try importing it into another document as an illustration, and see if it works. Dont execute any of the initialization operators (any operator that contains init or default, among others). As a rule of thumb, dont execute anything in the prologue (including save), and dont dene anything new in the script (unless it is a state variable). Dont try to put save and restore around your whole job, prologue and all. That is done for you. It also makes the le non-conforming if you do. Do put save and restore around the individual pages of the script, or even more frequently, if appropriate.
166
11.1 INTRODUCTION
One goal of software integration is to be able to produce complex documents. For instance, a report may need to contain a graph of data stored in a company database. This involves integrating data into a graphics application and merging it with text. Often the result of this integration is a printed page or a transparency for an overhead presentation. In a PostScript environment, this can be accomplished at print time in much the same way that photographs are added directly to the plates (stripped in) for an offset printer. If the software used to produce the graphs can produce a PostScript le as output, it can be merged with the PostScript output from a word processor to print the graph on the same page as the text. Essentially, it means the ability to paste in illustrations composed by other software. The nature of the PostScript language makes this work quite well. The coordinate system can be scaled and translated before the illustration is executed, and the entire graph can be printed at some percentage of its original size, and even rotated. However, some cooperation between the composing software and the importing software is necessary to allow it to work smoothly.
167
This chapter is devoted to the relationship that should exist between the composing software and the importing software. It is closely related to Chapter 10, in that the best way to successfully merge PostScript les from different sources is to make sure that all the les are conforming PostScript les.
168
A PostScript program should only change those elements of the ground state of the interpreter that are necessary for its own execution, and it should return the state of the interpreter to that same condition when it is through executing. The recovery can be accomplished simply by using save and restore, but it is the programmers responsibility not to disturb any parts of the machines state that do not need to be modied for the task at hand. In particular, it is important never to initialize the state of the interpreter.
169
listing 11-1
%!PS-Adobe-2.0 %%Title: include.ps %%EndComments %%BeginProcSet: include.ps 1.0 0 /beginexecute { %def /level0 save def /showpage { } def /jobname exch def } bind def /endexecute { %def level0 restore } bind def %%EndProcSet %%EndProlog % including "Illustration.ps" (Illustration.ps) beginexecute 0 0 translate % if needed 1 1 scale % if needed %%BeginDocument: Illustration.ps %!PS-Adobe-2.0 %%Title: Illustration.ps jobname = flush %%Trailer %%EndDocument endexecute showpage %%Trailer
This disables the showpage operator for the duration of the execution of Illustration.ps, and reinstates it to its original value
170
(whatever it may have been) after the restore. Notice also that the name of the le is made available in the jobname entry, which can be reported if an error occurs.
171
the rst place, and must be represented by a convention to which each application agrees. 3. Interpret the PostScript Code. If the PostScript le can be executed and displayed directly, no simulation is required. This can be thought of as a preview capability if the software does not use the PostScript language as its display model already. In each of these approaches, the importing application can handle the le better if it knows that it is a PostScript language illustration. In particular, it can consult the bounding box information in the le to display the appropriate rectangle, or it can display the simulated bit image on the screen if it knows where to look for it. A current convention in the industry is for a drawing application to produce conforming PostScript les with a preview bit image along with them, in a standard format which importing applications can understand. It is then up to the importing program to display the bit image on the screen and keep the PostScript program to send to the printer. As the PostScript language becomes more commonplace for screen drivers, the applications may not need to use the previewed bit image stored with the le, but may choose to interpret the PostScript code directly.
172
173
12.3 COMMUNICATIONS
The low-level considerations of maintaining two-way communication with a PostScript device are a critical part of printer management. There are some aspects of this communication which differ greatly from other printing devices, and which need attention in the design phase. The batch job model under which PostScript printers normally operate is founded on individual print les. That is, a single job is dened from the initial byte through the nal end-of-le indication. The end-of-le (EOF) should never be contained in the document le itself; it is part of the communication dynamics. In fact, the EOF marker for, say, a serial port is much different than the EOF indication on a packet network. The PostScript interpreter will echo an EOF to match each EOF that it receives. This is a conrmation that the entire le has been received. A second job should never be sent until the rst
174
one has completely nished executing (including echoing back the EOF indication).
MESSAGES
PostScript printers tend to generate ASCII text messages back to the host system. Some of these are in response to status requests, and some of them appear whether or not you ask for them. For example, on many devices findfont will return the font Courier if it cannot nd the font asked for. When this substitution is performed, a message is generated back to the host computer that looks like this: StoneSans not found, using Courier. If a PostScript error is triggered, the standard error handler produces a message back to the host system containing the name of the error and the token that triggered it: %%[ Error: typecheck; OffendingCommand: if ]%% Printer management software should be prepared for any kind of text that may be sent back from the printer. Here is a reasonable approach for dealing with these messages: If the text is thought to be an error message, it should be communicated back to whatever software submitted the job to be printed, or displayed for the user to see. If the text is not an error message, then it should be compared to a list of known standard messages that may be issued by that printer (as listed in the Printer Description File). Some of these may safely be ignored. If the text is not recognized, it should be communicated back to the application which submitted the job, and/or saved in a disk le and made accessible to the user. Under many circumstances, a disk le containing everything that the printer sent back to the host may be appropriate, both as an error log and as a way to extract specic bits of information from the printer.
175
There are messages which are more or less standard depending on the physical capabilities of the printing device. For example, following are a number of relatively common (self-explanatory) messages. These are all very device-specic, and the Printer Description File should be consulted for a precise list of them for a given printer, as in the following list: %%[ PrinterError: out of paper ]%% %%[ PrinterError: cover open ]%% %%[ PrinterError: warming up ]%% %%[ PrinterError: out of toner ]%% %%[ PrinterError: paper jam ]%% %%[ PrinterError: recorder not responding ]%% These messages are similar in syntax to the PostScript execution error messages, but are only issued when the marking engine is not ready to print a page for some reason. As an aside, it is generally not a good idea to issue the executive operator (available on most devices) while in batch mode. The executive operator is intended for interactive use; it echoes back everything typed, permits simple line editing, and presents a prompt. Batch jobs are not guaranteed to run precisely the same way in executive mode (due to the nature of command-line editing) and it is therefore not recommended for tracking the execution of a le. It is, however, quite handy for typing short PostScript language sequences and observing the results.
176
are made outside the server loop by using exitserver will remain as part of the permanent state of the interpreter for all subsequent jobs. This only applies to changes to VM (like procedure denitions), since the stacks and graphics state are cleared after each job. Note: The exitserver operator is a system-level command. It is not intended for general use by applications programs. Using exitserver is forbidden in conforming documents. See section 10.1 for further information on conformance specications. The exitserver mechanism can be used to download fonts or procedure bodies that will stay resident in the printer until the device is powered off. exitserver also is required for changing any of the persistent parameters such as communications protocols or default conditions (such as the default job timeout value). Using exitserver initiates a new PostScript job. It should always be the rst line of code in the program. The condition of being outside the server loop persists until the next end-of-le indication is read by the interpreter. Any changes made to the state of VM during the job will remain until the interpreter is restarted. Here is the necessary PostScript language sequence to exit the server loop: serverdict begin 0 exitserver The exitserver operator resides in the serverdict dictionary, and this dictionary must be made available in order to execute exitserver. It is not necessary to use end to remove it from the dictionary stackwhen the new job is started the contents of all of the stacks are cleared. In this example, 0 is placed on the operand stack as the password required by exitserver. This password generally may be changed by using the setpassword operator.
177
%%DocumentFonts
The %%DocumentFonts comment is used in the header section of the comments (or it may be deferred to the trailer). It should give a list of all fonts that are used by the document (if a findfont is performed on the font name anywhere in the document, it should be in this list). Here is an example of its use (notice the %%+ syntax for continuation; the lines should not exceed 255 characters in length): %!PS-Adobe-2.0 %%DocumentFonts: Courier Times-Roman %%+ StoneSerif %%+ StoneSerif-SemiboldItalic %%+ Optima %%EndComments This comment may be used rst to determine if there are any font needs that are not resolved within the document. If all the fonts in this list are determined to be resident in the PostScript device, no further attention is necessary.
178
%%IncludeFont
This comment can appear anywhere within the body of a PostScript document le. It is a resource request. The named font should be inserted at exactly the point in the le that the comment is found (if it is determined that the font is not resident; otherwise %%IncludeFont can be ignored). The list of fonts for which the %%IncludeFont comment is used is made available in the documents header comments section through the comment %%DocumentNeededFonts. This has the same syntax as the %%DocumentFonts comment (see listing 12-1).
listing 12-1
%!PS-Adobe-2.0 %%DocumentFonts: Courier Times-Roman %%+ StoneSerif %%+ StoneSerif-SemiboldItalic %%+ Optima %%DocumentNeededFonts: Optima %%+ StoneSerif %%EndComments %%EndProlog %%Page: 1 1 save %%IncludeFont: Optima restore %%Page: 2 2 save %%IncludeFont: Optima %%IncludeFont: StoneSerif restore %%Trailer
These comments are directives. If they name fonts that are determined not to be resident in the printer, then the requests need to be satised, or the document will not print successfully.
179
%%BeginFont, %%EndFont
When a downloadable font le is embedded in a document le (either before it reaches the print spooler, or by the spooler itself), the font should be bracketed with %%BeginFont and %%EndFont comments. The %%DocumentSuppliedFonts comment should be used to indicate the names of any fonts supplied within the body of the document: %!PS-Adobe-2.0 %%DocumentFonts: Sonata %%DocumentSuppliedFonts: Sonata %%EndComments %%EndProlog save %%BeginFont: Sonata % Sonata font le is embedded here %%EndFont restore %%Trailer The le should execute correctly without any attention by the print spooler, since the font is supplied within the document. However, there are a few ways in which the spooler might benet by using these comments: The fonts may be collected and stored in the spoolers font area for later use with %%IncludeFont comments. The fonts may be removed from the print stream if they are determined to already be resident in the printer. However, the fonts should always be replaced by an instance of %%IncludeFont so that it is reversible. Whenever a font is added or deleted from a print le, the comments should be adjusted accordingly. %%IncludeFont and %%BeginFont/%%EndFont are essentially inverses of one another (each should be replaced by the other, as appropriate). There are exactly analogous comments and operations dened for les. Details on these can be found in the specication of the PostScript Document Structuring Conventions.
180
listing 12-2
%!PS-Adobe-2.0 ExitServer %%EndComments /$timestamp where { %if pop (time is: ) print $timestamp = flush stop % stop the current job } if %%BeginExitServer: 0 serverdict begin 0 exitserver %%EndExitServer /$timestamp (Tue Oct 27 17:53:43 PST 1987) def %%Trailer %%EOF
A good approach to managing the list of currently available fonts is to query the printer for the initial list of fonts, and to keep that list available. If a font is downloaded to the printer, the list should be updated. If the printer is restarted, the list should be re-generated. Noticing that the printer has restarted can be accomplished by placing a time stamp in the printer, then
181
checking for it each time a new connection to the printer is opened (listing 12-2).
182
task. Beyond the core of the PostScript language, there are many differences between one product and another. The most important of these differences are formalized in Printer Description Files (available from printer vendors and from Adobe Systems). These les contain a list of the features of the printers and the necessary PostScript code to invoke them properly. Here are the kinds of features which are supported through these les: Paper Sizes. Printers may support various different paper sizes, paper trays, and even continuous-feed rolls of paper, which can virtually be any size. These are detailed in Printer Description les by keywords indicating the paper size. There is also a great deal of information on the dimensions and imageable regions of each paper size. Resident Fonts. A list of all the fonts that come standard in the printer, including the version number and whether or not it has a standard encoding vector. Query Strings: these are miniature PostScript programs that will query the device for the presence or state of many of the features found in the le (for example, whether a particular font is available, or the current paper tray). Available Memory. This provides the amount of memory available in the initial conguration of the printer. Special Resources: this includes the availability of a resident le system, variable paper sizing, the default exitserver password, and others. The details on the format of Printer Description Files are kept in a separate document entitled PostScript Printer Description Files, which is available from Adobe Systems.
183
There is typically anywhere from about 200 kilobyes to several megabytes of usable VM after the interpreter is initialized. The vmstatus operator can be used to determine the amount of free memory in the interpreter.
185
For example, each of the following allocates 80 bytes of memory in a current implementation: 80 string 4 dict
186
10 array [ 1 2 3 4 5 6 7 8 9 10 ] true { %if exch pop 3 -1 roll exch show 0 10 rmoveto } if It is good to understand the ways in which memory is allocated in order to be able to conserve it. It is not generally true that you can rst get a program to work and then go back and add some kind of memory management. Since save and restore are the mechanism for controlling memory use, and since they affect more than just the use of memory, this must be designed in from the start.
SAVE OBJECTS
The save and restore mechanism works in a somewhat unusual fashion. The save operator produces what is known as a save object. This object is like a coat check couponin order to get your save level back, you must present the save object to the clerk. A save object contains a generation number that is assigned sequentially. It is possible to restore to any save level by pre-
187
senting the appropriate save object to the restore operator, but restoring level 1 makes levels 2 and 3 no longer valid (for obvious reasons). Since save produces an object and restore requires one, they can be used somewhat like gsave and grestore. However, the save object must stay on the operand stack: save count 1 eq { (save object is on stack) = } if restore Since the save object must be presented to restore, it is usually better to store it in a dictionary than to leave it on the operand stack for an extended period of time: /saveobj save def count 1 eq { %ifelse (save object is on stack) = }{ %else (save object is NOT on stack) = } ifelse saveobj restore The sequence /saveobj save def seems a little unusual at rst. Since save produces an object on the stack, and since /saveobj is just a name object resting on the operand stack, it is a simple denition. The save does not affect the operand stack, and since the name object is created before the save, it does not affect the state of VM that is saved. The object is then retrieved by that name when it is needed later.
188
posite object on one of the stacks (operand stack, dictionary stack, or execution stack) that points to a value in memory somewhere that would be reclaimed by the restore operation. Since restore does not affect any of the stacks, it is considered to be an error condition for an object there to have its composite value removed from under it. The invalidrestore error can be prevented simply by never leaving a composite object on any of the stacks that was created more recently than the last save level (at least, dont have it on one of the stacks if you are about to do a restore). The most common of these, actually, is to leave a dictionary on the dictionary stack. For example, the following PostScript fragment provokes the invalidrestore error: save /mydict 28 dict def mydict begin restore It is difcult to get a composite object onto the execution stack that would cause an invalidrestore, but it is possible. Since the most likely object to nd on the execution stack is a procedure body, this is the place to look (particularly innocent procedure bodies like those presented to ifelse). The following example provokes an invalidrestore error because the remainder of the procedure body in which the restore is found is still on the execution when restore is executed, but it was created more recently than the save object. save Ypos 36 lt { restore showpage } if The only way to prevent this is to dene the procedure before the save is executed, or not to execute restore from within the procedure. For example, the following short procedure denition xes the problem: /restorestate { Ypos 36 lt { restore showpage } if } bind def save restorestate
189
190
less space than standard arrays. A packed array is read-only. All array operators other than put and putinterval will work with packed arrays. In general, sequential access to packed arrays is as fast as with normal arrays, but random access is slower. Packed arrays are typically about 50 to 75 percent smaller than their unpacked counterparts. Packed arrays are perfectly suited for procedure bodies, which are read-only arrays. A typical way to use packed arrays is to turn the feature on at the beginning of the prologue denitions, and turn it back off at the end of the prologue, so that all the procedure bodies created in the prologue will be stored as packed arrays, saving space: %!PS-Adobe-2.0 %%EndComments /setpacking where { /currpack currentpacking def pop true setpacking } if /prologue { packed array procedures } bind def /setpacking where { pop currpack setpacking } if %%EndProlog %%Trailer All procedure bodies constructed while packing is set to true will be packed arrays. There is also an operator called packedarray which is analogous to the array operator, except that it constructs a packed array object instead of an array object. This is a more specic operator that can be used to allocate a single packed array object; the setpacking operator is more appropriate for general use.
191
default frame buffer in place for each device. The framedevice operator takes a specied width and height and allocates the entire chunk of memory necessary for rendering that page size. It is not a recommended operator, however. The amount of memory required for a frame buffer can be determined easily. One byte of memory contains 8 bits of information, each of which is used as a pixel in device space (unless it is a grayscale or color device, in which case there may be many bits per pixel). An 8-1/2 inch by 11 inch frame buffer on a 300 dotper-inch printer requires the following amount of memory: 300 (8.5 x 11) 8 This is 1,051,875 bytes of memory (just over one megabyte). Changing page sizes therefore can affect the amount of usable memory that is available for a user job.
192
ing, and closing le streams present in all implementations of PostScript interpreters. Many PostScript devices do not have a le system available to them directly (unless there is a disk and le system built into the printer, for instance). However, the input stream and output stream are treated as le objects, and the le operators may be used to read and write these streams.
listing 13-1
%!PS-Adobe-2.0 %%Title: currentfile example /buff 128 string def /F { findfont exch scalefont setfont } def %%EndProlog 12 /Helvetica F 10 10 moveto currentfile buff readline This line is actually data pop show showpage %%Trailer
The interpreter scans and executes the currentfile operator, which produces a le object and leaves it on the operand stack. The interpreter then executes buff, which places a string object on the stack. Then readline is scanned and executed.
193
The le pointer for the input stream is left at the point just after the readline token. When readline is executed, the le object from which it reads is the one left by currentfile (the standard input stream). The readline operator picks up exactly where the interpreter left off, and reads one line of input (In this case, it reads the string This line is actually data into the input buffer buff). When readline nishes, it has advanced the le pointer to a position just after the line of data, and the interpreter picks up where readline left off. The interpreter then scans and executes show, which prints the data string at location 10 10 on the current page. This technique of reading from the current input stream is used in many ways. It can be used by a printer emulator to read data intended for a different printer. It may be used to read bitmap data for the image operator directly from the input stream (using readhexstring rather than readline). Listing 13-2 contains a short program that reads a font program from the current le and write it to a disk le, assuming that a le system is available to the PostScript interpreter. The le name needs to be changed for each different font downloaded (this is done by a front-end program). Since the program reads through end-of-le, it must be in a job by itself. Subsequent jobs can then access the font from the le system simply by calling findfont. This example uses the font from listing 9-3.
listing 13-2
%!PS-Adobe-2.0 %%Title: writetodisk.ps %%Creator: Glenn Reid %%EndComments /buff 128 string def /fd (fonts/Logo-Font) (w) file def { %loop currentfile buff readstring { %ifelse fd exch writestring }{ %else dup length 0 gt { %ifelse fd exch writestring }{ %else
194
pop } ifelse fd closefile exit } ifelse } bind % file should follow the "loop" token.... loop %%BeginFont: Logo-Font 1.0 0 12 dict begin % leave one empty slot for FID /FontName /Logo-Font def /FontMatrix [ 1 0 0 1 0 0 ] def /FontType 3 def /FontBBox [ -5 -12 85 50 ] def /BuildChar { %def exch begin 70 0 -5 -12 85 50 setcachedevice Encoding exch get load exec end } bind def /Encoding 256 array %def dup 0 1 255 { /.notdef put dup } for pop dup 71 /logo put % "G" character def /logo { %def % draw at current point 20 0 moveto gsave /Times-Roman findfont 48 scalefont setfont (G) show -13.5 -14.5 rmoveto (R) show grestore -4 -8 rmoveto /Helvetica findfont 11 scalefont setfont (glenn) show 22 3 rmoveto (reid) show } bind def currentdict end dup /FontName get exch definefont pop %%EndFont: Logo-Font
195
14.1 INTRODUCTION
Programs written in the PostScript language are extremely portable. They are high-level source code intended to be executed on any number of different PostScript interpreter, even if the software that produces the program cannot physically be hooked up to the particular device. This chapter addresses some tactics for making PostScript programs as robust as possible. It does not specically address printer differences and portability issues, which are dealt with in other chapters. The focus is placed instead on the error handling capabilities of the PostScript language and how this mechanism can be used to advantage in applications programs.
14.2 STRATEGIES
The kind of errors that can be caught by an error handling strategy are typically execution errors. Assuming that the program produced by the application has been debugged to a reasonable degree, there are only a few possible failure modes which can arise in the PostScript language environment.
NON-STANDARD OPERATORS
One possible source of error is to make reference to a specialized operator that takes advantage of a feature that may not be present in all implementations of the PostScript interpreter (typically giving rise to the undefined error). For instance,
197
invoking a printer-specic feature like manual paper feed or a particular paper tray will not work on a printer which does not offer that feature. A simple mechanism to avoid this is to use the known operator to check for the existence of the operator before you use it: statusdict /ledgertray known { %ifelse statusdict begin ledgertray end }{ %else 90 rotate 0 -612 translate 612 792 div dup scale } ifelse This tests to see if the ledgertray operator exists in statusdict. If it does, it is invoked. If it does not, then the standard letter-size page is placed into landscape mode and scaled down to simulate the layout of ledgertray. The where operator may also be used in a similar fashion.
198
especially high resolution. The next section describes the use of the stopped operator, which may be used to catch this kind of execution error. Another error which can arise when implementation limits are exceeded is VMerror. This means simply that the PostScript interpreter has run out of memory. Unfortunately, this error is not always caught by all versions of the PostScript interpreter (depending on what was being executed when it ran out of memory). One symptom of exceeding the memory of a PostScript device may be that the interpreter will restart without explanation. VMerrors can be avoided by proper use of save and restore.
gure 14.1
199
object). It copies that object to the execution stack (like exec) and executes it. The stopped operator determines whether or not stop was executed, places a boolean on the top of the stack, and returns control to the interpreter. Listing 14-1 is an example of using stopped to catch a limitcheck operation on a flattenpath operation.
listing 14-1
%!PS-Adobe-2.0 %%Title: stopped-example %%EndComments /STROKE { %def currentflat { %loop { flattenpath } stopped { %ifelse currentflat 1 add setflat ("limitcheck" on "flattenpath": ) print (trying again with flatness of ) print currentflat ( ) cvs = flush }{ %else exit } ifelse } bind loop stroke setflat % restore original flatness } bind def %%EndProlog %%Page: 1 1 % build a complicated path: 0 1 30 { %for 12 exch 2 mul translate 20 0 moveto 0 0 20 0 360 arc } for STROKE showpage %%Trailer
This program will loop until the flattenpath operation succeeds. When it fails, the atness is increased slightly and the
200
procedure tries flattenpath again. This approach can be used to catch any kind of errors, although it is complicated enough that it is only encouraged under some circumstances. Another use of the stopped operator is to catch possible execution errors in included PostScript les. For instance, if your application contains a feature for including arbitrary PostScript les from other applications (or potentially hand-written code), it might be worthwhile to trap the errors and take some suitable action. A procedure called userexec is dened in listing 14-2 that will execute any object in the stopped context and return control to the procedure when it has nished (even if it terminates with an error).
listing 14-2
%!PS-Adobe-2.0 %%Title: userexec.ps %%Creator: Glenn Reid %%EndComments /buff 512 string def /p /print load def /bp { buff cvs print } bind def /jobdef { %def errordict begin /jobname exch def end } bind def /userexec { %def stopped { %ifelse $error /newerror get { %if $error begin (%%[Error: ) print /errorname load bp (; OffendingCommand: ) p /command load bp (; JobName: ) p errordict /jobname known { %ifelse errordict /jobname get }{ (stdin) } ifelse bp (]%%\n) print flush end
201
{ %loop currentfile buff readline { %ifelse (%ENDuserexec) eq { exit } if }{ %else /UnexpectedEOF % report error errordict /rangecheck get exec } ifelse } loop } if % if newerror is true }{ %else % end of file must have been reached. Is this an % error? Sometimes it means the improper use of % "currentfile readstring" and/or "image." } ifelse $error /newerror false put } bind def %%EndProlog (ORIGjob) jobdef % execute some included job directly in-line: /svobj save def (USERjob) jobdef currentfile cvx userexec %%BeginDocument: included.ps %!PS %%Title: included.ps %%For: triggering an error /var1 10 def /var2 20 def %%EndProlog %%Page: 1 1 var1 var2 triggererror more program statements which should be ignored showpage %%Trailer %%EndDocument % should never get this far: (USERjob completed.\n) print flush stop %ENDuserexec % here is where ORIGjob picks up again svobj restore % will restore ORIGjob name, too (Continuing with ) print errordict /jobname get print (\n) print flush %%Trailer
202
When an error is encountered, the userexec procedure must ush the rest of the included illustration. This is done by searching the rest of the le for a line containing only the string %ENDuserexec. This string would have been inserted at the end of the included code by the document composition software. The search is accomplished in this case with the readline and eq operators.
203
The job server loop contains a standard error handling mechanism that looks for and invokes another standard procedure called handleerror (it expects to nd it in errordict) whenever the stopped operator returns true. The handleerror procedure produces the familiar error message (by looking in $error for the information stored there by the error operators): %%[Error: limitcheck; OffendingCommand: flattenpath]%% See the next section for an example of how to redene the handleerror procedure.
listing 14-3
%!PS-Adobe-2.0 %%EndComments errordict begin /*limitcheck /limitcheck load def /limitcheck { %def (Free VM: ) print vmstatus exch sub == flush pop *limitcheck } bind def end %errordict %%Trailer
204
This procedure produces a message back to the host computer indicating the amount of free memory in the printer when the limitcheck error occurred, and then invokes the original limitcheck procedure as well (it has been saved under the name *limitcheck in this example). In listing 14-4 is an example redenition of the handleerror procedure that produces a stack trace back to the host system when a PostScript error is triggered:
listing 14-4
%!PS-Adobe-2.0 %%Title: errstack.ps %%EndComments %%EndProlog %%BeginSetup /orighandleerror errordict /handleerror get def errordict begin /handleerror { %def orighandleerror errordict begin $error begin (STACK:) = flush currentdict /ostack known { ostack aload pop pstack flush clear } if end end } bind def end %%EndSetup %%Trailer
This procedure calls the existing error handler, and also returns a readable representation of the operand stack back to the host system.
205
206
15.1 INTRODUCTION
Since the PostScript language is interpreted, it is fairly easy to debug programs written in the language. However, the fact that the programs typically are executed on a remote processor (for example, in a laser printer) can make the process a little more difcult. In this chapter, it is assumed that the working environment consists of a host computer connected through some kind of communications interface to a printer containing a PostScript interpreter. There are other possible situations, but this is a common environment and one in which the debugging issues are perhaps the most difcult.
207
Since a large part of debugging consists of trying to determine what really happened, establishing solid communications is always a good rst step. This may consist of purchasing or writing a downloading utility program, or it may mean implementing the communications layer of your application rst.
SERIAL COMMUNICATIONS
There are three main aspects to setting up good serial communications: Get the cable congured correctly. Get ow control working properly. Be prepared to receive information at any moment from the PostScript device. Flow control may be implemented in software (the XON/XOFF protocol for serial connections, or a network protocol such as AppleTalk) or it may be handled by the hardware (the DTR/ DSR protocol, for example). Most PostScript devices are initially congured for software handshaking, and need explicitly to be set to handled other kinds of protocols. The following is an example that will work on most PostScript devices with serial communications (please consult the particular printer manual for possible differences): serverdict begin 0 exitserver statusdict begin 25 9600 4 setsccbatch end In this example, 25 refers to the 25-pin connector (as opposed to the 9-pin connector, for example). 9600 is the baud rate of the communications. 4 is a number from 0 to 7 that represents a combination of parity setting and the handshaking mechanism. Values from 0 to 3 use software handshaking (XON/XOFF), and values from 4 to 7 will turn on hardware handshaking instead (which may not be available on all devices). The setsccbatch operator establishes the default serial (scc) communications for the given port in batch mode (as opposed to interactive mode).
208
PARALLEL COMMUNICATIONS
Parallel ports are not available on all PostScript devices. Parallel communication ports in general do not support bi-directional transmissions, so if the parallel port is used, some special care must be used to receive any messages. Some devices may allow you to open the serial port explicitly to send information back to the host, but you have to know about it and it is not supported on all devices (please consult the documentation for the individual printer for more specic information). Parallel communications probably are not the best mode to use for general debugging of PostScript programs.
209
%%[ Error: typecheck; OffendingCommand: moveto ]%% %%[ Error: stackunderflow; OffendingCommand: moveto ]%% Either too few items are available on the operand stack (causing stackunderflow) or those items are not numbers (causing a typecheck). Its that simple. Understanding why some PostScript error occurred usually requires a certain amount of context. For instance, it is helpful to know some of the following things: What items were on the operand stack at the moment the error occurred? At what point in the input le did the error occur? What dictionaries were on the dictionary stack? What was the current value of some variable or aspect of the interpreters state? Probably the most useful of these is to determine what objects are on the operand stack. The contents of the stack usually provide all the information that is necessary to establish what the context of the error was (although a stackunderflow error is certainly an exception to this, since there is nothing on the operand stack). See Section 15.5 for more details on getting stack traces.
ERROR: UNDEFINED
One of the more common errors in PostScript programming is the undefined error. This error is generated by the PostScript interpreter if a name lookup fails or if an explicit get, or load cannot nd its argument in the context of the current dictionary stack. Here are some possible causes of the undefined error: The name is spelled wrong. PostScript names are case sensitive. This is probably the most common problem.
210
A procedure name was used before it was dened. This sometimes results when procedures are inadvertently moved in the le, or when the slash is missing from a name (see Section 2.4). If the name given as the OffendingCommand appears to be only part of one of the names in your program le, this may mean that the communications are not solid. If part of the le is lost on its way to the printer, the interpreter may pick up in the middle of a token, and give an error. Check especially for ow control problems. If the get operator is being used at all in the program, perhaps its arguments are in the wrong order or just incorrect. Oddly, the OffendingCommand will not be get, but will be the key that is passed to get. If the name being looked up is in a special dictionary (for instance, statusdict, serverdict, or a local dictionary dened by the program), that dictionary may not be on the dictionary stack. Automatic name lookup works only in the context of the dictionary stack. Use the begin operator to push dictionaries onto the dictionary stack. ^D: A control-D character (decimal ASCII 04), is the end-of-le indication over most serial connections. However, it is not an end-of-le over networks like AppleTalk. These should be removed from the le stream. Actually, they should never be put there to begin with. (See Section 12.3.) If the byte gets through to the PostScript interpreter (across AppleTalk, for instance) it will provoke an undefined error.
ERROR: TYPECHECK
This is probably the most common error in PostScript programming. Typically it results from the incorrect manipulation of objects on the operand stack. Sometimes this is a result of not realizing that a given PostScript operator might return something to you on the stack. Here are some common causes of the typecheck error:
211
stringwidth returns both an x and a y value. For most fonts, the y component is 0. If this value is not needed, it should be removed from the stack with the pop operator. Very often an extra integer turns up on the stack from forgetting how the stringwidth operator works. Some operators require several operands. Be careful to supply all of them, and in the correct order. There is a difference between an integer and a real number. Make sure you supply each operator with the correct type. This is especially important with the idiv operator, since early versions of the PostScript interpreter graciously permitted real operands as arguments when they should not have. Do not supply an integer where a boolean is required. 0 and 1 are not equivalent to true and false, which are genuine PostScript objects of type booleantype. Use the pstack operator to help you debug problems with stack manipulation. Sometimes the roll or exch operators can help when operators are in the wrong order, although typically it means that the operands should have been transmitted in a different order.
212
are not available. Placing debug begin at the beginning of a document (and end at the end, of course, if the program gets that far) will cause the le to execute with these helpful debugging procedures available.
listing 15-1
%!PS-Adobe-2.0 %%Title: pathtrace.ps %%Creator: Glenn Reid %%EndComments /debug 10 dict def debug begin /str 128 string def /*moveto /moveto load def /*lineto /lineto load def /moveto { %def (moveto: ) print exch dup str cvs print % X-val ( ) print exch dup == % Y-val flush *moveto % execute real moveto } bind def /lineto { %def (lineto: ) print exch dup str cvs print % X-val ( ) print exch dup == % Y-val flush *lineto % execute real lineto } bind def end %%EndProlog debug begin 100 100 translate 100 100 moveto 200 200 lineto stroke showpage end %%Trailer
213
214
will not echo back onto your screen as you type it, but the characters will be received by the PostScript interpreter. If you type it correctly, you will see a brief message and a PS> prompt. (If you dont, you will get an error.) From that point on (through the next end-of-le), you will be in interactive mode. PostScript language commands received by the interpreter in interactive mode will be interpreted in the same way that batch les are interpreted, including executing procedures, printing characters, using showpage; the whole language is at your ngertips.
215
The relationship between rotate and scale is not a commutative one. That is, you cannot reverse the order of evaluation and get the same result. There is a further discussion of coordinate system transformations in Section 9.4.
listing 15-2
%!PS-Adobe-2.0 %%EndComments /msg { %def print (\n) print flush } bind def %%EndProlog %%Page: 1 1 (Got past the prologue.) msg %%Trailer (made it to the trailer) msg
216
appendix A
217
/== { /cp 0 def typeprint nl } def /typeprint { dup type dup currentdict exch known {exec}{ unknowntype }ifelse } readonly def /lmargin 72 def /rmargin 72 def /tprint { %def dup length cp add rmargin gt { nl /cp 0 def } if dup length cp add /cp exch def prnt } readonly def /cvsprint { =string cvs tprint ( ) tprint } readonly def /unknowntype { %def exch pop cvlit (??) tprint cvsprint } readonly def /integertype { cvsprint } readonly def /realtype { cvsprint } readonly def /booleantype { cvsprint } readonly def /operatortype { (//) tprint cvsprint } readonly def /marktype { pop (-mark- ) tprint } readonly def /dicttype { pop (-dictionary- ) tprint } readonly def /nulltype { pop (-null- ) tprint } readonly def /filetype { pop (-filestream- ) tprint } readonly def /savetype { pop (-savelevel- ) tprint } readonly def /fonttype { pop (-fontid- ) tprint } readonly def /nametype { %def dup xcheck not { (/) tprint } if cvsprint } readonly def /stringtype { %def dup rcheck { %ifelse (\() tprint tprint (\) )tprint }{ %else pop (-string- ) tprint } ifelse } readonly def /arraytype { %def dup rcheck { %ifelse dup xcheck { %ifelse ({ ) tprint { typeprint } forall ( }) tprint } { %else ([ ) tprint { typeprint } forall ( ]) tprint } ifelse }{ %else pop (-array- ) tprint
218
appendix A
} ifelse } readonly def /packedarraytype { %def dup rcheck { %ifelse dup xcheck { %ifelse ({ ) tprint { typeprint } forall ( }) tprint }{ %else ([ ) tprint { typeprint } forall ( ]) tprint } ifelse }{ %else pop (-packedarray- ) tprint } ifelse } readonly def /courier /Courier findfont 10 scalefont def /OLDhandleerror errordict /handleerror get def end %$brkpage /handleerror { %put systemdict begin $error begin $brkpage begin newerror { %ifelse /newerror false store vmstatus pop pop 0 ne { grestoreall } if initgraphics courier setfont lmargin 720 moveto (ERROR: ) prnt errorname prnt nl (OFFENDING COMMAND: ) prnt /command load prnt $error /ostack known { %if nl nl (STACK:) prnt nl nl $error /ostack get aload length { == } repeat } if systemdict /showpage get exec /newerror true store /OLDhandleerror load end end end exec }{ %else end end end } ifelse } dup 0 systemdict put % replace name by actual dict object dup 4 $brkpage put % replace name by dict object bind readonly errordict 3 1 roll put % put proc in errordict as /handleerror
appendix A
219
INDEX
Non-Alphabetic
%!PS-Adobe- 91 %%BeginFont 166, 182 %%BoundingBox 167 %%DocumentFonts 166, 181 %%DocumentNeededFonts 166, 181 %%DocumentSuppliedFonts 166 %%EndFont 166, 182 %%IncludeFont 166, 181 %%Page 168 %stdin 59 conceptual error 95 conforming files 91, 98, 159 context graphic 78 context dependencies 91 coordinate system 69, 217 coordinate systems 77 copypage 146 curly brace 26 current dictionary 18 current file 59 current font 49 current path 43, 49 current point 75 currentfile operator 59, 171, 195
A
addprocs 31 Adobe Illustrator xii AFM files 103, 104 arc operator 85 array 188 arrowhead procedure 46 ascender 111 ashow 102 attributes 15 awidthshow 70
D
data acquisition procedure 126, 128 data compression 133 data transmission time 81 data, program-generated 90 debugging 209 defaultmatrix 79 defaults coordinate system 92 current font 93 current page 92 current path 93 default context 92 dictionary stack 93 error handler 208 exitserver password 179 deferred execution 26 definefont 192 descender 111 device space 217 dictionary 2 dictionary stack 213 disk user partition 194 disk management 194 displays 173 Downloadable fonts 192 driver 79
B
band buffers 193 batch mode 11 baud rate 210 begin operator 213 bi-directional communication 209 bind 28 bounding box 104, 174
C
capital height 111 case sensitive 212 character metric 102 character set 116 character width 103 character widths 51, 102 circle procedure 46 clip operator 53, 140, 200 clipping region 140 comments, structure 91, 98, 159 communications and spoolers 176 packet network 211 parallel 211 serial 210 composite characters 119 computation 81 computation in PostScript 73 concat 151 concatenating procedures 31
E
efficiency 11, 81, 83, 85 calculations 80 imaging model translation 79 Einstein, Albert 1 emulator 57 escape sequences 59, 61 fonts 57 line printer 57 using search 59 using stringwidth 61
INDEX
223
encoding vector 117 end-of-file 34, 176, 179 error execution 199, 211 tactics 199 typecheck 213 error handler 207 error handling mechanism 205 error messages 209 error procedures 206 errordict 206 escape character 61 escape sequences 63 exec operator 31 executable array 27 executable name 20 execution environment 93 execution error 199 execution stack 32, 34 execution, deferred 26 executive operator 178, 216 exitserver 178
graphics problem solving 139 graphics state 92, 189 gray shades 133 grestoreall 79 grids 148 ground state 92
H
halftone angle 133, 134 cells 134 device dependencies 139 frequency 133, 134 pattern fills 139 pixel priority 137 setscreen operator 134 spot function 133, 134, 137 halftoning 125, 133 handleerror 206, 216 high resolution devices 200
F
features 160 file formats, existing 68 file object 32, 34, 195 file system 194 flattenpath 200 font cache 55 font management 183 FontDirectory 192 FontMatrix 192 fonts accented characters 119 and emulators 69 ascender height 111 capital height 111 character bounding box 104 character widths 61 descender height 111 dictionaries 192 downloadable 192 encoding 116 handling different 108 in emulators 57 leading 111 memory use 192 metrics 103 non-Roman 119 point size 111 screen fonts 103 substitution 70 x-height 111 Frame buffers 193 framedevice 193 functional independence 78
I
illustrations 170 images bits per pixel 126 data acquisition procedure 126 image matrix 126 matrix 126 parameters 125 premature end-of-file 131 scanned 127 synthetic data 131 using image operator 127 image operator 125 imaging model 37, 57, 71 designing the driver 77 efficiency 79 immediate execution 27 implementation limits 200 income tax form 145 indentation xiii index 221, 225 initgraphics 79, 93 input stream 32, 195 integration 169 interactive PostScript 216 interpretation overhead 81 interpretation time 85 invalidpassword 178 invalidrestore error 190, 191
J
job 34 job execution 11 jobname 173, 203 justified text 62, 70
G
get operator 212 graphic design 101 graphic imaging model 71 graphics internal representation 38
K
kerning 101, 106, 112 kernshow procedure 114 kernstackshow procedure 114 key-value pair 28 kshow operator 113
224
INDEX
L
landscape mode 79 leading 111 ledger paper 161 ledgertray 161 legibility 107 letter spacing 106 ligatures 116 limitcheck 53, 140, 200 line weight 148, 149 line wrapping 74 literal name 20 load operator 212 logotypes 145
M
machine-generated code 79 margins 101, 104 matrix operations 151 memory array object 192 strings 188 VMerror 201 memory, virtual 187 merging files 169 messages, printer 177 modularity 92, 93 graphic modularity 95 save and restore 95 moveto 105
N
name lookup, automatic 20 names binding 28 nesting, program file 170 newpath 38, 49
O
object properties 96 objects access attribute 15 attributes 15 composite 13 executable 15 length 15 literal 15 simple 13 type 15 OffendingCommand 211 operand stack 13, 94, 212 operators 19, 21 operators, non-standard 199 optimization 73 rmoveto vs. widthshow 73 output stream 195
page layout 77 page, top of 78 pages page independence 90 painting operators 38 paper sizes 161 parallel processing 146 parameters 94 pasteup 169 path construction 38 paths 37, 38 pattern fills 139 pattern font 140 performance 85 pixel locking 149 Poe, Edgar Allan 105 point size 111 PostScript Document Structuring Conventions 98 PostScript error 211 PostScript Interpreter 3 PostScript Printer Description Files 160 preview 174 print flush 218 print format translator 57, 68 printer emulator 57, 196 printer management 176 problems 139 procedure 25, 145 procedure bodies xiii procedure body 26 procedure definitions 90 procedure sets 89 procedures concatenating 31 designing prologue 80 short names 82 very large 29 program design 77 programming style xii prologue 89 related to script 80 prologue procedures 80 properties, object 96 pstack operator 214
Q
queries, printer 164 query 164 font 183 query comments 164 queueing 175
R
raster devices 72 rasterization 54, 193 readhexstring 130, 196 readline operator 59, 195 readstring 59 rectangle procedure 44 redefining operators 214 restore 189 and execution stack 191 and the dictionary stack 191
P
packed arrays 192 page element 78 page elements 89 page independence 78
INDEX
225
S
sampled images 125 save save objects 189 versus gsave 190 save & restore server loop 34 save & restore 52, 54, 188, 189 scale 151 scan time 82 scanner 23 strings 23 screen fonts 103 script 89 machine-generated 80 search 59, 61 server loop 34, 178 serverdict 179, 213 setcharwidth 55 setsccbatch 210 setscreen 134 show 49, 62, 102, 105, 117 showpage 54, 172 sidebearings 103 spooler 175 stack 4 stack trace 216 stack, data on 44 stack-based 2 stackshow procedure 114 stackunderflow 94, 211, 212 state using existing 79 statusdict 213 stopped context 205 stopped operator 171, 201 string objects 23 string, literal 188 strings 23 stringwidth 52, 61, 108 stroke 148 style ease of editing xii indentation xiii readability xii switching fonts 70 syntaxerror 24 systemdict 214
line length 104 margins 104, 105 token operator 24 tokenization 23, 81 trailer 89 transform 149 transformation matrices 151 transformations 217 translate 151 translator optimization 72 typecheck 94, 212, 213 typography 101
U
undefined 212 units 68 user space 217 user-defined font 147 userexec procedure 203
V
variables 15 virtual memory 187 VM 187 dictionary stack 187 execution stack 187 operand stack 187 userdict 187 VMerror 201 vmstatus 187
W
well-formed 89 where operator 200 widths, character 61, 62, 103 widthshow 70, 73, 102, 107 word spacing 70, 107
X
x-height 111
T
tab character 61 text baseline 49 centered 104 columns 78 flow 104 flush left 104 flush right 104 justified 104 leading 111
226
INDEX
BOOK DESIGN
This book design is borrowed from an earlier design by Robert Ishi for the PostScript Language Reference Manual and was adapted by Glenn Reid to the technology at hand and the unique demands of the book. The type used is entirely from the Stone family, designed at Adobe Systems by Sumner Stone, Director of Typography. Chapter headings are set in Stone Sans Semibold 24 point, section headings are set in Stone Sans 12 point, and the body text is set in 10 on 12 point Stone Serif with Italic and SemiBold. All example PostScript language programs and listings are set in 10 point Stone Informal, using the regular and Semibold weights.