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

Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasn’t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »

Price Scanner via MacPrices.net

13-inch M2 MacBook Airs in stock today at App...
Apple has 13″ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty is included,... Read more
New today at Apple: Series 9 Watches availabl...
Apple is now offering Certified Refurbished Apple Watch Series 9 models on their online store for up to $80 off MSRP, starting at $339. Each Watch includes Apple’s standard one-year warranty, a new... Read more
The latest Apple iPhone deals from wireless c...
We’ve updated our iPhone Price Tracker with the latest carrier deals on Apple’s iPhone 15 family of smartphones as well as previous models including the iPhone 14, 13, 12, 11, and SE. Use our price... Read more
Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9″ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more

Jobs Board

DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.