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 theyre a stones 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 LWs 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 doesnt say how to write your own printer driver, I had to dig deeper into the many undefined nooks and crannies. In this article, Ill examine the application side of printing, while in a future article, Ill 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, its necessary to introduce the concept of a printer driver, corresponding to those cute little icons in the System Folder that handle whatever printer youre using (Figure 1). How your program communicates with those drivers will be discussed later.
If youre 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 printers 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, its somewhat risky to delve below the defined specifications of a system such as the Macintosh Operating System (the Toolbox isnt much used here), since theres 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 doesnt 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 doesnt mean that the printer drivers cant be improved, or new printer drivers cant 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 Dennys 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 youre only printing with your application, not writing a printer driver, youre primarily concerned with the Printing Manager calls, which will be the subject of the remainder of this article.
User-level interface
From the users 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 Dennys earlier article, so I wont 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 its 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). Its 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 claritys 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 isnt the print record stored as a resource? It would have made sense, and theres even a resource type used by a few applications (PREC), but most programs -- including Apples -- 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. Dont assume that there will always be a margin, since its 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 youre using. The upper half of the 16-bit word ph^^.prStl.wDev gives the device youre using. Why the upper half of a word is used and not a separate field, I dont 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 shouldnt use these values without first checking the device field to understand its interpretation (and if the upper byte is 4 or greater, dont try to intrepret the lower byte!)
Lower byte
value | use | field | ImageWriter | LaserWriter
|
1 | hi resolution | fHiRes | Quality: Best | Font Substitution: on
|
2 | portrait orientation | fPortrait | Orientation: (portrait) | Orientation: (portrait)
|
4 | square pixels | fSqPix | Tall Adjusted: on | Smoothing: on
|
8 | zoomed 2x | f2xZoom | 50% Reduction: on | --
|
16 | | fScroll | -- | --
|
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 its 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), youll 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. Wouldnt you expect iHRes and iVRes to return 300? They dont. 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 MacWrites 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 thats 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 wouldnt help you for future printers, since Apple hasnt indicated an official mechanism for determining the actual reduction or printed page size on any printer. (Is anyone in Cupertino listening?) Its 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. Its 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 youre 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), thats 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 printers 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 Macintoshs 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 isnt 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 its 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 youre 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 dont 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? Youd 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 Apples 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, Ive 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 Pascals 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 wasnt 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 das. Even if you dont 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