TweetFollow Us on Twitter

MACINTOSH C
MACINTOSH C: A Hobbyist's Guide To Programming the Mac OS in C
Version 2.3

� 2000 K. J. Bricknell

Go to Contents

(Chapter 15)

PRINTING

A link to the associated demonstration program listing is at the bottom of this page



The Printing Manager

The Printing Manager is a collection of system software functions that your application can use to print to any type of connected printer using the same QuickDraw functions that your application uses for screen display. When printing, your application calls the same Printing Manager functions regardless of the type of printer selected by the user.

You can use the Printing Manager to:

  • Print documents.

  • Display and alter printing dialog boxes.

  • Handle printing errors.
To use the Printing Manager, you must first initialise QuickDraw, the Font Manager, the Window Manager, the Menu Manager, TextEdit, and the Dialog Manager.

Printer Drivers

The Printing Manager uses a printer driver to do the actual printing. A printer driver does any necessary translation of QuickDraw drawing functions and, when requested by your application, sends the translated instructions and data to the printer.

Printer drivers are stored in printer resource files, which are located in the Extensions folder inside the System Folder. Each type of printer has its own printer driver. The current printer is the printer driver that actually implements the functions defined by the Printing Manager.

The current printer is the printer that the user last selected from the Chooser.

Types and Characteristics of Printer Drivers

In general, there are two types of types of printer driver:

  • QuickDraw printer drivers.

  • PostScript printer drivers.

QuickDraw Printer Drivers

QuickDraw printer drivers render images using QuickDraw and then send the rendered images to the printer as bitmaps or pixel maps. Since they rely on the rendering capabilities of the Macintosh computer, QuickDraw printers are not required to have any intelligent rendering capabilities. Instead, they simply accept instructions from the printer driver to place dots on the page in specified places.

A QuickDraw printer captures the image of an entire page either in memory or in a temporary disk file known as a spool file, translates the pixels into dot placement instructions, and sends these instructions to the printer.

Given that over 7 million pixels are required to render an 8-by-10-inch image at 300 dots per inch, QuickDraw printers are relatively slow; accordingly, many QuickDraw printers use some form of data compression to improve their performance. The large memory requirements involved in printing to a colour printer using 8 bits per pixel may require the driver to process the image in horizontal strips, which further impairs printing speed.

PostScript Printers

Unlike QuickDraw printers, PostScript printers have their own rendering capabilities. Instead of rendering the entire page on the Macintosh computer and sending all the pixels to the printer, PostScript printer drivers convert QuickDraw operations into equivalent PostScript operations and send the resulting drawing commands directly to the printer. The printer then renders the images by interpreting these commands. In this way, image processing is offloaded from the computer.

Whereas QuickDraw printer drivers must capture an entire page before sending any of it to the printer, PostScript printer drivers are able to send commands as soon as they are generated. Although this results in faster printing, it does not allow the driver to examine entire pages for their use of colour, fonts, or other resources that the printer needs to have specially processed. Accordingly, some PostScript printer drivers may capture page images in a spool file so that the driver can analyse the pages before sending them to the printer.

Background Printing, Deferred Printing, and Spool Files

Some printer drivers allow users to specify background printing, which allows a user to work with an application while documents are printing in the background. These printer drivers send printing data to a spool file in the PrintMonitor Documents folder in the System Folder.

Some QuickDraw printer drivers provide two methods of printing documents: deferred printing and draft-quality. Deferred printing was designed to allow ImageWriter printers to spool a page image to disk when printing under the low memory conditions of the original 128 KB Macintosh. With deferred printing, a printer driver records each page of the document's printed image in a structure similar to a QuickDraw picture, which the printer driver writes to a spool file. PrPicFile is then used to instruct these drivers to turn the QuickDraw picture into bit images and send them to the printer.

Do not confuse the different uses of spool files. With background printing, print files are spooled to disk so that the user can work with an application while documents are printing. You do not need to use PrPicFile to send these spool files to the printer - in fact, there is no reliable way to determine whether a printer driver is using a spool file for background printing. A spool file created by a printer driver using deferred printing is another matter. (As will be seen, you can readily determine whether a printer driver is using deferred printing.)

Printer Drivers and Picture Comments

For most applications, sending QuickDraw's picture-drawing functions to the printer driver is sufficient. However, some applications may rely on printer drivers to provide several features (for example, rotated text or dashed lines) which are not available, or which are difficult to achieve, using QuickDraw. If your application requires these features, you may want to create two versions of your drawing code: one that uses picture comments to take advantage of these features on capable printers, and another that provides QuickDraw approximations of those features.

Picture comments are data or commands, created with the QuickDraw function PicComment, used for special processing by output devices such as printer drivers. They may be included in the code an application sends to a printer driver or they may be stored in the definition of a picture.

Printer Resolution

Resolution is usually specified in dots-per-inch (dpi) in the x and y directions.

A printer driver supports either discrete resolution or variable resolution. If a printer driver supports discrete resolution, an application can choose from only a limited number of resolutions pre-defined by the printer driver. If a printer driver supports variable resolution, an application can define any resolution within a range bounded by maximum and minimum values defined by the printer driver.

Page and Paper Rectangles

When printing a document, you should consider the physical size of the paper and the area of the paper that the printer can use to format the document. This is usually smaller than the physical sheet of paper, generally because of the mechanical limitations of the printer.

Page Rectangle

The page rectangle (see Fig 1) represents the boundaries of the printable area of the page. Its upper-left coordinates are always (0,0). The coordinates of the lower-right corner give the maximum page height and width attainable on the given printer. These coordinates are specified by the units used to express the resolution of the printing graphics port (see below). For example, the lower-right corner of a page rectangle used by the PostScript LaserWriter printer driver for an 8.5-by-11-inch page is (730,552) at 72 dpi.

Your application should always use the page rectangle sizes provided by the printer driver and should not attempt to change them or add new ones.

(Paper and page rectangles)

Paper Rectangle

The paper rectangle (see Fig 1) gives the physical paper size, defined in the same coordinate system as the page rectangle. Thus the upper left coordinates of the paper rectangle are typically negative, and its lower-right coordinates are greater than those of the page rectangle.

Job Dialog Box, Style Dialog Box, and the TPrint Structure

Job Dialog Box and Style Dialog Box

If it is likely that the user will want to print the data created with your application, you should support both the Page Setup... command and the Print... command in your application's File menu.

In response to the Page Setup... command, your application should display the current printer's style dialog box, which allows the user to specify printing options, such as paper size and printing orientation, that your application needs for formatting the document in the frontmost window. Each printer driver defines its own style dialog box. Fig 2 shows the style dialog box for the Color StyleWriter 2500 printer.

(Style dialog)

In response to the Print... command, your application should display the current printer's job dialog box, which solicits printing information from the user (such as the number of copies to print, the print quality and the range of pages to print) for the document in the frontmost window. Each printer driver defines its own job dialog box. Fig 3 shows the job dialog box for the Color StyleWriter 2500 printer.

(Job dialog)

Note that many applications add items to the basic style and job dialog boxes so as to provide the user with additional control over printing operations within that application.

Preserving the User's Printing Preferences

The only information you should preserve each time the user prints the document should be that obtained via the style dialog box. The information supplied by the user through the job dialog box should pertain to the document only while the document prints, and you should not re-use this information if the user prints the document again.

A TPrint structure (see below) stores information about the user's choices made via the style (and the job) dialog box. Thus you can preserve the information obtained via the style dialog box by saving the TPrint structure associated with a document in that document's data or resource fork.

The values specified by the user through the style dialog box apply only to the printing of the document in the active window. In general, the user should have to specify these values only once per document (although the user can, of course, choose to change the settings at any time).

Displaying the Style and Job Dialog Boxes

PrStlDialog is used to display the style dialog box defined by the resource file for the current printer. PrJobDialog is used to display the job dialog box defined by the resource file for the current printer. These functions handle all user interaction in the items defined by the printer driver until the user clicks the OK or Cancel button. You must call PrOpen before calling PrStlDialog because the current printer driver must be open for your application to successfully call PrStlDialog.

Customised Style and Job Dialog Boxes

If you wish to customise the style and/or job dialog boxes so as to solicit additional information from the user, you must provide an initialisation function, an item evaluation function and, possibly, an event filter function. A universal procedure pointer to the initialisation function is passed in the second parameter of the function PrDlgMain. PrDlgMain, not PrStlDialog and PrJobDialog, is used to display a customised style or job dialog box. (See Customising the Style or Job Dialog Box, below.)

The TPrint Structure

To print a document, you need to create a print structure. The TPrint structure is a data structure of type TPrint. Most Printing Manager functions require that you provide a handle to a TPrint structure as a parameter.

You application allocates the memory for a TPrint structure itself, using NewHandle, and then initialises the TPrint structure using PrintDefault. Your application may also use an existing TPrint structure, in which case you can validate the structure using PrValidate. (PrValidate checks all fields of the TPrint structure to ensure compatibility with the current printer.)

When the user chooses the Print... command, your application passes a handle to a TPrint structure to PrJobDialog (or PrDlgMain in the case of customised job dialog boxes) to display a job dialog box to the user. PrJobDialog (or PrDlgMain) alters the prJob field (a TPrJob structure) of the TPrint structure according to the user's responses.

When the user chooses the Page Setup... command, your application passes a handle to a TPrint structure to PrStlDialog (or PrDlgMain in the case of customised style dialog boxes) to display a style dialog box to the user. PrStlDialog (or PrDlgMain) alters the prInfo field (a TPrInfo structure) of the TPrint structure according to the user's responses.

The TPrint structure, including its constituent TPrJob and TPrInfo structures, is shown at Fig 4. Note also the prInfo field (a TPrInfo structure), which contains resolution and page rectangle information.

(TPrint structure)

The Printing Graphics Port

PrOpenDoc, which opens a printing graphics port, returns a pointer to a TPrPort structure. The TPrPort structure, which defines a printing graphics port, is as follows:

     struct TPrPort
     {
       GrafPort gPort;   // Printer's graphics port structure.
       QDProcs  gProcs;  // Functions for printing in the graphics port.
       ...               // More fields for internal use.
     };
     typedef struct TPrPort TPrPort;
     typedef TPrPort *TPPrPort;

Field Descriptions

You print text and graphics by drawing into the printing graphics port using QuickDraw drawing functions, just as if you were drawing on the screen. The printer driver installs its own versions of QuickDraw's low-level drawing functions in this field.

gPort A graphics port structure, which may be either a CGrafPort or a GrafPort structure, depending on whether the current printer supports colour and greyscale.
gProcs A QDProcs structure, which contains pointers to functions which the printer driver may have designated to take the place of QuickDraw functions.

Print Status Dialog Boxes and Idle Procedure

Because the user must wait for a document to print (that is, the application must draw the data in the printing graphics port and the data must be sent either to the printer or a spool file before the user can continue working), many printer drivers display a print status dialog box informing the user that the printing process is under way and that the process may be aborted by pressing Command-period.

A user should always be able to cancel printing by pressing Command-period. To determine whether the user has cancelled printing, the printer driver periodically runs an idle function.

The TPrJob structure contains a pointer to an idle function in its pIdleProc field (see Fig 4). If this field contains the value NULL, then the printer driver uses its default idle function. The default idle function checks for Command-period keyboard events and sets the iPrAbort error code if one occurs so that your application can cancel the print job at the user's request. Note, however, that the default idle function does not display a print status dialog box. It is up to the printer driver or your application to display a print status dialog box.

To handle update information in your status dialog box during the printing operation, you should install your own idle function in the pIdleProc field of the TPrJob structure. Your idle function should also check whether the user has pressed Command-period, in which case your application should stop its printing operation. If your status dialog box contains a button to cancel the printing operation, your idle function should also check for clicks in the button and respond accordingly.

If you do not provide your own idle function, you can determine whether the user has cancelled printing by calling PrError to check for the iPrAbort error code after each call to a Printing Manager function.

Printing a Document - The Printing Loop

That part of your application's code which handles printing is referred to as the printing loop. A printing loop calls all the Printing Manager functions necessary to print a document, checking for printing errors at every step. In general, the printing loop should perform the following tasks:

  • Unload Unused Code Segments. Unused code segments should be unloaded to ensure that the maximum possible memory is available for printing. (This applies to 680x0 code only.)

    See Chapter 23- Miscellany.

  • Open the Printing Manager and Current Printer Driver. Use PrOpen to initialise the Printing Manager and to open the printer driver for the current printer (that is, the printer the user last selected in the Chooser).

  • Create or Validate a TPrint Structure. Use NewHandle to allocate storage for a TPrint structure, and then initialise that TPrint structure using PrintDefault. Alternatively, if you are using an existing TPrint structure, use PrValidate to check that the structure is compatible with the current printer and its driver.

  • Display the Job Dialog Box. Use PrJobDialog to display the job dialog box and to handle all user interaction in the standard dialog items until the user clicks the Print or Cancel button. Your application should print the document in the active window if the user clicks the Print button in the job dialog box.

    The PrDlgMain function is used to display a customised job dialog box.

  • Determine the Number of Copies and Number of Pages to Print. Determine the number of copies to print, and the number of pages required to print the requested range of pages, by examining the fields of the TPrint structure. (Note that, depending on the page rectangle of the current printer, the amount of data you can fit on a physical page of paper may differ from that displayed on the screen, although it is usually the same.)

  • Display a Status Dialog Box (Optional). If required, display a printing status dialog box indicating to the user the status of the current printing operation.

  • Install an Idle Function (Optional). If a status dialog box is used, install an idle function in the pIdleProc field of the TPrJob structure to update information in the status dialog box and to check whether the user wants to cancel the printing operation.

  • Print the Requested Range of Pages. Print the requested range of pages for each requested copy as follows:

    • Open a Printing Graphics Port. Call PrOpenDoc to open a printing graphics port if the current page number is the first page or a multiple of the value represented by the constant iPFMaxPgs (maximum pages in a spool file).

      The value represented by iPFMaxPgs is 128.

    • Open a Page for Printing. Call PrOpenPage to set up the printing graphics port for the page. (PrOpenPage initialises the fields of the graphics port, and must be called for every page to be printed.)

    • Draw in the Printing Graphics Port. Use appropriate QuickDraw functions to draw into the printing graphics port.

    • Close the Page. When your application has finished drawing into the page, close the page using PrClosePage.

    • Close the Printing Graphics Port. Call PrCloseDoc to close the printing graphics port and begin printing the requested range of pages

    • Check for Deferred Printing. Check whether the printer driver is using deferred printing and, if so, call PrPicFile to send the spool file to the printer. (The bjDocLoop field of the TPrJob structure is set to bDraftLoop (0) for draft and bSpoolLoop (1) for deferred printing.)

  • Close the Printing Manager. The printing loop should then close the Printing Manager using PrClose. PrClose releases the Printing Manager dialog and other resources, but it leaves the printer driver open. (The printer driver may be closed using PrDrvrClose.)

Creating and Validating the TPrint Structure

The following example shows how to create a TPrint structure. Note that PrintDefault is called to initialise the fields of the TPrint structure according to the current printer's default values. (The default values are stored in the printer driver's resource file.)

     THPrint tPrintHdl;
     ...

     tPrintHdl = (THPrint) NewHandleClear(sizeof(TPrint));
     if(tPrintHdl != NULL )
     {
       PrintDefault(tPrintHdl);   // Sets appropriate default values for current driver.
       if(printError = PrError())
         doPrintError(printError);
     }
     else
       // Handle error.

You can also use an existing TPrint structure (for example, one saved with a document). The following example application-defined function reads a structure that the application has saved with a document as a resource of type 'SPRC'. Note that PrValidate is called to make sure that the TPrint structure is valid for the current version of the Printing Manager and for the current printer driver.

     OSErr  doGetPrintStructure(SInt16 refNum,THPrint tPrintHdl,Boolean *prRecChanged)
     {
       SInt16 saveResFile;
       OSErr  result;

       saveResFile = CurResFile();
       UseResFile(refNum};

       tPrintHdl = (THPrint) Get1Resource('SPRC',kDocPrintRec);
       if(tPrintHdl != NULL)
       {
         DetachResource((Handle) tPrintHdl);
         prRecChanged = PrValidate(tPrintHdl);  // Check validity of TPrint structure.
         UseResFile(saveResFile);
         return(PrError());
       }
       else
       {
         UseResFile(saveResFile);
         return(kNilHandlePrintErr);
       }
     }

Drawing in the Graphics Port

Observe the following general rules when drawing in the printing graphics port:

  • Do not depend on values in the printing graphics port remaining identical from page to page. With each new page, you generally get re-initialised font information and other characteristics for the printing graphics port.

  • Do not make calls which do not do anything on the printer. For example, QuickDraw erase functions are quite time-consuming and normally are not needed on the printer. Paper does not need to be erased the way the screen does.

  • Do not use clipping to select text to be printed. There are a number of subtle differences between the way text appears on the screen and the way it appears on the printer, and you cannot count on knowing the exact dimensions of the rectangle occupied by the text.

  • Do not use fixed-width fonts to align columns. Explicitly move the pen to where you want it.

  • Do not use the outline font to create white text on a black background.

  • Avoid changing fonts frequently.
Note that, because of the way rectangle intersections are determined, you slow printing substantially if your clipping region falls outside the rectangle given by the rPage field of the TPrInfo structure.

Handling Printing Errors

The Printing Manager must necessarily bear the heavy burden of maintaining backward compatibility with early Apple printer models and of maintaining compatibility with a great many existing printer drivers. For this reason, you must be especially wary of, and defensive about, possible error conditions when using Printing Manager functions and data structures.

PrError returns the result of the last Printing Manager function call. PrError returns noErr if no error occurred.

If you determine that an error has occurred after the completion of a printing function, stop printing and call the close function that matches any open function you have called. For example, if you call PrOpenDoc and receive an error, skip to the next call to PrCloseDoc. If you call PrOpenPage and get an error, skip to the next calls to PrClosePage and PrCloseDoc.

Do not display an alert or dialog box to report an error until the end of the printing loop. Once at the end of the loop, check for the error again. If there is no error, assume that the printing completed normally. If the error is still present, alert the user. This technique is important for two reasons:

  • If you display a dialog box in the middle of a printing loop, it could cause errors that might terminate an otherwise normal printing operation.

  • The printer driver may have already displayed its own dialog box in response to an error. In this instance, the printer driver posts an error to let the application know that something went wrong and that it should cancel printing.

An Example Printing Loop

The following is an example of a printing loop:

     void  printLoop(DocumentRecordHld docToPrint,Boolean displayJobDialog)
     {
       GrafPtr    oldPort;
       SInt16     numberOfPages, numberOfCopies;
       Boolean    userClickedOK;
       SInt16     firstPage, lastPage, copy, page;
       TPrStatus  tprStatus;
       SInt16     printError;

       GetPort(&oldPort);
       doUnloadSegments();

       PrOpen();
       if(PrError() == noErr)
       {
         gPrintResFile = CurResFile();
         gTPrintHdl = (*docToPrint)->docPrintRecordHdl;.
         changed = PrValidate(gTPrintHdl);

         if(PrError() == noErr)
         {
           numberOfPages = doCalulateNumberOfPages((*gTPrintHdl)->prInfo.rPage);

           if(displayJobDialog)
             userClickedOK = PrJobDialog(gTPrintHdl);
           else
             userClickedOK = doJobMerge(gTPrintHdl);

           if(userClickedOK)
           {
             numberOfCopies = (*gTPrintHdl)->prJob.iCopies;

             firstPage = (*gTPrintHdl)->prJob.iFstPage;
             lastPage = (*gTPrintHdl)->prJob.iLstPage;

             (*gTPrintHdl)->prJob.iFstPage = 1;
             (*gTPrintHdl)->prJob.iLstPage = iPrPgMax;

             if(numberOfPages < lastPage)
               lastPage = numberOfPages;
             
             // Next block is optional
             
             doActivateFrontWindow(false,oldPort);
             gPrintStatusDlg = GetNewDialog(rPrintStatus,NULL,(WindowPtr) -1);
             doDialogBoxItems(docToPrint);
             ShowWindow(gPrintStatusDlg);
             (*gTPrintHdl)->prJob.pIdleProc = &doPrintIdle;
        
             for(copy=1;copy<numberOfCopies+1;copy++)
             {
               UseResFile(gPrintResFile);

               for(page=firstPage;page<lastPage+1;page++)
               {
                 if((page - firstPage) % iPFMaxPgs == 0)
                 {  
                   if(page != firstPage)
                   {
                     PrCloseDoc(gPrintPortPtr);
                
                     if(((*gTPrintHdl)->prJob.bJDocLoop == bSpoolLoop) && 
                        (PrError() == noErr))
                       PrPicFile(gTPrintHdl,NULL,NULL,NULL,&tprStatus);
                   }
                   gPrintPortPtr = PrOpenDoc(gTPrintHdl,NULL,NULL);
                 }  
                 if(PrError() == noErr)
                 {
                   PrOpenPage(gPrintPortPtr,NULL);
                   if(PrError() == noErr)
                   {
                     doDrawPrintPage((*gTPrintHdl)->prInfo.rPage,docToPrint,
                                     (GrafPtr) gPrintPortPtr,page);
                   } 
                   PrClosePage(gPrintPortPtr);
                 }
               }

               PrCloseDoc(gPrintPortPtr);

               if(((*gTPrintHdl)->prJob.bJDocLoop == bSpoolLoop) && 
                  (PrError() == noErr))
                 PrPicFile(gTPrintHdl,NULL,NULL,NULL,&tprStatus);
             }
           }
         }
       }
  
       printError = PrError();

       PrClose();

       if(printError != noErr)
         doPrintError(printError);

       DisposeDialog(gPrintStatusDlg);
       SetPort(oldPort);
       doActivateFrontWindow(true,oldPort);
     }

Preliminaries

printLoop begins by saving a pointer to the current graphics port and swapping out code segments not required during printing. It then opens the Printing Manager, together with the current printer driver and its resource file, by calling PrOpen. Note that the current resource file is now the printer driver's resource file. Assuming no error, the current resource file is saved so that, if printLoop's idle function changes the resource chain in any way, it can restore the current resource file before returning.

Swapping out code segments applies only to 680x0 code. See Chapter 23 - Miscellany.

PrValidate is then used to change any values in the TPrint structure associated with the document to match those specified by the current driver. (PrValidate, rather than PrDefault, is used so as to preserve any values the user may have previously set through the style dialog box.)

Calculate Number of Pages

The application-defined function doCalculateNumberOfPages is called to divide the data in the file into sections that fit within the printable page rectangle stored in the rPage field of the TPrInfo structure and, by so doing, to determine the number of pages required to print the document.

Display Job Dialog Box or Perform Job Merge

If the calling function so specifies, the job dialog box is then displayed. (If the user prints multiple documents at once, the calling function sets the displayJobDialog parameter to true for the first document and false for the rest. This allows the user to specify the values in the job dialog box only once when printing multiple documents. It also facilitates the printing of documents in the background (for example, as the result of responding to the required Apple event Print Documents) without requiring the application to display the job dialog box.)

If displayJobDialog was set to false by the calling function, the application-defined function doJobMerge would, amongst other things, use PrJobMerge to copy data from the first print structure to the print structure for the document about to be printed.

Get First Page, Last Page, and Number of Copies

If true is returned by either the call to PrJobDialog (that is, the user clicked the Print (OK) button) or the call to doJobMerge (that is, there is another document to print), the number of copies, first page and last page are retrieved from the relevant fields of the TPrJob structure. Since the only information which should be preserved between separate printings of the same document is that obtained via the style dialog box, the fields of the TPrJob structure which store the first and last page numbers are then set back to 1 and iPrPgMax (9999) respectively.

If the last page number specified by the user exceeds the total number of pages in the document, the variable holding the last page value is set to the actual number of pages.

Display a "Print Status" Dialog Box and Install an Idle Function (Optional)

Before sending the pages off to be printed, a "print status" dialog is displayed to inform the user of the current status of the printing operation. If the dialog provides a button, or reports on the progress of the printing operation, an idle function must be installed to handle events in the dialog. The printer driver calls the idle function periodically during the printing process.

The following is an example of an application-defined idle function which assumes the use of a "print status" modal dialog box to display printing status information:

     pascal void  doPrintIdle(void)
     {
       GrafPtr      oldPort;
       EventRecord  eventRec;
       Boolean      gotEvent;
       SInt16       itemHit;
       Boolean      handled, cancelled;

       GetPort(&oldPort);
       SetPort(gPrintStatusDlg);

       gotEvent = WaitNextEvent(everyEvent,&eventRec,15,NULL);

       if(gotEvent)
       {
         // doHandleEvent should handle update and activate events.  This also
         // enables background applications to receive update events while the 
         // "print status" modal dialog is open.

         handled = doHandleEvent(gPrintStatusDlg,&eventRec,&itemHit);

         // doDidUserCancel should scan for Command-period key-down events (see
         // Chapter 23 - // Miscellany) and also for mouse-down events indicating
         // that the user clicked the Stop Printing button.

         cancelled = doDidUserCancel();
         if(cancelled)
           itemHit = kStopButton;

         // To handle hits in the "print status" dialog, doHandleHitsInStatusBox
         // should check the item number passed to it.  For the Stop Printing 
         // button, it should call PrSetError, specifying the error code iPrAbort.
         //  For hits in other items, it should set the cursor to a wristwatch
         // cursor.

         handled = doHandleHitsInStatusBox(itemHit);
       };

       // doUpdateStatus should update those items in "print status" dialog box
       // that report printing status the user.

       doUpdateStatusInformation(cancelled);  

       SetPort(oldPort);
     }

The following guidelines should be followed when writing your own idle function:

  • If you draw anything within the idle function, save the printing graphics port upon entry to the idle function and restore it upon exit, as shown in the example.

  • If your idle function changes the resource chain, save the reference number of the printer driver's resource file by calling CurResFile at the beginning of your idle function. Upon exit, restore the resource chain using UseResFile.

    See Chapter 17 - More on Resources.

  • Avoid calling PrError within the idle function.

Copies Loop

Before beginning the actual printing process, printLoop displays its own status dialog box and installs its own idle function. A loop, which will cycle once for each of the specified number of copies, is then entered. The current resource file is restored to the printer driver's resource file at the top of this loop.

Pages Loop

A nested loop is then entered for the printing of each page. The maximum number of pages that can be printed at a time is represented by the constant iPFMaxPgs (128). If 128 (or 256, etc.) pages have been printed, the printing graphics port is closed by a call to PrCloseDoc and, if the printer driver is using deferred printing, PrPicFile is called to send the spool file to the printer. If this is either the first page of all or the first page after the first 128 (or 256, etc.) have been printed, PrOpenDoc is called to initialise a printing graphics port and make it the current port.

For each page, PrOpenPage is called to initialise the printing graphics port, the application-defined function doDrawPrintPage is called to draw the page in the printing graphics port, and PrClosePage is called to wrap up printing of the current page. (Note that the parameters taken by doDrawPrintPage are the size of the page rectangle, the document containing the page to print, the printing graphics port in which to draw, and the page number. This allows the application to use the same code to print a page as it uses to draw the same page on the screen.)

Exit From the Copies Loop

When all pages have been printed, PrCloseDoc is called to close the printing graphics port. If the printer driver is using deferred printing, PrPicFile is called to send the spool file to the printer. Finally, PrClose is called to release memory associated with the Printing Manager (except the printer driver). It then remains to dispose of the status dialog, reset the current graphics port and activate the application's front window.

Getting and Setting Printer Information

By using PrGeneral you can determine the resolution of the printer, set the printer resolution, ascertain if the user has set landscape printing, and force enhanced draft-quality printing.

To achieve these ends, you use PrGeneral with one of five opcodes: getRslDataOp, setRslOp, getRotnOp, draftBitsOp, or noDraftBitsOp. These opcodes have data structures associated with them. When you call PrGeneral, PrGeneral, in turn, calls the current printer driver to get or set the desired information.

Checking Whether the Current Printer Driver Supports PrGeneral

Note that not all printer drivers support all of the features provided by PrGeneral. The following example application-defined function checks whether the current printer driver supports PrGeneral.

     Boolean  doIsPrGeneralThere(void)
     {
       TGetRotnBlk  getRotRec;
       OSErr        printError;

       printError = 0;
       getRotRec.iOpCode = getRotnOp;  // Set opcode used to find if landscape chosen.
       getRotRec.hPrint = gTPrintHdl;  // TPrint structure this operation applies to.

       PrGeneral((Ptr) &getRotRec);

       printError = PrError();
       PrSetError(noErr);

       if(printError == resNotFound)
         return(false);
       else
         return(true);
     }

Using PrGeneral to Determine Page Orientation

The principal use of PrGeneral is probably to determine page orientation. This can be useful where, for example, an image will only fit on the page in landscape orientation, the user has not selected landscape, and you want your application to remind the user to select landscape before printing so as to avoid a clipped printed image. The following is an example application-defined function which returns a value indicating whether the user has selected landscape orientation:

     SInt16  doGetPageOrientation(void)
     {
       TGetRotnBlk getRotRec;

       if(doIsPrGeneralThere)
       {
         getRotRec.iOpCode = getRotnOp;
         getRotRec.hPrint = gTPrintHdl;
         PrGeneral((Ptr) &getRotRec);
         if((getRotRec.iError == noErr) && (PrError() == noErr) && 
            getRotRec.fLandscape)
           return(kInLandscapeOrientation);
         else
           return(kInPortraitOrientation);
       }
       else
         return(kPrGeneralAbsent);
     }  

Error Handling

When using PrError and PrGeneral, be prepared to receive the errors noSuchRsl (printer does not support the requested resolution), opNotImpl (printer does not support the PrGeneral opcode selected) and resNotFound (current printer driver does not support PrGeneral). If you receive a resNotFound result code, clear the error by calling PrSetError with a value of noErr.

Text on the Screen and the Printed Page

At the application level, printing on the Macintosh computer is not fundamentally different from drawing on the screen. That said, printing text poses special challenges.

A common complication results from the difference in resolution and pixel size between screen and printer. QuickDraw measurements are theoretically in terms of points, which are nominally equivalent to screen pixels. High resolution printers have very much smaller pixels, although printer drivers are expected to take this into account so that the same QuickDraw calls will produce text lines of the same width on the screen and on the printer. Nevertheless, this higher resolution, and the fact that printers can use different fonts from those used for screen display, can result in some loss of fidelity from the screen to the printed page. In this regard, the following is relevant:

  • QuickDraw places text glyphs on the screen at screen pixel intervals, whereas a printer can provide much finer placements on the printed page. This situation presents a choice between optimising the appearance of text on the screen or on the printed page. In effect, that choice is whether to specify fractional glyph widths or integer glyph widths.

    A glyph is the visual representation of a character. See Chapter 19 - Text. and TextEdit.

    Fractional glyph widths are measurements of a glyph's width which can include fractions of a pixel. Using fractional glyph widths improves the appearance of printed text because it makes it possible for the printer, with its very high resolution, to print with better spacing. However, because screen glyphs are made up of whole pixels, QuickDraw cannot draw a fractional glyph on the screen, so it rounds off the fractional parts. This results in some degradation in the appearance of the text, in terms of character spacing, on the screen.

    The alternative (integer glyph widths) gives more pleasing screen results because the characters are drawn with regular pixel spacing, but this may possibly be at the price of a printed page which is typographically unacceptable.

    The Font Manager function SetFractEnable is used to turn fractional glyph widths on and off. SetFractEnable affects functions which draw text and which calculate text and character widths.

  • Printer drivers attempt to reproduce faithfully the text formatting as drawn by QuickDraw on the screen, including keeping the same intended character spacing, line breaks and page breaks. However, because printers can have resident fonts that are different from the fonts that QuickDraw uses, because the drivers may handle text layout somewhat differently than QuickDraw, and because font metrics do not always scale linearly, fidelity may not always be achieved. Typically, identical line breaks and page breaks can be maintained, but character spacing can be noticeaby different.

Customising the Style or Job Dialog Box

As previously stated, you may want to add additional options to the style and job dialog boxes so that the user can further customise the printing process. For example, you might want to add a "skip blank pages" checkbox to a job dialog box. Also as previously stated, the function PrDlgMain, not PrStlDialog and PrJobDialog, is used to display a customised style or job dialog.

Preamble - How the Dialogs Work

The TPrDlg structure contains all of the information pertaining to a print dialog and is thus of central importance to the matter of customising style and job dialogs:

     struct TPrDlg
     {
       DialogRecord    Dlg;        // A dialog structure.
       ModalFilterUPP  pFltrProc;  // The event filter function.
       PItemUPP        pItemProc;  // The item evaluation function.
       THPrint         hPrintUsr;  // Handle to a TPrint structure.
       Boolean         fDoIt;      // true means user clicked OK.
       Boolean         fDone;      // true means user clicked OK or Cancel.
       long            lUser1;
       long            lUser2;
       long            lUser3;
       long            lUser4;
     };
     typedef struct TPrDlg TPrDlg;
     typedef TPrDlg *TPPrDlg;

Note particularly the fields pFltrProc and pItemProc. When your application calls PrStlDialog and PrJobDialog, the printer driver calls PrDlgMain. PrDlgMain is declared as follows:

     Boolean PrDlgMain(THPrint hPrint,PDlgInitUPP pDlgInit);

PrDlgMain, in turn, calls functions which set up the dialog structure, assign a universal procedure pointer to the standard event filter function to the pFltrProc field of the TPrDlg structure, and assign a universal procedure pointer to the item evaluation function to the pItemProc field. When an item is hit, the function pointed to by the pItemProc field is called to handle the hit. When the Print (OK) button is hit, the THPrint structure is validated.

Adding Items to the Dialog Box

To customise a print dialog, you must modify the contents of the TprDlg structure before the dialog is drawn on the screen. This involves:

  • Providing a 'DITL' resource containing the required additional items.

  • Defining an item evaluation function that handles hits on items in the dialog.

  • Defining an initialisation function that:

    • Calls AppendDITL to append the items to the dialog box.

    • Saves the universal procedure pointer to the driver's item evaluation function in the pItemProc field of the TprDlg structure. (This will be need to be called from your item evaluation function to handle hits on the dialog's standard items.)

    • Assigns a universal procedure pointer to your item evaluation function in the pItemProc field.

    • Returns a pointer to the TPrDlg structure.

  • If required, defining a custom event filter function and assigning a universal procedure pointer to it to the pFltrProc field of the TprDlg structure.
A universal procedure pointer to the initialisation function should then be passed in the pDlgInit parameter of PrDlgMain.

Printing From the Finder

Users generally print documents that are open on the screen one at a time while the application that created the document is running. However, users can also print one or more documents from the Finder by selecting the documents and choosing Print... from the Finder's File menu. This causes the Finder to launch the application and pass it a required Apple event (the Print Documents event) indicating the documents to be printed. In response to a Print Documents event, your application should:

  • Open windows for the documents only if your application can interact with the user (see Chapter 10 - Required Apple Events.)

  • Use saved or default style settings instead of displaying the style dialog box.

  • Display the job dialog box once only, and use PrJobMerge to apply the information specified by the user to all of the selected documents. (Note that PrJobMerge preserves the fields of the TPrint structure that are specific to each document, that is, the fields that are set through the style dialog box.)

  • Remain open unless and until the Finder sends it a Quit Application event.


Main Printing Manager Constants, Data Types and Functions

Constants

iPFMaxPgs   = 128  Maximum pages in spool file.
iPrPgFract  = 120  Page scale factor.
iPrPgFst    = 1  Page range constant - first page.
iPrPgMax    = 9999  Page range constant - last page.
bDraftLoop  = 0  Draft-quality printing.
bSpoolLoop  = 1  Deferred printing.

PrGeneral Opcodes

getRslDataOp  = 4  Get resolutions for current printer.
setRslOp      = 5  Set resolutions for a TPrint structure.
draftBitsOp   = 6  Force enhanced draft-quality printing.
noDraftBitsOp = 7  Cancel enhanced draft-quality printing.
getRotnOp     = 8  Get page orientation of a TPrint structure.
NoSuchRsl     = 1  Resolution not supported.

Data Types

Print Structure

struct TPrint
{
  short    iPrVersion;  // (Reserved)
  TPrInfo  prInfo;      // PrInfo data associated with the current style.
  Rect     rPaper;      // Paper rectangle (offset from rPage).
  TPrStl   prStl;       // This print request's style.
  TPrInfo  prInfoPT;    // (Reserved)
  TPrXInfo prXInfo;     // (Reserved)
  TPrJob   prJob;       // Print Job request.
 short  printX[19];     // (Reserved)
};
typedef struct TPrint TPrint;
typedef TPrint *TPPrint, **THPrint;

Printer Information Structure

struct TPrInfo
{
  short  iDev;   // (Reserved)
  short  iVRes;  // Vertical resolution of printer in dpi.
  short  iHRes;  // Horizontal resolution of printer in dpi.
  Rect   rPage;  // Page (printable) rectangle in device coordinates.
};
typedef struct TPrInfo TPrInfo;
typefdef TPrInfo *TPPrInfo;

Print Job Structure

struct TPrJob
{
  short     iFstPage;   // First page of page range.
  short     iLstPage;   // Last page of page range.
  short     iCopies;    // Number of copies.
  SInt8     bJDocLoop;  // Printing method - draft or deferred.
  Boolean   fFromUsr;   // (Reserved)
  PrIdleUPP pIdleProc;  // Pointer to an idle function.
  StringPtr pFileName;  // Spool file name: NULL for default.
  short     iFileVol;   // Spool file volume: set to 0 initially.
  SInt8     bFileVers;  // Spool file version: set to 0 initially.
  SInt8     bJobX;      // (Reserved)
};
typedef struct TPrJob TPrJob;
typedef TPrJob *TPPrJob;

Printing Style Structure

struct TPrStl
{
  short  wDev;    // Device number of printer.
  short  iPageV;  // (Reserved)
  short  iPageH;  // (Reserved)
  SInt8  bPort;   // (Reserved)
  TFeed  feed;    // Feed type.
};
typedef struct TPrStl TPrStl;
typedef TPrStl *TPPrStl;

Printing Graphics Port Structure

struct TPrPort
{
  GrafPort gPort;     // Graphics port for printing.
  QDProcs  gProcs;    // Functions for printing in graphics port.
  long     lGParam1;  // (Reserved)
  long     lGParam2;  // (Reserved)
  long     lGParam3;  // (Reserved)
  long     lGParam4;  // (Reserved)
  Boolean  fOurPtr;   // (Reserved)
  Boolean  fOurBits;  // (Reserved)
};
typedef struct TPrPort TPrPort;
typedef TPrPort *TPPrPort;

Printing Status Structure

struct TPrStatus
{
  short      iTotPages;   // Total pages in print File.
  short      iCurPage;    // Current page number.
  short      iTotCopies;  // Current copies requested.
  short      iCurCopy;    // Current copy number
  short      iTotBands;   // (Reserved)
  short      iCurBand;    // (Reserved)
  Boolean    fPgDirty;    // true if current page has been written to.
  Boolean    fImaging;    // (Reserved)
  THPrint    hPrint;      // Handle to the active printer structure.
  TPPrPort   pPrPort;     // Pointer to the active printing graphics port.
  PicHandle  hPic;        // Handle to the active picture.
};
typedef struct TPrStatus TPrStatus;
typedef TPrStatus *TPPrStatus;

Print Dialog Box Structure

struct TPrDlg
{
  DialogRecord    Dlg;        // A dialog structure.
  ModalFilterUPP  pFltrProc;  // The event filter function.
  PItemUPP        pItemProc;  // The item evaluatiion function.
  THPrint         hPrintUsr;  // Handle to a TPrint structure.
  Boolean         fDoIt;      // true means user clicked OK.
  Boolean         fDone;      // true means user clicked OK or Cancel.
  long            lUser1;     // (Storage for your application.)
  long            lUser2;     // (Storage for your application.)
  long            lUser3;     // (Storage for your application.)
  long            lUser4;     // (Storage for your application.)
};
typedef struct TPrDlg TPrDlg;
typedef TPrDlg *TPPrDlg;

Page Orientation Structure

struct TGetRotnBlk
{
  short    iOpCode;    // The getRotnOp opcode.
  short    iError;     // Result code returned by PrGeneral.
  long     lReserved;  // (Reserved)
  THPrint  hPrint;     // Handle to current TPrint structure.
  Boolean  fLandscape; // true if user selected landscape printing.
  SInt8     bXtra;     // (Reserved)
};
typedef Rect *TPRect;
typedef pascal void (*PrIdleProcPtr)(void);
typedef pascal void (*PItemProcPtr)(DialogPtr theDialog, short item);

Functions

Opening and Closing the Printing Manager

void  PrOpen(void);
void  PrClose(void);

Initialising and Validating TPrint Structures

void     PrintDefault(THPrint hPrint);
Boolean  PrValidate(THPrint hPrint);

Displaying and Customising Print Dialog Boxes

Boolean  PrStlDialog(THPrint hPrint);
Boolean  PrJobDialog(THPrint hPrint);
Boolean  PrDlgMain(THPrint hPrint,PDlgInitUPP pDlgInit);
TPPrDlg  PrStlInit(THPrint hPrint);
TPPrDlg  PrJobInit(THPrint hPrint);
void     PrJobMerge(THPrint hPrintSrc,THPrint hPrintDst);

Printing a Document

TPPrPort  PrOpenDoc(THPrint hPrint,TPPrPort pPrPort,Ptr pIOBuf);
void      PrCloseDoc(TPPrPort pPrPort);
void      PrOpenPage(TPPrPort pPrPort,TPRect pPageFrame);
void      PrClosePage(TPPrPort pPrPort);
void      PrPicFile(THPrint hPrint, TPPrPort pPrPort, Ptr pIOBuf, Ptr pDevBuf, 
          TPrStatus *prStatus);

Optimising Printing

void  PrGeneral(Ptr pData);

Creating Routine Descriptors

#define  NewPDlgInitProc(userRoutine) 
         (PDlgInitUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
         uppPDlgInitProcInfo,GetCurrentArchitecture())
#define  NewPItemProc(userRoutine) 
         (PItemUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), 
         uppPItemProcInfo,GetCurrentArchitecture())

Handling Printing Errors

short  PrError(void);
void  PrSetError(short iErr);

Go to Demo

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links... | Read more »
Price of Glory unleashes its 1.4 Alpha u...
As much as we all probably dislike Maths as a subject, we do have to hand it to geometry for giving us the good old Hexgrid, home of some of the best strategy games. One such example, Price of Glory, has dropped its 1.4 Alpha update, stocked full... | Read more »
The SLC 2025 kicks off this month to cro...
Ever since the Solo Leveling: Arise Championship 2025 was announced, I have been looking forward to it. The promotional clip they released a month or two back showed crowds going absolutely nuts for the previous competitions, so imagine the... | Read more »
Dive into some early Magicpunk fun as Cr...
Excellent news for fans of steampunk and magic; the Precursor Test for Magicpunk MMORPG Crystal of Atlan opens today. This rather fancy way of saying beta test will remain open until March 5th and is available for PC - boo - and Android devices -... | Read more »
Prepare to get your mind melted as Evang...
If you are a fan of sci-fi shooters and incredibly weird, mind-bending anime series, then you are in for a treat, as Goddess of Victory: Nikke is gearing up for its second collaboration with Evangelion. We were also treated to an upcoming... | Read more »
Square Enix gives with one hand and slap...
We have something of a mixed bag coming over from Square Enix HQ today. Two of their mobile games are revelling in life with new events keeping them alive, whilst another has been thrown onto the ever-growing discard pile Square is building. I... | Read more »
Let the world burn as you have some fest...
It is time to leave the world burning once again as you take a much-needed break from that whole “hero” lark and enjoy some celebrations in Genshin Impact. Version 5.4, Moonlight Amidst Dreams, will see you in Inazuma to attend the Mikawa Flower... | Read more »
Full Moon Over the Abyssal Sea lands on...
Aether Gazer has announced its latest major update, and it is one of the loveliest event names I have ever heard. Full Moon Over the Abyssal Sea is an amazing name, and it comes loaded with two side stories, a new S-grade Modifier, and some fancy... | Read more »
Open your own eatery for all the forest...
Very important question; when you read the title Zoo Restaurant, do you also immediately think of running a restaurant in which you cook Zoo animals as the course? I will just assume yes. Anyway, come June 23rd we will all be able to start up our... | Read more »
Crystal of Atlan opens registration for...
Nuverse was prominently featured in the last month for all the wrong reasons with the USA TikTok debacle, but now it is putting all that behind it and preparing for the Crystal of Atlan beta test. Taking place between February 18th and March 5th,... | Read more »

Price Scanner via MacPrices.net

AT&T is offering a 65% discount on the ne...
AT&T is offering the new iPhone 16e for up to 65% off their monthly finance fee with 36-months of service. No trade-in is required. Discount is applied via monthly bill credits over the 36 month... Read more
Use this code to get a free iPhone 13 at Visi...
For a limited time, use code SWEETDEAL to get a free 128GB iPhone 13 Visible, Verizon’s low-cost wireless cell service, Visible. Deal is valid when you purchase the Visible+ annual plan. Free... Read more
M4 Mac minis on sale for $50-$80 off MSRP at...
B&H Photo has M4 Mac minis in stock and on sale right now for $50 to $80 off Apple’s MSRP, each including free 1-2 day shipping to most US addresses: – M4 Mac mini (16GB/256GB): $549, $50 off... Read more
Buy an iPhone 16 at Boost Mobile and get one...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering one year of free Unlimited service with the purchase of any iPhone 16. Purchase the iPhone at standard MSRP, and then choose... Read more
Get an iPhone 15 for only $299 at Boost Mobil...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering the 128GB iPhone 15 for $299.99 including service with their Unlimited Premium plan (50GB of premium data, $60/month), or $20... Read more
Unreal Mobile is offering $100 off any new iP...
Unreal Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering a $100 discount on any new iPhone with service. This includes new iPhone 16 models as well as iPhone 15, 14, 13, and SE... Read more
Apple drops prices on clearance iPhone 14 mod...
With today’s introduction of the new iPhone 16e, Apple has discontinued the iPhone 14, 14 Pro, and SE. In response, Apple has dropped prices on unlocked, Certified Refurbished, iPhone 14 models to a... Read more
B&H has 16-inch M4 Max MacBook Pros on sa...
B&H Photo is offering a $360-$410 discount on new 16-inch MacBook Pros with M4 Max CPUs right now. B&H offers free 1-2 day shipping to most US addresses: – 16″ M4 Max MacBook Pro (36GB/1TB/... Read more
Amazon is offering a $100 discount on the M4...
Amazon has the M4 Pro Mac mini discounted $100 off MSRP right now. Shipping is free. Their price is the lowest currently available for this popular mini: – Mac mini M4 Pro (24GB/512GB): $1299, $100... Read more
B&H continues to offer $150-$220 discount...
B&H Photo has 14-inch M4 MacBook Pros on sale for $150-$220 off MSRP. B&H offers free 1-2 day shipping to most US addresses: – 14″ M4 MacBook Pro (16GB/512GB): $1449, $150 off MSRP – 14″ M4... Read more

Jobs Board

All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.