TweetFollow Us on Twitter

Downloader
Volume Number:4
Issue Number:1
Column Tag:Postscript Programming

Overview & Downloader in C

By Nicholas Pavkovic, Perimeter, Chicago, IL

In this article, I’m going to present a programmer’s 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, I’ll 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 that’s 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 that’s 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.

PostScript’s design shows the influence of SmallTalk. Though it isn’t 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 PostScript’s principal designer, John Warnock, worked during the late 70’s and early 80’s. 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 doesn’t provide any additional information about the operator: an operator referred to as a function won’t necessarily return a value. The data used by operators are technically called operands, but I often use arguments and parameters instead. It’s 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 PostScript’s capabilities, I’d 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 don’t 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 you’ve 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 pen’s characteristics when fill or stroke is called. The PostScript pen’s 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, it’s a somewhat tricky procedure. Usually you’ll 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).

PostScript’s setlinewidth is the PostScript version of QuickDraw’s PenSize routine; it’s preceded by the desired line weight (vertical width), which can be fractional. This command doesn’t alter the horizontal width of the pen. 0 setlinewidth generates the thinnest line available on the PostScript device you’re using. Lines are drawn from the center; half of the line’s 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, you’ll need a moveto to position the pen after you’ve used one of these operators. The erasure of the current path causes problems if you want to create a figure that’s 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 you’ve 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

It’s 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 don’t 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) aren’t standard on the LaserWriter.

After you’ve set the font, you’ll 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 don’t affect any text or graphics that have already been drawn.

What’s Missing?

There are a few features that QuickDraw programmers will miss in PostScript.

As mentioned above, there are no transfer modes; you’ll have to be careful about the order in which objects are drawn and make use of PostScript’s clip operator.

Though PostScript’s text processing is more sophisticated than QuickDraw’s, PostScript contains no operators for wrapping text (there’s no TextBox equivalent). You can write a simple line-breaking procedure in PostScript, but for reasons of speed you’ll probably want to do any heavy-duty hyphenation and justification on the Mac’s processor.

Regions are missing from PostScript, as are most of QuickDraw’s routines for drawing geometric shapes. In both cases, it’s not difficult to program functional equivalents.

Visualizing the Stack

Like FORTH, PostScript is a stack-based language. FORTH programmers will find their experience with FORTH’s 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 doesn’t use its operand stack for is return addresses, so you don’t have to worry about accidently branching into a data structure if your calls aren’t 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 it’s reverse polish notation, then this will appeal to you. -Ed] As each number is read, it’s 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 you’re 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. He’s 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 you’re coding. Since many of the simple operators in PostScript don’t do much to the stack, it’s tempting to look at them simply as functions preceded by their arguments and forget that the stack exists. Unfortunately, this approach won’t get you far when you’re trying to understand PostScript’s stack manipulation operators.

dup duplicates the stack’s top element:

exch reverses the order of the top two elements:

roll rotates the order of elements on the stack. It’s 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, they’ll 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. It’s frustratingly similar to Rubik’s cube: though it’s 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 that’s on top of the stack. exch’s 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 Manual’s 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 I’m writing a tricky program (or debugging code that I had believed to be simple), I’ll 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 variable’s name and type before it can be used. In PostScript, an assignment statement serves as the declaration. The variable’s 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 you’ll rarely need them.

Let’s 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 variable’s 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 PostScript’s 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 can’t 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 it’s been defined.

The code /counter counter 1 add def increments the counter. counter 1 add pushes counter’s 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, we’ve 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 it’s 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 system’s global variables. There are a few characteristics of PostScript’s dictionaries to keep in mind:

• Because of the order of the dictionaries, it’s 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. There’s no way of allocating private local variables and then de-allocating them at the end of a procedure.

• There’s 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 don’t define variables recklessly, you’re not likely to hit userdict’s 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, I’d 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 it’s designed to show off some of the stack operators. After you’ve read through its explanation, I’ll present a different version that’s 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 doesn’t 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 you’re working with a non-Western font that’s written from top to bottom). We don’t 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 string’s 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 point’s 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 that’s 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 there’s any doubt about how to call CenterString, here’s 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 they’re 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 it’s 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 it’s 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 can’t 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 weren’t accessible through the driver because QuickDraw didn’t 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 wouldn’t recommend driver modifications to anyone lacking endurance. If you’re 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 driver’s 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 you’re 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 Mac’s 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 Mac’s screen. Though there are some page layout programs that provide the user with control over these refinements, they can’t 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. It’s 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

You’ll 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.

Adobe’s 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 Adobe’s Blue Book. The example programs aren’t quite as complex, which is good, and they’re 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 doesn’t cover PostScript, but it presents some of the mathematical concepts behind PostScript’s implementation. It also describes three-dimensional drawing and shading techniques. It’s available at technical bookstores.

Hooking Up

There are two ways to run PostScript programs on the LaserWriter. The LaserWriter’s 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, you’ll 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>. You’re 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, you’ll 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, you’ll 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 doesn’t 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. They’re 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 it’s probably most efficient if the Macintosh uses a buffer of that size as well.

The LaserStatus buffer contains a status message that’s 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 that’s 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 connection’s 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 it’s 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, it’s safe to close the connection using PapClose. One can simply close the connection after the final PapWrite’s 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 PAP’s 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 isn’t 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 printer’s AppleTalk address. Subsequent calls will execute more quickly using the node address.

The PAP manager routines described above (and a few more that aren’t 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, I’ve 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 routine’s entry point.

The source for this downloader is based on code presented in Mike Schuster’s “Laser Print DA for Postscript” (MacTutor, February 1986) and Alan Oppenheimer’s 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 -- I’ve 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 (don’t need whole file) */
#define fnfErr -43
#define eofErr -39
extern int FSFCBLen : 0x3F6;
extern int BootDrive : 0x210;
extern int SysMap : 0xA58;

/* WindowMgr.h (don’t 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 couldn’t 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, there’s no reason that the directions window should be a 
dialog, but doing so avoids the bug described in Tech Note #99 (SFD doesn’t 
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( \pCan’t 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 doesn’t 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 => There’s 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 handle’s 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 can’t 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 routine’s 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

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.