TweetFollow Us on Twitter

Printer Sleuth
Volume Number:3
Issue Number:3
Column Tag:Resource Roundup

Printer Sleuthing

By Joel West, Western Software Technology, MacTutor Contributing Editor

Weekend diversions

Normally, the NFL season is considered to last from early September until some time in December. However, around here, the season has ended early the last few years, as the home team has, for all practical purposes, been eliminated a few weeks into the season. When San Diego found itself well behind the four other teams in its division, I was faced with a question of what to do on a Sunday afternoon. (Presumably not a problem at MacTutor, since they’re a stone’s throw from the Anaheim Rams, who always seem to have a winning record...)

About the same time as I gave up on the Chargers, I was discussing a design issue with my friends at Silicon Beach Software, who recently released their painting/drawing program, SuperPaint. The program treats documents prepared for an Imagewriter slightly differently than those for a LaserWriter. The former are edited at 72 dots per inch, while the latter are normally shown at 75 dots per inch, since that is an even multiple of the LW’s 300 dpi resolution, allowing clean scaling and accurate positioning of bit maps and objects. (The LaserBits™ uses the actual 300 dpi resolution.)

Anyway, I was suggesting that it would be nice to pick up the target printer from the Chooser desk accessory, rather than have a separate option to specify the target printer. The latter would mean that a novice user could easily have set the document for an Imagewriter while printing on a LaserWriter, or vice versa. But, as was pointed out to me, Mac 512 owners with a single 400k internal disk can hold System, Finder, SuperPaint (about 140k) and either Imagewriter or LaserWriter, but not both. As my floppy-swapping days are only a few months remote, I could understand the problem.

Which led me to try to build a dummy LaserWriter driver, that would provide “Page Setup” functionality similar to the actual driver, but taking up less disk space.

But since Inside Macintosh doesn’t say how to write your own printer driver, I had to dig deeper into the many undefined nooks and crannies. In this article, I’ll examine the application side of printing, while in a future article, I’ll examine how a printer driver works and give some tips on how to write your own printer driver.

About Printing

When it comes time to print, what does your program do? First, it’s necessary to introduce the concept of a printer driver, corresponding to those cute little icons in the System Folder that handle whatever printer you’re using (Figure 1). How your program communicates with those drivers will be discussed later.

If you’re a good, compatible Macintosh programmer (i.e., your software will work with the next video display), everything your program draws on the Macintosh screen is done with QuickDraw. QuickDraw does everything in terms of GrafPorts, which are sometimes served ala carte, but usually part of a full course of data provided by the Window Manager or the Dialog Manager.

Fig. A Output from our PrintTest Program

To print, you just draw everything again, only this time in the printer’s GrafPort. The printer intercepts the QuickDraw operations and does whatever is necessary to translate those operations into dots on the printed page.

Of course, there are some restrictions and limitations on what operations should be used or how they should be used, particularly on the LaserWriter and LaserWriter Plus. Also, you may need to perform certain printer-specific operations, particularly involving PostScript, as will be discussed later.

But who does the work?

The specifications of the entire implementation of printing are not well defined. Part of this is because it was a late topic into Inside Macintosh; part of it is because some of the information is device-dependent, and your program should be designed to work with any device. Also, Apple expected it would write the only Macintosh printer drivers, although third party drivers have been written by independent software developers.

Normally, it’s somewhat risky to delve below the defined specifications of a system such as the Macintosh Operating System (the Toolbox isn’t much used here), since there’s no guarantee that these implementation details will remain the same in future releases. In fact, the fact that these details are not specified may mean that Apple is deliberately reserving the right to change the implementation in the future.

However, there are a few safe exceptions. Sometimes, there are de facto standards that exist that are just as ironclad as written standards, and it turns out that printing is one such case.

When you open the chapter on “The Printing Manager” in Inside Macintosh, you will see throughout the chapter the notation

[not in ROM]

What this means is that the entire Printing Manager, as such, is in the “glue” that is linked into your application when it references printing operations.

This glue, in turn, references system resources and the appropriate printer driver. Figure 2 illustrates the linkages for the LaserWriter driver; the AppleTalk ImageWriter driver is similar (the ordinary ImageWriter doesn’t use AppleTalk.)

There are no traps to implement these routines and thus, no trap routines. For a trap, Apple can issue a new ROM with new trap routines, or put a PTCH resource into the System file. However, if you link your program this afternoon (or linked it two years ago), there is exactly one version of the Printing Manager available to your program, despite whatever changes Apple might make (or, more accurately, wish it could make).

That doesn’t mean that the printer drivers can’t be improved, or new printer drivers can’t be written. Rather, it means that the specifications are fixed up to the entry points of the printer drivers.

What happens when your program prints? The Chooser desk accessory has previously selected one of the available printers, and modified a system resource accordingly. (For a full explanation, see Bob Denny’s excellent “How the Chooser Works,” in the July 1986 MacTutor, or the “Device Manager” chapter of Inside Mac, Volume IV.)

The Printing Manager opens the corresponding printer driver file. Once upon a time, this had to be on the boot disk. Nowadays, it is expected to be in the “blessed” HFS folder, i.e., the one with the Finder and System in it.

The glue code will reference the PDEF (Printer DEFinition) code in the printer driver, which does the actual printing and user dialogs. For some control calls, it will call the standard DRVR named “.Print”, which in turns calls the appropriate DRVR in the printer driver.

If you’re only printing with your application, not writing a printer driver, you’re primarily concerned with the Printing Manager calls, which will be the subject of the remainder of this article.

User-level interface

From the user’s standpoint, there are three active steps that can be taken in conjunction with printing:

1. Use the “Chooser” to select a printer. This is well covered by Denny’s earlier article, so I won’t say more here.

2. Select the “Page Setup ” dialog, referrred to as the style dialog (Figure 3). This sets printing characteristics that may affect the size or layout of the edited text. For example, selecting the (nearly obsolete) “US Legal” paper would indicate to the application that a taller drawing size is available.

3. Select the “Print ” dialog, or job dialog (Figure 4). This prepares a job immediately for printing.

The user should be able to cancel any of these steps, without any affect. And it’s not necessary, of course, for the user to go through all three steps each time, particularly for similar documents (omit step 2) on the same printer (omit step 1). It’s also possible for an application to allow printing without the third dialog, although this should be unusual.

The user should, however, ALWAYS select “Page Setup” after changing the printer using the Chooser, and it even includes a message to this effect. This allows your application to assure that its current GrafPort is consistent (for page breaks, rulers, etc.) with the actual target output device, since your application should already allow for changing the page size, orientation, and resolution, which is available to the user in the style dialog.

There are also several steps that are not seen by the user, but must be taken by an application to assure consistency of the printing operations. To see how this works first requires examining the mechanism used to communicate information about how to print a document, the print record.

Print Records

Most of the control information for printing is carried by a TPrint Pascal record (C struct.) Table 1 shows a psuedo-declaration of the TPrint record. TPrint is normally used through a TPPrint pointer or a THPrint handle.

This is only a psuedo-declaration; each of the embedded records are actually declared as new types (shown in the comments). For clarity’s sake, these embedded records are shown inline, since only two fields are directly part of the TPrint record, one of them the filler. The enumerations are also shown inline. Also not normally shown are the offsets, which are essential if you have to reverse engineer a TPrint from a hex dump, or want to debug application printing code at the assembly level.

The final field is a filler to round the record out to an even 120 bytes -- the amount of memory returned by sizeof(Tprint) in both Pascal and C, and the amount of memory you need to reserve in your document for holding the values. (See, for example, the discussion of the MacWrite file format in Technical Note #12.) Why isn’t the print record stored as a resource? It would have made sense, and there’s even a resource type used by a few applications (PREC), but most programs -- including Apple’s -- include it in the data fork.

For our purposes, there are several fields of interest. Assuming the declaration

 ph: THPrint;  { Pascal }
 THPrint ph;/* C */

then, the ph^^.prInfo ((*ph)->prInfo in C) subrecord contains most of the information the application will access directly. In particular, the program will want to know the horizontal and vertical resolution used for drawing, so that it can put up, for example, the appropriate rulers. (MacDraw worries about this, but MacWrite just cheats and always assumes the same resolution for ruler purposes.)

Most significantly, the field ph^^.prInfo.rPage gives the size of the drawing area, in units of the horizontal and vertical resolution. This works out to be 10.44 inches vertically by about 8 inches for an Imagewriter using US Letter paper. (Tall adjusted changes the horizontal resolution, but the equivalent measurement of rPage in inches remains the same.)

For the LaserWriter and LaserWriter Plus, rPage works out to be 10.11 inches by 7.66 inches using US Letter paper. This size is odd because there is not quite enough RAM on either model to image a full-page bit map. The vertical .33 inches is a missing line in MacWrite, while MacDraw rounds the horizontal 7.66 down to the nearest half inch, breaking the display into multiples of 7.5 inches.

The size of ph^^.rPaper gives the size of the actual paper, since none of the existing printers allow you to print to the margin. This is slightly wider on all four sides of rPage. As shown in Figure 5, the local coordinate origin (as QuickDraw terms it) for rPaper is the same as for rPage, in that (0,0) is the upper-left corner of the actual drawing area. As you are usually concerned with rPage and not rPaper, this is a reasonable choice. Don’t assume that there will always be a margin, since it’s conceivable that rPaper could be the same as rPage: for example, “No Gaps Between Pages” on the ImageWriter sets the top and bottom coordinates of the two rectangles (for US Letter) to 0 and 792, respectively.

Another field of interest for our purposes is the byte that tells you which printer you’re using. The upper half of the 16-bit word ph^^.prStl.wDev gives the device you’re using. Why the upper half of a word is used and not a separate field, I don’t know, but the lower half includes other information about the printing characteristics, as shown in Table 2. (The “W” is not capitalized in the name of the original Imagewriter, according to Apple product documentation, but all the other Writers are capitalized.)

Offset
 TPrint: RECORD
0iPrVersion: INTEGER
 prInfo: RECORD  { TYPE TPrInfo }
2iDev: INTEGER;
4iVRes: INTEGER; { dots per inch }
6iHRes: INTEGER; { dots per inch }
8-15    rPage: Rect;
 END;
16-23 rPaper: Rect;
 prStl: RECORD   { TYPE TPrStl }
24 wDev: INTEGER;{ printer type in upper byte }
26 iPageV: INTEGER;
28 iPageH: INTEGER;
30 bPort: CHAR;
31 feed: (feedCut,feedFanfold,feedMechCut,feedOther);
 END;
 prInfoPT: RECORD{ TYPE TPrInfo }
32 iDev: INTEGER;
34 iVRes: INTEGER;
36 iHRes: INTEGER;
38-45   rPage: Rect;
 END;
 prXInfo: RECORD { TYPE TPrXInfo }
46 iRowBytes: INTEGER;
48 iBandV: INTEGER;
50 iBandH: INTEGER;
52 iDevBytes: INTEGER;
54 iBands: INTEGER;
56 bPatScale: Byte;
57 bUlThick: Byte;
58 bUlOffset: Byte;
59 bUlShadow: Byte;
60 scan: (scanTB,scanBT,scanLR,scanRL);
61 bXInfoX: Byte;{ filler }
 END;
 prJob: RECORD   { TYPE TPrJob }
62 iFstPage: INTEGER;{ default 1 }
64 iLstPage: INTEGER;{ default 9999 }
66 iCopies: INTEGER; { not used by LaserWriter }
68 bJDocLoop: (bDraftLoop, bSpoolLoop);
69 fFromUsr: BOOLEAN;
70 pIdleProc: ProcPtr;
74 pFileName: StringPtr;
78 iFileVol: INTEGER;
80 bFileVers: Byte;
81 bJobX: Byte;  { filler }
 END;
82-119  printX: ARRAY[1..19] OF INTEGER;     { in C, 0..18}
 END;

 TPPrint = ^TPrint;{ the pointer }
 THPrint = ^TPPrint; { the handle }

Table 1: Contents of a Print Record

As you can see, except for the use of fPortrait, the interpretation of the lower byte is very device-dependent, so you shouldn’t use these values without first checking the device field to understand its interpretation (and if the upper byte is 4 or greater, don’t try to intrepret the lower byte!)

Lower byte

valueusefieldImageWriterLaserWriter
1hi resolutionfHiResQuality: BestFont Substitution: on
2portrait orientationfPortraitOrientation: (portrait) Orientation: (portrait)
4square pixelsfSqPixTall Adjusted: onSmoothing: on
8zoomed 2xf2xZoom50% Reduction: on--
16fScroll----

Table 2A: Decoding the value of ph^^.prStl.wDev

Upper byte

0 -- screen

1 bDevCItoh Imagewriter, ImageWriter Wide

ImageWriter II (with or w/o AppleTalk)

2 -- DaisyWriter (old LQP)

3 bDevLaser LaserWriter, LaserWriter Plus

Table 2B: Decoding the value of ph^^.prStl.wDev

Officially, your application is supposed to ignore much of the TPrint information -- used internally by the printer driver -- in particular the rest of prStl and the entire prInfoPT. However, as shown in Table 3, the device-dependent prStl.feed indicates how the paper is being fed, which may affect, say, some of your user instructions.

Finally, the prJob does have several fields it’s officially ok for your application to look at, such as the page range. The field prJob.bJDocLoop, shown in Table 3, indicates the steps your application needs to take to print the document, as discussed in “To Spool or not to Spool?” below.

Table 3: Other TPrint values

When 72 80 75

The fields ph^^.prInfo.iHRes and ph^^.prInfo.iVRes indicate the horizontal and vertical resolution of the print device in dots per inch. This allows your application to translate the rPage pixel units into actual inches (centimeters, etc.), such as when you display a ruler on the screen.

For the ImageWriter, the value of iVRes is always 72 dpi. If you are using the landscape printing orientation, or if you select the “Tall Adjusted” checkbox for portrait orientation, then the value of iHRes will also be 72 dpi, and circles will come out round, etc. Tall Adjusted should be default for any graphics program.

However, the default selection for the ImageWriter is 80 dpi horizontal resolution. The original fonts were designed for this resolution, and it is slightly faster. If you try portrait printing with and without Tall Adjusted, using a bit-map font (Geneva, New York), you’ll see that the slightly higher horizontal resolution produces a noticeably better result.

So much for that. Now suppose you were printing on the LaserWriter, which has a 300 dpi resolution. Wouldn’t you expect iHRes and iVRes to return 300? They don’t. How about 75, exactly 4:1? Nope.

Instead, the LaserWriter returns 72 for each, the same as the ImageWriter in Tall Adjusted (except for page size), and thus allowing earlier programs designed for the ImageWriter to always work (like MacWrite’s fixed-size ruler.)

The LaserWriter scales all coordinate locations by 300:72, which is not an integral value. However, it prints all bit maps at 300:75 (4:1) scaling. This means that a square drawn 72 pixels long (in QuickDraw coordinates) will come out 300 pixels on the LaserWriter (with LaserWriter driver 3.1), while a 72x72 bit map will come out as 288 pixels long, or 4% smaller than 300:72.

If the LaserWriter instead reported its resolution as 75 dpi, then everything would be hunky dory, since both coordinates and bit maps would be scaled 4:1. (You could also produce bit maps at 300:75, which means every 24th pixel would be 5x5 instead of 4x4 LaserWriter dots.)

One more oddity. Suppose you enter “50%” in the Reduce box for your LaserWriter, or check “50% Reduction” for the ImageWriter. You might think that would increase your resolution to 144, since that’s the number of QuickDraw points now printed on each inch of an output page.

Wrong. The resolution remains the same (again, probably, to assure compatibility for those early programs that make erroneous assumptions). Instead, your paper size is reported as being twice as large! This works out to be 16 x 20.88 or 15.33 by 20.22 respectively.

To catch this 50% reduction, the field prStl.wDev has bit f2xZoom set for the ImageWriter, while field prXInfo.iBandH contains the actual reduction (or enlargement) percentage for the LaserWriter only.

You could probably use this to correct your rulers (to the actual size) for these two printers, but it wouldn’t help you for future printers, since Apple hasn’t indicated an official mechanism for determining the actual reduction or printed page size on any printer. (Is anyone in Cupertino listening?) It’s probably just better to document for your users a consistent treatment for all printers (the false sizes) until this is fixed.

Printing steps

Figure 6 shows a flow chart of the steps your program will typically take in using the TPrint record. An application does not (usually) change any of the TPrint fields directly, instead using Printing Manager routines (which in turn call the appropriate driver) to do the actual work. However, the modified TPrint should be saved by your application with the document for future retrieval.

When you start a new document, you should allocate a relocatable TPrint, as in

ph:= TPrint(NewHandle(SIZEOF(TPrint)));
ph = (TPrint)NewHandle(sizeof(TPrint));

for Pascal and C, respectively, and then pass the handle to PrintDefault. This routine sets the default values for the currently selected printer. If you have an existing TPrint, the routine PrValidate will fix up any fields for consistency with the currently selected printer.

This handle is passed around to most of the Printing Manager calls. It’s omitted from the actual printing operations since PrOpenDoc stuffs in the GrafPort (TPrPort, actually) a copy of the handle.

When you start printing, you can specify your own background procedure that will run whenever printing is in progress, but the Printing Manager has some idle time on its hands. This is normally used to put up several pushbuttons, and allow the user to abort printing by pushing a button (rather than the Command-period provided by the Printing Manager default procedure.) To use this option, set prJob.pIdleProc to the address of your procedure.

Table 4 shows the standard results obtained from the style and job dialogs for the Imagewriter and the LaserWriter. The boolean expressions are shown in C form for compactness.

Note that several fields are very device-dependent. The ph^^.prXInfo.iBandH is used to indicate the reduction in the LaserWriter, while its original interpretation (used by the ImageWriter) has to do with the horizontal printing band size.

After any routine, there may be an error, so you should check the routine PrError for it. If you’re a speed freak, the Pascal reference

CONST
   PrintErr = $944;
TYPE
  WordPtr = ^INTEGER;
  
 IF WordPtr(PrintErr)^ <> noErr 
 THEN
   

or the C code

#define Word short /* 16-bit integer */
#define PrintErr *((Word *) 0x944)
  
 if (PrintErr != noErr)  

will grab it directly.

To spool, or not to spool?

The previous flow chart described the easiest case of printing. If bJDocLoop equals bDraftLoop (0), that’s all there is to it; the QuickDraw operations are directly translated into printer commands. This corresponds to the low-quality (Draft) ImageWriter output, which uses the printer’s ASCII output capabilitites, but necessarily ignores any graphical primitives.

Draft mode also is used for any quality for the LaserWriter, since the PostScript capabilities and built-in logic can produce the graphics directly.

However, the miracle of the original Macintosh and Imagewriter is that a simple nine-pin dot-matrix printer could produce such results. In order to do so, the Macintosh’s QuickDraw routines must be used to convert the graphics operations into a simple bit map, which is then output a line at a time to the printer.

This requires a lot of memory, and was even more of a miracle on the 128k Mac. Even more memory may be required by LaserWriter printing, for both the RAM-based AppleTalk drivers on 64k ROM machines, and for the conversion from QuickDraw to PostScript, as discussed in the next section.

As a consequence, the standard advice to assure enough memory for printing is:

• Put printing in its own segment (or in the main segment, if memory isn’t otherwise a problem.)

• Unload every other possible segment prior to printing.

• Do everything you can to avoid heap fragmentation.

The actual printing in spool mode is handled by a call to PrPicFile after the call to PrCloseDoc. The spool image may be in memory (if available) or on disk in the blessed folder.

The field prJob.iCopies may be set by the PrJobDialog for the number of copies to print. PrPicFile handles multiple copies of each page automatically; as shown later in the example, the application is responsible for sending multiple copies to the driver when printing in draft mode.

However, the LaserWriter firmware has a setting (the PostScript #copies showpage) to churn out multiple copies, so it’s not necessary for your application to send multiple copies to the LaserWriter (which always uses draft mode.) To provide compatibility, the PrJobDialog places the copy count in iRowBytes and “hides” any indication of multiple copies from the application by always setting iCopies to 1.

Postscript on PostScript

If you bought an early application in 1984, then you may have been pleasantly suprised to find out that it would print on an Apple LaserWriter released a year later. Any program that uses normal QuickDraw operations should work with any Apple printer, now or in the future.

If you know anything about printing, then you’re probably aware that the LaserWriter and LaserWriter Plus have their own printing protocol: PostScript from Adobe Systems, Inc. The issue of PostScript complicates everything said so far.

PostScript is, as previous MacTutor articles have noted, far more versatile than QuickDraw (see “Laser Print DA for PostScript” by Mike Schuster, February 1986.) Any Macintosh program that prints to either printer will eventually require PostScript commands to display an output.

Normally, the LaserWriter driver does this for you. It translates your QuickDraw commands into a special set of abbreviated codes. These are usually two- and three-letter abbreviations, while the standard PostScript normally consists of words or recognizable abbreviations (get, currentgray).

A PostScript program (the Laser Prep file) suitable for interpretting these codes is automatically downloaded by the driver when the printer is first used after it has been reset. This currently works out to be about 25k of PostScript. These codes take advantage of only a subset of PostScript corresponding to the QuickDraw view of graphics.

If you want to see the PostScript produced for a document, select “Print ” and make the appropriate PrJobDialog selections. Hold down Command-F and mouse-select the OK button; the LaserWriter status dialog will indicate that the PostScript is being saved. You will find a text-only file (MDS Edit as the creator) in the current default directory. You can also use Command-K in the same way to include the Laser Prep before the actual document.

You don’t need to have an actual LaserWriter handy to obtain the PostScript dump. All you need is the LaserWriter 3.1 driver (and Laser Prep), and to select the LaserWriter with the Chooser.

More exotic LaserWriter printing schemes are possible for higher performance, at great risk of future compatibility. Pagemaker uses its own version of Laser Prep, which can give other programs problems. Some programs output direct PostScript, since it is assumed that only the lowly ImageWriter is without that capability. However, what if Apple should (as rumor has it) introduce a new low-cost laser printer without PostScript? You’d be stuck with limited (or no) graphics performance on this new high-resolution device.

Needless to say, using QuickDraw limits your flexibility, and there are also problems with Apple’s implementation of the interpreter. Version 3.1 of LaserWriter is vastly superior to its predecessors, and you can bet that further improvements (fixing the notorious 4% shrinkage of bit maps!) are a high priority for Apple in keeping its hold on the desktop publishing market.

The official way to handle all this is to use QuickDraw. Apple is not going to abandon QuickDraw, and has incorporated it into its Apple IIGS as part of a long-term plan to merge its two computer lines. If you want to do fancier stuff, you can include some additional PostScript features (rotated text) in your QuickDraw picture, or even merge direct PostScript if necessary: see Macintosh Technical Note #91: “Optimizing for the LaserWriter.”

About the example

Finally, I’ve included PrintTest, an application that analyzes the contents of TPrint fields. It allows you to select various printing dialog options, and see the effect on the TPrint record. Since it is a printing demo, it naturally allows you to get a permanent record by printing out the result.

Since this column documents the basic Macintosh technology, I took a few days to convert my original C prototype to MPW Pascal, not a trivial chore, due to syntax differences, and because of Pascal’s abysmal (lack of) formatting utilities.

[Note: Due to the fact that MPW is still in limited release through APDA and few people have it, we have also made available a Lightspeed Pascal version, which is printed here. Both the MPW and the LSP versions are also available on the source code disk for this issue (See the MacTutor mail order store). The LSP version should be readily transportable to any Pascal system for the Mac. MPW uses a number of useful utilities and functions, but they are generally not supported by other systems, making porting more difficult. There should be no problem going from LSP back to MPW however. -Ed]

The C original used sprintf, a nifty general-purpose routine that will format many fields into a string. For Pascal, I wrote a StringFormat unit to provide a subset of these capabilities, and each sprintf has been replaced by a Pascal call for each field.

I started with my favorite example program, File by Cary Clark of Apple, a simple text editor. About two thirds was concerned with windows, scrolling, controls, etc. and wasn’t relevant, so I took it out. (If you were building an actual application, you might want to get a copy of File and add some of this code back in.)

File did include a main event loop, basic initialization, a first approach towards memory management. It displays text in a screen window using TextEdit. PrintTest adds its analysis lines to the end of a TextEdit record, and scrolls up the display so that the last line is always visible.

The printing logic had to be totally rewritten. File recalculated the line breaks for printing, since it wrapped text (both displayed and printed) to the size of the destination rectangle, while in this example, line breaks are only at returns.

File also had an elaborate scheme for background printing, using an idle procedure. PrintTest puts up a dialog box (showing progress reports) advising the user of the default Command-period option.

More important, however, are the new features necessary for the printing loop. The version of File (v1.1, May 85) used TextBox to print each page. TextBox is very slow on the LaserWriter, since it uses EraseRect. PrintTest uses DrawText for each line of text instead.

The example will check for a printing error, and puts up an alert indicating the error by number. In a real application, you would use a resource to look up the text of the error messages. Note that the logic ignores certain psuedo-errors, such as a user-requested abort. Also note that resources are used for the alert and progress strings, since an internationalizable program should include all literals as resources.

ImageWriter

PrStlDialog

Setting How to tell§

Paper: US Letter SubPt(rPaper.topleft,rPaper.botRight)

is 8.5" by 11" or 11" by 8.5"

Tall Adjusted: offÝ !(wdev & fSqPix)

50% Reduction: off !(wdev & f2xZoom)

No Gaps Between Pages: off ((rPaper.top == 0) &&

(rPaper.bottom ==

prInfo.rPage.bottom)

Orientation: (portrait)ÝÝ (wdev & fPortrait)

PrJobDialog

Quality: Faster (!(wdev & fHiRes) &&

(prJob.bJDocLoop == bSpoolLoop))

Page Range: All ((prJob.iFstPage == 1) &&

(prJob.iLstPage == 9999))

Copies: 1 (prJob.iCopies == 1)

Paper Feed: Automatic (prStl.feed == feedFanfold)

Ý Also, prInfo.iHres==80

ÝÝ Of course, (prInfo.rPage.bottom > prInfo.rPage.right)

LaserWriter

PrStlDialog

Setting How to tell§

Paper: US Letter SubPt(rPaper.topleft,rPaper.botRight)

is 8.5" by 11" or 11" by 8.5"

Font Substitution: on (wdev & fHiRes)

Smoothing: on (wdev & fSqPix)

Reduce or Enlarge: 100%Ý (prXInfo.iBandH == 100)

Orientation: (portrait)ÝÝ (wdev & fPortrait)

PrJobDialog

Copies: 1 (prJob.iRowBytes == 1)

Pages: All ((prJob.iFstPage == 1) &&

(prJob.iLstPage == 9999))

Cover Page: No (prXInfo.iBandV == 0)

Paper Source: Paper Cassette (prStl.feed == feedMechCut)

Ý This modifies prInfo.rPage and rPaper, but not prInfo.iVRes or

prInfo.iHRes, meaning that the size of the “page” (in inches) actually

changes instead of the resolution!

ÝÝ Of course, (prInfo.rPage.bottom > prInfo.rPage.right)

§ C conditional expression; for Pascal, use these equivalents:

C Pascal

== =

! NOT

&& AND

& BitAnd()

Table 4: Default dialog settings for ImageWriter and LaserWriter

PrintTest includes a page number centered at the top of each page. Since the output is so repetitive, I added this so I could debug my page selection logic.

Finally, PrintTest shows an example of how to save time printing a portion of a large document. With a little extra effort, you can check directly to see which pages the user wants from the prJob information, and print only those pages.

Suppose the user requests printout starting at page 11 of a 20-page document. You would have

prJob.iFstPage = 11
prJob.iLstPage = 9999

since 9999 is currently the default last page number set by PrJobDialog. The application would then pass all (20) pages to the Printing Manager, which would not print the first 10 pages, as counted by calls to PrOpenPage. If your application always prints all pages, the Printing Manager select only those indicated.

However, your program can skip the unnecessary pages. First, change the values to

prJob.iFstPage = 1
prJob.iLstPage = 9999

so the Printing Manager will start printing from the first page. Your application should start with page 11 and continue through to the end. iLstPage can actually be set to any value greater than or equal to 10 (20-11+1), since the application will only send 10 pages.

PrintTest keeps two separate TPrint records. One is used for analysis purposes, while the other is used for printing the analysis. Two sets of commands are provided on the File menu, one set that does the actual Page Setup and Print operations, and one that simulates these operations and analyzes the new contents of the TPrint.

PrintTest supports desk accessories, so you can use the Control Panel and Chooser da’s. Even if you don’t have all the printers, if you have the appropriate drivers in your System Folder, you can use PrintTest to try out the corresponding dialogs. For example, if you need to know the exact size of an A4 (European) page for both the LaserWriter and ImageWriter, PrintTest will show that and provide a permanent record.

Lightspeed Pascal Version
PROGRAM PrintTest;

{$I-}

{ Examine and display TPrint record values.}
{ Written by Joel West , Western Software Technology}
{ LS Pascal source conversion by D. Smith }
{Begun with an underlying skeleton , using part of }
{an example from Apple User Education}
{File : Example code for a text editor by Cary Clark , }
{Macintosh Technical Support Version 1.1 May 13 , 1985 }

{Portions copyright © 1986 by}
{ Joel West , Western Software Technology,}
{ for use by MacTutor. }


{  A document in this program (built by TextEdit) is used to}
{display the contents of a modified TPrint record.}
{There are Job & Style dialogs for modifying the}
{record, and for actually printing out the document.}

 USES
 MacPrint, MyGlobals, StringFormat, DumpTPrint, 
Windows, Printing, SetupMenus;


{------------Alert for About ------------------}
PROCEDURE AboutThisProgram;
 VAR
 itemhit : INTEGER;
 hand : StringHandle;
BEGIN
 DialogueDeactivate;
 hand := GetString(STR_id);
 ParamText(hand^^, '', '', '');
 itemhit := NoteAlert(ALRT_about, NIL);
END; {AboutThisProgram}

{---------------Handle menu command------------}
PROCEDURE DoCommand (commandkey : BOOLEAN);
 VAR
 daname : Str255;
 refnum, theMenu, theItem : INTEGER;
 menuResult :   LONGINT;
 daedit : BOOLEAN;
BEGIN
 IF commandkey THEN
 menuResult := MenuKey(theChar)
 ELSE
 menuResult := MenuSelect(myEvent.where);
 theMenu := HiWord(menuResult);
 theItem := LoWord(menuResult);
 CASE theMenu OF
 appleMenu : 
 BEGIN
 IF theItem = 1 THEN
 AboutThisProgram
 ELSE
 BEGIN
 GetItem(myMenus[appleMenu], theItem, daname);
 refNum := OpenDeskAcc(daname)
 END
 END;
 fileMenu : 
 BEGIN
 CASE theItem OF
 newItem :{New }
 OpenAWindow;
 closeItem :        {Close }
 CloseAWindow;
 stlItem :{PrStlDialog  }
 BEGIN
 PrOpen;
 DialogueDeactivate;
 IF PrStlDialog(wdh^^.theTHP) THEN
 DumpPrint('After PrintStlDialog( )', wdh^^.theTHP);
 PrClose
 END;
 jobItem :{PrJobDialog  }
 BEGIN  { just modifying TPrint }
 PrOpen;
 DialogueDeactivate;
 IF PrJobDialog(wdh^^.theTHP) THEN
 DumpPrint('After PrJobDialog( )', wdh^^.theTHP);
 PrClose
 END;
 setupItem :{Page Setup  }
 BEGIN
 PrOpen;
 DialogueDeactivate;
 IF PrStlDialog(PrintHdl) THEN
 BEGIN
 END;
 PrClose
 END;
 printItem :{Print }
 printFlag := TRUE;{Do it after segs unloaded}
 quitItem : {Quit }
 doneFlag := TRUE;
 OTHERWISE{required for LSP!}
 BEGIN
 END;
 END {CASE theItem}
 END; {fileMenu}
 editMenu : 
 daedit := SystemEdit(theitem - 1);
 OTHERWISE
 BEGIN
 END;
 END; {CASE theMenu}

 HiLiteMenu(0);
END;  {DoCommand}

{---------------The main event loop--------------}
PROCEDURE MainEventLoop;
 VAR
 tempwindow : WindowPtr; {the find window}
BEGIN
 REPEAT
 SystemTask;
 IF printFlag THEN
 BEGIN
 PrOpen;
 DoPrinting;
 PrClose
 END;
 IF GetNextEvent(everyEvent, myEvent) THEN
 BEGIN
 CASE myEvent.what OF
 mouseDown : 
 BEGIN
 CASE FindWindow(myEvent.where, tempwindow) OF
 inMenuBar : 
 DoCommand(FALSE);
 inSysWindow : 
 SystemClick(myEvent, tempwindow);
 inDrag : 
 DragWindow(tempwindow, myEvent.where, dragRect);
 inContent : 
 SysBeep(1);
 inGoAway : 
 IF TrackGoAway(tempwindow, myEvent.where) THEN
 CloseAWindow;
 OTHERWISE
 BEGIN
 END;
 END {CASE FindWindow }
 END; {of mouseDown }
 keyDown, autoKey : 
 BEGIN
 theChar := CHR(BitAnd(myEvent.message, charCodeMask));
 IF BitAnd(myEvent.modifiers, CmdKey) <> 0 THEN
 DoCommand(TRUE) { do menu equivalent }
 ELSE
 SysBeep(1);{ no typing allowed! }
 END; {of keyDown}
 activateEvt : 
 MyActivate;
 updateEvt : 
 DrawWindow;
 OTHERWISE
 BEGIN
 END;
 END; {CASE Event.what }
 CheckWindowMode;
 END  {of true GetNextEvent}
 ELSE IF (myEvent.what = nullEvent) AND doneFlag AND (FrontWindow <> 
NIL) THEN
 CloseAWindow;
{ leave lots of memory available, so unload everything }
 UnloadSeg(@SWrite); {segment StringFormat}
 UnloadSeg(@PrintLine);   {segment DumpTPrint}
 UnloadSeg(@OpenAWindow); {segment Windows}
 UnloadSeg(@DoPrinting);  {segment Printing}

 UNTIL doneFlag AND (FrontWindow = NIL);
END;

{-------- crash recovery ---------------}

PROCEDURE crash;
BEGIN
 ExitToShell;
END;

{-------------Memory initialization & Setup ---------}
PROCEDURE SetUpMemory;
BEGIN
 InitGraf(@thePort);
 InitFonts;
 InitWindows;
 InitMenus;
 TEInit;
 InitDialogs(@crash);
 InitCursor;
 MaxApplZone;
 MoreMasters;
 MoreMasters;
 MoreMasters;
 MoreMasters;

 FlushEvents(everyEvent, 0);

 watchHdl := GetCursor(WatchCursor);
 HNoPurge(Handle(watchHdl));

 printHdl := THPrint(NewHandle(SizeOf(TPrint)));
 PrOpen;
 PrintDefault(printHdl); {one used for actual printing }
 PrClose;

 Linebuff := ''; {init required for LSP}

END;


{-------------Main program-------------}

BEGIN {main program }
 SetUpMemory;
 SetUpMenus;
 SetUpWindow;
 unloadseg(@SetUpMenus);
 MainEventLoop;
 SetCursor(watchHdl^^);
END.  {main}


UNIT MyGlobals;

INTERFACE

USES
 MacPrint;

TYPE
 WordPtr = ^INTEGER;
 MyWindMode = (NullMode, OpenMode, DAMode);
 WindowData = RECORD { only one handle in wRefCon }
 theTE :  TEHandle;{ for TextEdit record }
 theTHP : THPrint; { the TPrint we are analyzing }
 END;
 WindowDataPtr = ^WindowData;
 WindowDataHandle = ^WindowDataPtr;

CONST
{ Change these to suit your taste }
 myStdFont =   monaco;
 myStdSize =   9;
 myHdgFont =   systemFont;{ aka Chicago }
 myHdgSize =   12;
{ menus }
 appleMenu =   1;
 fileMenu = 2;
 newItem =  1;
 closeItem =   2;
 stlItem =  4;
 jobItem =  5;
 setupItem =   7;
 printItem =   8;
 quitItem = 10;
 lastFileItem = 10;
 editMenu = 3;
 lastMenu = 3;   {Number of menus}
{ Resources }
 ALRT_about =  256;{ About  message }
 ALRT_printerr = 257;{ report printing error }
 DLOG_printing = 258;{ printing status dialog }
 STR_id = 256; {about message}
 STR_pagehead = 257; { page heading }
 STR_prepare =   300;{ messages for printing status }
 STR_printing = 301;
 STR_spooling = 302;
 STR_of = 303;
 STR_prspool =   304;
 STRN_scan =   256;{ enumeration literals }
 STRN_feed =   257;
 STRN_wdev =   258;
 STRN_job = 259;
 STRN_bool =   260;
 WIND_main =   256;
{Constant declared for field windowKind}
 myDocument =  8;
{ Character }
 Return = $0D;

VAR

 myWindow : WindowPtr;
 myPeek : WindowPeek;
 hTE :  TEHandle; {The active text edit handle}
 printHdl : THPrint; {for actual printing}
 myMenus : ARRAY[1..lastMenu] OF MenuHandle;
 dragRect :  Rect;
 theChar :  CHAR; {Keyboard input here}
 doneFlag : BOOLEAN;
 printFlag : BOOLEAN; {user selected 'Print ' }
 currWMode : MyWindMode;  { sets menu options }
 myEvent :  EventRecord; {Shared by all routines}
 watchHdl : CursHandle; {The wait cursor}
 wdh :  WindowDataHandle; { temporary }
 ph :   THPrint; { temporary for TPrint record }
 spare : Ptr;  {to be used by the next window}
 linebuff : Str255;

IMPLEMENTATION

END.

UNIT StringFormat;

{WHAT:  Definition of string formatting library}
{WHO: Joel West, Western Software Technology}

INTERFACE

 PROCEDURE SWrite (VAR s : Str255; c : CHAR);
 PROCEDURE SWriteHex (VAR s : Str255; n : LongInt; 
 w : INTEGER);
 PROCEDURE SWriteInt (VAR s : Str255; n : LongInt;
 w : INTEGER);
 PROCEDURE SWriteString (VAR s : Str255; s2 : Str255);

IMPLEMENTATION

{WHAT:  Implementation of UNIT StringWrite}
{WHO: Joel West, Western Software Technology}
{WHEN:  November 1986}
{HOW: Formatted output to Pascal strings.  Names match}
{Modula-2 InOut module.  Developed to replace --}
{albeit awkwardly -- use of C sprintf.}

{ As with all the Pascal equivalents, output the specified }
{ field width or the minimum necessary number of digits, } 
{ whichever is greater.}

{   format a character  }
PROCEDURE SWrite; {(var s : Str255;c : CHAR);}
 VAR
 i : INTEGER;
BEGIN
 i := length(s) + 1;
 IF i < 255 THEN
 insert(c, s, i);
END; (* SWrite *)

{    format a number in hex }
PROCEDURE SWriteHex; {(var s : Str255;n : LongInt;w :          
 INTEGER);}
 VAR
 d, i : INTEGER;
 s2 : Str255;
BEGIN
 s2 := '';
 FOR i := 1 TO w DO
 s2 := concat(s2, ' ');
 WHILE w > 0 DO
 BEGIN
 d := BitAnd(n, $0F);
 n := BitShift(n, -4); {right shift}
 IF d < 10 THEN
 IF w < 255 THEN
 BEGIN
 delete(s2, w, 1);
 insert(CHR(ORD('0') + d), s2, w)
 END
 ELSE IF w < 255 THEN
 BEGIN
 delete(s2, w, 1);
 insert(CHR(ORD('A') - 10 + d), s2, w);
 END;
 w := w - 1;
 END; {while}
 SWriteString(s, s2);
END; (* SWriteHex *)

{     format a number in decimal }
PROCEDURE SWriteInt; {(var s : Str255;n : LongInt;w :          
 INTEGER);}
 VAR
 i : INTEGER;
 s2 : Str255;
BEGIN
 NumToString(n, s2);
 i := w - Length(s2);
 WHILE i > 0 DO
 BEGIN
 SWrite(s, ' '); (* Leading spaces *)
 i := i - 1;
 END;
 SWriteString(s, s2);
END; (* SWriteInt *)

{    format a character string}
PROCEDURE SWriteString; {(var s : Str255; s2 : Str255);}
BEGIN
 s := Concat(s, s2);
END; (* SWriteString *)

END.

UNIT SetupMenus;

INTERFACE

 USES
 MacPrint, MyGlobals, Windows;

 PROCEDURE SetUpMenus;
 PROCEDURE SetUpWindow;

IMPLEMENTATION

{ These routines used only once then segment dumped }
PROCEDURE SetUpMenus;
 VAR
 counter : INTEGER;
BEGIN
 FOR counter := 1 TO lastMenu DO
 myMenus[counter] := GetMenu(counter);
 AddResMenu(myMenus[1], 'DRVR'); {desk accessories }
 FOR counter := 1 TO lastMenu DO
 InsertMenu(myMenus[counter], 0);
 DrawMenuBar;
END; {SetUpMenus}

PROCEDURE SetUpWindow;
 VAR
 r : Rect;
BEGIN
 dragRect := screenbits.bounds;
 dragRect.top := dragRect.top + 20; {room for menu bar}
 InsetRect(dragRect, 4, 4); {dragged rect on screen}
 doneFlag := FALSE;
 printFlag := FALSE;
 currWMode := nullMode;

 OpenAWindow; {WindowStuff routine}

END; {SetUpWindow}

END.


UNIT Windows;

INTERFACE

 USES
 MacPrint, MyGlobals, DumpTPrint;

 PROCEDURE CheckWindowMode;
 PROCEDURE CloseAWindow;
 PROCEDURE DialogueDeactivate;
 PROCEDURE DrawWindow;
 PROCEDURE MyActivate;
 PROCEDURE OpenAWindow;

IMPLEMENTATION

{ Windows segment }

{-----------Update menus based on windows---------}
PROCEDURE CheckWindowMode;
 VAR
 newmode : MyWindMode;
 fileset : SET OF 1..lastFileItem;
 item : INTEGER;
BEGIN { This routine sets menu items based on window mode }
 myPeek := WindowPeek(FrontWindow);
 IF myPeek = NIL THEN
 newmode := NullMode { no windows open }
 ELSE IF myPeek^.windowKind = MyDocument THEN
 newmode := OpenMode { document window on top }
 ELSE
 newmode := DAMode;{ assume must be D.A. on top }
 IF newmode <> currWMode THEN { Must change menus }
 BEGIN
 CASE newmode OF
 NullMode : { No windows open }
 fileset := [newItem, quitItem];
 OpenMode : { One window open and on top }
 fileset := [closeItem, stlItem, jobItem, setupItem, printItem, quitItem];
 DAMode : { DA on top }
 fileset := [closeItem, quitItem];
 OTHERWISE
 BEGIN
 END;
 END; {CASE newmode}
 FOR item := 1 TO lastFileItem DO
 IF item IN fileset THEN
 EnableItem(myMenus[fileMenu], item)
 ELSE
 DisableItem(myMenus[fileMenu], item);

 IF newmode = DAMode THEN
 EnableItem(myMenus[editMenu], 0)
 ELSE
 DisableItem(myMenus[editMenu], 0);

 DrawMenuBar;    { menu dimming must be updated }
 currWMode := newmode;
 END; {IF newmode <> currWMode}
END; {CheckWindowMode}

{----------Close the front window----------}
PROCEDURE CloseAWindow;
BEGIN
{ This routine closes an appl (or DA) window, either after}
{• clicking go-away box   }
{• selecting "Close" in File menu  }
 myPeek := WindowPeek(FrontWindow);
IF myPeek^.windowKind = myDocument THEN
 BEGIN
 wdh := WindowDataHandle(GetWRefCon(WindowPtr(myPeek)));
 ph := wdh^^.theTHP;
 DisposHandle(Handle(ph));
 TEDispose(hTE);
 hTE := NIL;
 DisposHandle(Handle(wdh));
 DisposeWindow(myWindow);
 END {myDocument window}
ELSE { Must be a DA }
 CloseDeskAcc(myPeek^.windowKind)
END; {CloseAWindow}

{-----------------Deactivate before dialog----------}
{Deactivate the top window if we're about to put up a dialog}
PROCEDURE DialogueDeactivate;
 VAR
 temprect : Rect;
BEGIN
 SetCursor(arrow);
 IF hTE <> NIL THEN {for documents, only}
 TEDeactivate(hTE);
END; {DialogueDeactivate}

{---------Draw a document window----------}
{ Handles window Update Event}
PROCEDURE DrawWindow;
 VAR
 tempport : GrafPtr;
 temprect, rectToErase : Rect;
 temppeek : WindowPeek;
 whichwindow : WindowPtr;
 temphTE : TEHandle;
BEGIN
 whichwindow := WindowPtr(myEvent.message);
 BeginUpdate(whichwindow);
 GetPort(tempport);
 SetPort(whichwindow);
 temppeek := WindowPeek(whichwindow);
 IF temppeek^.windowKind = myDocument THEN
 BEGIN
 temprect := whichwindow^.Portrect;
 wdh := WindowDataHandle(GetWRefCon(whichwindow));
 temphTE := wdh^^.theTE;
 SetRect(temprect, -32767, -32767, 32767, 32767);
 ClipRect(temprect);
 {erases the window past the end of text, if any}
 WITH temphTE^^ DO
 IF nLines < (viewRect.bottom - viewRect.top + lineHeight) DIV lineHeight 
THEN
 BEGIN
 rectToErase := viewRect;
 rectToErase.top := (nLines) * lineHeight;
 EraseRect(rectToErase)
 END; {nLines}
 TEUpdate(whichwindow^.visRgn^^.rgnBBox, temphTE)
 END; {myDocument stuff}
 SetPort(tempport);
 EndUpdate(whichwindow)
END; {DrawWindow}

{-------------Handle (de)activate events-----------}
PROCEDURE MyActivate;

BEGIN {This activates or deactivates current selection}
 myWindow := WindowPtr(myEvent.message);
 myPeek := WindowPeek(myWindow);
 IF myPeek^.windowKind = myDocument THEN
 BEGIN  { document window }
 wdh := WindowDataHandle(GetWRefCon(myWindow));
 hTE := wdh^^.theTE;
 IF ODD(myEvent.modifiers) THEN 
 { BitAnd(myEvent.modifiers,activeFlag)>0 }
 TEActivate(hTE) {this window is now top most}
 ELSE   {this window is no longer top most}
 BEGIN
 TEDeactivate(hTE);
 hTE := NIL {a TextEdit window is no longer on top}
 END;
 END;
END; {MyActivate}

{-------------Create a new document window-------}
PROCEDURE OpenAWindow;

 VAR
 r : Rect;

BEGIN {A window is created here}

 myWindow := GetNewWindow(WIND_main, NIL, Pointer(-1));
 wdh := WindowDataHandle(NewHandle(SIZEOF(WindowData)));
 SetWRefCon(myWindow, ORD(wdh)); 
 { stash pointer to TEHandle in window }

 SetPort(myWindow);
 myPeek := WindowPeek(myWindow);
 TextFont(myStdFont);
 TextSize(myStdSize);
 DrawChar(' ');
 SetFontLock(TRUE);
 myPeek^.windowKind := myDocument; {id type of window}

 r := myWindow^.Portrect;
 InsetRect(r, 8, 4);
 hTE := TENew(r, r);
 wdh^^.theTE := hTE;
 hTE^^.destRect := hTE^^.viewRect;
 hTE^^.crOnly := -1; { no automatic CR }

 PrOpen;
 ph := THPrint(NewHandle(SIZEOF(TPrint)));
 PrintDefault(ph);
 wdh^^.theTHP := ph;
 DumpPrint('After PrintDefault( )', ph);
 PrClose;

END; {OpenAWindow}
END.


UNIT DumpTPrint;
INTERFACE
 USES
 MacPrint, MyGlobals, StringFormat;
 PROCEDURE PrintLine;
 PROCEDURE PrintTab;
 PROCEDURE PrintHex (n : LONGINT; w : INTEGER);
 PROCEDURE PrintInt (n : LONGINT);
 PROCEDURE PrintString (s : Str255);
 PROCEDURE PrintStrNum (s : Str255; n : LONGINT);
 PROCEDURE PrintStrHex (s : Str255; n : LONGINT;
 w : INTEGER);
 PROCEDURE DumpEnum (msg : Str255; val, resid : INTEGER);
 PROCEDURE DumpRect (msg : Str255; r : Rect);
 PROCEDURE DumpPrInfo (msg : Str255; prinf : TPrInfo);
 PROCEDURE DumpPrXInfo (msg : Str255; prxi : TPrXInfo);
 PROCEDURE DumpPrStl (msg : Str255; ps : TPrStl);
 PROCEDURE DumpPrJob (msg : Str255; pj : TPrJob);
 PROCEDURE DumpPrintX (msg : Str255; tpp : TPPrint);
 PROCEDURE DumpPrint (msg : Str255; hand : THPrint);

IMPLEMENTATION

{-----------Add a line to document-------------}
PROCEDURE PrintLine;
 VAR
 p : Ptr;
 c : SignedByte;
BEGIN   {this adds the line to end of the display}
 p := @linebuff;
 TEInsert(Pointer(ORD4(p) + 1), Length(linebuff), hTE);
 c := Return;
 TEInsert(@c, 1, hTE);  { add CR }
 WITH hTE^^ DO   { Check if beyond bottom of page }
 IF (lineHeight * nLines) > (viewRect.bottom - viewRect.top) THEN
 TEScroll(0, -hTE^^.lineHeight, hTE);{ scroll up one line }
 linebuff := '';
END; {PrintLine}

{------------Formatting utilities----------------}
PROCEDURE PrintTab;
 VAR
 col, nexttab : INTEGER;
BEGIN   { add spaces to next multiple of 8 }
 col := Length(linebuff);
 nexttab := col - INTEGER(BitAnd(col, 7)) + 8;
 WHILE col < nexttab DO
 BEGIN
 col := col + 1;
 IF col < 255 THEN
 insert(' ', linebuff, col);
 END;
END;
PROCEDURE PrintHex; {(n : LONGINT; w : INTEGER );}
BEGIN
 SWriteHex(linebuff, n, w); { actual width }
END;
PROCEDURE PrintInt; {(n : LONGINT);}
BEGIN
 SWriteInt(linebuff, n, 0); { minimum width }
END;

PROCEDURE PrintString; {(s : Str255);}
BEGIN
 SWriteString(linebuff, s);
END;
PROCEDURE PrintStrNum; {(s : Str255; n : LONGINT );}
BEGIN   { format a string and integer }
 PrintTab;
 SWriteString(linebuff, s);
 SWriteInt(linebuff, n, 0); { minimum width }
END;
PROCEDURE PrintStrHex; {(s : Str255;n: LONGINT;w : INTEGER);}
BEGIN   { format a string and hex }
 PrintTab;
 SWriteString(linebuff, s);
 SWriteHex(linebuff, n, w);
END;
{ ------------- Format enumeration ------------ }
PROCEDURE DumpEnum; {(msg : Str255; val, resid : INTEGER);}
 VAR
 s : Str255;
 rh : Handle;
 limitp : WordPtr;
 err : boolean;
BEGIN
 err := TRUE;
 PrintTab;
 PrintString(msg);
 rh := GetResource('STR#', resid);
 IF (rh <> NIL) THEN { if we screwed up, don't try to format }
 BEGIN
 limitp := WordPtr(rh^);  { number of strings }
 IF (val >= 0) AND (val < limitp^) THEN
 BEGIN  { in range defined }
 GetIndString(s, resid, val + 1);
 PrintString(s);
 err := FALSE;
 END
 END;
 IF err THEN
 PrintInt(val);  { no string, show the integer }
END;

{ --------------- Format Rect ------------- }
PROCEDURE DumpRect; {(msg : Str255; r : Rect);}
 BEGIN
 PrintString(msg);
 PrintString(': {');
 PrintInt(r.top);
 PrintString(', ');
 PrintInt(r.left);
 PrintString(', ');
 PrintInt(r.bottom);
 PrintString(', ');
 PrintInt(r.right);
 PrintString('}');
 PrintLine;
 END;
{ -------------- Format TPrInfo ----------- }
PROCEDURE DumpPrInfo; {(msg : Str255; prinf : TPrInfo);}
 BEGIN
 PrintString(msg);
 PrintStrNum('iDev: ', prinf.iDev);
 PrintStrNum('iVRes: ', prinf.iVRes);
 PrintStrNum('iHRes: ', prinf.iHRes);
 PrintLine;
 DumpRect('rPage', prinf.rPage);
 END;
{ ------------- Format TPrXInfo ------------- }
PROCEDURE DumpPrXInfo; {(msg : Str255; prxi : TPrXInfo);}
 BEGIN
 PrintString(msg);
 PrintStrNum('iRowBytes: ', prxi.iRowBytes);
 PrintStrNum('iBandH: ', prxi.iBandV);
 PrintStrNum('iBandV: ', prxi.iBandH);
 PrintLine;
 PrintStrNum('iDevBytes: ', prxi.iDevBytes);
 PrintStrNum('iBands: ', prxi.iBands);
 PrintLine;
 PrintStrNum('bPatScale: ', prxi.bPatScale);
 PrintStrNum('bUlThick: ', prxi.bUlThick);
 PrintStrNum('bUlOffset: ', prxi.bUlOffset);
 PrintStrNum('bUlShadow: ', prxi.bUlShadow);
 PrintLine;
 DumpEnum('scan: ', ORD(prxi.scan), STRN_scan);
 PrintStrNum('bXInfoX: ', prxi.bXInfoX);
 PrintLine;
 END;
{ ------------- Format TPrStl ------------ }
PROCEDURE DumpPrStl; {( msg : Str255;ps : TPrStl);}
 BEGIN
 PrintString(msg);
 PrintStrHex('wDev: $', ps.wDev, 4);
 DumpEnum('(', BitShift(ps.wDev, -8), STRN_wdev);
 PrintString(')');
 PrintLine;
 PrintStrNum('iPageV: ', ps.iPageV);
 PrintStrNum('iPageH: ', ps.iPageH);
 PrintStrNum('bPort: ', ps.bPort);
 DumpEnum('feed: ', ORD(ps.feed), STRN_feed);
 PrintLine;
 END;
{ ------------- Format TPrJob ------------- }
PROCEDURE DumpPrJob; {(msg : Str255; pj : TPrJob);}
 BEGIN
 PrintString(msg);
 PrintStrNum('iFstPage: ', pj.iFstPage);
 PrintStrNum('iLstPage: ', pj.iLstPage);
 PrintStrNum('iCopies: ', pj.iCopies);
 DumpEnum('bJDocLoop: ', ORD(pj.bJDocLoop), STRN_job);
 PrintLine;
 DumpEnum('fFromUsr: ', ORD(pj.fFromUsr), STRN_bool);
 PrintStrHex('pIdleProc: ', ORD4(pj.pIdleProc), 8);
 PrintStrHex('pFileName ', ORD4(pj.pFileName), 8);
 PrintLine;
 PrintStrNum('iFileVol: ', pj.iFileVol);
 PrintStrNum('pFileVers: ', pj.bFileVers);
 PrintStrNum('bJobX: ', pj.bJobX);
 PrintLine;
 END;
{ ------------ Format printX Array ---------- }
PROCEDURE DumpPrintX; {( msg : Str255;tpp : TPPrin    );}
 VAR
 i, max : INTEGER;
BEGIN { Outputs non-zero values, if any}
max := 0;
FOR i := 1 TO 19 DO
 IF (tpp^.printX[i] <> 0) THEN
 max := i;  { ignore trailing zeroes }
IF (max > 0) THEN
 BEGIN
 PrintString(msg);
 FOR i := 1 TO max DO
 BEGIN
 PrintStrNum('[', i);
 PrintString(']: ');
 PrintHex(tpp^.printX[i], 4);
 IF (((i MOD 4) = 0) OR (i = max)) THEN
 PrintLine; { every 4th or last one }
 END
 END
END;
{ -------------- Format TPrint ------------ }
PROCEDURE DumpPrint; {(msg : Str255;hand : THPrint);}
 VAR
 tpp : TPPrint;
 i : INTEGER;
BEGIN
 HLock(Handle(hand));
 tpp := hand^; { pointer to a TPrint }
 PrintLine;
 PrintString(msg);
 PrintLine;
 FOR i := 1 TO Length(msg) DO
 PrintString('-');
 PrintLine;
 PrintString('iPrVersion: ');
 PrintInt(tpp^.iPrVersion);
 PrintLine;
 DumpPrInfo('prInfo', tpp^.prInfo);
 DumpRect('rPaper', tpp^.rPaper);
 DumpPrStl('prStl', tpp^.prStl);
 DumpPrInfo('prInfoPT', tpp^.prInfoPT);
 DumpPrXInfo('prXInfo', tpp^.prXInfo);
 DumpPrJob('prJob', tpp^.prJob);
 DumpPrintX('printX', tpp);
 PrintString('------------------------------------
 ------------------------------------------');
 PrintLine;
 HUnLock(Handle(hand));
END; {DumpPrint}
END.


UNIT Printing;

INTERFACE

 USES
 MacPrint, MyGlobals, Windows;

 PROCEDURE DoPrinting;


IMPLEMENTATION

{---------Print out a document window-----------}
PROCEDURE DoPrinting;

CONST
 bottommargin = 20; { pixel margin inset from rPage }
 leftmargin = 30;
 rightmargin = 10;
 topmargin = 36;

VAR
 txth : Handle;
 printTE : TEHandle;
 MyPPort : TPPrPort;
 dlogptr : DialogPtr;
 txtptr : Ptr;
 linesperpage, height, firstoffset: INTEGER; 
 lastoffset, leftpos, toppos: INTEGER; 
 fstpos, lineno, lastline, linecount: INTEGER; 
 pageno, firstpage, lastpage, numpages: INTEGER;
 copyno, numpasses, dummyitem, errno : INTEGER;
 pagerect : Rect;
 currstr, laststr, heading : Str255;
 strh0, strh1, hdgstrh : StringHandle;
 status : TPrStatus;
 info : FontInfo;
 lastonpage : ARRAY[0..99] OF INTEGER; { last line # on each page }

{ NOTES }

{  This section images each page, using QuickDraw via }
{  TextEdit. A few special cases:}
{  1. For spooled output (IW only), image and then print}
{2. For IW draft mode, must send multiple copies ourself}
{   This has been rewritten from skeleton code, for a number}
{   of key reasons:}
{     1. A location is found for a line, then DrawText is}
{   used to draw the line.  This requires setting the font}
{   directly in the printing GrafPort.  The skeleton used }
{   TextBox for each page; TextBox uses EraseRect which, }
{   according to Technical Note #72, is very slow on the }
{ LaserWriter.}
{2. We use crOnly, so only returns are used for line }
{   breaks.  Thus, we don't need a new TECalText for the}
{   printing destRect, but instead use the TextEdit }
{   lineStarts established for display purposes.}
{3. This routine figures out the actual pages selected }
{   and then prints only those pages.  (The values of }
{   prJob.iFstPage and iLstPage need to be fudged to }
{do this.)}
{4. Put a heading on each page, showing page number.}
{5. Put up an Alert if a printing error is encountered.}  
{Not strictly necessary, since the most commonly found}
{   "errors" are user- specified aborts that should be }
{ignored.}

BEGIN
printFlag := FALSE;{ so we don't print again }
DialogueDeactivate;
IF PrJobDialog(printHdl) THEN
 BEGIN
 SetCursor(watchHdl^^); { Put up progress dialog }
 strh0 := GetString(STR_prepare);
 ParamText(strh0^^, '', '', '');
 dlogptr := GetNewDialog(DLOG_printing, NIL, Pointer(-1));
 DrawDialog(dlogptr);
 printTE := hTE;
 { Calculate # of pages & line numbers for each page }
 WITH printTE^^, printHdl^^.PrInfo DO
 BEGIN
 txth := hText;
 height := lineHeight;
 linecount := nLines;
 linesperpage := (rPage.bottom - rPage.top - bottommargin - topmargin) 
DIV height;
 pagerect := rPage;{ top margin allows for heading }
 pagerect.left := pagerect.left + leftmargin;
 pagerect.right := pagerect.right - rightmargin;
 pagerect.bottom := pagerect.top + topmargin + (linesperpage * height);
 fstpos := pagerect.top + topmargin + fontAscent;
      { base line of first line of text in document }
 END; {WITH}
 lastonpage[0] := 0;
 pageno := 1;
 lineno := 0;
 WHILE lineno < linecount DO{ until out of pages }
 BEGIN
 lineno := lineno + linesperpage;
 IF lineno < linecount THEN { all but last page }
 lastonpage[pageno] := lineno - 1 { last line }
 ELSE   { last page }
 lastonpage[pageno] := linecount - 1;
 { lines numbered 0..n }
 pageno := pageno + 1;
 END; {WHILE lineno}
 numpages := pageno - 1;

{ We could skip page calculations, but then we would image }
{ all pages and Print Manager would print only those}
{ selected.  Obviously this is inefficient for }
{  large documents.  Instead, fool Print Manager into}
{  thinking enough pages are selected and then do }
{  actual printing starting at the selected page. }  
{  This MUST be done before PrOpenDoc. }

 WITH printHdl^^.PrJob DO
 BEGIN
 firstpage := iFstPage; { page numbers requested }
 IF firstpage < 1 THEN
 firstpage := 1;
 lastpage := iLstPage;
 IF lastpage > numpages THEN
 lastpage := numpages;  { limit to available pages }
 numpages := lastpage - firstpage + 1; { actual length }
 iFstPage := 1;  { fool print manager }
 iLstPage := numpages;  { reset by next PrJobDialog }

{ Manual handling of multiple copies for draft mode only}
{  ImageWriter spooling handles this directly; the }
{  LaserWriter PrJobDialog always sets iCopies := 1 and }
{  hides the actual number of copies from us }
{  Also set up appropriate progress message }

 IF bJDocLoop = bSpoolLoop THEN
 BEGIN
 numpasses := 1; { only one pass through }
 strh0 := GetString(STR_spooling); {"Now spool.. "}
 END
 ELSE
 BEGIN
 numpasses := iCopies;  { draft, multiple passes }
 strh0 := GetString(STR_printing);{"Now print.. "}
 END;
 END; {WITH}
 strh1 := GetString(STR_of);{ " of " }
 hdgstrh := GetString(STR_pagehead); { "Page " }

{ Now do actual printing (or imaging, for spool mode }
{  Get a drawing port: TPrint should be frozen by now }
{  Go through it once for every copy (if necessary) }
{  and once per page. Show dialog progress in terms of }
{pages to be printed }

 MyPPort := PrOpenDoc(printHdl, NIL, NIL);
 NumToString(numpages, laststr); { # of pages to print }
 FOR copyno := 1 TO numpasses DO
 BEGIN
 MoveHHi(txth);
 HLock(txth);
 txtptr := txth^;

 FOR pageno := firstpage TO lastpage DO
 BEGIN  { Image each page; does printing draft mode }
 IF PrError = noErr THEN
 BEGIN
 NumToString(pageno - firstpage + 1, currstr);                 {relative 
page #}
 ParamText(strh0^^, currstr, strh1^^, laststr);
 DrawDialog(dlogptr);{ update the status }
 PrOpenPage(MyPPort, NIL); { changes GrafPort  }
  { First put a heading on the page.  Since MoveTo location }
  {  for drawing text is the base line, need ascent to } 
  {position heading within pagerect }
 TextFont(myHdgFont);
 TextSize(myHdgSize);
 GetFontInfo(info);{ need ascent height }
 NumToString(pageno, heading);{ abs. page # }
 heading := Concat(hdgstrh^^, heading); {Page 1 }
 WITH pagerect DO
 BEGIN
 leftpos := left + ((right - left - StringWidth(heading)) DIV 2);
 { center }
 MoveTo(leftpos, top + info.ascent);{base line }
 DrawString(heading); { print page heading }
 { Now print actual document for this page }
 leftpos := left;{ left margin for text }
 toppos := fstpos; { base line for 1st line }
 END;
 TextFont(printTE^^.txFont);{ set for display }
 TextSize(printTE^^.txSize);
 lineno := lastonpage[pageno - 1];{ line of TERec }
 firstoffset := printTE^^.lineStarts[lineno];
 lastline := lastonpage[pageno];
 { Draw each line in TERec, except CR at line end. }
 WHILE lineno <= lastline DO
 BEGIN
 MoveTo(leftpos, toppos);
 lineno := lineno + 1;
 IF lineno >= linecount THEN
 lastoffset := printTE^^.teLength  {last}
 ELSE
 lastoffset := printTE^^.lineStarts[lineno] - 1;
 DrawText(txtptr, firstoffset, lastoffset - firstoffset);
 toppos := toppos + height;
 firstoffset := lastoffset + 1;
 END; {each line}
 PrClosePage(MyPPort);  { done with this page }
 END; {If no Prerror}
 END; {for each page}

 HUnLock(txth);
 END; {each copy}
 PrCloseDoc(MyPPort);
 { If spooled, the file isimaged and need to print it }
 IF (printHdl^^.prJob.BJDocLoop = BSpoolLoop) AND (PrError = noErr) THEN
 BEGIN
 strh0 := GetString(STR_prspool);{ "Now spool.." }
 ParamText(strh0^^, '', '', '');
 DrawDialog(dlogptr);
 PrPicFile(printHdl, NIL, NIL, NIL, status);
 END;
 { Drop the advice dialog }
 DisposDialog(dlogptr);
 SetCursor(arrow);
 errno := PrError;
 IF (errno <> noErr) AND (errno <> iPrAbort) AND (errno <> iIOAbort) 
THEN  { indicate a printing error, unless  }
 { user hit command-period }
 { user cancel on "not responding" alert }
 BEGIN
 NumToString(errno, currstr); { error number }
 ParamText(currstr, '', '', '');
 dummyitem := StopAlert(ALRT_printerr, NIL);
 END;
 END {IF PrJobDialog}
ELSE { Cancel in PrJobDlog }
 PrSetError(iPrAbort);
END; {DoPrinting}

END.

* PrintTest.R
* Copyright © 1986 by 
*Joel West 
*  Western Software Technology
*    for MacTutor

PrintTest.RSRC
????????

Type JWES = STR 
PrintTest,0
PrintTest by Joel West, Version 1.0: 30-Nov-86

Type FREF
PrintTestAppl,128
APPL 0

Type BNDL
PrintTest,128
JWES 0
ICN#
0 128 
FREF 
0 128 

* ------ Switcher events ---------

Type SIZE = GNRL
 ,-1
.I
16384 ;;set bit 14 for resume
.L
98304 ;; 128K preferred
.L
98304 ;; 128K minimum

* ------------- Alerts ----------

TYPE ALRT
 ,256
40 131 140 381
256
4444

TYPE ALRT
 ,257
40 131 140 381
257
F765

* --------- Dialogs --------------

type DLOG
 ,258
Print Messages
100 120 200 392 
Visible NoGoAway 
1
0
258

type DITL
 ,258
1

StatText Disabled
15 40 85 232 
^0^1^2^3 \0D\0D ++
To cancel, type \11-.

type DITL
 ,256
2

BtnItem 
60 105 80 175 
OK

StatText Disabled
10 64 42 264
^0

type DITL
 ,257
2

BtnItem 
70 60 90 130 
OK

StatText Disabled
10 64 58 172
Printing error, ID = ^0.


* ------------- menus -------------

Type MENU
* the desk acc menu
 ,1
\14;;apple menu
 About PrintTest 
 (-

* the file menu
 ,2
File
 (New
 Close
 (-
 PrStlDialog  /S
 PrJobDialog  /J
 (-
 Page Setup 
 Print  /P
 (-
   Quit /Q


* the edit menu
 ,3
Edit
 (Undo /Z
 (-
 Cut /X
 Copy /C
 Paste /V
 Clear

TYPE WIND
 ,256
PrintTest
46 8 327 507
Visible GoAway
4
0

TYPE STR 
 ,256
PrintTest by Joel West\0D ++
Version 1.0: 30-Nov-86

TYPE STR 
 ,257
Page 

TYPE STR 
 ,300
Preparing document for printing

TYPE STR 
 ,301
Now printing page 

TYPE STR 
 ,302
Now spooling page 

TYPE STR 
 ,303
 of 

TYPE STR 
 ,304
Now printing ++ 
spooled document 

Type STR#
 ,256
4
scanTB
scanBT
scanLR
scanRL

Type STR#
 ,257
4
feeCut
feedFanfold
feedMechCut
feedOther

Type STR#
 ,258
4
screen
Imagewriter
Daisywriter
LaserWriter

Type STR#
 ,259
2
bDraftLoop
bSpoolLoop

Type STR#
 ,260
2
false
true

TYPE ICN# = GNRL
PrintTestAppl,128
.H
0000 0000 0000 0000 
1FFF FFF0 1000 0010 
12A0 0010 1000 0010 
13C0 0010 1240 0010 
1240 E010 13C0 E010 
13C0 0010 1000 0010 
1000 0010 1000 0010 
8FFF FF91 5000 0052 
307F F83C 0800 0010 
087F F810 0800 0010 
087F F810 0800 0010 
0800 0010 0FFF FFF0 
0000 0000 0000 0000 
0000 0000 0000 0000 
0000 0000 0000 0000 
0000 0000 0000 0000 
* mask
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 
FFFF FFFF FFFF FFFF 

 

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.