5

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...

AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
  • Your title contains the phrase *actual result*, but in your question you state that you never created a physical print-out. In other words, you do not even know whether it will produce the expected result. If you have a question about a physical print-out you need to verify the results. If, on the other hand, you want to find out why different print previews produce different results, you should update your question to reflect that. – IInspectable Jun 07 '14 at 17:23
  • 3
    @IInspectable Using virtual printers to test code like this is perfectly legitimate. Those printers will produce exactly the same results as a hardware printer with the same characteristics. – Carey Gregory Jun 07 '14 at 17:28
  • Have a look at the [GetTextExtentPoint32 function](http://msdn.microsoft.com/en-us/library/dd144938%28VS.85%29.aspx). – Carey Gregory Jun 07 '14 at 17:29
  • 1
    @Carey I'm not sure how you come to the conclusion that virtual printers will produce exactly the same results as a hardware printer, when the question clearly states, that two different virtual printers produce different results. In fact, even different hardware printers will produce different results. – IInspectable Jun 07 '14 at 17:55
  • 2
    @IInspectable Because I said "with the same characteristics." Given the same resolution, color depth, paper size, and unprintable margins, any two correctly functioning printers will produce the same results whether they're real or virtual. So as long as you take the different characteristics of two printers into account, it doesn't really matter if you use a hardware or virtual printer for testing. Both are equally valid. – Carey Gregory Jun 07 '14 at 19:22
  • 1
    @AlwaysLearningNewStuff To make your testing simpler, quit measuring in pixels. Use one of the device independent mapping modes such as `MM_HIMETRIC` or `MM_HIENGLISH`. Doing that will 1) prevent your output from being affected by the resolution of the printer and 2) allow you to actually measure the output with a ruler to see if it's correct. – Carey Gregory Jun 07 '14 at 19:25
  • @CareyGregory: I think that the different output is related to DPI, please see my edit at the bottom of my post. As for `GetTextExtentPoint32`, I do not see how can I use it to adjust the cell of my grid. Maybe I am not very clear so if I need to further clarify something please tell me. Best regards. – AlwaysLearningNewStuff Jun 08 '14 at 01:03
  • The different output is definitely related to DPI. That's what I said (resolution == DPI). `GetTextExtentPoint32` will measure your output string as it will actually appear, using the current font, resolution of the output device, etc. It's exactly what you need to adjust the cell of your grid. Study it more carefully and look for examples. – Carey Gregory Jun 08 '14 at 03:46
  • @CareyGregory: I am trying to engineer a solution for text wrapping using the API you recommended, so for now I will ask a question about DPI only : Is there an equation I can apply to secure constant font height ? The way I see it, my problem is that I pass `14` as a font height but with different DPI the height changes. If I could calculate scaling factor somehow and multiply it with `14` then my font should appear properly, right? Can you help me with that, or should I stick with meters/inches as you have suggested earlier? – AlwaysLearningNewStuff Jun 08 '14 at 13:09
  • You're never going to calculate text wrapping with any formula involving the font size. Font sizes describe the height of the bounding box (the Em) surrounding the largest character in the font. But character widths vary (in variable width fonts), and because of kerning they vary even more. So, for example, the characters string `ii` will be much narrower than the string `MM` even though they're both two chars long. `GetTextExtentPoint32` takes all this into account. I would stick with mm/inches rather than pixels. And just so you know, text wrapping is an extraordinarily complex algorithm. – Carey Gregory Jun 08 '14 at 16:20
  • Oh, and your font height shouldn't be changing with different DPIs. A size 14 font means that the tallest character in the font will be slightly less than 14 * 1/72 inches tall. Changing DPIs will not change that. If one printer is 300 dpi and another is 600 dpi, characters printed on the higher resolution printer will have twice as many pixels but they will be the same physical size. – Carey Gregory Jun 08 '14 at 16:30
  • @CareyGregory: Using `GDI+` for drawing a text inside rectangle, and for creating font, I was able to get consistent results. The only thing left for me is to properly draw grid and choose proper font size so the text can fit in cells. – AlwaysLearningNewStuff Jun 09 '14 at 13:18
  • http://stackoverflow.com/questions/2240243/how-do-i-do-print-preview-in-win32-c/2241414#2241414 – Adrian McCarthy Jun 09 '14 at 16:49
  • @CareyGregory: Real printers are quirky, buggy, and do surprising things that virtual printers don't (like font substitutions). – Adrian McCarthy Jun 09 '14 at 17:13
  • @AdrianMcCarthy: I have already stumbled upon your answer to the linked question but can not "convert" it to actual code... – AlwaysLearningNewStuff Jun 09 '14 at 17:23
  • @AdrianMcCarthy And vice versa. I've been developing print drivers, port monitors, and print processors for well over 10 years now, so I am a wee bit knowledgeable in the area. A good virtual printer is an excellent test bed for something like the OP is working on. Should he test his app with a range of printers, including hardware printers, before calling it complete? Of course. – Carey Gregory Jun 09 '14 at 22:54

1 Answers1

1

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.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Thank you for your help. I have changed "font creating" code to use `GDI+` and everything works fine. Reading through [this MSDN link](http://msdn.microsoft.com/en-us/library/ms969894.aspx) ( look at *GDI+* section ) I have decided to stick with `GDI+` and not trying to "reinvent the wheel" ( especially being inexperienced developer ). Upvoted. I will wait for a while to see what will "pop up", while bounty is still on, before officially accepting and awarding the bounty. Best regards. – AlwaysLearningNewStuff Jun 10 '14 at 23:42
  • @AlwaysLearningNewStuff I can't disagree with this answer and by all means award the rep, but it pretty much just goes back to what I told you almost a week ago. Quit using MM_TEXT mapping mode and forget about pixels. Use a real world mapping mode and work in real world units and you can forget all this pixel nonsense. – Carey Gregory Jun 12 '14 at 06:17
  • @CareyGregory: Thank you for your help. The reason I wish to use pixels is because I can use *the same code* fro generating print preview, otherwise I would follow your advice. Or maybe there is a way to implement your suggestion and have the same code for print preview/printing? – AlwaysLearningNewStuff Jun 12 '14 at 13:44
  • @AlwaysLearningNewStuff You absolutely can use the same code for preview and printing. In fact, it would be much easier because then you wouldn't have to worry about device resolution. Windows would take care of all those calculations for you. For example, suppose you want a cell in your grid to be 1 inch wide. The calculations for drawing that cell in MM_HIENGLISH mapping mode will be identical on a 600 dpi printer and a 96 dpi screen, but in MM_TEXT mode you'll have to query the resolution and include it in virtually every calculation you make. – Carey Gregory Jun 12 '14 at 14:30
  • 1
    @AlwaysLearningNewStuff that's the whole reason why the mapping modes exist, to make it *easy* to use the same code on different devices. Personally I prefer to know exactly how many pixels are being rendered, but that's just me and it's doing it the hard way. – Mark Ransom Jun 12 '14 at 14:32
  • @CareyGregory does the mapping mode affect the way font sizes are calculated or are those always in pixels? – Mark Ransom Jun 12 '14 at 14:41
  • @CareyGregory: I have added `SetMapMode( pdx.hDC, MM_HIENGLISH );` just before font initialization ( in my function submitted in the post ). Grid and rectangle were not painted at all, nor was the rectangle filled with gray brush, but the text was drawn. However, the result was inconsistent, so I have changed font height from `long lfHeight = -MulDiv( 14, GetDeviceCaps( pdx.hDC, LOGPIXELSY), 72 );` to `long lfHeight = 250;` and the text output was consistent on both `XPS` and `OneNote`. Grid and rectangle were not painted, nor was rectangle filled with gray brush. Rest is in the below comment: – AlwaysLearningNewStuff Jun 12 '14 at 15:07
  • @CareyGregory: To continue the above comment : Can you help me out by modifying the function in my post to properly use mapping mode `MM_HIENGLISH` so I can have something to start with? I would like to start from there, since the code is rather simple and short. Thank you. – AlwaysLearningNewStuff Jun 12 '14 at 15:10
  • @AlwaysLearningNewStuff I think you just answered my question about font size, so use `1000` instead of `GetDeviceCaps(...)` in the `MulDiv` call. As for making the rest of your drawing work, adjust all your constants as if you were using a 1000 DPI device, e.g. the rectangle would be (0, 0, 1833, 333). – Mark Ransom Jun 12 '14 at 15:21
  • @MarkRansom: My pardon for asking, but how did you calculate the coordinates for rectangle ( 0, 0, 1833, 333 ) ? – AlwaysLearningNewStuff Jun 12 '14 at 15:33
  • @AlwaysLearningNewStuff you said the rectangle was the correct size at 300 DPI so I just multiplied all the coordinates by 1000/300. – Mark Ransom Jun 12 '14 at 15:44
  • @MarkRansom Mapping mode applies to everything unless you find a statement to the contrary in the MSDN. – Carey Gregory Jun 13 '14 at 03:28
  • @AlwaysLearningNewStuff No, you need to modify it yourself. You're doing fine, so keep going. :-) – Carey Gregory Jun 13 '14 at 03:29