Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expected and actual printing results do not match

INTRODUCTION AND RELEVANT INFORMATION:

I am trying to bypass another problem in my application by trying to do printing/print preview on my own.

I am trying to create a table that would look like in the picture below:

enter image description here

I am using C++ and WinAPI, on WindowsXP SP3. I work in MS Visual Studio 2008.

I do not have a printer, so I am testing the results by printing to MS OneNote and XPS file.

PROBLEM:

Text is obtained from database and is of variable length. Since it might not fit into the original cell, I will need to expand the cell and fit the text appropriately, like in the above image.

SIDE EFFECT:

The result of my testing code gives inconsistent results regarding font size.

In OneNote the print result seems fine :

enter image description here

However, in XPS it looks different :

enter image description here

MY EFFORTS TO SOLVE THIS TASK:

I have checked MSDN documentation to get started. So far I am able to successfully draw text and lines on a printing surface.

I have used DrawTextEx to perform word breaking ( by using flag DT_WORDBREAK ).

To obtain the size of the printing area I have used GetDeviceCaps, and to obtain printer device context I have used print property sheet.

QUESTIONS:

IMPORTANT REMARKS:

If the following questions are considered too broad please leave a comment and I will edit my post. I still believe that my mistakes are minor and can be explained in a single post.

1. Can you explain me how to adjust cells so the entire string can fit ?

  1. Why is my font inconsistently drawn ?

As always, here are the instructions for creating SSCCE :

1) In Visual Studio, create default Win32 project.

2) in stdafx.h file comment out #define WIN32_LEAN_AND_MEAN so print property sheet can work properly.

3) In stdafx.h add the following, below #include <windows.h> line :

#include <windowsx.h>
#include <commctrl.h>

#pragma comment( linker, "/manifestdependency:\"type='win32' \
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
    language='*'\"")

4) Add the following function above window procedure :

// hWnd is the window that owns the property sheet.
HRESULT DisplayPrintPropertySheet(HWND hWnd)
{
    HRESULT hResult;
    PRINTDLGEX pdx = {0};
    LPPRINTPAGERANGE pPageRanges = NULL;

    // Allocate an array of PRINTPAGERANGE structures.
    pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));

    if (!pPageRanges)
        return E_OUTOFMEMORY;

    //  Initialize the PRINTDLGEX structure.
    pdx.lStructSize = sizeof(PRINTDLGEX);
    pdx.hwndOwner = hWnd;
    pdx.hDevMode = NULL;
    pdx.hDevNames = NULL;
    pdx.hDC = NULL;
    pdx.Flags = PD_RETURNDC;
    pdx.Flags2 = 0;
    pdx.ExclusionFlags = 0;
    pdx.nPageRanges = 0;
    pdx.nMaxPageRanges = 10;
    pdx.lpPageRanges = pPageRanges;
    pdx.nMinPage = 1;
    pdx.nMaxPage = 1000;
    pdx.nCopies = 1;
    pdx.hInstance = 0;
    pdx.lpPrintTemplateName = NULL;
    pdx.lpCallback = NULL;
    pdx.nPropertyPages = 0;
    pdx.lphPropertyPages = NULL;
    pdx.nStartPage = START_PAGE_GENERAL;
    pdx.dwResultAction = 0;

    //  Invoke the Print property sheet.

    hResult = PrintDlgEx(&pdx);

    if ( ( hResult == S_OK )    
        && ( pdx.dwResultAction == PD_RESULT_PRINT ) )
    {

        // User clicked the Print button, 
        // so use the DC and other information returned in the 
        // PRINTDLGEX structure to print the document.

        /***************** IMPORTANT INFO : ********************/
        /****** I have added additional test code here *********/
        /**** please refer to the edited part of this post *****/
        /***************** at the very bottom !! ***************/

        DOCINFO diDocInfo = {0};
        diDocInfo.cbSize = sizeof( DOCINFO ); 
        diDocInfo.lpszDocName = L"Testing printing...";

        //******************** initialize testing font *****************//

        HFONT font, oldFont; 

        long lfHeight = -MulDiv( 14, GetDeviceCaps( pdx.hDC, LOGPIXELSY), 72 );

        font = CreateFont( lfHeight, 
            0, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, 0, 0, 0, 
            0, 0, L"Microsoft Sans Serif" );

        oldFont = SelectFont( pdx.hDC, font );

        SetBkMode( pdx.hDC, TRANSPARENT );

        SetTextColor( pdx.hDC, RGB( 255, 0, 0 ) );

        //******************** end of initialization ******************//

        if( StartDoc( pdx.hDC, &diDocInfo ) > 0 )
        {
            if( StartPage( pdx.hDC ) > 0 )
            {
                // get paper dimensions
                int pageWidth, pageHeight;

                pageWidth = GetDeviceCaps( pdx.hDC, HORZRES );
                pageHeight = GetDeviceCaps( pdx.hDC, VERTRES );

                /************ draw a testing grid ***************/

                // draw vertical lines of the grid
                for( int i = 0; i < pageWidth; i += pageWidth / 4 )
                {
                     MoveToEx( pdx.hDC, i, 0, NULL );
                     LineTo( pdx.hDC, i, pageHeight );
                }

                // draw horizontal lines of the grid
                for( int j = 0; j < pageHeight; j += pageWidth / 10 )
                {
                     MoveToEx( pdx.hDC, 0, j, NULL );
                     LineTo( pdx.hDC, pageWidth, j );
                }

                /************************************************/

                // test rectangle for drawing the text
                RECT r;
                r.left = 0;
                r.top = 0;
                r.right = 550;
                r.bottom = 100;

                // fill rectangle with light gray brush
                // so we can see if text is properly drawn

                FillRect( pdx.hDC, &r, 
                    (HBRUSH)GetStockObject(LTGRAY_BRUSH) );

                // draw text in test rectangle 
                if( 0 == DrawTextEx( pdx.hDC, 
                     L"This is test string!", 
                     wcslen( L"This is test string!" ), 
                     &r, 
                     DT_CENTER | DT_WORDBREAK | DT_NOCLIP, NULL ) )
                     // for now pop a message box saying something went wrong
                     MessageBox( hWnd, L"DrawText failed!", L"Error", MB_OK );

                if( EndPage( pdx.hDC ) < 0 )
                     // for now pop a message box saying something went wrong
                     MessageBox( hWnd, L"EndDoc failed!", L"Error", MB_OK );
            }

            EndDoc( pdx.hDC );

            SelectFont( pdx.hDC, oldFont );
            DeleteFont( font );
        }
    }

    if (pdx.hDevMode != NULL) 
        GlobalFree(pdx.hDevMode); 

    if (pdx.hDevNames != NULL) 
        GlobalFree(pdx.hDevNames); 

    if (pdx.lpPageRanges != NULL)
        GlobalFree(pPageRanges);

    if (pdx.hDC != NULL) 
        DeleteDC(pdx.hDC);

    return hResult;
}

5) In WM_COMMAND handler, modify case IDM_ABOUT like this :

case IDM_ABOUT:   // test our printing here
    {
        if( FAILED( DisplayPrintPropertySheet( hWnd ) ) )
            MessageBox( hWnd, 
                L"Can't display print property sheet!", 
                L"Error", MB_OK );
    }
    //DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
    break;

EDITED on June, 8th 2014 :

After the block if ( ( hResult == S_OK ) && ( pdx.dwResultAction == PD_RESULT_PRINT ) ) in the submitted SSCCE I have added the following for testing purposes :

int xDpi = GetDeviceCaps( pdx.hDC, LOGPIXELSX ),
    yDpi = GetDeviceCaps( pdx.hDC, LOGPIXELSY );

int mapMode = GetMapMode( pdx.hDC );

wchar_t displayDPI[50];
swprintf_s( displayDPI, 50, L" xDPI = %s , yDPI = %s", xDpi, yDpi );
MessageBox( hWnd, displayDPI, L"", MB_OK );

switch( mapMode )
{
case MM_ANISOTROPIC:
    MessageBox( hWnd, L"MM_ANISOTROPIC", L"", MB_OK );
    break;
case MM_HIENGLISH:
    MessageBox( hWnd, L"MM_HIENGLISH", L"", MB_OK );
    break;
case MM_HIMETRIC:
    MessageBox( hWnd, L"MM_HIMETRIC", L"", MB_OK );
    break;
case MM_ISOTROPIC:
    MessageBox( hWnd, L"MM_ISOTROPIC", L"", MB_OK );
    break;
case MM_LOENGLISH:
    MessageBox( hWnd, L"MM_LOENGLISH", L"", MB_OK );
    break;
case MM_LOMETRIC:
    MessageBox( hWnd, L"MM_LOMETRIC", L"", MB_OK );
    break;
case MM_TEXT:
    MessageBox( hWnd, L"MM_TEXT", L"", MB_OK );
    break;
case MM_TWIPS:
    MessageBox( hWnd, L"MM_TWIPS", L"", MB_OK );
    break;
default:
    MessageBeep(0);
    break;
}

In both cases mapping mode was the same ( MM_TEXT ) but for XPS I got xDPI = 600 , yDPI = 600 in the MessageBox while OneNote had xDPI = 300 , yDPI = 300.

This leads to the conclusion that comments made by member * Carey Gregory* were correct -> with the same characteristics virtual printers will reproduce the same result. This also explains why OneNote printed properly into XPS when I tested it, and why my application failed. To solve this problem I need to find DPI aware solution...

EDITED on June, 9th 2014 :

Using GDI+ to create font and draw text I was able to get consistent results ( DPI is no longer a problem ). Still, if anyone knows how to achieve the same result using only GDI I would be still interested.

The only thing left for me is to draw a proper grid so the text can fit into cells properly.

EDITED on June, 10th 2014 :

After carefully reading through this MSDN link I was able to alter the font creating code to achieve ( in my opinion ) stable results ( the actual height of the font ends up way smaller, but I could use bigger number I guess ) :

 font = CreateFont( 
    // DPI aware, thanks to the below equation ( or so it seems... )
    lfHeight / ( GetDeviceCaps( pdx.hDC, LOGPIXELSY ) / 96 ), 
    0, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, 0, 0, 0,      // remained the same
    0, 0, L"Microsoft Sans Serif" );                    // remained the same

Just to be safe, I will try to stick with GDI+ but will update this post with the testing results when GDI and the mentioned equation is used in case someone else stumbles upon the same problem. I just hope it will save that persons time...

like image 956
AlwaysLearningNewStuff Avatar asked Nov 10 '22 06:11

AlwaysLearningNewStuff


1 Answers

The problem is simple. You are adjusting the font size (in pixels) to match the DPI of the device you're drawing to, but you're not adjusting the size of the rectangle. Since your mapping mode is MM_TEXT both are measured in pixels, and your bounding box is effectively half the size on the device with twice the resolution.

The solution is to scale the rectangle similarly to the way you scale the font size. In this case since you've determined that these coordinates are correct at 300 DPI, that's the constant we'll scale relative to.

RECT r;
r.left = 0 * GetDeviceCaps(pdx.hDC, LOGPIXELSX) / 300;
r.top = 0 * GetDeviceCaps(pdx.hDC, LOGPIXELSY) / 300;
r.right = 550 * GetDeviceCaps(pdx.hDC, LOGPIXELSX) / 300;
r.bottom = 100 * GetDeviceCaps(pdx.hDC, LOGPIXELSX) / 300;

Regarding your edit of June 10, it only works because you've made the font much smaller so that it fits in both the full-size bounding box and the half-size one. I'd recommend going back to the original definition you had for font size, which is consistent with most other Windows applications.

like image 190
Mark Ransom Avatar answered Nov 15 '22 07:11

Mark Ransom