[PATCH 1/7] dwrite: Implement pixel snapping in Draw() (try2)

Nikolay Sivov nsivov at codeweavers.com
Sun Jul 12 15:34:43 CDT 2015


try2: this time actually handle renderer transform

-------------- next part --------------
>From 1954ca25f50cf6a9d49247a6153f9276aac1e418 Mon Sep 17 00:00:00 2001
From: Nikolay Sivov <nsivov at codeweavers.com>
Date: Sun, 12 Jul 2015 23:22:50 +0300
Subject: [PATCH 1/7] dwrite: Implement pixel snapping in Draw()

---
 dlls/dwrite/layout.c       |  65 ++++++++++++++-
 dlls/dwrite/tests/layout.c | 203 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 256 insertions(+), 12 deletions(-)

diff --git a/dlls/dwrite/layout.c b/dlls/dwrite/layout.c
index b07aec0..8b4c2cd 100644
--- a/dlls/dwrite/layout.c
+++ b/dlls/dwrite/layout.c
@@ -21,6 +21,7 @@
 #define COBJMACROS
 
 #include <stdarg.h>
+#include <math.h>
 
 #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 0fc101b..76bcdff 100644
--- a/dlls/dwrite/tests/layout.c
+++ b/dlls/dwrite/tests/layout.c
@@ -21,6 +21,7 @@
 #define COBJMACROS
 
 #include <assert.h>
+#include <math.h>
 
 #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);
 }
-- 
2.1.4



More information about the wine-patches mailing list