Click here to Skip to main content
Click here to Skip to main content

Printing support functions

, 6 Feb 2000 CPOL
Rate this:
Please Sign up or sign in to vote.
Two methods for obtaining consistent output between printers.

Introduction

Using the default properties supplied for printer output does not give consistent results. The printable region and the pixel density cause variations. If you are outputting pages for a formal report, consistent margins and font size are often required. The following functions provide a method for obtaining consistent output between printers.

Included functions are:

  1. UserPage which returns a CRect which defines a consistent printable area for each printer (Your margins must be within the printable region of all printers of course!)
  2. CreateFontSize which returns a CSize which defines the font attribute with respect to the desired point size and printer characteristics.

The remaining code shows sample usage of these functions.

//Input: pointer to device context for printer
//Input: desired margin
//Output: CRect to use for printing area
CRect CChildView::UserPage(CDC * pDC, float margin)
{
    // This function returns the area in device units to be used to
    // prints a page with a true boarder of "margin".
    //
    // You could use individual margins for each edge
    // and apply below as needed.
    //
    // Set Map Mode - We do not want device units
    // due to lack of consistency.
    // If you do not use TWIPS you will have to change
    // the scaling factor below.
    int OriginalMapMode = pDC->SetMapMode(MM_TWIPS);

    // Variable needed to store printer info.
    CSize PrintOffset,Physical,Printable;

    // This gets the Physical size of the page in Device Units
    Physical.cx = pDC->GetDeviceCaps(PHYSICALWIDTH);
    Physical.cy = pDC->GetDeviceCaps(PHYSICALHEIGHT);
    // convert to logical
    pDC->DPtoLP(&Physical);

    // This gets the offset of the printable area from the
    // top corner of the page in Device Units
    PrintOffset.cx = pDC->GetDeviceCaps(PHYSICALOFFSETX);
    PrintOffset.cy = pDC->GetDeviceCaps(PHYSICALOFFSETY);
    // convert to logical
    pDC->DPtoLP(&PrintOffset);

    // Set Page scale to TWIPS, Which is 1440 per inch,
    // Zero/Zero is the upper left corner
    // Get Printable Page Size (This is in MM!) so convert to twips.
    Printable.cx =  (int)((float)pDC->GetDeviceCaps(HORZSIZE)*56.69);
    Printable.cy = (int)((float)pDC->GetDeviceCaps(VERTSIZE)*56.69);

    // Positive X -> RIGHT
    // Positive Y -> UP
    // Ref Zero is upper left corner
    int inch = 1440; // Scaling Factor Inches to TWIPS
    int Dx1, Dx2, Dy1, Dy2; // Distance printable area is from edge of paper
    Dx1 = PrintOffset.cx;
    Dy1 = PrintOffset.cy;
    // calculate remaining borders
    Dy2 = Physical.cy-Printable.cy-Dy1;
    Dx2 = Physical.cx-Printable.cx-Dx1;
    //
    // Define the User Area's location
    CRect PageArea;
    PageArea.left = (long)(margin*inch-Dx1);
    PageArea.right = (long)(Printable.cx-margin*inch+Dx2);
    PageArea.top = (int)-(margin*inch-Dy1); // My scale is inverted for y
    PageArea.bottom = (int)-(Printable.cy-margin*inch+Dy2);
    // now put back to device units to return to the program.
    pDC->LPtoDP(&PageArea);
    //
    // return
    return PageArea;
}
// Input: pointer to device context for printer
// Input: desired point size of font (in points).
// Output: integer height to send to CreateFont function.
int CChildView::CreateFontSize(CDC *pdc, int points)
{
    // This will calculate the font size for the printer that is specified
    // by a point size.
    //
    // if points is:
    //  (-) negative uses height as value for Net Font Height
    //                                         (ie. point size)
    //  (+) positive height is Total Height plus Leading Height!
    CSize size;
    int perinch = pdc->GetDeviceCaps(LOGPIXELSY);
    size.cx = size.cy = (perinch*points)/72;
    pdc->DPtoLP(&size);
    return size.cy;
}

To use CreateFontSize, just insert the function call into the CreateFont function:

    BaseFont.CreateFont( -CreateFontSize(pdc,11), 0, 0, 0, FW_MEDIUM,
        FALSE, FALSE, 0, ANSI_CHARSET, 
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, 
        DEFAULT_QUALITY, DEFAULT_PITCH , "Courier New" );

The UserPage function can be used internal to your OnPrint function. Where you call it, use the returned area rather than the region found from the pDC's GetDeviceCaps function. (Usually sent in the PrintInfo data.) Or you can call it to set the region in to be passed.

void CChildView::PrintSetup(int item)
{
    // Create Standard windows dialog.
    BOOL bStdSetUpDlg = TRUE;
    // See PRINTDLG for flags to set defaults in dialog.
//    DWORD dwFlags = PD_ALLPAGES | PD_USEDEVMODECOPIES | 
         PD_NOPAGENUMS | PD_HIDEPRINTTOFILE | PD_NOSELECTION;
    DWORD dwFlags = PD_ALLPAGES;
    // Parent (may be NULL)
    CWnd *pParent = this;
    CPrintDialog MyPrintDlg(bStdSetUpDlg,dwFlags,pParent);
    // Print Info
    CPrintInfo MyPrintInfo;
    // first link with dialog so data is shared
    // Your input into min and max pages is now shared.
    MyPrintInfo.m_pPD = &MyPrintDlg;
    //
    // Get Users Answer;
    int MyAnswer;
    MyAnswer = MyPrintDlg.DoModal();
    // Allow the user to cancel
    if(MyAnswer==IDCANCEL) return;
    //
    // Get the mode the printer is in from the Print Dialog.
    // This memory block must be unlocked later.
    DEVMODE *MyPrintMode;
    MyPrintMode = MyPrintDlg.GetDevMode();
    // 
    // Create our Printer Context
    CDC MyPrintDC;
    MyPrintDC.CreateDC(MyPrintDlg.GetDriverName(), // Ignored for Printer DC's
        MyPrintDlg.GetDeviceName(), // The only required item for Printer DC's
        MyPrintDlg.GetPortName(), // Ignored for Printer DC's
        MyPrintMode); // Optional Item for Printer DC's
    //
    // Start the Document for our document
    DOCINFO MyDocInfo;
    MyDocInfo.cbSize=sizeof(DOCINFO);
    CString DocName;
    DocName.LoadString(AFX_IDS_APP_TITLE);
    MyDocInfo.lpszDocName="DocName";
    MyDocInfo.lpszOutput="";
    //
    // Start the document
    int iErr = MyPrintDC.StartDoc(&MyDocInfo);
    if(iErr < 0)
    {
        //success returns positive value
        MyPrintDC.AbortDoc();
        GlobalUnlock(MyPrintMode); // Release the print mode.
        return;
    }
    // success so set flag to printing
    MyPrintDC.m_bPrinting=TRUE;
    // Most programs us the device's printable region found with
    // MyPrintDC.GetDevicecaps(****) functions.
    // However this is not consistent between printers so -->
    // The UserPage functions calculates what margins
    // to specify so we have the
    // actual distance from the edge of the page
    // to be consistent between printers.
    CRect MyArea;
    // fixed margin in inches (you can change this)
    MyArea = UserPage(&MyPrintDC, 0.9f);
    MyPrintInfo.m_rectDraw.SetRect(MyArea.left, 
            MyArea.top,MyArea.right,MyArea.bottom);
    //
    // We are now into personal preferences based on your program needs.
    //
    // We can call OnBeginPrinting and OnEndPrinting functions
    // to initialize and clean up
    // and loop through calls to OnPrint
    // (calling Startpage and EndPage functions)
    //
    // or as I have done here->
    // Call the StartPage the first time EndPage at the end
    // with the print fnuction handling the begin
    // and end when needed internally.
    //
    // Start the page. (This allways sets the DC to device units!) 
    MyPrintDC.StartPage();
    // Set mode.
    MyPrintDC.SetMapMode(MM_TEXT);
    //
    // We are now ready to print our data. Switch to the options allowed.
    // For our usage we will end and restart
    // each page in the functions called
    // based on the location of the current print location on the page.
    //
    // Internal to the fucntions we need to call:
    //     pdc->EndPage();
    //    pdc->StartPage(); // Returns in Device units
    //    pdc->SetMapMode(MM_LOENGLISH); // Reset to our desired mode
    //  Reset position to draw, etc....
    // as needed.
    //
    switch(item)
    {
    case(1):
        PrintLoose(&MyPrintDC,MyArea);
        break;
    case(2):
        PrintRecord(&MyPrintDC,MyArea);
        break;
    }
    // We are all done. Clean up
    MyPrintDC.m_bPrinting=FALSE;
    // end last page
    MyPrintDC.EndPage();
    // end the document
    MyPrintDC.EndDoc();
    // Release the device context
    GlobalUnlock(MyPrintMode); // Release the print mode.
    return;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael A. Barnhart
Systems Engineer
United States United States
Began programming in 1968 on a Wang 720. Move to Fortran and began developing FEM (finite element model) applications on an IBM 360 in 1973. Developed custom FEM editors for most of my career until 1995. Since then I have been focusing on improving information flow and quality with web based communications (Web Services and SOA concepts.) Mostly in an evangelist role.

Comments and Discussions

 
GeneralRe: PageArea Calc Pinmembercdoersom27-Nov-02 6:04 
GeneralRe: PageArea Calc PinmemberMichael A. Barnhart27-Nov-02 12:59 
GeneralInkjet printers give dpi different PinmemberRichard Jones20-Nov-02 4:36 
GeneralRe: Inkjet printers give dpi different PinmemberMichael A. Barnhart20-Nov-02 13:27 
GeneralRe: Inkjet printers give dpi different PinmemberRichard Jones22-Nov-02 2:25 
GeneralProb getting color setting from DEVMODE Pinmemberjcn12319-Apr-02 8:45 
GeneralRe: Prob getting color setting from DEVMODE Pinmemberi.Wahn25-Jul-02 21:32 
GeneralTwo small problems PinmemberTony Brown17-Jul-01 1:11 
Thank you for some very clear simple code and easy to use compared to the nightmare printing examples I have come across.
 
There are a couple of nitpicky errors. The lines :-
MyDocInfo.lpszDocName="DocName";
MyDocInfo.lpszOutput="";
 
I think should read
MyDocInfo.lpszDocName=DocName;
// If you really want your app name
MyDocInfo.lpszOutput=0;
// At least to get Startdoc to not generate an error on one of our 95 boxes
 
and before the GlobalUnlock reset the pd to avoid a heap error:-
MyPrintInfo.m_pPD = 0;
// We set it, so we uninitialise it
GlobalUnlock(MyPrintMode);
// Release the print mode.
 


GeneralRe: Two small problems PinmemberMichael A. Barnhart17-Jul-01 13:33 
GeneralRe: Two small problems PinmemberDuncan123452-Apr-03 1:57 
GeneralThe same, but slightly different PinmemberJoep Oude Veldhuis21-Feb-01 0:29 
GeneralGood Pinmemberraja sekhar18-Feb-01 21:17 
GeneralRe: Good PinmemberMichael A. Barnhart19-Feb-01 12:10 
GeneralPost Script Printer Pinmembernavid_ahsan4-Dec-00 16:59 
GeneralIn millimeters !?!?! PinsussLuc Bergeron21-Sep-00 10:46 
GeneralRe: In millimeters !?!?! PinsussMichael A. Barnhart22-Sep-00 12:18 
GeneralPrinter Differences PinsussSujay Ghosh16-Jul-00 22:51 
GeneralRe: Printer Differences PinsussMichael Barnhart17-Jul-00 3:03 
GeneralMissing Line in UserPage PinsussMichael Barnhart9-Feb-00 6:14 
GeneralCrystal Edit PinsussMichael Barnhart9-Feb-00 6:08 
QuestionHow to use within Andrei's CrystalEdit Pinsusskaizen9-Feb-00 2:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.2 | Last Updated 7 Feb 2000
Article Copyright 2000 by Michael A. Barnhart
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid