2

I have the following code in an otherwise default VisualStudio project. It passes DT_CALCRECT to DrawTextW to calculate the rectangle to draw some text, then it uses that rectangle to draw the text. To test it yourself just paste this code into a default VisualStudio project:

case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    // TODO: Add any drawing code here...

    {
        wchar_t txt[] = L"abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef\r\nabc\r\n123456";
        BOOL useDT_RIGHT = TRUE; // <<< ** SWITCH THIS BETWEEN TRUE AND FALSE **
        wchar_t buf1[100] = {0};
        wchar_t buf2[100] = {0};
        RECT r1 = {0, 0, 192, 1000};
        RECT r2 = {r1.right + 10, r1.top, r1.right + 400, r1.top + 100};
        int ret1, ret2;

        FillRect(hdc, &r1, (HBRUSH)GetStockObject(GRAY_BRUSH));

        ret1 = DrawTextW(hdc, txt, -1, &r1,
            DT_CALCRECT |
            DT_WORDBREAK |
            (useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
        );
        if(ret1 == 0) MessageBoxW(NULL, L"ret1 == 0", NULL, MB_OK);

        wsprintfW(buf1, L"useDT_RIGHT = %i\r\nDT_CALCRECT returned %i %i %i %i\r\nret1 = %i\r\n", useDT_RIGHT, r1.left, r1.top, r1.right, r1.bottom, ret1);

        ret2 = DrawTextW(hdc, txt, -1, &r1,
            DT_WORDBREAK |
            (useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
        );
        if(ret2 == 0) MessageBoxW(NULL, L"ret2 == 0", NULL, MB_OK);

        wsprintfW(buf2, L"%sret2 = %i", buf1, ret2);
        DrawTextW(hdc, buf2, -1, &r2, DT_LEFT);
    }

    EndPaint(hWnd, &ps);
    break;

In the code, if useDT_RIGHT is set to FALSE, the text is left aligned and DT_CALCRECT returns the correct rectangle, as shown below:

http://i64.tinypic.com/2ptw2dk.png

If useDT_RIGHT is set to TRUE, the text is right aligned but DT_CALCRECT returns an incorrect rectangle, as shown below:

http://i68.tinypic.com/nwx9co.png

Or rather, it may be returning a correct rectangle, and the subsequent call to actually draw the text is drawing it incorrectly, it's impossible to tell.

The docs for DT_CALCRECT say "If there are multiple lines of text, DrawText uses the width of the rectangle pointed to by the lpRect parameter and extends the base of the rectangle to bound the last line of text. If the largest word is wider than the rectangle, the width is expanded. If the text is less than the width of the rectangle, the width is reduced."

What I would expect is that the rectangle returned by DrawTextW would be the right size to draw the text (in the actual code, the rectangle is also used for positioning the surrounding controls, so just expanding it willy nilly won't really help). I would also expect the text to be properly right aligned (ie. the opposite of the left aligned text), and not the mess it is as shown in the second screenshot above. By properly right aligned I mean as shown in this screenshot of wordpad:

http: //i63.tinypic.com/qqya1u.png (Please remove the space from this link to make it work.)

What is wrong with this code? Why does DT_CALCRECT with DT_RIGHT not produce the expected results? Or, if it is, why is the second call to DrawTextW not drawing it correctly?

Alice
  • 23
  • 1
  • 3
  • Is it possible that there are spaces at the end of each line on the right-aligned text and that's why the rectangle width is offset? – VuVirt Oct 05 '16 at 11:41
  • I'm not sure what you mean by offset. It's the same text in both cases. It's the same code running in both cases. The only difference between the two screenshots is that one uses DT_RIGHT and the other DT_LEFT. Yet DrawText is drawing the right aligned one mangled. – Alice Oct 05 '16 at 11:56
  • No it's not the same text. The left aligned one in the first screenshot has an extra 123456 text. I'm not sure I understand what your actual problem is. Can you explain as it is not obvious from the screenshots? – VuVirt Oct 05 '16 at 12:06
  • You can also try DT_EDITCONTROL to see if it makes any difference. – VuVirt Oct 05 '16 at 12:12
  • I just tried DT_EDITCONTROL but it made no difference. It *is* the same text. In the second screenshot DrawText fails to draw the 123456. If you paste the code into a default VS project you can see for yourself. My actual problem is as stated. DrawText, when passed that text, DT_CALCRECT and DT_LEFT, returns a rectangle that can be used in the second call to DrawText to actually draw the text. If you do the same thing, changing only DT_LEFT to DT_RIGHT, the rectangle is either too small, or the second call to DrawText draws the text mangled. – Alice Oct 05 '16 at 12:25
  • You cannot tell whether the rectangle is wrong or the second call draws it wrong because you cannot see what DrawText is doing internally. Either way, with DT_RIGHT the text is not properly right aligned (ie. as in the third screenshot) and is not completely drawn (the 123456 is missing). – Alice Oct 05 '16 at 12:27
  • Ok I managed to replicate the problem. It seems that DrawText doesn't include the last line when right-aligned. I don't know why this is happening. It may be a problem with the selected font, so you can try to select a different font in the HDC. However, I can offer you two workarounds: use DT_NOCLIP when drawing the text, or add an extra \r\n at the end of the text (after "123456" like this "123456\r\n"). Both workarounds fix the problem in my project. – VuVirt Oct 05 '16 at 12:37

1 Answers1

2

It seems that this behavior is either a bug or by design. Maybe DT_WORDBREAK removes spaces and that's why it produces a narrower rectangle when using DT_RIGHT. Anyway, here is a way to make DrawText behave the same way when using DT_CALCRECT with either DT_LEFT or DT_RIGHT, you can test this code (check the comment that starts with FIX):

case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    // TODO: Add any drawing code here...

    {
        wchar_t txt[] = L"abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef\r\nabc\r\n123456";
        BOOL useDT_RIGHT = FALSE; // TRUE; // <<< ** SWITCH THIS BETWEEN TRUE AND FALSE **
        wchar_t buf1[100] = { 0 };
        wchar_t buf2[100] = { 0 };
        RECT r1 = { 0, 0, 192, 1000 };
        RECT r2 = { r1.right + 10, r1.top, r1.right + 400, r1.top + 100 };
        int ret1, ret2;

        FillRect(hdc, &r1, (HBRUSH)GetStockObject(GRAY_BRUSH));

        ret1 = DrawTextW(hdc, txt, -1, &r1,
            DT_CALCRECT |
            DT_WORDBREAK | 
            (useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT) 
            );
        if (ret1 == 0) MessageBoxW(NULL, L"ret1 == 0", NULL, MB_OK);

        // FIX: The following two lines make DrawText with DT_CALCRECT behave the same way for DT_LEFT and DT_RIGHT
        r1.right = 192;
        r1.bottom = ret1;

        wsprintfW(buf1, L"useDT_RIGHT = %i\r\nDT_CALCRECT returned %i %i %i %i\r\nret1 = %i\r\n", useDT_RIGHT, r1.left, r1.top, r1.right, r1.bottom, ret1);

        ret2 = DrawTextW(hdc, txt, -1, &r1,
            DT_WORDBREAK |
            (useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
            );
        if (ret2 == 0) MessageBoxW(NULL, L"ret2 == 0", NULL, MB_OK);

        wsprintfW(buf2, L"%sret2 = %i", buf1, ret2);
        DrawTextW(hdc, buf2, -1, &r2, DT_LEFT);
    }

    EndPaint(hWnd, &ps);
    break;
VuVirt
  • 1,887
  • 11
  • 13
  • Actually the second line "r1.bottom = ret1;" is redundant because r1.bottom equals ret1 anyway, but I'll leave it for clarity. The idea is to reset the width of the rectangle to the original value before calling the second DrawText to render the text. – VuVirt Oct 05 '16 at 13:01
  • This is the correct answer because you get both the actual size of the rectangle that DrawText is drawing into back from DT_CALCRECT (0 0 184 96 in the example), and then DrawText draws the text correctly right aligned in that rectangle. Having to set r1.right back to 192 before the second call to DrawText, together with the fact that the first call returns the correct rectangle, means it's the actual drawing of the right aligned text that is going wrong. – Alice Oct 05 '16 at 14:15
  • 1
    This must be the first bug I've ever seen in a core function that seems to have absolutely no references on the internet pointing it out (or else I wasn't using the right search terms). The bug must be decades old. Maybe I'm the first person who ever tried to right align text using DrawText! – Alice Oct 05 '16 at 14:15