The "GdiInterop" is a sample application (source code is avaiable in [Github][source_code], relative guide in [MSDN][msdn_guide]) that demostracts how to display DirectWrite text on a GDI surface. After set dpi to 168 in `winecfg`, and run sample application with wine, the font size is normal, but the position of glyph is incorrect.
| Dpi: 96 | Dpi: 168 | |:----------------:|:-----------------:| | ![ss1][sc_96dpi] | ![ss2][sc_168dpi] |
There is the sample application after compile: [Release_x64.tar.gz](/uploads/c70e32824efcaa5d16ab39bbb4b86e30/Release_x64.tar.gz)
After change transform matrix that pass to `IDWriteFactory7_CreateGlyphRunAnalysis`, it can display glyph correctly.
[source_code]: https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7S... [msdn_guide]: https://learn.microsoft.com/en-us/windows/win32/directwrite/render-to-a-gdi-... [sc_96dpi]: /uploads/59cc343cfaa025f4ff4c32a84ab8cfeb/图片.png [sc_168dpi]: /uploads/938be653362bb6ac58471b69ab66099a/图片.png
-- v2: dwrite: Return render bounds if text not intersect to BitmapRenderTarget dwrite: Fix incorrect position of glyph when rendering with BitmapRenderTarget in HiDPI
From: Tingzhong Luo luotingzhong@uniontech.com
Signed-off-by: Tingzhong Luo luotingzhong@uniontech.com --- dlls/dwrite/gdiinterop.c | 9 ++- dlls/dwrite/tests/font.c | 111 ++++++++++++++++++++++++++++ dlls/dwrite/tests/rect_fallback.sfd | 75 +++++++++++++++++++ dlls/dwrite/tests/rect_fallback.ttf | Bin 0 -> 1600 bytes dlls/dwrite/tests/resource.rc | 3 + 5 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 dlls/dwrite/tests/rect_fallback.sfd create mode 100644 dlls/dwrite/tests/rect_fallback.ttf
diff --git a/dlls/dwrite/gdiinterop.c b/dlls/dwrite/gdiinterop.c index 5b542d93e32..c07f9e094ba 100644 --- a/dlls/dwrite/gdiinterop.c +++ b/dlls/dwrite/gdiinterop.c @@ -347,7 +347,7 @@ static HRESULT WINAPI rendertarget_DrawGlyphRun(IDWriteBitmapRenderTarget1 *ifac DWRITE_RENDERING_MODE1 rendermode; DWRITE_GRID_FIT_MODE gridfitmode; DWRITE_TEXTURE_TYPE texturetype; - DWRITE_GLYPH_RUN scaled_run; + DWRITE_MATRIX scaled_matrix; IDWriteFontFace3 *fontface; RECT target_rect, bounds; HRESULT hr; @@ -429,9 +429,10 @@ static HRESULT WINAPI rendertarget_DrawGlyphRun(IDWriteBitmapRenderTarget1 *ifac return hr; }
- scaled_run = *run; - scaled_run.fontEmSize *= target->ppdip; - hr = IDWriteFactory7_CreateGlyphRunAnalysis(target->factory, &scaled_run, &target->m, rendermode, measuring_mode, + scaled_matrix = target->m; + scaled_matrix.m11 *= target->ppdip; + scaled_matrix.m22 *= target->ppdip; + hr = IDWriteFactory7_CreateGlyphRunAnalysis(target->factory, run, &scaled_matrix, rendermode, measuring_mode, gridfitmode, target->antialiasmode, originX, originY, &analysis); if (FAILED(hr)) { diff --git a/dlls/dwrite/tests/font.c b/dlls/dwrite/tests/font.c index 81008db6f23..a5e5d296865 100644 --- a/dlls/dwrite/tests/font.c +++ b/dlls/dwrite/tests/font.c @@ -1205,11 +1205,41 @@ static void test_CreateFontFromLOGFONT(void) ok(ref == 0, "factory is not released, %lu\n", ref); }
+static IDWriteFontFace *create_rectfallback_fontface(IDWriteFactory *factory) +{ + static IDWriteFontFileLoader rloader = { &resourcefontfileloadervtbl }; + IDWriteFontFace *face; + IDWriteFontFile *file; + HRSRC fontsrc; + HRESULT hr; + + hr = IDWriteFactory_RegisterFontFileLoader(factory, &rloader); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + fontsrc = FindResourceA(GetModuleHandleA(NULL), (LPCSTR)MAKEINTRESOURCE(2), (LPCSTR)RT_RCDATA); + ok(fontsrc != NULL, "Failed to find font resource\n"); + + hr = IDWriteFactory_CreateCustomFontFileReference(factory, &fontsrc, sizeof(HRSRC), &rloader, &file); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(file != NULL, "Failed to create font file reference\n"); + + hr = IDWriteFactory_CreateFontFace(factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &file, 0, DWRITE_FONT_SIMULATIONS_NONE, &face); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(face != NULL, "Failed to create font face\n"); + + hr = IDWriteFactory_UnregisterFontFileLoader(factory, &rloader); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IDWriteFontFile_Release(file); + return face; +} + static void test_CreateBitmapRenderTarget(void) { IDWriteBitmapRenderTarget *target, *target2; IDWriteBitmapRenderTarget1 *target1; IDWriteRenderingParams *params; + DWRITE_GLYPH_OFFSET offsets[2]; IDWriteGdiInterop *interop; IDWriteFontFace *fontface; IDWriteFactory *factory; @@ -1217,6 +1247,7 @@ static void test_CreateBitmapRenderTarget(void) HBITMAP hbm, hbm2; UINT16 glyphs[2]; DWRITE_MATRIX m; + RECT box, box2; DIBSECTION ds; XFORM xform; COLORREF c; @@ -1507,6 +1538,86 @@ static void test_CreateBitmapRenderTarget(void) &run, params, RGB(255, 0, 0), NULL); ok(hr == S_OK, "Failed to draw a run, hr %#lx.\n", hr);
+ hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(target, 1.0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Check glyph position */ + IDWriteFontFace_Release(fontface); + fontface = create_rectfallback_fontface(factory); + + /* 'A' map to a full black rectangle */ + ch = 'A'; + glyphs[0] = 0; + hr = IDWriteFontFace_GetGlyphIndices(fontface, &ch, 1, glyphs); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(glyphs[0] > 0, "got %u\n", glyphs[0]); + glyphs[1] = glyphs[0]; + + offsets[0].advanceOffset = 0; + offsets[0].ascenderOffset = 0; + offsets[1].advanceOffset = 6; + offsets[1].ascenderOffset = 0; + + memset(&run, 0, sizeof(run)); + run.glyphCount = 2; + run.fontEmSize = 30.0f; + run.fontFace = fontface; + run.glyphIndices = glyphs; + run.glyphOffsets = offsets; + hdc = IDWriteBitmapRenderTarget_GetMemoryDC(target); + ok(hdc != NULL, "got %p\n", hdc); + + hr = IDWriteBitmapRenderTarget_Resize(target, 400, 400); + ok(hr == S_OK, "Failed to resize target, hr %#lx.\n", hr); + + SetDCBrushColor(hdc, 0); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + Rectangle(hdc, 0, 0, 400, 400); + SetRectEmpty(&box); + hr = IDWriteBitmapRenderTarget_DrawGlyphRun(target, 30.0f, 65.0f, DWRITE_MEASURING_MODE_GDI_NATURAL, + &run, params, RGB(255, 0, 0), &box); + ok(hr == S_OK, "Failed to draw a run, hr %#lx.\n", hr); + ok(!IsRectEmpty(&box), "got empty rect\n"); + + /* hit first glyphs */ + c = GetPixel(hdc, box.left + 10, box.top + 10); + ok(c == RGB(255, 0, 0), "got 0x%08lx\n", c); + + /* blank between glyphs */ + c = GetPixel(hdc, (box.right + box.left) / 2, (box.top + box.bottom) / 2); + ok(c == RGB(0, 0, 0), "got 0x%08lx\n", c); + + /* Doubled ppdip */ + hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(target, 2.0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + SetDCBrushColor(hdc, 0); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + Rectangle(hdc, 0, 0, 400, 400); + SetRectEmpty(&box2); + hr = IDWriteBitmapRenderTarget_DrawGlyphRun(target, 30.0f, 65.0f, DWRITE_MEASURING_MODE_GDI_NATURAL, + &run, params, RGB(255, 0, 0), &box2); + ok(hr == S_OK, "Failed to draw a run, hr %#lx.\n", hr); + ok(!IsRectEmpty(&box2), "got empty rect\n"); + + /* blank between glyphs */ + c = GetPixel(hdc, (box.right + box.left) / 2, (box.top + box.bottom) / 2); + ok(c == RGB(0, 0, 0), "got 0x%08lx\n", c); + + /* cleared */ + c = GetPixel(hdc, box.left + 10, box.top + 10); + ok(c == RGB(0, 0, 0), "got 0x%08lx\n", c); + + /* hit first glyphs */ + c = GetPixel(hdc, box2.left + 10, box2.top + 10); + ok(c == RGB(255, 0, 0), "got 0x%08lx\n", c); + + /* bounds should be scaled */ + ok(abs(box2.left - box.left * 2) <= 2, "unexpect left bounds: %ld -> %ld\n", box.left, box2.left); + ok(abs(box2.right - box.right * 2) <= 2, "unexpect right bounds: %ld -> %ld\n", box.right, box2.right); + ok(abs(box2.top - box.top * 2) <= 2, "unexpect top bounds: %ld -> %ld\n", box.top, box2.top); + ok(abs(box2.bottom - box.bottom * 2) <= 2, "unexpect bottom bounds: %ld -> %ld\n", box.bottom, box2.bottom); + IDWriteRenderingParams_Release(params);
/* Zero sized target returns earlier. */ diff --git a/dlls/dwrite/tests/rect_fallback.sfd b/dlls/dwrite/tests/rect_fallback.sfd new file mode 100644 index 00000000000..4d2925a2366 --- /dev/null +++ b/dlls/dwrite/tests/rect_fallback.sfd @@ -0,0 +1,75 @@ +SplineFontDB: 3.2 +FontName: rect_fallback +FullName: rect_fallback +FamilyName: rect_fallback +Weight: Regular +Copyright: Copyright (c) 2023, LTZ +UComments: "2023-9-5: Created with FontForge (http://fontforge.org)" +Version: 001.000 +ItalicAngle: 0 +UnderlinePosition: -102 +UnderlineWidth: 51 +Ascent: 819 +Descent: 205 +InvalidEm: 0 +LayerCount: 2 +Layer: 0 0 "+gMxmbwAA" 1 +Layer: 1 0 "+Uk1mbwAA" 0 +XUID: [1021 32 1840475288 9054806] +OS2Version: 0 +OS2_WeightWidthSlopeOnly: 0 +OS2_UseTypoMetrics: 1 +CreationTime: 1693875904 +ModificationTime: 1693887136 +OS2TypoAscent: 0 +OS2TypoAOffset: 1 +OS2TypoDescent: 0 +OS2TypoDOffset: 1 +OS2TypoLinegap: 0 +OS2WinAscent: 0 +OS2WinAOffset: 1 +OS2WinDescent: 0 +OS2WinDOffset: 1 +HheadAscent: 0 +HheadAOffset: 1 +HheadDescent: 0 +HheadDOffset: 1 +OS2Vendor: 'PfEd' +MarkAttachClasses: 1 +DEI: 91125 +Encoding: ISO8859-1 +UnicodeInterp: none +NameList: AGL For New Fonts +DisplaySize: -48 +AntiAlias: 1 +FitToEm: 0 +WinInfo: 24 24 10 +BeginPrivate: 0 +EndPrivate +BeginChars: 256 1 + +StartChar: A +Encoding: 65 65 0 +Width: 1024 +VWidth: 0 +Flags: HWO +LayerCount: 2 +Back +SplineSet +0 816 m 1 + 1024 816 l 1 + 1024 -207 l 1 + 0 -207 l 1 + 0 816 l 1 +EndSplineSet +Fore +SplineSet +0 823 m 1 + 1024 823 l 1 + 1024 -201 l 1 + 0 -201 l 1 + 0 823 l 1 +EndSplineSet +EndChar +EndChars +EndSplineFont diff --git a/dlls/dwrite/tests/rect_fallback.ttf b/dlls/dwrite/tests/rect_fallback.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3e1a00f11c867858b587ee20838b00239284d5cf GIT binary patch literal 1600 zcmdT^Jx>%t7=C7V??B}<(U_2nSx$^#xMSH1oRBL(<fOoWNH77CV>#}FcwadV1F<1i z#zq=rG<J3rCS3dl7Dgk+#=?R`ZEO^cg|We$&+P7@7%42B&CL6}A2a*RJMRnxfCj8V z#auKxdGTS#`g6eBMyv0_`Qa#z;VAW5sgGUmGN--s9zdk2&&9J=0gdVx>TlT3#jllg z_?4T0>Z4yvSxbdVg)75L6}73%YVy*|)4TL{QZJ+v*21YPFJBOUV((4UP;*C(a4q@& zwsf|%QU~~{Kcn81$;U0sW0LcA>W8z|N&zG2qkn|Dp0l!v-uvIf^gjTUa3Q}`s=P%n z;CW5|8OXh|zsh~si8V&P;fO{9@ZQ%o;K&&rsDw4BTm!XGq?L_x)Nmz?Stp*OT<Q0w z*%obYj+`lM+W>-gpvI7OFn7v5Ws8>Ejuame5{GDohojr}tv;R{Uced#F{p+rA<U8+ zPpHbkW~xJw6Z6ERyPbl0x4X}cjZl*-`xvBCYW$==$TPCX%9HA<f(KS5A>>}FnIN=V z)mhd5CguPC?`GFB>qdTOxfR8(MN`>eCTkGf6zGbSYc%Ybn_ka$q^v50*i@z2BhBR+ zuh@;0nZg}2)ZE&9I@Em9FPc7YZ7CZ+ru<6MPP1Ea%ojsiHSDt5cAd;M;L+Ye9Eagz zfqE>sSRs=eE>_WwZ5L~(7p*S#pizvu*h~9$SqYk~H(8qw7HHvJa<IY|q3B{2<GAHw z4K2dsVh>J;E*E=gk6{RT6tK#fi%21j6058oadc3c<ZdE_PU2DC;AvneUsx?JrqU(7 zJ>H?4h8gPAM<?khBEeZD#E^uA48M8GIIhx_h?inXE0dYG;#4OXnZhz7EY8A2BDI{c z2%=<?;|fw18IiCV1j#{1Kl{OdaQr<8@Kp)yu|_nXD@F6gR6;j{hTgC5<3YVA&=-(9 z_!sh1q*cTclO@UOBxj(T2O^~bOeKm-i}{>x7~Mg`py6QBf2O5+TPu&5@n09&sVF(h lM?x^F%{B?%f5oj1ppNgo^gAzDb+(X^@4kBFyT9+f{0W|L%P#-`
literal 0 HcmV?d00001
diff --git a/dlls/dwrite/tests/resource.rc b/dlls/dwrite/tests/resource.rc index b761ebcfc32..9353cd6cae5 100644 --- a/dlls/dwrite/tests/resource.rc +++ b/dlls/dwrite/tests/resource.rc @@ -22,3 +22,6 @@
/* @makedep: wine_test.ttf */ 1 RCDATA wine_test.ttf + +/* @makedep: rect_fallback.ttf */ +2 RCDATA rect_fallback.ttf
From: Tingzhong Luo luotingzhong@uniontech.com
Signed-off-by: Tingzhong Luo luotingzhong@uniontech.com --- dlls/dwrite/gdiinterop.c | 1 + dlls/dwrite/tests/font.c | 7 +++++++ 2 files changed, 8 insertions(+)
diff --git a/dlls/dwrite/gdiinterop.c b/dlls/dwrite/gdiinterop.c index c07f9e094ba..764ddf1a4df 100644 --- a/dlls/dwrite/gdiinterop.c +++ b/dlls/dwrite/gdiinterop.c @@ -452,6 +452,7 @@ static HRESULT WINAPI rendertarget_DrawGlyphRun(IDWriteBitmapRenderTarget1 *ifac } texturetype = DWRITE_TEXTURE_CLEARTYPE_3x1; } + if (bbox_ret) *bbox_ret = bounds;
if (IntersectRect(&target_rect, &target_rect, &bounds)) { diff --git a/dlls/dwrite/tests/font.c b/dlls/dwrite/tests/font.c index a5e5d296865..8199147b607 100644 --- a/dlls/dwrite/tests/font.c +++ b/dlls/dwrite/tests/font.c @@ -1541,6 +1541,13 @@ static void test_CreateBitmapRenderTarget(void) hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(target, 1.0); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ /* Got render bounds if not intersect to render target */ + SetRectEmpty(&box); + hr = IDWriteBitmapRenderTarget_DrawGlyphRun(target, 0.0f, 0.0f, DWRITE_MEASURING_MODE_GDI_NATURAL, + &run, params, RGB(255, 0, 0), &box); + ok(hr == S_OK, "Failed to draw a run, hr %#lx.\n", hr); + ok(!IsRectEmpty(&box), "got empty rect\n"); + /* Check glyph position */ IDWriteFontFace_Release(fontface); fontface = create_rectfallback_fontface(factory);
On Mon Sep 4 10:14:24 2023 +0000, Nikolay Sivov wrote:
As I recall the idea was that bumping fontEmSize is more desirable than scaling outlines up, using smaller size. I think this will need some more testing for CreateGlyphRunAnalysis() itself. I haven't tried, but differences might be easier to spot in aliased mode.
Thanks for reviewing this patch! I have added a test to check the bounding box returned from `rendertarget_DrawGlyphRun`:
```c hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(target, 1.0); hr = IDWriteBitmapRenderTarget_DrawGlyphRun(target, 30.0f, 65.0f, DWRITE_MEASURING_MODE_GDI_NATURAL, &run, params, RGB(255, 0, 0), &box); /* ... */ hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(target, 2.0); hr = IDWriteBitmapRenderTarget_DrawGlyphRun(target, 30.0f, 65.0f, DWRITE_MEASURING_MODE_GDI_NATURAL, &run, params, RGB(255, 0, 0), &box2); /* ... */ /* bounds should be scaled */ ok(abs(box2.left - box.left * 2) <= 2, "unexpect left bounds: %ld -> %ld\n", box.left, box2.left); ok(abs(box2.right - box.right * 2) <= 2, "unexpect right bounds: %ld -> %ld\n", box.right, box2.right); ok(abs(box2.top - box.top * 2) <= 2, "unexpect top bounds: %ld -> %ld\n", box.top, box2.top); ok(abs(box2.bottom - box.bottom * 2) <= 2, "unexpect bottom bounds: %ld -> %ld\n", box.bottom, box2.bottom); ```
this check passes in Windows 10, but it fails if we just only bumping `fontEmSize` in Wine:
```plaintext font.c:1623: Test failed: unexpect left bounds: 30 -> 30 font.c:1624: Test failed: unexpect right bounds: 96 -> 156 font.c:1625: Test failed: unexpect top bounds: 40 -> 16 font.c:1626: Test failed: unexpect bottom bounds: 71 -> 77 ```
So just bumping fontEmSize is not enouth. we also need to scale `glyphAdvance`, `glyphOffsets`, as well as `originX` and `originY` manually, this is another version of this patch which can work properly:
```patch diff --git a/dlls/dwrite/gdiinterop.c b/dlls/dwrite/gdiinterop.c index 2eafe4064d2..f2361f3c701 100644 --- a/dlls/dwrite/gdiinterop.c +++ b/dlls/dwrite/gdiinterop.c @@ -347,7 +347,9 @@ static HRESULT WINAPI rendertarget_DrawGlyphRun(IDWriteBitmapRenderTarget1 *ifac DWRITE_RENDERING_MODE1 rendermode; DWRITE_GRID_FIT_MODE gridfitmode; DWRITE_TEXTURE_TYPE texturetype; + DWRITE_GLYPH_OFFSET *scaled_off; DWRITE_GLYPH_RUN scaled_run; + FLOAT *scaled_advances; IDWriteFontFace3 *fontface; RECT target_rect, bounds; HRESULT hr; @@ -429,10 +431,38 @@ static HRESULT WINAPI rendertarget_DrawGlyphRun(IDWriteBitmapRenderTarget1 *ifac return hr; }
scaled_run = *run; scaled_run.fontEmSize *= target->ppdip; + scaled_advances = NULL; + scaled_off = NULL; + if (run->glyphAdvances) + { + scaled_advances = calloc(1, sizeof(FLOAT) * run->glyphCount); + + if (!scaled_advances) + return E_OUTOFMEMORY; + for (int i = 0; i < run->glyphCount; i++) + scaled_advances[i] = run->glyphAdvances[i] * target->ppdip; + scaled_run.glyphAdvances = scaled_advances; + } + if (run->glyphOffsets) + { + scaled_off = calloc(1, sizeof(DWRITE_GLYPH_OFFSET) * run->glyphCount); + + if (!scaled_off) + return E_OUTOFMEMORY; + for (int i = 0; i < run->glyphCount; i++) + { + scaled_off[i].advanceOffset = run->glyphOffsets[i].advanceOffset * target->ppdip; + scaled_off[i].ascenderOffset = run->glyphOffsets[i].ascenderOffset * target->ppdip; + } + scaled_run.glyphOffsets = scaled_off; + } + hr = IDWriteFactory7_CreateGlyphRunAnalysis(target->factory, &scaled_run, &target->m, rendermode, measuring_mode, - gridfitmode, target->antialiasmode, originX, originY, &analysis); + gridfitmode, target->antialiasmode, originX * target->ppdip, originY * target->ppdip, &analysis); + if (scaled_advances) free((void*) scaled_advances); + if (scaled_off) free((void*) scaled_off); if (FAILED(hr)) { WARN("failed to create analysis instance, 0x%08lx\n", hr); ```
This patch will pass the check, and the sample application can render correctly. But we have to dump all `glyphAdvances` and `glyphOffsets` in this patch, I'm not sure whether it's desirable to use this version.
On Tue Sep 5 08:24:25 2023 +0000, Nikolay Sivov wrote:
This one should be a separate change.
Now it has been split into a new commit.
Tingzhong Luo (@tzluo) commented about dlls/dwrite/tests/font.c:
- hr = IDWriteBitmapRenderTarget_DrawGlyphRun(target, 0.0f, 0.0f, DWRITE_MEASURING_MODE_GDI_NATURAL,
&run, params, RGB(255, 0, 0), &box);
- ok(hr == S_OK, "Failed to draw a run, hr %#lx.\n", hr);
- ok(!IsRectEmpty(&box), "got empty rect\n");
- /* Check glyph position */
- IDWriteFontFace_Release(fontface);
- fontface = create_rectfallback_fontface(factory);
- /* 'A' map to a full black rectangle */
- ch = 'A';
- glyphs[0] = 0;
- hr = IDWriteFontFace_GetGlyphIndices(fontface, &ch, 1, glyphs);
- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- ok(glyphs[0] > 0, "got %u\n", glyphs[0]);
- glyphs[1] = glyphs[0];
A new font has been created via FontForge for this test.