uiLengthDrawn should represent the number of characters that have been processed. However, the original implementation uses len (the count of displayed characters in the current line), which is not accurate. When text requires line breaks or special processing (such as adding an ellipsis), the actual number of processed characters may differ from the number of displayed characters. When the DT_CALCRECT flag is absent, len gets decremented to 0 during the drawing loop. Consequently, uiLengthDrawn becomes inaccurate since it relies on len's value for statistics.
@zhiyi @julliard
Signed-off-by: chenjiangyi chenjiangyi@uniontech.com
-- v2: user32: Fix the number of characters processed by DrawTextExW.
From: chenjiangyi chenjiangyi@uniontech.com
uiLengthDrawn should represent the number of characters that have been processed. However, the original implementation uses len (the count of displayed characters in the current line), which is not accurate. When text requires line breaks or special processing (such as adding an ellipsis), the actual number of processed characters may differ from the number of displayed characters. When the DT_CALCRECT flag is absent, len gets decremented to 0 during the drawing loop. Consequently, uiLengthDrawn becomes inaccurate since it relies on len's value for statistics.
Signed-off-by: chenjiangyi chenjiangyi@uniontech.com --- dlls/user32/tests/text.c | 29 +++++++++++++++++++++++++++++ dlls/user32/text.c | 7 ++++--- 2 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/dlls/user32/tests/text.c b/dlls/user32/tests/text.c index d3228eae1e0..c60a4e1e1a0 100644 --- a/dlls/user32/tests/text.c +++ b/dlls/user32/tests/text.c @@ -656,6 +656,35 @@ static void test_DrawTextCalcRect(void) ok(rect.top == rect2.top, "unexpected value %ld, got %ld\n", rect.top, rect2.top); ok(rect.bottom == rect2.bottom , "unexpected value %ld, got %ld\n", rect.bottom, rect2.bottom);
+ /* further tests for dtp */ + SelectObject(hdc, hOldFont); + ret = DeleteObject(hFont); + ok(ret, "DeleteObject error %lu\n", GetLastError()); + lf.lfHeight = 200 * 9 / 72; + hFont = CreateFontIndirectA(&lf); + ok(hFont != 0, "CreateFontIndirectA error %lu\n", GetLastError()); + hOldFont = SelectObject(hdc, hFont); + + SetRect( &rect, 0,0, 100, 25); + memset(&dtp, 0, sizeof(dtp)); + dtp.cbSize = sizeof(dtp); + textheight = DrawTextExW(hdc, textW, lstrlenW(textW), &rect, DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK, &dtp); + ok(dtp.uiLengthDrawn == 10, "Unexpected uiLengthDrawn %d\n", dtp.uiLengthDrawn ); + + memset(&dtp, 0, sizeof(dtp)); + dtp.cbSize = sizeof(dtp); + textheight = DrawTextExW(hdc, textW, lstrlenW(textW), &rect, DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT, &dtp); + ok(dtp.uiLengthDrawn == 16, "Unexpected uiLengthDrawn %d\n", dtp.uiLengthDrawn ); + + memset(&dtp, 0, sizeof(dtp)); + dtp.cbSize = sizeof(dtp); + textheight = DrawTextExW(hdc, (LPWSTR)L"a 1\tb2c3", lstrlenW(L"a 1\tb2c3"), &rect, DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK, &dtp); + ok(dtp.uiLengthDrawn == 8, "Unexpected uiLengthDrawn %d\n", dtp.uiLengthDrawn ); + + memset(&dtp, 0, sizeof(dtp)); + dtp.cbSize = sizeof(dtp); + textheight = DrawTextExW(hdc, (LPWSTR)L"a 1\tb2c3", lstrlenW(L"a 1\tb2c3"), &rect, DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT, &dtp); + ok(dtp.uiLengthDrawn == 8, "Unexpected uiLengthDrawn %d\n", dtp.uiLengthDrawn );
SelectObject(hdc, hOldFont); ret = DeleteObject(hFont); diff --git a/dlls/user32/text.c b/dlls/user32/text.c index 1896627c893..b026ca7b4ed 100644 --- a/dlls/user32/text.c +++ b/dlls/user32/text.c @@ -871,7 +871,7 @@ INT WINAPI DrawTextExW( HDC hdc, LPWSTR str, INT i_count, WCHAR *retstr; size_t size_retstr; WCHAR line[MAX_BUFFER]; - int len, lh, count=i_count; + int len, lh, old_count, count=i_count; TEXTMETRICW tm; int lmargin = 0, rmargin = 0; int x, y, width; @@ -978,7 +978,10 @@ INT WINAPI DrawTextExW( HDC hdc, LPWSTR str, INT i_count, last_line = !(flags & DT_NOCLIP) && y - ((flags & DT_EDITCONTROL) ? 2*lh-1 : lh) < rect->bottom; else last_line = !(flags & DT_NOCLIP) && y + ((flags & DT_EDITCONTROL) ? 2*lh-1 : lh) > rect->bottom; + + old_count = count; strPtr = TEXT_NextLineW(hdc, strPtr, &count, line, &len, width, flags, &size, last_line, retstr, tabwidth, &prefix_offset, &ellip); + if (dtp) dtp->uiLengthDrawn += old_count - count;
if (flags & DT_CENTER) x = (rect->left + lmargin + rect->right - rmargin - size.cx) / 2; @@ -1051,8 +1054,6 @@ INT WINAPI DrawTextExW( HDC hdc, LPWSTR str, INT i_count, y -= lh; else y += lh; - if (dtp) - dtp->uiLengthDrawn += len; } while (strPtr && !last_line);
On Thu Aug 7 03:56:56 2025 +0000, Zhiyi Zhang wrote:
I don't know why you put tests in another MR. That's not going to help get this in. Please put them in the same MR. Then, after you fix the bug, remove todo_wine from the tests. You need to have a better description of the fix. If I understand it correctly, the problem is that uiLengthDrawn is not calculated correctly without DT_CALCRECT. So please focus on that. Also, could you simplify the tests a bit? Some of them seem to be duplicates.
Thank you for your suggestions. I've made adjustments according to your recommendations. Please review the updated implementation when you have time.
On Thu Aug 7 03:56:56 2025 +0000, JiangYi Chen wrote:
Thank you for your suggestions. I've made adjustments according to your recommendations. Please review the updated implementation when you have time.
Please add the tests first in a separate commit. Mark the tests failing on Wine with todo_wine, then remove those todo_wines in the second commit after you've fixed the bug. This way, it's obvious what's wrong.
Let's use "user32: Fix uiLengthDrawn calculation in DrawTextExW()." for the commit subject.
Zhiyi Zhang (@zhiyi) commented about dlls/user32/tests/text.c:
ok(rect.top == rect2.top, "unexpected value %ld, got %ld\n", rect.top, rect2.top); ok(rect.bottom == rect2.bottom , "unexpected value %ld, got %ld\n", rect.bottom, rect2.bottom);
- /* further tests for dtp */
Let's use `/* Test uiLengthDrawn calculation */`
Zhiyi Zhang (@zhiyi) commented about dlls/user32/tests/text.c:
- hOldFont = SelectObject(hdc, hFont);
- SetRect( &rect, 0,0, 100, 25);
- memset(&dtp, 0, sizeof(dtp));
- dtp.cbSize = sizeof(dtp);
- textheight = DrawTextExW(hdc, textW, lstrlenW(textW), &rect, DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK, &dtp);
- ok(dtp.uiLengthDrawn == 10, "Unexpected uiLengthDrawn %d\n", dtp.uiLengthDrawn );
- memset(&dtp, 0, sizeof(dtp));
- dtp.cbSize = sizeof(dtp);
- textheight = DrawTextExW(hdc, textW, lstrlenW(textW), &rect, DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT, &dtp);
- ok(dtp.uiLengthDrawn == 16, "Unexpected uiLengthDrawn %d\n", dtp.uiLengthDrawn );
- memset(&dtp, 0, sizeof(dtp));
- dtp.cbSize = sizeof(dtp);
- textheight = DrawTextExW(hdc, (LPWSTR)L"a 1\tb2c3", lstrlenW(L"a 1\tb2c3"), &rect, DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK, &dtp);
You can remove `(LPWSTR)` cast.
Zhiyi Zhang (@zhiyi) commented about dlls/user32/tests/text.c:
ok(rect.top == rect2.top, "unexpected value %ld, got %ld\n", rect.top, rect2.top); ok(rect.bottom == rect2.bottom , "unexpected value %ld, got %ld\n", rect.bottom, rect2.bottom);
- /* further tests for dtp */
- SelectObject(hdc, hOldFont);
- ret = DeleteObject(hFont);
- ok(ret, "DeleteObject error %lu\n", GetLastError());
- lf.lfHeight = 200 * 9 / 72;
Could you explain `200 * 9 / 72`?
Zhiyi Zhang (@zhiyi) commented about dlls/user32/tests/text.c:
ok(rect.top == rect2.top, "unexpected value %ld, got %ld\n", rect.top, rect2.top); ok(rect.bottom == rect2.bottom , "unexpected value %ld, got %ld\n", rect.bottom, rect2.bottom);
- /* further tests for dtp */
- SelectObject(hdc, hOldFont);
- ret = DeleteObject(hFont);
- ok(ret, "DeleteObject error %lu\n", GetLastError());
- lf.lfHeight = 200 * 9 / 72;
- hFont = CreateFontIndirectA(&lf);
- ok(hFont != 0, "CreateFontIndirectA error %lu\n", GetLastError());
- hOldFont = SelectObject(hdc, hFont);
- SetRect( &rect, 0,0, 100, 25);
Remove space after `(` and add a space between `0,` and `0`.
Zhiyi Zhang (@zhiyi) commented about dlls/user32/text.c:
last_line = !(flags & DT_NOCLIP) && y - ((flags & DT_EDITCONTROL) ? 2*lh-1 : lh) < rect->bottom;
else last_line = !(flags & DT_NOCLIP) && y + ((flags & DT_EDITCONTROL) ? 2*lh-1 : lh) > rect->bottom;
- old_count = count; strPtr = TEXT_NextLineW(hdc, strPtr, &count, line, &len, width, flags, &size, last_line, retstr, tabwidth, &prefix_offset, &ellip);
- if (dtp) dtp->uiLengthDrawn += old_count - count;
`dtp->uiLengthDrawn += old_count - count;` should be on a new line.
Zhiyi Zhang (@zhiyi) commented about dlls/user32/tests/text.c:
ok(dtp.uiLengthDrawn==1337, "invalid dtp.uiLengthDrawn = %i\n",dtp.uiLengthDrawn); dtp.uiLengthDrawn = 1337; textheight = DrawTextExW(hdc, NULL, 0, &rect, 0, &dtp); if (conform_xp)
Add a new line before a new paragraph in your commit message body.
And you don't need `Signed-off-by:` anymore now that we use Gitlab.