![](/file/12652/www.mactech.com.tar/www.mactech.com/sites/all/themes/custom_front/images/you_are_here_red.gif)
![](/file/12652/www.mactech.com.tar/www.mactech.com/sites/default/files/beta-site.gif)
|
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
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 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
![](/file/12652/www.mactech.com.tar/www.mactech.com/sites/all/themes/custom_front/img/search_text.gif)
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine