Drawing on Printer using C

Window kernel and GDI subsystem manage screen and printer uniformly through the help of Device Context(DC). All the Win32 GDI drawing APIs work as it is with printer device context. We use GetDC() or BeginPrint() to get the DC while drawing on windows screen. However, getting the handle to the device context of a printer is different. Here we get the handle from printer subsystem using Win32 PrintDlg() call.

Drawing on the Printer DC

Printing on a printer is defined by a windows printer spooling job. This job is directly linked to a document which is getting printed. A print job document must have a name. This appears in printer job section or in in spooler in printer control panel.

StartDoc() API is used to start a job in the printer. Printer device context area is defined by one or more pages. StartPage() API is used to start an area where drawing can be done. The width and height of this page is defined by page size which user selected in printer dialog. A4,A3 A2 etc are like available page size. Printer drawing area in printer DC is proprotinal to page dimension. Printer resolution or dots per inch is the factor or multiplier. Better solution means the area of printer DC is bigger. HORZRES x VERTRES is the actual area of printer DC which is returned by GetDeviceCaps() Programmers can use same Win32 GDI drawing APIs as it is used in screen and windows. Programmers should calculate the width and height of the each objects getting printed. Objects should not overlap and it should go to next page when there is no room left for this page. EndPage() can be called and next to this another StartPage() to go to the next page. These calls are sequential and there is no way to go to previous page. Once drawings are done the EndPage() can be called and then EndDoc() is called to end the printer job. Printer is comparatively slow process and there is a chance that user abort the job in between. Win32 subsystem triggers the AbortProc callback when printer job is aborted. User can install an abort callback routine using SetAbortProc().

print program flow diagram

Win32 APIs for Printing

We have an example program to demo the drawing on the printer DC. We have used printer PRINTDLGA data structure and PrintDlg() printer dialog call. These two will get the printer dc from the user input. Later we have used APIs like InitDocStruct(), StartDoc(), StartPage(), EndPage(), EndDoc().

PRINTDLGA structure

This is the context data structure that the PrintDlg API uses to initialize the Print Dialog Box. This acts as both input and output parameter for PrintDlg(). User selects the settings and after that user presses okay/print and dialog ends. Programmer uses this structure to know the information about the user's selections.

typedef struct tagPDA {
  DWORD           lStructSize;
  HWND            hwndOwner;
  HGLOBAL         hDevMode;
  HGLOBAL         hDevNames;
  HDC             hDC;
  DWORD           Flags;
  WORD            nFromPage;
  WORD            nToPage;
  WORD            nMinPage;
  WORD            nMaxPage;
  WORD            nCopies;
  HINSTANCE       hInstance;
  LPARAM          lCustData;
  LPPRINTHOOKPROC lpfnPrintHook;
  LPSETUPHOOKPROC lpfnSetupHook;
  LPCSTR          lpPrintTemplateName;
  LPCSTR          lpSetupTemplateName;
  HGLOBAL         hPrintTemplate;
  HGLOBAL         hSetupTemplate;
} PRINTDLG, *LPPRINTDLG;

PrintDlg function

Pops a Print Dialog Box or a Print Setup dialog box before the user. It enables the user to specify the properties (printer name, number of copies, pages etc) of a particular print job.

BOOL PrintDlg(
  LPPRINTDLG lppd
);

A pointer to a PRINTDLG structure is used as input and output argument. For input this contains information used to initialize the dialog box. PrintDlg() function blocks until user dismisses the dialog or presses okay/print. On return this structure contains information about the user's selections.

This returns true or nonzero once user clicks the OK button. Programmer should check the members of the PRINTDLG structure which have been given by the user. User might cancel or closed the Print dialog or an error might occurred. False or zero will be returned on these cases. Extended error information like system errors or other failures etc can be retrieved with CommDlgExtendedError function.

Printing DOCINFO structure

The DOCINFO structure contains the document name, output file names and other information used by the API StartDoc. Size of the structure (cbSize) and document name (lpszDocName) should be initialized before calling to StartDoc().

typedef struct _DOCINFO {
  int    cbSize;
  LPCSTR lpszDocName;
  LPCSTR lpszOutput;
  LPCSTR lpszDatatype;
  DWORD  fwType;
} DOCINFO, *LPDOCINFO;

Print API functions

Print APIs take the printer DC as the first argument. The name of these APIs tells what function they do. So these are pretty much easy to understand and use.

/* Starts the print document */
int StartDoc(
  HDC            hdc,
  const DOCINFO *lpdi
);

/* Starts one page in document */
int StartPage(
  HDC hdc
);

/* Ends the page in document */
int EndPage(
  HDC hdc
);

/* Ends the print document */
int EndDoc(
  HDC hdc
);

Printer DC demo application

Here is a demo application to show the utilities of printer related functions.

#include <commdlg.h>
#include <stdio.h>

/*===============================*/ 
/* Initialize DOCINFO structure */ 
/* ==============================*/ 
void InitPrintJobDoc( DOCINFO* di, char* docname)
{
  memset( di, 0, sizeof( DOCINFO ) );
  /* Fill in the required members. */
  di->cbSize = sizeof( DOCINFO );
  di->lpszDocName = docname;
}

/*===============================*/ 
/* Drawing on the DC on Page */ 
/* ==============================*/ 
void DrawPage( HDC hdc, UINT Page)
{
  char line[50];
  int nWidth, nHeight;

  nWidth = GetDeviceCaps(hdc, HORZRES);
  nHeight = GetDeviceCaps(hdc, VERTRES);

  SelectObject(hdc, CreatePen(PS_SOLID,2,RGB(255,0,0)));
  Rectangle(hdc, 0, 0, nWidth-4, nHeight-2);
  sprintf (line,
           "Test Printing, page#%u width x height (%dx%d)",
       Page,
       nWidth,
       nHeight);
  SetBkMode(hdc, TRANSPARENT); 
  TextOut(hdc, 4, 4, line, lstrlen(line) );
}
  
/*===============================*/ 
/* The Abort Procudure */ 
/* ==============================*/ 
BOOL CALLBACK AbortProc( HDC hDC, int Error )
{
   MSG   msg;
   while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
   {
     TranslateMessage( &msg );
     DispatchMessage( &msg );
   }
   return TRUE;
}
/*===============================*/ 
/* Obtain printer device context */ 
/* ==============================*/ 
HDC GetPrinterDC(void)
{
  PRINTDLG pdlg;

  /* Initialize the PRINTDLG structure. */
  memset( &pdlg, 0, sizeof( PRINTDLG ) );
  pdlg.lStructSize = sizeof( PRINTDLG );
  /* Set the flag to return printer DC. */
  pdlg.Flags =  PD_RETURNDC;

  /* Invoke the printer dialog box. */
  PrintDlg( &pdlg );
  
  /* hDC member of the PRINTDLG structure contains the printer DC. */
  return pdlg.hDC;
}
/*==============================================*/ 
/* Sample code : Typical printing process */ 
/* =============================================*/ 
void PrintJob( void )
{
  HDC        hDC;
  DOCINFO    di;

  /* Need a printer DC to print to. */
  hDC = GetPrinterDC();

  /* Did you get a good DC? */
  if( !hDC)
  {
    MessageBox (NULL,
                "Error creating DC",
                "Error",
                MB_APPLMODAL | MB_OK );
    return;
  }

  /* You always have to use an AbortProc(). */
  if( SetAbortProc( hDC, AbortProc ) == SP_ERROR )
  {
    MessageBox (NULL,
              "Error setting up AbortProc",
                "Error",
                MB_APPLMODAL | MB_OK);
    return;
  }

  /* Init the DOCINFO and start the document. */
  InitPrintJobDoc( &di, "MyDoc");
  StartDoc( hDC, &di );

  /* Print 1st page. */
  StartPage( hDC );
  DrawStuff( hDC, 1 );
  EndPage( hDC );
  
  /* Print 2nd page. */
  StartPage( hDC );
  DrawStuff( hDC, 2 );
  EndPage( hDC );
  /* Indicate end of document.*/
  EndDoc( hDC );
  /* Clean up */
  DeleteDC( hDC );
}

/* Print DC demo with 2 pages */
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

  if(MessageBox (NULL,
                 "Do you want to test print 2 pages?",
         "Test Print",
         MB_YESNO  ) != 0)
  {
    PrintJob();
  }
  return 0;
}

Printer demo output

Print demo first dialog

Print demo PDF printer

Print demo Windows XPS printer

Print demo output PDF and Windows XPS file

About our authors: Team EQA

Further readings

Where is WinMain() function in MFC application ?

MFC hides WinMain in its framework and includes source file on WinMain(). This explains how framework calls global CWinApp::Initinstance() from entry WinMain.

What is the utility of CWinApp class?

This is constructed during global C++ objects are constructed and is already available when Windows calls the WinMain function, which is supplied by the ...

Basic steps in Win32 GUI Application with source code.

Define a custom Window class structure, Register the class name, CreateWindow, Show windows and write message get and dispatch loop statements. Define the Window CallBack procedure and write the handlers.

What is a Window CallBack procedure and what is its utility?

DispatchMessage() is a API which indirectly triggers the Window CallBack procedure. Message structure members from this function are passed to the CallBack procedure. CallBack procedure should implement event handlers depending on the need of the application.

What are LPARAM and WPARAM in window proc function?

LPARAM and WPARAM are the two parameters in Window CallBack procedure. They signifies parameters of various events. They are used in handing individual events.

What are the basic steps of a typical MFC based application?

We need to write WinMain and need to follow all these in a Win32 application. However we need not to write much if we are writing an application with MFC ...

#