Downloader
Volume Number: | | 4
|
Issue Number: | | 1
|
Column Tag: | | Postscript Programming
|
Overview & Downloader in C
By Nicholas Pavkovic, Perimeter, Chicago, IL
In this article, Im going to present a programmers overview of PostScript. This overview includes comparisons between PostScript and QuickDraw, information about using the stack and a brief explanation of the LaserWriter driver. At the end of the article, Ill present the PAP manager calls (which allow direct access to the LaserWriter over AppleTalk) and C source code for a PostScript downloader.
Overview of PostScript
PostScript is a graphics language thats designed to set type and graphics on high-resolution devices such as laser printers and typesetting machines. In most of its implementations it runs on a dedicated processor thats contained in or connected to a printer. Though these printers vary considerably in their capabilities, PostScript is device-independent, so programs written for one PostScript device will usually run on another.
In addition to its graphic capabilities, PostScript contains a full complement of standard language features (floating point arithmetic, string and array manipulation, etc.) and could conceivably be used for non-graphics applications. It most closely resembles FORTH in its syntax and overall feel (but, thankfully, PostScript carries no social stigma). However, most FORTH systems provide the option of controlling the processor at a very low level and PostScript (because it is device-independent) allows only high-level programming.
PostScripts design shows the influence of SmallTalk. Though it isnt really object-oriented (there is no messaging system and no inheritance), the documentation describes instances of the data types as objects, and the interpreter performs comprehensive type checking. The influence of SmallTalk can probably be traced to PARC, where PostScripts principal designer, John Warnock, worked during the late 70s and early 80s. Since SmallTalk and QuickDraw share common ground, Mac programmers will recognize some PostScript commands.
Terminology
In the PostScript documentation, the built-in commands are usually referred to as operators. In this article, they will be referred to in a number of more familiar ways, such as routines, procedures, functions, etc. The name used doesnt provide any additional information about the operator: an operator referred to as a function wont necessarily return a value. The data used by operators are technically called operands, but I often use arguments and parameters instead. Its good to use the correct PostScript terminology, but unfamiliar terms can make a new language seem even more foreign.
PostScript and QuickDraw
To give you a better sense of PostScripts capabilities, Id like to cover some of the similarities and differences between PostScript and QuickDraw. While reading this section, think of the PostScript function calls as Pascal or C calls in which the arguments precede the routine names. Note that the PostScript interpreter is case-sensitive and all of the built-in commands are written with lowercase letters.
Coordinates, Navigation and the Path
Unlike QuickDraw, PostScript uses a Cartesian coordinate system with the origin located in the lower left hand corner of the page. The x coordinates increase from left to right and the y coordinates increase from bottom to top. In this coordinate system, the basic unit is the point which is 1/72 wide (the width of a pixel on the Macintosh screen). Since PostScript supports floating point, coordinates dont have to be integers.
Like QuickDraw, PostScript uses a pen and positions it with the moveto command. Since the arguments precede the routine, the code to move the pen to position (72, 18) would read 72 18 moveto. To move the pen relative to its current position, use rmoveto. Straight line segments are drawn using lineto and rlineto. These commands are preceded by an x and y coordinate (or offset, if relative movement is involved).
Unlike QuickDraw, line segments are not drawn on the page as the commands are executed. The segments are accumulated into the current path, a hidden data structure that keeps track of the lines drawn on the page. Once youve created a path, you can draw (outline) it using the stroke operator or fill its interior using the fill operator (neither operator takes any arguments). The nature of the fill or the outline depend on the pens characteristics when fill or stroke is called. The PostScript pens state differs somewhat from the QuickDraw pen. The PostScript pen does not have a transfer mode; whatever you draw will cover anything printed underneath it. And though the PostScript pen can be set up to draw with a pattern, its a somewhat tricky procedure. Usually youll use the PostScript operator setgray, which allows you to fill the path with different levels of gray. setgray is preceded by the gray level, a decimal that ranges from 0 (black) to 1 (white).
PostScripts setlinewidth is the PostScript version of QuickDraws PenSize routine; its preceded by the desired line weight (vertical width), which can be fractional. This command doesnt alter the horizontal width of the pen. 0 setlinewidth generates the thinnest line available on the PostScript device youre using. Lines are drawn from the center; half of the lines weight is above the segment specified and half is below.
fill and stroke have one surprising side-effect: they erase the current path and make the current pen position indefinite. Because the pen position is indefinite, youll need a moveto to position the pen after youve used one of these operators. The erasure of the current path causes problems if you want to create a figure thats both stroked and filled. To do so, you must either draw the object twice (fill it once and stroke it the second time) or use the commands gsave and grestore. These commands save and restore the graphics state, which includes the current path and pen state (among other things). gsave fill grestore stroke fills and strokes the current path.
Once youve finished drawing and you want to see your creation, execute showpage. Up to this point, all drawing has been done on a bit image of the page that exists only in memory. showpage transfers the memory image to the physical page and ejects the page.
Setting Text
Before you can draw any text, you must indicate the font that you wish to use. The commands needed to set the font to 18 point Times-Roman are:
/Times-Roman findfont
12 scalefont
setfont
Its a little too early to explain exactly why this works, but I will point out that findfont can be preceded by any valid font name (beginning with a backslash) and scalefont is preceded by the desired font size. If you dont know the correct spellings of the font names, you can find them by executing FontDirectory pop = forall, another piece of code that will have to remain unexplained. Note that PostScript uses a different name for each font style, and certain standard Macintosh styles (Outline, Shadow and Underline) arent standard on the LaserWriter.
After youve set the font, youll need to move into position with moveto. Then use show to draw the text. show is preceded by the text to be drawn, enclosed in parentheses, as in (Hello, world) show. The current gray level will be used to draw the text.
Coordinate Transformations
Before being drawn, every coordinate is transformed by the current transformation matrix or CTM. To modify the drawing space, you can adjust the CTM directly with some general matrix operators, or you can avoid the linear algebra and use the following convenience operators:
x y scale Scales the drawing space
Default: 1 1 scale
rotate Rotates the axes
x y translate Translates the origin to the point (x,y)
Note that these operators affect drawing that is done after they are invoked; they dont affect any text or graphics that have already been drawn.
Whats Missing?
There are a few features that QuickDraw programmers will miss in PostScript.
As mentioned above, there are no transfer modes; youll have to be careful about the order in which objects are drawn and make use of PostScripts clip operator.
Though PostScripts text processing is more sophisticated than QuickDraws, PostScript contains no operators for wrapping text (theres no TextBox equivalent). You can write a simple line-breaking procedure in PostScript, but for reasons of speed youll probably want to do any heavy-duty hyphenation and justification on the Macs processor.
Regions are missing from PostScript, as are most of QuickDraws routines for drawing geometric shapes. In both cases, its not difficult to program functional equivalents.
Visualizing the Stack
Like FORTH, PostScript is a stack-based language. FORTH programmers will find their experience with FORTHs stack very useful when learning to use PostScript.
Assembly language programmers will also benefit from their familiarity with the stack. The stack preparation required by PostScript procedures is conceptually similar to the setup that occurs in assembler prior calling a Pascal function or on entry to a definition routine that follows the Pascal calling conventions. In most respects, however, PostScript and assembler are quite different. The PostScript stack is intelligent in the sense that it keeps track of its elements types and sizes. PostScript relies on it for arithmetic operations that assembler normally handles with registers, and PostScript provides a full set of methods for changing the order of elements on the stack. One thing that PostScript doesnt use its operand stack for is return addresses, so you dont have to worry about accidently branching into a data structure if your calls arent set up correctly.
For programmers used to high-level languages, the stack will seem somewhat cumbersome at first. For example, subtracting 5 from 7 is accomplished with the code 7 5 sub. [If you remember the great HP 65 calculator and its reverse polish notation, then this will appeal to you. -Ed] As each number is read, its pushed onto the top of the stack. The prior contents of the stack are moved downwards. Keeping in mind that PostScript is interpreting the statement from left to right, consider the following visual representation of what happens:
As you can see, the sum is pushed onto the stack after the addends are removed. If youre actually trying this code out and want the result to appear on your screen, type = after the code. = pops the result off the top of the stack and echoes it to the output device (it ends up on your screen).
The way that the stack is used may remind you of a RPN calculator. An associate of mine who is studying PostScript prefers to think of the stack in terms of shifting blocks. Hes cut a large piece of erasable whiteboard into rectangular sections, and he emulates the stack by writing values onto them and shifting their positions. You may not need to go this far to acquire proficiency with the stack, but you will need to retain a mental image of the stack while youre coding. Since many of the simple operators in PostScript dont do much to the stack, its tempting to look at them simply as functions preceded by their arguments and forget that the stack exists. Unfortunately, this approach wont get you far when youre trying to understand PostScripts stack manipulation operators.
dup duplicates the stacks top element:
exch reverses the order of the top two elements:
roll rotates the order of elements on the stack. Its often used to move the bottommost element to the top of the stack:
roll takes two integer arguments. The first argument determines the number of elements that are affected by the rotation. The second argument specifies how many rotations occur and the direction of these rotations. A negative integer will move elements from the bottom of the stack to the top and a positive integer will move elements from the top of the stack to the bottom. The code necessary to accomplish the above rotation would read 3 -1 roll. Three elements are affected by the operation (if there are any elements beneath the third element, theyll remain in place). The -1 causes the elements to rotate once, moving the third element to the top of the stack. If -2 had been the second argument, 9 would have been moved to the top of the stack first and 8 would have been moved to the top of the stack next.
Obviously, rolls can get quite complex. Its frustratingly similar to Rubiks cube: though its easy to get a particular element into place, all the other elements get moved around in the process. To help keep track of the stack, most programmers rely on a standard notation that looks like this:
a b c -- b c
The letters denote elements on the stack. The ones placed before the dash represent the state of the stack before an operation and those following the dash represent the stack state after the operation. The letter farthest to the right represents the element thats on top of the stack. exchs effect on the stack might be described by a b -- b a. For some operations, you may want to use descriptive names instead of letters. The stack effects of add could be written addend1 addend2 -- sum. The PostScript Language Reference Manuals reference section describes each PostScript operator using a variant of this notation. add, for example, contains the description num1 num2 add sum. The dash has been replaced with the name of the operator. A short dash is used to represent an empty stack, as in - stroke -. Stroke takes nothing from the stack and leaves nothing on the stack after execution.
The stack notation can be used to describe the effect of a single instruction or it can summarize the effect of numerous operations. If Im writing a tricky program (or debugging code that I had believed to be simple), Ill sometimes use the notation to describe the state of the stack before and after the execution of each line.
Declarations, Types and Dictionaries
Many conventional languages require you to declare a variables name and type before it can be used. In PostScript, an assignment statement serves as the declaration. The variables type is determined by the value you assign to it. For instance, /counter 1 def defines an integer variable named counter and assigns it the value 1.
In addition to integers, you can assign floating point values (by including decimals), strings (using parentheses as delimiters instead of quotation marks) and arrays (bracketing the elements with [ and ]). Arrays can be heterogeneous -- the first element might be an integer, the second a floating point value and the third a string. This allows the implementation of complex data structures, though youll rarely need them.
Lets examine the variable definition a little more closely. The first token, /counter, consists of the variable name counter preceded by a backslash. When PostScript normally encounters a variable name, it pushes the variables value onto the stack. In this case, however, the backslash indicates that the identifier is to be treated as a name (technically a literal name) and pushed onto the stack as such. After placing /counter on the stack, 1 is placed on top of it and the PostScript operator def is invoked. def transforms /counter into a usable variable. First, it checks PostScripts symbol table to see if the named variable already exists. PostScript stores its symbols and their values in a data structure called a dictionary. If def cant find the variable name in the dictionary, it creates an entry in the dictionary using the supplied name, allocates memory for the data and makes the necessary assignment. A variable can be used in expressions as soon as its been defined.
The code /counter counter 1 add def increments the counter. counter 1 add pushes counters value onto the stack and adds one to it, placing the result on the stack. The surrounding code, /counter def is the same as it was in the original declaration. When def is executed, though, it discovers that a variable named counter already exists and simply assigns the new value to it.
def is also used to define procedures. The syntax is the same, except that the body of a procedure is used instead of a variable value. The body consists of PostScript operators (and their arguments) enclosed by curly brackets. When PostScript is interpreting a program and finds that a name refers to a previously defined procedure, the bracketed instructions are executed.
Until now, weve been speaking of a single dictionary for variable and procedure names. PostScript actually uses multiple dictionaries when searching for an identifier. It searches through a list of dictionaries consecutively until the symbol is found or the last dictionary is searched. The order in which the dictionaries are searched is determined by the dictionary stack. Normally, the dictionary on the top of this stack is the userdict, and (since its first in the list) this dictionary holds the variables and procedures created with def. The systemdict dictionary is underneath userdict on the dictionary stack, and it contains the built-in PostScript operators and the systems global variables. There are a few characteristics of PostScripts dictionaries to keep in mind:
Because of the order of the dictionaries, its possible to override a built-in operator in systemdict by defining one with an identical name in userdict.
Symbols in userdict only remain there for the duration of the job. A program cannot use variables or procedures defined during an earlier download.
Symbols in userdict are global. Theres no way of allocating private local variables and then de-allocating them at the end of a procedure.
Theres no way to remove entries from userdict during the execution of a job.
userdict cannot store an unlimited number of entries.
Efficiency-concious programmers may be tempted to put everything on the stack and move the values into place as needed using the stack operators. This approach will certainly avoid the dictionary overhead, but the code necessary to do this is extremely difficult to write and debug, and the efficiency benefits are often imperceptible. If you dont define variables recklessly, youre not likely to hit userdicts limit (around 200 symbols), and the speed overhead incurred by adding a symbol to the dictionary is minor when compared to the amount of time the LaserWriter takes to place marks on the page.
PostScript in Perspective
To provide a point of reference for programmers accustomed to conventional languages, Id like to compare the following C function, CenterString, to a similar PostScript function. The C version takes a point, a length and a string pointer as arguments. It draws the string so that it is centered on the interval [point.h, point.h+length] at the height point.v. The PostScript version does much the same thing, drawing on the page instead of the screen. Each line of the PostScript code contains a comment (delimited by a %) that details its effect on the stack.
CenterString( StartingPoint, CenterLength, StringPtr )
PointStartingPoint;
intCenterLength;
Str255 *StringPtr; /* A Pascal String */
{
int Width;
Width = StringWidth( StringPtr );
StartingPoint.h = StartingPoint.h +
(CenterLength - Width)/2;
MoveTo( StartingPoint.h, StartingPoint.v );
DrawString( StringPtr );
}
Once again, in PostScript:
/CenterString % x y clen str --
{
dup % x y clen str -- x y clen str str
stringwidth % x y clen str str -- x y clen str stringx stringy
pop% x y clen str stringx stringy -- x y clen str stringx
3 -1 roll % x y clen str stringx -- x y str stringx clen
exch % x y str stringx clen -- x y str clen stringx
sub % x y str clen stringx -- x y str clen-stringx
2 div % x y str clen stringx -- x y str (clen-stringx)/2
% offset = clen-stringx/2
4 -1 roll % x y str offset -- y str offset x
add % y str offset x -- y str offset+x
3 -1 roll % y str (offset+x) -- str (offset+x) y
moveto % str (offset+x) y -- str
show % str -- -
} def
Before we examine the PostScript version, let me point out that its designed to show off some of the stack operators. After youve read through its explanation, Ill present a different version thats less stack-intensive.
Most of the code in CenterString is devoted to arranging the stack so that everything is in order for the mathematical operators. Since we need to reference the string twice in this procedure (once to measure it and once to draw it), dup is used to make a copy of the string. Actually, dup doesnt copy the string; rather, it copies a reference (pointer?) to the string. The next operator, stringwidth, differs somewhat from its QuickDraw relative: the PostScript version returns both a horizontal and a vertical size (just in case youre working with a non-Western font thats written from top to bottom). We dont need the vertical component, so we pop it off the stack. Next, we use roll to move clen to the top of the stack. After an exch, the two elements on the top of the stack are in the correct order for the subtraction. sub leaves the difference between the centering length and the strings width on the top of the stack. To compute the x coordinate of the centering point, we need to halve this difference (2 div) and add the quotient to the x coordinate of the starting point. Since the x coordinate of the starting point is on the bottom of the stack and the stack is 4 elements deep, we use 4 -1 roll to move it to the top and add the offset to it. Now that the new x coordinate has been determined, we need to move the y coordinate to the top stack, so that the stack will be in order for moveto. The stack is now 3 elements deep and the starting points y coordinate is on the bottom, so we use 3 -1 roll to bring it to the top of the stack. moveto moves the pen to the x and y coordinates that are on top of the stack, and show draws the string, which is the only remaining element on the stack.
The following version of CenterString avoids most of the stack reorganization described above. On entry, this routine removes the parameters from the stack and places them into global variables:
/CenterString % x y clen str --
{
/str exch def % x y clen str -- x y clen
/clen exch def % x y clen -- x y
/ypos exch def % x y -- x
/xpos exch def % x -- -
clen % - -- clen
str stringwidth pop% clen -- clen stringx
sub% clen stringx -- clen-stringx
2 div % clen-stringx -- (clen-stringx)/2
xpos add % (clen-stringx)/2 -- ((clen-stringx)/2) + xpos
/xpos exch def % ((clen-stringx)/2) + xpos -- -
xpos ypos moveto % - -- -
str show% - -- -
} def
exch def is one of the most common sequences in PostScript programs. Recall that def requires a literal name and a value, with the value on top of the stack. To set a variable equal to a value thats on top of the stack, push the literal name on top of the value and then use exch to get the elements in the correct order for def.
In case theres any doubt about how to call CenterString, heres a sample program that uses it:
% set the font
/Times-Roman findfont 54 scalefont setfont
% center on 8 page
0 371 612 (Hello, World!) CenterString
showpage% draw and eject the page
The LaserWriter Driver
The job of the LaserWriter driver is to convert whatever is drawn on the screen (by QuickDraw) into PostScript for the printer. Though the concepts involved in the translation are fairly simple, the implementation is almost frighteningly complex.
When you open a printing port on the Macintosh, the printer driver replaces all of the low-level drawing procedures with procedures that drive the printer. This is done by resetting the thirteen quickdraw primitives that draw basic objects on the screen, with an alternative set of drawing primitives that draw to the printer. These replacement procedures are contained in the printer resource file, LaserWriter. [For more information on this aspect of the problem, see the printer driver article in the Nov. & Dec. issue of MacTutor. -Ed] In the case of the LaserWriter driver, the replacement procedures generate a PostScript program that will reproduce the page on the printer. However, the generated code is not pure PostScript, it relies heavily on a library of PostScript procedures or macros, that are designed to emulate QuickDraw routines. These procedures are defined in the LaserPrep file and are downloaded to the LaserWriter before any pages are printed. You can see what these definitions are by typing cmd-K after hitting the print button in the print dialog.
The LaserWriter file contains the replacements for the drawing routines and the PAP (Printing Access Protocol) manager, which coordinates communications with the LaserWriter over AppleTalk. LaserPrep contains the definitions of the PostScript routines that emulate QuickDraw. These routines are placed in their own dictionary, and theyre downloaded in a special way that allows them to remain in memory until the LaserWriter is turned off. Before the driver prints, it always checks if the LaserPrep dictionary is in memory; if its not, Laser Prep is downloaded while you watch the message initializing printer... Once downloaded, the dictionary consumes at least 62k of LaserWriter memory. Note that Pagemaker uses its own prep file called AldusPrep. Having both of these in the printer memory at the same time can sometimes run the printer out of memory. Too bad the printer cant automatically swap prep files in and out to avoid this problem!
When I first became acquainted with PostScript, it was clear that many PostScript features werent accessible through the driver because QuickDraw didnt support them. I considered modifying the driver, but soon found that I lacked the patience necessary to do so. LaserPrep contains more than 100 PostScript routines, many of which rely on other routines defined by the driver. Tracing through a single one can involve jumping to fifteen or twenty different places in the source. Even worse, the names given to the routines are literally cryptic (names were reduced to several characters to save space) and there was no LaserPrep documentation. Apple has since documented the LaserPrep routines (Appendix B of the LaserWriter Reference Manual), but I still wouldnt recommend driver modifications to anyone lacking endurance. If youre ready to test your patience, you can alter LaserPrep using FEDIT.
There are two common tricks that are indispensible when studying the driver. To save the PostScript code generated by a program that uses the standard LaserWriter driver, press Command-F as the LaserWriter printing dialog disappears. The code will be stored in a TEXT file named PostScript instead of being send to the LaserWriter. To save the code generated as well as the drivers PostScript replacement routines, press Command-K as the printing dialog disappears (it will also be stored in a TEXT file named PostScript).
Under some circumstances, the saved code can simply be downloaded to the LaserWriter. Unfortunately, though, simple downloading is not always enough. Under normal printing conditions, the driver downloads the LaserPrep file (if necessary) as well as any downloadable fonts used in the document. Unless the downloader youre using is intelligent and can perform these functions, you might not be able to get Command-F files to print successfully. The downloader presented with this article simply sends PostScript code to the LaserWriter and is best for downloading pure PostScript files. [It appears that the new version of the driver downloads fonts at the start of the job, because a cmd-K after printing a Reflex Plus report had both the prep file and the downloaded laser font definition contained within it, so that it could be directly downloaded to the LaserWriter at a later date. This change may have been made to accomodate the new batch printing of MultiFinder. -Ed]
The Failure of WYSIWYG
In the days of the IBM PC and the daisywheel printer, the choice between a WYSIWYG word processor and a code-based word processor was largely a matter of preference. WYSIWYG was useful because the capabilities of the screen usually matched those of the printer. Though the Macintosh and the LaserWriter represent significant advances in technology, there are differences between what can be done using the Macs screen with QuickDraw and what can be done on the LaserWriter using PostScript. These differences make WYSIWYG all but useless for dedicated publishing. The production of high-quality printed materials involves typographical refinements (fractional point sizes and leadings, kerning, hairline rules, etc.) much too subtle to be displayed on the Macs screen. Though there are some page layout programs that provide the user with control over these refinements, they cant be displayed. This forces the user into an iterative process -- pages have to be printed and reprinted until everything looks right. Reprinting highlights the flaws in the system software and hardware. The rather baroque translation from QuickDraw to PostScript is slow and the size of the memory-resident LaserPrep dictionary leaves little room on the LaserWriter for downloadable fonts or larger page sizes. Consequently, the printing of documents that use non-resident fonts is slowed to a crawl by the downloading and re-downloading of fonts and AppleTalk gets rather tied up as well. When it takes twenty minutes to print a page, one is lucky to make deadlines -- much less worry about refinements.
There are no easy solutions to these problems. The screen resolution difficulties can only be solved by very high resolution displays and QuickDraw modifications. The translation problem can be eased with a large dose of additional memory (impossible on current LaserWriters) and a dedicated font downloader (kludge) . The clean solution, of course, is to provide programs with the ability to generate pure PostScript and output it directly to the LaserWriter. Its clear that this ability will become a common feature of all software (not just page layout programs) in the future; witness the rise of encapsulated postcript as a way of blocking out postscript graphics on the page in a program like Pagemaker.
Sources of Information
Youll find the following sources useful when learning PostScript.
PostScript Language Reference Manual
(Red Book)
PostScript Language Tutorial and Cookbook
(Blue Book)
Poscript Language Journal
The Red Book begins with a comprehensive description of PostScript and some important examples. The remainder of the book covers all the PostScript commands and provides some specific information about the LaserWriter. The Blue Book is (as its full title implies) a tutorial with many documented examples. Since these books are published by Addison-Wesley, you can purchase them at most technical bookstores. Watch for the rumored Green book that will cover advanced programming techniques.
Postscript Language Journal is a technical Journal devoted to postscript programming and related Postscript based Mac products. Published by Pipeline Associates, PO Box 5763, Parsippany, NJ 07054. Only $15 per year, quarterly. Highly recommended.
Adobes telephone support is phenomenal, so phenomenal that it almost makes up for the fact that they distribute no technical notes about PostScript. Call them for help, or information about their PostScript programming classes. Adobe Systems Incorporated, 1585 Charleston Road, P.O. Box 7900, Mountain View, California 94039-7900 (415) 961-4400.
Understanding PostScript Programming
by David A. Holzgang.
Sybex, Inc., 1987.
This is the first 3rd Party book about PostScript and it appears to be a much more readable introduction to the language than Adobes Blue Book. The example programs arent quite as complex, which is good, and theyre explained in detail. Sybex, Inc. 2021 Challenger Drive, #100, Alameda, California 94501
Apple LaserWriter Reference
This document contains invaluable information about printing with the LaserWriter driver. #KNBLRM, from A.P.D.A., 290 SW 43 Street, Renton, Washington 98055 (206) 251-6548
PostScript Halftoning
by Henry Bortman.
A three part series in the November 86, December 86 and January 87 issues of The Macazine. Bortman gives a very detailed explanation of how shades of gray are rendered by PostScript. Back Issues/The Macazine, P.O. Box 9802-919, Austin, Texas 78767 (800) 624-2346; $3.75/issue
Principles of Interactive Computer Graphics
by William M. Newman and Robert F. Sproull.
McGraw-Hill, 1979.
This book doesnt cover PostScript, but it presents some of the mathematical concepts behind PostScripts implementation. It also describes three-dimensional drawing and shading techniques. Its available at technical bookstores.
Hooking Up
There are two ways to run PostScript programs on the LaserWriter. The LaserWriters PostScript interpreter can operate interactively (like BASIC or FORTH) or it can accept programs downloaded from the Macintosh.
Follow these steps to program interactively:
Turn the LaserWriter off. On the side with the ports, youll find a mode switch. Set the switch to 9600.
Connect the printer to the Macintosh using a null modem cable (the original Apple Personal Modem cable works). Use the 25-pin (RS-232) serial port on the LaserWriter.
Start up your favorite communications software on the Macintosh. Setup: 9600 baud, 8 bits, no parity, half duplex.
Type executive. The PostScript copyright lines will be appear, followed by the system prompt, PS>. Youre now in the interactive mode.
The following sections present a downloader that sends PostScript TEXT files to the LaserWriter. There are also a number of text editors that have integral downloaders:
JustText, from Knowledge Engineering, P.O. Box 2139, New York, New York 10116. 212.473.0095.
PostHaste, from Micro Dynamics, Ltd., 8444 16th Street #802, Silver Spring, Maryland 20910. (301) 589-6300.
QuickScript, (under development) from Perimeter, 1608 N. Milwaukee, Chicago, Illinois 60647. (312) 278-9509.
About the Downloader...
The downloader presented here, PS PRINT, provides a convenient way of sending PostScript programs to the LaserWriter. When it launches, youll be presented with a file dialog containing the names of TEXT files. The file you select will be downloaded to the LaserWriter. When the download ends, youll be given a chance to save any printer feedback to a text file. Then the original file dialog will appear again, allowing you to download another file. If you click on Cancel, a file dialog containing applications will appear. You can use it to transfer to another application, or return to the Finder by clicking on Cancel.
There are many ways to send PostScript to a printer over AppleTalk. They range from writing your own AppleTalk routines to using high-level print manager functions (see the Apple LaserWriter Reference for details). PS PRINT uses the PAP manager.
The PAP Manager
Communication with the LaserWriter is accomplished via the PAP manager, which provides a high-level interface to AppleTalk. Its code resides in resources that are contained in the LaserWriter file. Oddly, the syntax of the PAP calls doesnt appear to be documented in the Apple LaserWriter Reference and there have been modifications to the calls since their initial presentation in Appendix E of Inside LaserWriter. The following information about the PAP calls is a revised version of the information that appeared in Appendix E. The declarations are presented in Lightspeed C (note that Lightspeed integers occupy 16 bits). These declarations are here to detail the requirements of the PAP routines. Theyre not declared this way in the downloader.
pascal int PapOpen(ConnectId, LaserName, FlowQuantum, LaserStatus, OpenState)
int*ConnectId;
char *LaserName;
intFlowQuantum;
papstatusptrLaserStatus;
int*OpenState;
PapOpen is used to establish a connection between the Macintosh and the LaserWriter over AppleTalk. It is executed asynchronously. This means that, though control returns to the caller, the connection does not necessarily open immediately. If the call returns with a nonzero value, the connection cannot be opened. Otherwise, OpenState will be set to a positive value while the connection is opening. When the connection is open, OpenState will be set to zero. If the opening fails after control has returned to the caller, OpenState will be set to a negative value.
LaserName should point to an AppleTalk EntityName (see the AppleTalk Manager section of Inside Macintosh). Usually this will be the name of the printer selected with the Chooser. The Chooser stores this name in the LaserWriter file resource PAPA -8192.
The FlowQuantum is the number of 512 byte buffers that are available for sending and receiving information. The LaserWriter uses a 4096 byte buffer (FlowQuantum = 8), so its probably most efficient if the Macintosh uses a buffer of that size as well.
The LaserStatus buffer contains a status message thats updated during the opening of the connection. Its structure is declared in the downloader source. The buffer is not cleared by PapOpen, so you should clear it if you intend to use it (PS PRINT does not).
pascal int PapWrite(ConnectId, WriteBuffer, WriteSize, WriteEof, WriteState)
intConnectId;
char *WriteBuffer;
intWriteSize;
intWritEof;
int*WriteState;
PapWrite is an asynchronous call that is used to send data to the LaserWriter. Like the other asynchronous PAP calls, it uses the ConnectId thats set by PapOpen and it will return a nonzero value if communications have failed. While it is sending the data, WriteState will be positive. If the transfer is successful, WriteState will be set to 0. Note that this does not mean that the data in the buffer has been processed by the LaserWriter; it has merely been received. If communications fail, WriteState will become negative.
The data being sent is placed in the buffer pointed to by WriteBuffer. The maximum size of this buffer is determined by FlowQuantum (see PapOpen). The application should set WriteSize to the actual size of the data being sent.
Before the last buffer of data is sent to the LaserWriter, the application should set WriteEof to a positive value. This signals the LaserWriter to notify the application (via PapRead) when it has completed processing the job.
pascal int PapRead(ConnectId, ReadBuffer, ReadSize, ReadEof, ReadState)
intConnectId;
char *ReadBuffer;
int*DataSize;
int*ReadEof;
int*ReadState;
PapRead is an asynchronous call that is used to read feedback from the LaserWriter. PAP requires continuous communications between the Macintosh and the LaserWriter. Whenever a connection is open, you must repeatedly call PapRead (using the connections ConnectId) to ensure that data coming from the LaserWriter is received by the Macintosh.
If communication has failed, PapRead will return a non-zero value, or ReadState will (possibly later) be set to a negative value. If a PapRead has not yet completed, ReadState will be positive. When its finished, ReadState will be zero.
If there is feedback from the LaserWriter, ReadSize will contain the number of bytes of feedback and the feedback itself will have been placed in the buffer pointed to by ReadBuffer. The size of the buffer is given by FlowQuantum (see PapOpen).
ReadEof is used to monitor the end of the information flow between the Mac and the LaserWriter. Just before the last PapWrite call, the application should set WriteEof to a positive value. This informs the LaserWriter that the final buffer is being sent. Once the buffer has been processed by the LaserWriter, a call to PapRead will return with ReadEof set to a nonzero value. At this point, its safe to close the connection using PapClose. One can simply close the connection after the final PapWrites WriteState becomes 0 (when the last buffer has been sent successfully) without waiting for a positive ReadEof, but this will eliminate the possibility of receiving any feedback generated by the final buffer.
pascal int PapClose(ConnectId)
intConnectId;
This call closes the connection referenced by ConnectId.
pascal int PapUnload()
This routine is usually executed after PapClose. It unloads PAPs private data structures and closes any connections that remain open.
pascal int PapStatus(LaserName, LaserStatus, LaserNode)
char *LaserName;
papstatusptrLaserStatus;
long *LaserNode;
This is a synchronous call that checks the status of the named printer. It can be executed even if there isnt an open connection with the printer.
LaserName should point to an AppleTalk EntityName (see the AppleTalk Manager section of Inside Macintosh). Usually this will be the name of the printer selected with the Chooser (see PapOpen above).
LaserStatus is a buffer that will contain the status information requested by the call. Its structure is declared in the downloader source.
The value pointed to by LaserNode should be set to zero the first time the routine is called. PapStatus will set it to the printers AppleTalk address. Subsequent calls will execute more quickly using the node address.
The PAP manager routines described above (and a few more that arent relevant to downloaders) are stored as resource PDEF 10 in the LaserWriter file. The entry points for the routines occur at the beginning of the resource. The downloader reads the resource into memory before any of the calls are made. In the downloader, Ive created glue routines with the same names as the PAP calls. As mentioned earlier, they do not have the above declarations. In PS PRINT, all of the variables in the above declarations are declared as globals. Each PAP glue routine pushes these globals onto the stack and JSRs to the routines entry point.
The source for this downloader is based on code presented in Mike Schusters Laser Print DA for Postscript (MacTutor, February 1986) and Alan Oppenheimers downloader, which was published in Appendix E of the original Inside LaserWriter.
Try It
Download the following code to the LaserWriter. Try experimenting with it -- Ive included some comments regarding parameters that you might want to change.
% This routine draws a string 3 times. Each time it is
% offset and drawn with a different shade of gray.
% If you want to experiment, use different values for
% setgray (anything between 0 and 1)
/ShadowShow
{
/thestring exch def
currentpoint
.6 setgray
thestring show
moveto
-4 4 rmoveto
currentpoint
.2 setgray
thestring show
moveto
-4 4 rmoveto
1 setgray
thestring show
} def
% setscreen is used to change the shading method.
% Normally, shades of gray are rendered with dots
% of various sizes. These arguments to setscreen
% make PostScript render grays with lines of varying
% weights instead of dots. For more info, consult the
% Red Book and Colophon, Volume 2
% the first argument (35) is the # lines/inch and the
% second is the degree at which the lines are drawn.
% Try different values.
35 45 {exch pop abs 1 exch sub} setscreen
% draw and fill the square on which the text will sit
50 280 moveto
50 500 lineto
480 500 lineto
480 280 lineto
50 280 lineto
.9 setgray
fill
% set the font
/Times-Bold findfont 72 scalefont setfont
% move into position
100 382 moveto
% skew the space. concat does a linear
% transformation of the CTM by the matrix
% that precedes it. Read the Red Book and
% dig out your linear algebra textbook.
[ 1 .1 1 1 0 0 ] concat
% Substitute whatever you want for the text wihtin
% the parentheses
(MacTutor) ShadowShow
% inverse-transform these absolute coordinates.
% we want their equivalents in the skewed space
100 318 [ 1 .1 1 1 0 0 ] itransform moveto
(Magazine) ShadowShow
showpage
Downloader Source
/*
PS PRINT: A PostScript Downloader
written by Nicholas Pavkovic
© Perimeter, 1987.
PERIMETER
1608 North Milwaukee Avenue
Chicago Illinois 60647
312 . 278 . 9509
Portions copyrighted by THINK Technologies, Inc.,
and Apple Computer, Inc.
Compiled with LightspeedC
Libraries linked: MacTraps, sprintf/sscanf
*/
#include <MacTypes.h>
#include <QuickDraw.h>
#include <EventMgr.h>
#include <StdFilePkg.h>
/* FileMgr.h Defs (dont need whole file) */
#define fnfErr -43
#define eofErr -39
extern int FSFCBLen : 0x3F6;
extern int BootDrive : 0x210;
extern int SysMap : 0xA58;
/* WindowMgr.h (dont need whole file) */
#define NewWindow(long) NewWindow
#define dBoxProc 1
/* General */
#define True1
#define False 0
#define Success 0
#define Failure -1
#define Fails == -1
#define NULL0L
/* Fall-through functions */
#define IssueRead()if( issueread() Fails ) return( Failure )
#define CheckIfCancelled()if( checkifcancelled() Fails ) return( Failure
)
#define DisplayStatus() if( displaystatus() Fails ) return( Failure )
#define OpenLChannel() if( openlchannel() Fails ) return( Failure)
#define PostMessage(x, y )if( postmessage(x, y) Fails ) return( Failure
)
/* PostMessage selectors */
#define FromPrinter1
#define FromProgram0
#define SIMPLEALERT1000
/* File-related */
intFErr;
intFileRef;
SFReply UserReply;
SFTypeListFylz;
Point SFPoint;
char *OpenName;
char NullString;
EventRecord theEvent;
Handle DummyHand1;
Rect DummyRect;
long DummyType;
/* Windows */
long PrintWinPtr;
long DirectWin;
/* Please include InfoWin if you compile and distribute the program */
long InfoWin;
char *InfoString = A PostScript Downloader written by Nicholas Pavkovic\r\r©
Perimeter 1987.\rPortions © THINK and Apple Computer, Inc.\rNon-commercial
distribution is permitted.\
\r\rPERIMETER\r1608 North Milwaukee Avenue\rChicago Illinois 60647\r312
. 278 . 9509\r \
Developers of LaserLabels and QuickScript\r\r Click on the mouse to
continue.;
/* PAP globals */
typedef struct
{
long int systemstuff;
char statusstr[256];
} papstatusrec, *papstatusptr;
papstatusrecLaserStatus;
Handle pap;
long papaddr;
intFlowQuantum;
long unsigned LaserNode;
intOpenState;
intWriteState;
intReadState;
intConnectId;
intWriteEof;
intReadEof;
char ReadSpace[4096];
char *WriteBuffer;
intWriteSize;
intReadSize;
char *LaserName;
Handle LaserHand;
char LaserNameBuff[64];
char VirtualPage[4096];
char MessageBuff[256];
Handle FeedbackHandle;
long FHandleSize;
long LastStatusDisplay;
/*
SimpleAlert:
Implements a general alert used for most of the terminal
error messages.
*/
SimpleAlert( m0 )
char *m0;
{
ParamText( m0, 0L, 0L, 0L );
NoteAlert( SIMPLEALERT, 0L ) ;
}
/*
SFDFilter:
Filter function for Standard File Dialog.
Makes the Open button context sensitive
and gives it a heavy outline.
*/
pascal Boolean SFDFilter( dlog, event, itemhit )
long dlog;
EventRecord *event;
int*itemhit;
{
if ( event->what == activateEvt && event->modifiers & activeFlag )
{
GetDItem( dlog, getOpen, &DummyType, &DummyHand1, &DummyRect );
SetCTitle( DummyHand1, OpenName );
PenSize(3, 3);
InsetRect( &DummyRect, -4, -4 );
FrameRoundRect( &DummyRect, 16, 16);
PenSize(1,1);
};
return( False );
}
/*
displaystatus:
Checks and display status every second,
even if routine is called more frequently.
*/
displaystatus()
{
if( TickCount() - LastStatusDisplay > 60 )
{
PapStatus();
PostMessage( &LaserStatus.statusstr, FromPrinter );
LastStatusDisplay = TickCount();
};
CheckIfCancelled();
}
/*
Directions:
Display text in the directions window.
*/
Directions( text )
char *text;
{
DummyRect.top = 10;
DummyRect.left = 10;
DummyRect.right = 342;
DummyRect.bottom = 80;
SetPort( DirectWin );
MoveTo( 10, 16 );
TextBox( &text[1], (long) text[0], &DummyRect, 0 );
}
main()
{
char *source, *dest;
int counter;
int sysVRef;
InitGraf(&thePort);
InitFonts();
InitWindows();
TEInit();
InitDialogs(0L);
FlushEvents(everyEvent, 0);
InitCursor();
/*
Read PAP routines into memory.
OpenResFile uses PMSP; we need only to find boot
drive. See Tech Note #77 for details.
*/
if( FSFCBLen )
{
FErr = GetVRefNum( SysMap, &sysVRef );
}
else
{
sysVRef = BootDrive;
};
SetVol( &NullString, sysVRef );
FErr = OpenResFile(\pLaserWriter);
if( FErr == -1 )
{
SysBeep(1);
SimpleAlert( \pThe LaserWriter file could not be found or couldnt be
opened. Returning to Finder.);
return;
};
/* Read the PAP code into memory */
if ( ( pap = GetResource( PDEF, 10 ) ) == NULL || ResError() )
{
SysBeep(1);
SimpleAlert( \pThe PAP routines could not be loaded. Returning to Finder.);
if( FErr != -1 ) CloseResFile( FErr );
return;
};
/* Detach PAP */
HLock(pap);
DetachResource(pap);
papaddr = (long) *pap;
/* Read printer name from LaserWriter file */
if ( ( LaserHand = GetResource( PAPA, -8192 ) ) == NULL || ResError()
)
{
SysBeep(1);
SimpleAlert( \pThe LaserWriter name could not be read. Returning to
Finder.);
return;
};
/* Create a C string version of the printer name (for
messages) and place it into LaserNameBuff */
HLock( LaserHand );
DetachResource( LaserHand );
LaserName = *LaserHand;
/*
Copy the printer name into LaserNameBuff,
a C string version used for messages to user.
*/
source = (char *) LaserName + 1;
dest = LaserNameBuff;
counter = (int) *LaserName;
while( counter-- ) *dest++ = *source++;
*dest = 0;
/* Done with LaserWriter file */
CloseResFile( FErr );
/* About PS PRINT... */
DummyRect.left = 62;
DummyRect.top = 36;
DummyRect.bottom = 318;
DummyRect.right = 446;
InfoWin = NewWindow( 0L, &DummyRect, &NullString, 0xFF, dBoxProc, -1L,
False, 0L );
SetPort( InfoWin );
TextFace( condense );
TextFont( 0 );
MoveTo( 10, 36 );
DrawString( \pPS PRINT );
PenPat( gray );
MoveTo( 64, 26 );
PenSize( 1, 12 );
LineTo( 362, 26 );
PenNormal();
TextFont( 1 );
DummyRect.top = 46;
DummyRect.left = 10;
DummyRect.bottom = 282;
DummyRect.right = 362;
TextBox( InfoString, 314L, &DummyRect, -1 );
while( GetNextEvent( everyEvent, &theEvent) == 0 || theEvent.what !=
mouseDown ) SystemTask();
DisposeWindow( InfoWin );
/* Setup instruction window. */
DummyRect.left = 80;
DummyRect.top = 36;
DummyRect.bottom = 90;
DummyRect.right = 428;
/*
Technically, theres no reason that the directions window should be a
dialog, but doing so avoids the bug described in Tech Note #99 (SFD doesnt
update file list when new disk inserted).
*/
DirectWin = (long) NewDialog( 0L, &DummyRect, &NullString, 0xFF, dBoxProc,
0L, 0, 0L, 0L );
SetPort( DirectWin );
TextFace( condense );
TextFont( 0 );
FeedbackHandle = NewHandle( 0 );
if( FeedbackHandle == 0 || MemError() )
{
SimpleAlert( \pCant Allocate Feedback Handle. Returning to Finder.
);
return;
};
SFPoint.v = 108;
/* Download loop */
while( 1 )
{
SetHandleSize( FeedbackHandle, 0L );
FHandleSize = 0;
/* Select file to download */
Directions( \p Select a file to download, or\r Click on Cancel to
transfer or return to the Finder );
SFPoint.h = 80;
Fylz[0] = TEXT;
OpenName = \pDownload;
SFPGetFile( SFPoint, &NullString, 0L, 1, Fylz, 0L, &UserReply, -4000,
SFDFilter );
/* If file selected, download , otherwise transfer */
if ( UserReply.good )
{
TextDownload( &UserReply.fName, UserReply.vRefNum );
}
else
{
Directions( \p Select an application to transfer to, or\r Click on
Cancel to return to the Finder );
Fylz[0] = APPL;
OpenName = \pTransfer;
SFPGetFile( SFPoint, &NullString, (ProcPtr) 0, 1, Fylz, 0L, &UserReply,
-4000, SFDFilter);
DisposDialog( DirectWin );
/* If user cancels, return to Finder */
if ( !UserReply.good ) return;
MessageBuff[0] = 0;
SetVol( &MessageBuff, UserReply.vRefNum );
Launch( 0, &UserReply.fName );
};
/* If feedback received, save it to a file. */
if( FHandleSize )
{
Directions( \p Enter a filename to save the printer feedback, or\r
Click on Cancel to continue );
TryAgain:
SFPoint.h = 104;
SFPutFile( SFPoint, \pSave the feedback as:, \pFeedback, 0L, &UserReply
);
/* If cancelled, loop to downloading code */
if ( !UserReply.good ) continue;
FErr = FSOpen( &UserReply.fName, UserReply.vRefNum, &FileRef );
/* If the specified file doesnt exist, create it */
if( FErr == fnfErr )
{
SetVol( &NullString, UserReply.vRefNum);
FErr = Create( &UserReply.fName, 0, EDIT, TEXT );
FErr = FSOpen( &UserReply.fName, UserReply.vRefNum, &FileRef );
};
if( FErr != 0 )
{
PtoCstr( &UserReply.fName );
sprintf( &MessageBuff,The file you requested, %s, is not currently
available. Error: %d, &UserReply.fName, FErr );
CtoPstr( &MessageBuff );
SimpleAlert( &MessageBuff );
goto TryAgain;
};
HLock( FeedbackHandle );
DummyType = FHandleSize;
SetFPos( FileRef, 1, 0L );
FSWrite( FileRef, &DummyType, *FeedbackHandle );
SetEOF( FileRef, DummyType );
FSClose( FileRef );
HUnlock( FeedbackHandle );
};
};
}
openlchannel()
{
long CurrentTime, StartOpening;
Directions(\p Press \021. (Command-Period) to cancel the download);
/* Set up print window. */
DummyRect.top = 108;
DummyRect.left = 80;
DummyRect.bottom = 296;
DummyRect.right = 428;
PrintWinPtr = NewWindow( 0L, &DummyRect, NullString, 0xFF, dBoxProc,
-1L, False, 0L );
SetPort( PrintWinPtr );
TextFont( 0 );
TextFace( condense );
PenSize( 1, 1 );
PenPat( black );
MoveTo( 10, 24 );
LineTo( 12, 24 );
MoveTo( 14, 28 );
DrawString( \pProgram );
Move( 2, -4 );
LineTo( 338, 24 );
LineTo( 338, 86 );
LineTo( 10, 86 );
LineTo( 10, 24 );
MoveTo( 10, 108 );
LineTo( 12, 108 );
MoveTo( 14, 112 );
DrawString( \pPrinter );
Move( 2, -4 );
LineTo( 338, 108 );
LineTo( 338, 170 );
LineTo( 10, 170 );
LineTo( 10, 108 );
TextFont( 1 );
/* Open connection to server */
sprintf( &MessageBuff, Looking for printer %s., &LaserNameBuff );
CtoPstr( &MessageBuff );
PostMessage( &MessageBuff, FromProgram );
FlowQuantum = 8;
ConnectId = 0;
if ( PapOpen() )
{
NoPrinter();
return( Failure );
};
sprintf( &MessageBuff, Establishing connection with %s., &LaserNameBuff
);
CtoPstr( &MessageBuff );
PostMessage( &MessageBuff, FromPrinter );
StartOpening = TickCount();
while( OpenState > 0 )
{
DisplayStatus();
CurrentTime = TickCount();
if( CurrentTime - StartOpening > 1800 )
{
NoPrinter();
return( Failure );
};
CheckIfCancelled();
};
if ( OpenState < 0 )
{
NoPrinter();
return( Failure );
};
PostMessage( \pConnection established., FromProgram );
ReadState = 0;
WriteState = 0;
ReadEof = 0;
WriteEof = 0;
ReadSize = 0;
WriteSize = 0;
return( Success );
}
issueread()
{
long oldsize;
char *fbptr, *readptr;
if ( ReadState <= 0 )
{
/* Negative ReadState indicates communications failure */
if ( ReadState < 0 )
{
EndPCom();
return( Failure );
};
/*
ReadState == 0 => successful read.
ReadSize>0 => Theres feedback.
*/
if ( ReadSize > 0 )
{
SysBeep(1);
/*Feedback messages use ASCII 10
(linefeed) instead of ASCII 13 (carriage
return). Convert them and display.
*/
readptr = &ReadSpace[ ReadSize - 1];
if( *readptr == 10 ) *readptr = 13;
*(++readptr) = \0;
CtoPstr(&ReadSpace);
PostMessage( &ReadSpace, FromPrinter );
Delay( (long int) 150, &DummyHand1);
/* Update the feedback handles size and
copy feedback. */
oldsize = FHandleSize;
FHandleSize += ReadSize;
SetHandleSize( FeedbackHandle, FHandleSize);
if( MemError() == noErr )
{
readptr = &ReadSpace[1];
fbptr = (char *) *FeedbackHandle + oldsize;
while( ReadSize-- ) *fbptr++ = *readptr++;
};
};
/* Issue another read */
if (PapRead() )
{
EndPCom();
return( Failure );
};
CheckIfCancelled();
}
else
{
/* Since ReadState > 0, last read not finished */
DisplayStatus();
};
return( Success );
}
CloseLChannel()
{
IssueRead();
/* set end-of-file */
WriteEof = 1;
/* Send empty buffer */
WriteBuffer = ;
WriteSize = 0;
if ( PapWrite() )
{
EndPCom();
return( Failure );
};
/* Wait for printer to indicate that processing is over */
while ( ReadEof == 0) IssueRead();
PapClose();
PapUnload();
DisposeWindow( PrintWinPtr );
return( Success );
}
/*
EndPCom:
Closes connection immediately.
*/
EndPCom()
{
PapClose();
PapUnload();
DisposeWindow( PrintWinPtr );
sprintf( &MessageBuff, Communication with the printer %s has ended.,
&LaserNameBuff );
CtoPstr( &MessageBuff );
SimpleAlert( &MessageBuff );
}
/*
postmessage:
Displays messages from printer and program in PrintWin.
*/
postmessage( s, source )
char *s;
intsource;
{
DummyRect.left = 13;
DummyRect.right = 335;
if( source == FromProgram )
{
DummyRect.top = 35;
DummyRect.bottom = 83;
}
else
{
DummyRect.top = 119;
DummyRect.bottom = 167;
};
TextBox( &s[1], (long) s[0], &DummyRect, 0 );
CheckIfCancelled();
return( Success );
}
/*
checkifcancelled:
Checks if user has cancelled the download with
Command-Period.
*/
checkifcancelled()
{
if( GetNextEvent( everyEvent, &theEvent) == 0 )
{
SystemTask();
return( Success );
};
if( theEvent.what == keyDown &&
theEvent.modifiers & cmdKey &&
(theEvent.message & charCodeMask ) == . )
{
EndPCom();
return( Failure );
};
}
/*
TextDownload:
Downloads the specified file to the printer
*/
TextDownload( FFileName, Vnum )
Str255 *FFileName;
intVnum;
{
long ItemsRead;
long FileSize, Remaining;
Str255 WorkingMessage;
OpenLChannel();
FErr = FSOpen( FFileName, Vnum, &FileRef );
PtoCstr( FFileName );
if ( FErr != noErr )
{
CloseLChannel();
sprintf( &MessageBuff, Sorry, the file %s could not be opened. Error:
%d, FFileName, FErr );
CtoPstr( &MessageBuff );
SimpleAlert( &MessageBuff );
return( Failure );
};
GetEOF( FileRef, &FileSize );
Remaining = FileSize;
while( Remaining )
{
sprintf( &WorkingMessage, Downloading %s\rComplete: %ld%%, FFileName,
100 - ((Remaining * 100 )/FileSize) );
CtoPstr( &WorkingMessage );
PostMessage( &WorkingMessage, FromProgram );
ItemsRead = 4096;
FErr = FSRead( FileRef, &ItemsRead, &VirtualPage);
if ( ItemsRead == 0 && FErr != eofErr )
{
CloseLChannel();
goto ErrorExit;
}
else if ( FErr == eofErr )
{
WriteSize = (int) ItemsRead;
if( sendtoprinter( VirtualPage, (int) ItemsRead ) Fails ) goto ErrorExit;
FSClose( FileRef );
sprintf( &MessageBuff, %s has been downloaded., FFileName, FileRef
);
CtoPstr( &MessageBuff );
PostMessage( &MessageBuff, FromProgram);
CloseLChannel();
return( Success );
};
if( sendtoprinter( VirtualPage, (int) ItemsRead ) Fails )
goto ErrorExit;
Remaining -= ItemsRead;
};
ErrorExit:
FSClose( FileRef );
sprintf( &MessageBuff, Sorry, the file %s could not be downloaded.,
FFileName );
CtoPstr( &MessageBuff );
SimpleAlert( &MessageBuff );
return( Failure );
}
/*
sendtoprinter:
General downloading routine (also handles
buffers > 4096 bytes)
*/
sendtoprinter( bffer, bffsize )
char *bffer;
int bffsize;
{
register char *bffptr;
bffptr = bffer;
while( bffsize > 0)
{
if( bffsize > 4096 )
{
WriteSize = 4096;
}
else
{
WriteSize = bffsize;
};
WriteBuffer = bffptr;
IssueRead();
if ( PapWrite() )
{
EndPCom();
return( Failure );
};
CheckIfCancelled();
/*
Issue reads to the printer while the
write is processing.
*/
while ( WriteState > 0 ) IssueRead();
if ( WriteState < 0 )
{
EndPCom();
return( Failure );
};
bffsize -= 4096;
bffptr += 4096;
};
return( Success );
}
/*
NoPrinter: Called if communications with printer cant be
established.
*/
NoPrinter()
{
PapUnload();
DisposeWindow( PrintWinPtr );
sprintf( &MessageBuff, Sorry, the printer %s is not currently available.,
&LaserNameBuff );
CtoPstr( &MessageBuff );
SimpleAlert( &MessageBuff );
return( Failure );
};
/*
PAP glue routines:
The routines start with a SUB command that makes room for the result
on the stack. Then they push the appropriate PAP globals (allocated above)
onto the stack, and jump to the routines entry point, which is at some
small offset from the beginning of pap. When they return from the call,
they pop the result off the stack and put it into retval, which is returned.
*/
PapOpen()
{
int retval;
asm{
SUBQ.L #2,A7
PEAConnectId
MOVE.L LaserName,-(A7)
MOVE.W FlowQuantum,-(A7)
PEALaserStatus
PEAOpenState
MOVE.L papaddr,A0
JSR0(A0)
MOVE.W (A7)+,retval
}
return( retval );
}
PapRead()
{
int retval;
asm{
SUBQ.L #2,A7
MOVE.W ConnectId,-(A7)
PEAReadSpace
PEAReadSize
PEAReadEof
PEAReadState
MOVE.L papaddr,A0
JSR4(A0)
MOVE.W (A7)+,retval
}
return( retval );
}
PapWrite()
{
int retval;
asm{
SUBQ.L #2,A7
MOVE.W ConnectId,-(A7)
MOVE.L WriteBuffer,-(A7)
MOVE.W WriteSize,-(A7)
MOVE.W WriteEof,-(A7)
PEAWriteState
MOVE.L papaddr,A0
JSR8(A0)
MOVE.W (A7)+,retval
}
return( retval );
}
PapStatus()
{
int retval;
asm{
SUBQ.L #2,A7
MOVE.L LaserName,-(A7)
PEALaserStatus
PEALaserNode
MOVE.L papaddr,A0
JSR12(A0)
MOVE.W (A7)+,retval
}
return( retval );
}
PapClose()
{
int retval;
asm{
SUBQ.L #2,A7
MOVE.W ConnectId,-(A7)
MOVE.L papaddr,A0
JSR 16(A0)
MOVE.W (A7)+,retval
}
return( retval );
}
PapUnload()
{
int retval;
asm{
SUBQ.L #2,A7
MOVE.L papaddr,A0
JSR 20(A0)
MOVE.W (A7)+,retval
}
return( retval );
}
END OF DOWNLOADER SOURCE
Resource text for RMaker:
Type DITL
,20963
2
* 1
BtnItem Enabled
80 62 102 134
Ok
* 2
StatText Enabled
10 62 65 363
^0
Type ALRT
,1000
78 65 189 446
20963
5555