diff --git a/dlls/dwrite/layout.c b/dlls/dwrite/layout.c index b07aec0565f..8b4c2cd33d0 100644 --- a/dlls/dwrite/layout.c +++ b/dlls/dwrite/layout.c @@ -21,6 +21,7 @@ #define COBJMACROS #include +#include #include "windef.h" #include "winbase.h" @@ -2732,13 +2733,42 @@ static HRESULT WINAPI dwritetextlayout_layout_GetLocaleName(IDWriteTextLayout2 * return get_string_attribute_value(This, LAYOUT_RANGE_ATTR_LOCALE, position, locale, length, r); } +static inline FLOAT renderer_apply_snapping(FLOAT coord, BOOL skiptransform, FLOAT ppdip, FLOAT det, + const DWRITE_MATRIX *m) +{ + FLOAT vec[2], vec2[2]; + + if (!skiptransform) { + /* apply transform */ + vec[0] = 0.0; + vec[1] = coord; + + vec2[0] = m->m11 * vec[0] + m->m12 * vec[1] + m->dx; + vec2[1] = m->m21 * vec[0] + m->m22 * vec[1] + m->dy; + + /* snap */ + vec2[0] = floorf(vec2[0] * ppdip + 0.5f) / ppdip; + vec2[1] = floorf(vec2[1] * ppdip + 0.5f) / ppdip; + + /* apply inverted transform, we don't care about X component at this point */ + vec[1] = (-m->m21 * vec2[0] + m->m11 * vec2[1] - (m->m11 * m->dy - m->m12 * m->dx)) / det; + } + else + vec[1] = floorf(coord * ppdip + 0.5f) / ppdip; + + return vec[1]; +} + static HRESULT WINAPI dwritetextlayout_Draw(IDWriteTextLayout2 *iface, void *context, IDWriteTextRenderer* renderer, FLOAT origin_x, FLOAT origin_y) { struct dwrite_textlayout *This = impl_from_IDWriteTextLayout2(iface); + BOOL disabled = FALSE, skiptransform = FALSE; struct layout_effective_inline *inlineobject; struct layout_effective_run *run; struct layout_strikethrough *s; + FLOAT det = 0.0, ppdip = 0.0; + DWRITE_MATRIX m = { 0 }; HRESULT hr; TRACE("(%p)->(%p %p %.2f %.2f)\n", This, context, renderer, origin_x, origin_y); @@ -2747,6 +2777,34 @@ static HRESULT WINAPI dwritetextlayout_Draw(IDWriteTextLayout2 *iface, if (FAILED(hr)) return hr; + hr = IDWriteTextRenderer_IsPixelSnappingDisabled(renderer, context, &disabled); + if (FAILED(hr)) + return hr; + + if (!disabled) { + hr = IDWriteTextRenderer_GetPixelsPerDip(renderer, context, &ppdip); + if (FAILED(hr)) + return hr; + + hr = IDWriteTextRenderer_GetCurrentTransform(renderer, context, &m); + if (FAILED(hr)) + return hr; + + /* it's only allowed to have a diagonal/antidiagonal transform matrix */ + if (ppdip <= 0.0 || + (m.m11 * m.m22 != 0.0 && (m.m12 != 0.0 || m.m21 != 0.0)) || + (m.m12 * m.m21 != 0.0 && (m.m11 != 0.0 || m.m22 != 0.0))) + disabled = TRUE; + else { + det = m.m11 * m.m22 - m.m12 * m.m21; + + /* on certain conditions we can skip transform */ + if (!memcmp(&m, &identity, sizeof(m)) || fabsf(det) <= 1e-10f) + skiptransform = TRUE; + } + } + +#define SNAP_COORD(x) renderer_apply_snapping((x), skiptransform, ppdip, det, &m) /* 1. Regular runs */ LIST_FOR_EACH_ENTRY(run, &This->eruns, struct layout_effective_run, entry) { const struct regular_layout_run *regular = &run->run->u.regular; @@ -2776,7 +2834,7 @@ static HRESULT WINAPI dwritetextlayout_Draw(IDWriteTextLayout2 *iface, IDWriteTextRenderer_DrawGlyphRun(renderer, context, run->origin_x + run->align_dx + origin_x, - run->origin_y + origin_y, + disabled ? run->origin_y + origin_y : SNAP_COORD(run->origin_y + origin_y), DWRITE_MEASURING_MODE_NATURAL, &glyph_run, &descr, @@ -2788,7 +2846,7 @@ static HRESULT WINAPI dwritetextlayout_Draw(IDWriteTextLayout2 *iface, IDWriteTextRenderer_DrawInlineObject(renderer, context, inlineobject->origin_x + inlineobject->align_dx + origin_x, - inlineobject->origin_y + origin_y, + disabled ? inlineobject->origin_y + origin_y : SNAP_COORD(inlineobject->origin_y + origin_y), inlineobject->object, inlineobject->is_sideways, inlineobject->is_rtl, @@ -2802,10 +2860,11 @@ static HRESULT WINAPI dwritetextlayout_Draw(IDWriteTextLayout2 *iface, IDWriteTextRenderer_DrawStrikethrough(renderer, context, s->run->origin_x, - s->run->origin_y, + disabled ? s->run->origin_y : SNAP_COORD(s->run->origin_y), &s->s, NULL); } +#undef SNAP_COORD return S_OK; } diff --git a/dlls/dwrite/tests/layout.c b/dlls/dwrite/tests/layout.c index 0fc101b1b4c..76bcdff97c2 100644 --- a/dlls/dwrite/tests/layout.c +++ b/dlls/dwrite/tests/layout.c @@ -21,6 +21,7 @@ #define COBJMACROS #include +#include #include "windows.h" #include "dwrite.h" @@ -417,29 +418,44 @@ static ULONG WINAPI testrenderer_Release(IDWriteTextRenderer *iface) return 1; } +struct renderer_context { + BOOL snapping_disabled; + DWRITE_MATRIX m; + FLOAT ppdip; + FLOAT originX; + FLOAT originY; +}; + static HRESULT WINAPI testrenderer_IsPixelSnappingDisabled(IDWriteTextRenderer *iface, - void *client_drawingcontext, BOOL *disabled) + void *context, BOOL *disabled) { - *disabled = TRUE; + struct renderer_context *ctxt = (struct renderer_context*)context; + if (ctxt) + *disabled = ctxt->snapping_disabled; + else + *disabled = TRUE; return S_OK; } static HRESULT WINAPI testrenderer_GetCurrentTransform(IDWriteTextRenderer *iface, - void *client_drawingcontext, DWRITE_MATRIX *transform) + void *context, DWRITE_MATRIX *m) { - ok(0, "unexpected call\n"); - return E_NOTIMPL; + struct renderer_context *ctxt = (struct renderer_context*)context; + ok(!ctxt->snapping_disabled, "expected enabled snapping\n"); + *m = ctxt->m; + return S_OK; } static HRESULT WINAPI testrenderer_GetPixelsPerDip(IDWriteTextRenderer *iface, - void *client_drawingcontext, FLOAT *pixels_per_dip) + void *context, FLOAT *pixels_per_dip) { - ok(0, "unexpected call\n"); - return E_NOTIMPL; + struct renderer_context *ctxt = (struct renderer_context*)context; + *pixels_per_dip = ctxt->ppdip; + return S_OK; } static HRESULT WINAPI testrenderer_DrawGlyphRun(IDWriteTextRenderer *iface, - void* client_drawingcontext, + void *context, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE mode, @@ -447,9 +463,15 @@ static HRESULT WINAPI testrenderer_DrawGlyphRun(IDWriteTextRenderer *iface, DWRITE_GLYPH_RUN_DESCRIPTION const *descr, IUnknown *effect) { + struct renderer_context *ctxt = (struct renderer_context*)context; struct drawcall_entry entry; DWRITE_SCRIPT_ANALYSIS sa; + if (ctxt) { + ctxt->originX = baselineOriginX; + ctxt->originY = baselineOriginY; + } + ok(descr->stringLength < sizeof(entry.string)/sizeof(WCHAR), "string is too long\n"); if (descr->stringLength && descr->stringLength < sizeof(entry.string)/sizeof(WCHAR)) { memcpy(entry.string, descr->string, descr->stringLength*sizeof(WCHAR)); @@ -3212,6 +3234,168 @@ static void test_SetReadingDirection(void) IDWriteFactory_Release(factory); } +static inline FLOAT get_scaled_font_metric(UINT32 metric, FLOAT emSize, const DWRITE_FONT_METRICS *metrics) +{ + return (FLOAT)metric * emSize / (FLOAT)metrics->designUnitsPerEm; +} + +static FLOAT snap_coord(const DWRITE_MATRIX *m, FLOAT ppdip, FLOAT coord) +{ + FLOAT vec[2], det, vec2[2]; + BOOL transform; + + /* has to be a diagonal matrix */ + if ((ppdip <= 0.0) || + (m->m11 * m->m22 != 0.0 && (m->m12 != 0.0 || m->m21 != 0.0)) || + (m->m12 * m->m21 != 0.0 && (m->m11 != 0.0 || m->m22 != 0.0))) + return coord; + + det = m->m11 * m->m22 - m->m12 * m->m21; + transform = fabsf(det) > 1e-10; + + if (transform) { + /* apply transform */ + vec[0] = 0.0; + vec[1] = coord; + + vec2[0] = m->m11 * vec[0] + m->m12 * vec[1] + m->dx; + vec2[1] = m->m21 * vec[0] + m->m22 * vec[1] + m->dy; + + /* snap */ + vec2[0] = floorf(vec2[0] * ppdip + 0.5f) / ppdip; + vec2[1] = floorf(vec2[1] * ppdip + 0.5f) / ppdip; + + /* apply inverted transform */ + vec[1] = (-m->m21 * vec2[0] + m->m11 * vec2[1] - (m->m11 * m->dy - m->m12 * m->dx)) / det; + } + else + vec[1] = floorf(coord * ppdip + 0.5f) / ppdip; + return vec[1]; +} + +static inline BOOL float_eq(FLOAT left, FLOAT right) +{ + return fabsf(left - right) < 1e-6f; +} + +struct snapping_test { + DWRITE_MATRIX m; + FLOAT ppdip; +}; + +static struct snapping_test snapping_tests[] = { + { { 0.0, 1.0, 2.0, 0.0, 0.2, 0.3 }, 1.0 }, + { { 0.0, 1.0, 2.0, 0.0, 0.0, 0.0 }, 1.0 }, + { { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, 1.0 }, /* identity transform */ + { { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, 0.9 }, + { { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, -1.0 }, + { { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, 0.0 }, + { { 1.0, 0.0, 0.0, 1.0, 0.0, 0.3 }, 1.0 }, /* simple Y shift */ + { { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, 10.0 }, /* identity, 10 ppdip */ + { { 1.0, 0.0, 0.0, 10.0, 0.0, 0.0 }, 10.0 }, + { { 0.0, 1.0, 1.0, 0.0, 0.2, 0.6 }, 1.0 }, + { { 0.0, 2.0, 2.0, 0.0, 0.2, 0.6 }, 1.0 }, + { { 0.0, 0.5, -0.5, 0.0, 0.2, 0.6 }, 1.0 }, + { { 1.0, 2.0, 0.0, 1.0, 0.2, 0.6 }, 1.0 }, + { { 1.0, 1.0, 0.0, 1.0, 0.2, 0.6 }, 1.0 }, + { { 0.5, 0.5, -0.5, 0.5, 0.2, 0.6 }, 1.0 }, /* 45 degrees rotation */ + { { 0.5, 0.5, -0.5, 0.5, 0.0, 0.0 }, 100.0 }, /* 45 degrees rotation */ + { { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, 100.0 }, + { { 0.0, 1.0, -1.0, 0.0, 0.2, 0.6 }, 1.0 }, /* 90 degrees rotation */ + { { -1.0, 0.0, 0.0, -1.0, 0.2, 0.6 }, 1.0 }, /* 180 degrees rotation */ + { { 0.0, -1.0, 1.0, 0.0, 0.2, 0.6 }, 1.0 }, /* 270 degrees rotation */ + { { 0.0, 2.0, 1.0, 0.0, 0.2, 0.6 }, 1.0 }, + { { 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 }, 1.0 }, +}; + +static DWRITE_MATRIX compattransforms[] = { + { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, + { 1.0, 0.0, 0.0, 1.0, 0.2, 0.3 }, + { 2.0, 0.0, 0.0, 2.0, 0.2, 0.3 }, + { 2.0, 1.0, 2.0, 2.0, 0.2, 0.3 }, +}; + +static void test_pixelsnapping(void) +{ + static const WCHAR strW[] = {'a',0}; + IDWriteTextLayout *layout, *layout2; + struct renderer_context ctxt; + DWRITE_FONT_METRICS metrics; + IDWriteTextFormat *format; + IDWriteFontFace *fontface; + IDWriteFactory *factory; + FLOAT baseline, originX; + HRESULT hr; + int i, j; + + factory = create_factory(); + + hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, 12.0, enusW, &format); + ok(hr == S_OK, "got 0x%08x\n", hr); + + fontface = get_fontface_from_format(format); + IDWriteFontFace_GetMetrics(fontface, &metrics); + + hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout); + ok(hr == S_OK, "got 0x%08x\n", hr); + + /* disabled snapping */ + ctxt.snapping_disabled = TRUE; + ctxt.ppdip = 1.0f; + memset(&ctxt.m, 0, sizeof(ctxt.m)); + ctxt.m.m11 = ctxt.m.m22 = 1.0; + originX = 0.1; + + hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, originX, 0.0); + ok(hr == S_OK, "got 0x%08x\n", hr); + + baseline = get_scaled_font_metric(metrics.ascent, 12.0, &metrics); + ok(ctxt.originX == originX, "got %f, originX %f\n", ctxt.originX, originX); + ok(ctxt.originY == baseline, "got %f, baseline %f\n", ctxt.originY, baseline); + ok(floor(baseline) != baseline, "got %f\n", baseline); + + ctxt.snapping_disabled = FALSE; + + for (i = 0; i < sizeof(snapping_tests)/sizeof(snapping_tests[0]); i++) { + struct snapping_test *ptr = &snapping_tests[i]; + FLOAT expectedY; + + ctxt.m = ptr->m; + ctxt.ppdip = ptr->ppdip; + ctxt.originX = 678.9; + ctxt.originY = 678.9; + + expectedY = snap_coord(&ctxt.m, ctxt.ppdip, baseline); + hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, originX, 0.0); + ok(hr == S_OK, "%d: got 0x%08x\n", i, hr); + ok(ctxt.originX == originX, "%d: got %f, originX %f\n", i, ctxt.originX, originX); + ok(float_eq(ctxt.originY, expectedY), "%d: got %f, expected %f, baseline %f\n", + i, ctxt.originY, expectedY, baseline); + + /* gdicompat layout transform doesn't affect snapping */ + for (j = 0; j < sizeof(compattransforms)/sizeof(compattransforms[0]); j++) { + hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 1, format, 500.0, 100.0, + 1.0, &compattransforms[j], FALSE, &layout2); + ok(hr == S_OK, "%d: got 0x%08x\n", i, hr); + + expectedY = snap_coord(&ctxt.m, ctxt.ppdip, baseline); + hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, originX, 0.0); + ok(hr == S_OK, "%d: got 0x%08x\n", i, hr); + ok(ctxt.originX == originX, "%d: got %f, originX %f\n", i, ctxt.originX, originX); + ok(float_eq(ctxt.originY, expectedY), "%d: got %f, expected %f, baseline %f\n", + i, ctxt.originY, expectedY, baseline); + + IDWriteTextLayout_Release(layout2); + } + } + + IDWriteTextLayout_Release(layout); + IDWriteTextFormat_Release(format); + IDWriteFontFace_Release(fontface); + IDWriteFactory_Release(factory); +} + START_TEST(layout) { static const WCHAR ctrlstrW[] = {0x202a,0}; @@ -3255,6 +3439,7 @@ START_TEST(layout) test_SetTextAlignment(); test_SetParagraphAlignment(); test_SetReadingDirection(); + test_pixelsnapping(); IDWriteFactory_Release(factory); }