/*
 *    Text layout/format tests
 *
 * Copyright 2012, 2014-2017 Nikolay Sivov for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS

#include <assert.h>
#include <math.h>
#include <limits.h>

#include "windows.h"
#include "dwrite_3.h"

#include "wine/test.h"

static const WCHAR tahomaW[] = {'T','a','h','o','m','a',0};
static const WCHAR enusW[] = {'e','n','-','u','s',0};

struct testanalysissink
{
    IDWriteTextAnalysisSink IDWriteTextAnalysisSink_iface;
    DWRITE_SCRIPT_ANALYSIS sa; /* last analysis, with SetScriptAnalysis() */
};

static inline struct testanalysissink *impl_from_IDWriteTextAnalysisSink(IDWriteTextAnalysisSink *iface)
{
    return CONTAINING_RECORD(iface, struct testanalysissink, IDWriteTextAnalysisSink_iface);
}

/* test IDWriteTextAnalysisSink */
static HRESULT WINAPI analysissink_QueryInterface(IDWriteTextAnalysisSink *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteTextAnalysisSink) || IsEqualIID(riid, &IID_IUnknown))
    {
        *obj = iface;
        return S_OK;
    }

    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI analysissink_AddRef(IDWriteTextAnalysisSink *iface)
{
    return 2;
}

static ULONG WINAPI analysissink_Release(IDWriteTextAnalysisSink *iface)
{
    return 1;
}

static HRESULT WINAPI analysissink_SetScriptAnalysis(IDWriteTextAnalysisSink *iface,
    UINT32 position, UINT32 length, DWRITE_SCRIPT_ANALYSIS const* sa)
{
    struct testanalysissink *sink = impl_from_IDWriteTextAnalysisSink(iface);
    sink->sa = *sa;
    return S_OK;
}

static HRESULT WINAPI analysissink_SetLineBreakpoints(IDWriteTextAnalysisSink *iface,
    UINT32 position, UINT32 length, DWRITE_LINE_BREAKPOINT const* breakpoints)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI analysissink_SetBidiLevel(IDWriteTextAnalysisSink *iface,
    UINT32 position, UINT32 length, UINT8 explicitLevel, UINT8 resolvedLevel)
{
    ok(0, "unexpected\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI analysissink_SetNumberSubstitution(IDWriteTextAnalysisSink *iface,
    UINT32 position, UINT32 length, IDWriteNumberSubstitution* substitution)
{
    ok(0, "unexpected\n");
    return E_NOTIMPL;
}

static IDWriteTextAnalysisSinkVtbl analysissinkvtbl = {
    analysissink_QueryInterface,
    analysissink_AddRef,
    analysissink_Release,
    analysissink_SetScriptAnalysis,
    analysissink_SetLineBreakpoints,
    analysissink_SetBidiLevel,
    analysissink_SetNumberSubstitution
};

static struct testanalysissink analysissink = {
    { &analysissinkvtbl },
    { 0 }
};

/* test IDWriteTextAnalysisSource */
static HRESULT WINAPI analysissource_QueryInterface(IDWriteTextAnalysisSource *iface,
    REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteTextAnalysisSource) || IsEqualIID(riid, &IID_IUnknown)) {
        *obj = iface;
        IDWriteTextAnalysisSource_AddRef(iface);
        return S_OK;
    }
    return E_NOINTERFACE;
}

static ULONG WINAPI analysissource_AddRef(IDWriteTextAnalysisSource *iface)
{
    return 2;
}

static ULONG WINAPI analysissource_Release(IDWriteTextAnalysisSource *iface)
{
    return 1;
}

static const WCHAR *g_source;

static HRESULT WINAPI analysissource_GetTextAtPosition(IDWriteTextAnalysisSource *iface,
    UINT32 position, WCHAR const** text, UINT32* text_len)
{
    if (position >= lstrlenW(g_source))
    {
        *text = NULL;
        *text_len = 0;
    }
    else
    {
        *text = &g_source[position];
        *text_len = lstrlenW(g_source) - position;
    }

    return S_OK;
}

static HRESULT WINAPI analysissource_GetTextBeforePosition(IDWriteTextAnalysisSource *iface,
    UINT32 position, WCHAR const** text, UINT32* text_len)
{
    ok(0, "unexpected\n");
    return E_NOTIMPL;
}

static DWRITE_READING_DIRECTION WINAPI analysissource_GetParagraphReadingDirection(
    IDWriteTextAnalysisSource *iface)
{
    ok(0, "unexpected\n");
    return DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
}

static HRESULT WINAPI analysissource_GetLocaleName(IDWriteTextAnalysisSource *iface,
    UINT32 position, UINT32* text_len, WCHAR const** locale)
{
    *locale = NULL;
    *text_len = 0;
    return S_OK;
}

static HRESULT WINAPI analysissource_GetNumberSubstitution(IDWriteTextAnalysisSource *iface,
    UINT32 position, UINT32* text_len, IDWriteNumberSubstitution **substitution)
{
    ok(0, "unexpected\n");
    return E_NOTIMPL;
}

static IDWriteTextAnalysisSourceVtbl analysissourcevtbl = {
    analysissource_QueryInterface,
    analysissource_AddRef,
    analysissource_Release,
    analysissource_GetTextAtPosition,
    analysissource_GetTextBeforePosition,
    analysissource_GetParagraphReadingDirection,
    analysissource_GetLocaleName,
    analysissource_GetNumberSubstitution
};

static IDWriteTextAnalysisSource analysissource = { &analysissourcevtbl };

static IDWriteFactory *create_factory(void)
{
    IDWriteFactory *factory;
    HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_ISOLATED, &IID_IDWriteFactory, (IUnknown**)&factory);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    return factory;
}

/* obvious limitation is that only last script data is returned, so this
   helper is suitable for single script strings only */
static void get_script_analysis(const WCHAR *str, UINT32 len, DWRITE_SCRIPT_ANALYSIS *sa)
{
    IDWriteTextAnalyzer *analyzer;
    IDWriteFactory *factory;
    HRESULT hr;

    g_source = str;

    factory = create_factory();
    hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextAnalyzer_AnalyzeScript(analyzer, &analysissource, 0, len, &analysissink.IDWriteTextAnalysisSink_iface);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    *sa = analysissink.sa;
    IDWriteFactory_Release(factory);
}

static IDWriteFontFace *get_fontface_from_format(IDWriteTextFormat *format)
{
    IDWriteFontCollection *collection;
    IDWriteFontFamily *family;
    IDWriteFontFace *fontface;
    IDWriteFont *font;
    WCHAR nameW[255];
    UINT32 index;
    BOOL exists;
    HRESULT hr;

    hr = IDWriteTextFormat_GetFontCollection(format, &collection);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_GetFontFamilyName(format, nameW, ARRAY_SIZE(nameW));
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontCollection_FindFamilyName(collection, nameW, &index, &exists);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontCollection_GetFontFamily(collection, index, &family);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteFontCollection_Release(collection);

    hr = IDWriteFontFamily_GetFirstMatchingFont(family,
        IDWriteTextFormat_GetFontWeight(format),
        IDWriteTextFormat_GetFontStretch(format),
        IDWriteTextFormat_GetFontStyle(format),
        &font);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFont_CreateFontFace(font, &fontface);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    IDWriteFont_Release(font);
    IDWriteFontFamily_Release(family);

    return fontface;
}

#define EXPECT_REF(obj,ref) _expect_ref((IUnknown*)obj, ref, __LINE__)
static void _expect_ref(IUnknown* obj, ULONG ref, int line)
{
    ULONG rc;
    IUnknown_AddRef(obj);
    rc = IUnknown_Release(obj);
    ok_(__FILE__,line)(rc == ref, "expected refcount %d, got %d\n", ref, rc);
}

enum drawcall_modifiers_kind {
    DRAW_EFFECT         = 0x1000
};

enum drawcall_kind {
    DRAW_GLYPHRUN      = 0,
    DRAW_UNDERLINE     = 1,
    DRAW_STRIKETHROUGH = 2,
    DRAW_INLINE        = 3,
    DRAW_LAST_KIND     = 4,
    DRAW_TOTAL_KINDS   = 5,
    DRAW_KINDS_MASK    = 0xff
};

static const char *get_draw_kind_name(unsigned short kind)
{
    static const char *kind_names[] = {
      "GLYPH_RUN",
      "UNDERLINE",
      "STRIKETHROUGH",
      "INLINE",
      "END_OF_SEQ",

      "GLYPH_RUN|EFFECT",
      "UNDERLINE|EFFECT",
      "STRIKETHROUGH|EFFECT",
      "INLINE|EFFECT",
      "END_OF_SEQ"
    };
    if ((kind & DRAW_KINDS_MASK) > DRAW_LAST_KIND)
        return "unknown";
    return (kind & DRAW_EFFECT) ? kind_names[(kind & DRAW_KINDS_MASK) + DRAW_TOTAL_KINDS] :
        kind_names[kind];
}

struct drawcall_entry {
    enum drawcall_kind kind;
    WCHAR string[10]; /* only meaningful for DrawGlyphRun() */
    WCHAR locale[LOCALE_NAME_MAX_LENGTH];
    UINT32 glyphcount; /* only meaningful for DrawGlyphRun() */
    UINT32 bidilevel;
};

struct drawcall_sequence
{
    int count;
    int size;
    struct drawcall_entry *sequence;
};

struct drawtestcontext {
    unsigned short kind;
    BOOL todo;
    int *failcount;
    const char *file;
    int line;
};

#define NUM_CALL_SEQUENCES 1
#define RENDERER_ID        0
static struct drawcall_sequence *sequences[NUM_CALL_SEQUENCES];
static struct drawcall_sequence *expected_seq[1];

static void add_call(struct drawcall_sequence **seq, int sequence_index, const struct drawcall_entry *call)
{
    struct drawcall_sequence *call_seq = seq[sequence_index];

    if (!call_seq->sequence) {
        call_seq->size = 10;
        call_seq->sequence = HeapAlloc(GetProcessHeap(), 0, call_seq->size * sizeof (struct drawcall_entry));
    }

    if (call_seq->count == call_seq->size) {
        call_seq->size *= 2;
        call_seq->sequence = HeapReAlloc(GetProcessHeap(), 0,
                                        call_seq->sequence,
                                        call_seq->size * sizeof (struct drawcall_entry));
    }

    assert(call_seq->sequence);
    call_seq->sequence[call_seq->count++] = *call;
}

static inline void flush_sequence(struct drawcall_sequence **seg, int sequence_index)
{
    struct drawcall_sequence *call_seq = seg[sequence_index];

    HeapFree(GetProcessHeap(), 0, call_seq->sequence);
    call_seq->sequence = NULL;
    call_seq->count = call_seq->size = 0;
}

static void init_call_sequences(struct drawcall_sequence **seq, int n)
{
    int i;

    for (i = 0; i < n; i++)
        seq[i] = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct drawcall_sequence));
}

static void ok_sequence_(struct drawcall_sequence **seq, int sequence_index,
    const struct drawcall_entry *expected, const char *context, BOOL todo,
    const char *file, int line)
{
    static const struct drawcall_entry end_of_sequence = { DRAW_LAST_KIND };
    struct drawcall_sequence *call_seq = seq[sequence_index];
    const struct drawcall_entry *actual, *sequence;
    int failcount = 0;

    add_call(seq, sequence_index, &end_of_sequence);

    sequence = call_seq->sequence;
    actual = sequence;

    while (expected->kind != DRAW_LAST_KIND && actual->kind != DRAW_LAST_KIND) {
        if (expected->kind != actual->kind) {
            if (todo) {
                failcount++;
                todo_wine
                    ok_(file, line) (0, "%s: call %s was expected, but got call %s instead\n",
                        context, get_draw_kind_name(expected->kind), get_draw_kind_name(actual->kind));

                flush_sequence(seq, sequence_index);
                return;
            }
            else
                ok_(file, line) (0, "%s: call %s was expected, but got call %s instead\n",
                    context, get_draw_kind_name(expected->kind), get_draw_kind_name(actual->kind));
        }
        else if ((expected->kind & DRAW_KINDS_MASK) == DRAW_GLYPHRUN) {
            int cmp = lstrcmpW(expected->string, actual->string);
            if (cmp != 0 && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: glyphrun string %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->string), wine_dbgstr_w(actual->string));
            }
            else
                ok_(file, line) (cmp == 0, "%s: glyphrun string %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->string), wine_dbgstr_w(actual->string));

            cmp = lstrcmpW(expected->locale, actual->locale);
            if (cmp != 0 && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: glyph run locale %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->locale), wine_dbgstr_w(actual->locale));
            }
            else
                ok_(file, line) (cmp == 0, "%s: glyph run locale %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->locale), wine_dbgstr_w(actual->locale));

            if (expected->glyphcount != actual->glyphcount && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: wrong glyph count, %u was expected, but got %u instead\n",
                    context, expected->glyphcount, actual->glyphcount);
            }
            else
                ok_(file, line) (expected->glyphcount == actual->glyphcount,
                    "%s: wrong glyph count, %u was expected, but got %u instead\n",
                    context, expected->glyphcount, actual->glyphcount);

            if (expected->bidilevel != actual->bidilevel && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: wrong bidi level, %u was expected, but got %u instead\n",
                    context, expected->bidilevel, actual->bidilevel);
            }
            else
                ok_(file, line) (expected->bidilevel == actual->bidilevel,
                    "%s: wrong bidi level, %u was expected, but got %u instead\n",
                    context, expected->bidilevel, actual->bidilevel);
        }
        else if ((expected->kind & DRAW_KINDS_MASK) == DRAW_UNDERLINE) {
            int cmp = lstrcmpW(expected->locale, actual->locale);
            if (cmp != 0 && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: underline locale %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->locale), wine_dbgstr_w(actual->locale));
            }
            else
                ok_(file, line) (cmp == 0, "%s: underline locale %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->locale), wine_dbgstr_w(actual->locale));
        }
        expected++;
        actual++;
    }

    if (todo) {
        todo_wine {
            if (expected->kind != DRAW_LAST_KIND || actual->kind != DRAW_LAST_KIND) {
                failcount++;
                ok_(file, line) (0, "%s: the call sequence is not complete: expected %s - actual %s\n",
                    context, get_draw_kind_name(expected->kind), get_draw_kind_name(actual->kind));
            }
        }
    }
    else if (expected->kind != DRAW_LAST_KIND || actual->kind != DRAW_LAST_KIND)
        ok_(file, line) (0, "%s: the call sequence is not complete: expected %s - actual %s\n",
            context, get_draw_kind_name(expected->kind), get_draw_kind_name(actual->kind));

    if (todo && !failcount) /* succeeded yet marked todo */
        todo_wine
            ok_(file, line)(1, "%s: marked \"todo_wine\" but succeeds\n", context);

    flush_sequence(seq, sequence_index);
}

#define ok_sequence(seq, index, exp, contx, todo) \
        ok_sequence_(seq, index, (exp), (contx), (todo), __FILE__, __LINE__)

static HRESULT WINAPI testrenderer_QI(IDWriteTextRenderer *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteTextRenderer) ||
        IsEqualIID(riid, &IID_IDWritePixelSnapping) ||
        IsEqualIID(riid, &IID_IUnknown)
    ) {
        *obj = iface;
        return S_OK;
    }

    *obj = NULL;

    /* IDWriteTextRenderer1 overrides drawing calls, ignore for now */
    if (IsEqualIID(riid, &IID_IDWriteTextRenderer1))
        return E_NOINTERFACE;

    ok(0, "unexpected QI %s\n", wine_dbgstr_guid(riid));
    return E_NOINTERFACE;
}

static ULONG WINAPI testrenderer_AddRef(IDWriteTextRenderer *iface)
{
    return 2;
}

static ULONG WINAPI testrenderer_Release(IDWriteTextRenderer *iface)
{
    return 1;
}

struct renderer_context {
    BOOL gdicompat;
    BOOL use_gdi_natural;
    BOOL snapping_disabled;
    DWRITE_MATRIX m;
    FLOAT ppdip;
    FLOAT originX;
    FLOAT originY;
    IDWriteTextFormat *format;
    const WCHAR *familyW;
};

static HRESULT WINAPI testrenderer_IsPixelSnappingDisabled(IDWriteTextRenderer *iface,
    void *context, BOOL *disabled)
{
    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 *context, DWRITE_MATRIX *m)
{
    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 *context, FLOAT *pixels_per_dip)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    *pixels_per_dip = ctxt->ppdip;
    return S_OK;
}

#define TEST_MEASURING_MODE(ctxt, mode) test_measuring_mode(ctxt, mode, __LINE__)
static void test_measuring_mode(const struct renderer_context *ctxt, DWRITE_MEASURING_MODE mode, int line)
{
    if (ctxt->gdicompat) {
        if (ctxt->use_gdi_natural)
            ok_(__FILE__, line)(mode == DWRITE_MEASURING_MODE_GDI_NATURAL, "got %d\n", mode);
        else
            ok_(__FILE__, line)(mode == DWRITE_MEASURING_MODE_GDI_CLASSIC, "got %d\n", mode);
    }
    else
        ok_(__FILE__, line)(mode == DWRITE_MEASURING_MODE_NATURAL, "got %d\n", mode);
}

static HRESULT WINAPI testrenderer_DrawGlyphRun(IDWriteTextRenderer *iface,
    void *context,
    FLOAT baselineOriginX,
    FLOAT baselineOriginY,
    DWRITE_MEASURING_MODE mode,
    DWRITE_GLYPH_RUN const *run,
    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) {
        TEST_MEASURING_MODE(ctxt, mode);
        ctxt->originX = baselineOriginX;
        ctxt->originY = baselineOriginY;
    }

    ok(descr->stringLength < ARRAY_SIZE(entry.string), "string is too long\n");
    if (descr->stringLength && descr->stringLength < ARRAY_SIZE(entry.string)) {
        memcpy(entry.string, descr->string, descr->stringLength*sizeof(WCHAR));
        entry.string[descr->stringLength] = 0;
    }
    else
        entry.string[0] = 0;

    /* see what's reported for control codes runs */
    get_script_analysis(descr->string, descr->stringLength, &sa);
    if (sa.shapes == DWRITE_SCRIPT_SHAPES_NO_VISUAL) {
        UINT32 i;

        /* glyphs are not reported at all for control code runs */
        ok(run->glyphCount == 0, "got %u\n", run->glyphCount);
        ok(run->glyphAdvances != NULL, "advances array %p\n", run->glyphAdvances);
        ok(run->glyphOffsets != NULL, "offsets array %p\n", run->glyphOffsets);
        ok(run->fontFace != NULL, "got %p\n", run->fontFace);
        /* text positions are still valid */
        ok(descr->string != NULL, "got string %p\n", descr->string);
        ok(descr->stringLength > 0, "got string length %u\n", descr->stringLength);
        ok(descr->clusterMap != NULL, "clustermap %p\n", descr->clusterMap);
        for (i = 0; i < descr->stringLength; i++)
            ok(descr->clusterMap[i] == i, "got %u\n", descr->clusterMap[i]);
    }

    entry.kind = DRAW_GLYPHRUN;
    if (effect)
        entry.kind |= DRAW_EFFECT;
    ok(lstrlenW(descr->localeName) < LOCALE_NAME_MAX_LENGTH, "unexpectedly long locale name\n");
    lstrcpyW(entry.locale, descr->localeName);
    entry.glyphcount = run->glyphCount;
    entry.bidilevel = run->bidiLevel;
    add_call(sequences, RENDERER_ID, &entry);
    return S_OK;
}

static HRESULT WINAPI testrenderer_DrawUnderline(IDWriteTextRenderer *iface,
    void *context,
    FLOAT baselineOriginX,
    FLOAT baselineOriginY,
    DWRITE_UNDERLINE const* underline,
    IUnknown *effect)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    struct drawcall_entry entry = { 0 };

    if (ctxt)
        TEST_MEASURING_MODE(ctxt, underline->measuringMode);

    ok(underline->runHeight > 0.0f, "Expected non-zero run height\n");
    if (ctxt && ctxt->format) {
        DWRITE_FONT_METRICS metrics;
        IDWriteFontFace *fontface;
        FLOAT emsize;

        fontface = get_fontface_from_format(ctxt->format);
        emsize = IDWriteTextFormat_GetFontSize(ctxt->format);
        IDWriteFontFace_GetMetrics(fontface, &metrics);

        ok(emsize == metrics.designUnitsPerEm, "Unexpected font size %f\n", emsize);
        /* Expected height is in design units, allow some absolute difference from it. Seems to only happen on Vista */
        ok(fabs(metrics.capHeight - underline->runHeight) < 2.0f, "Expected runHeight %u, got %f, family %s\n",
                metrics.capHeight, underline->runHeight, wine_dbgstr_w(ctxt->familyW));

        IDWriteFontFace_Release(fontface);
    }

    entry.kind = DRAW_UNDERLINE;
    if (effect)
        entry.kind |= DRAW_EFFECT;
    lstrcpyW(entry.locale, underline->localeName);
    add_call(sequences, RENDERER_ID, &entry);
    return S_OK;
}

static HRESULT WINAPI testrenderer_DrawStrikethrough(IDWriteTextRenderer *iface,
    void *context,
    FLOAT baselineOriginX,
    FLOAT baselineOriginY,
    DWRITE_STRIKETHROUGH const* strikethrough,
    IUnknown *effect)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    struct drawcall_entry entry = { 0 };

    if (ctxt)
        TEST_MEASURING_MODE(ctxt, strikethrough->measuringMode);

    entry.kind = DRAW_STRIKETHROUGH;
    if (effect)
        entry.kind |= DRAW_EFFECT;
    add_call(sequences, RENDERER_ID, &entry);
    return S_OK;
}

static HRESULT WINAPI testrenderer_DrawInlineObject(IDWriteTextRenderer *iface,
    void *context,
    FLOAT originX,
    FLOAT originY,
    IDWriteInlineObject *object,
    BOOL is_sideways,
    BOOL is_rtl,
    IUnknown *effect)
{
    struct drawcall_entry entry = { 0 };
    entry.kind = DRAW_INLINE;
    if (effect)
        entry.kind |= DRAW_EFFECT;
    add_call(sequences, RENDERER_ID, &entry);
    return S_OK;
}

static const IDWriteTextRendererVtbl testrenderervtbl = {
    testrenderer_QI,
    testrenderer_AddRef,
    testrenderer_Release,
    testrenderer_IsPixelSnappingDisabled,
    testrenderer_GetCurrentTransform,
    testrenderer_GetPixelsPerDip,
    testrenderer_DrawGlyphRun,
    testrenderer_DrawUnderline,
    testrenderer_DrawStrikethrough,
    testrenderer_DrawInlineObject
};

static IDWriteTextRenderer testrenderer = { &testrenderervtbl };

/* test IDWriteInlineObject */
static HRESULT WINAPI testinlineobj_QI(IDWriteInlineObject *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteInlineObject) || IsEqualIID(riid, &IID_IUnknown)) {
        *obj = iface;
        IDWriteInlineObject_AddRef(iface);
        return S_OK;
    }

    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI testinlineobj_AddRef(IDWriteInlineObject *iface)
{
    return 2;
}

static ULONG WINAPI testinlineobj_Release(IDWriteInlineObject *iface)
{
    return 1;
}

static HRESULT WINAPI testinlineobj_Draw(IDWriteInlineObject *iface,
    void* client_drawingontext, IDWriteTextRenderer* renderer,
    FLOAT originX, FLOAT originY, BOOL is_sideways, BOOL is_rtl, IUnknown *drawing_effect)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI testinlineobj_GetMetrics(IDWriteInlineObject *iface, DWRITE_INLINE_OBJECT_METRICS *metrics)
{
    metrics->width = 123.0;
    return 0x8faecafe;
}

static HRESULT WINAPI testinlineobj_GetOverhangMetrics(IDWriteInlineObject *iface, DWRITE_OVERHANG_METRICS *overhangs)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI testinlineobj_GetBreakConditions(IDWriteInlineObject *iface, DWRITE_BREAK_CONDITION *before,
    DWRITE_BREAK_CONDITION *after)
{
    *before = *after = DWRITE_BREAK_CONDITION_MUST_BREAK;
    return 0x8feacafe;
}

static HRESULT WINAPI testinlineobj2_GetBreakConditions(IDWriteInlineObject *iface, DWRITE_BREAK_CONDITION *before,
    DWRITE_BREAK_CONDITION *after)
{
    *before = *after = DWRITE_BREAK_CONDITION_MAY_NOT_BREAK;
    return S_OK;
}

static IDWriteInlineObjectVtbl testinlineobjvtbl = {
    testinlineobj_QI,
    testinlineobj_AddRef,
    testinlineobj_Release,
    testinlineobj_Draw,
    testinlineobj_GetMetrics,
    testinlineobj_GetOverhangMetrics,
    testinlineobj_GetBreakConditions
};

static IDWriteInlineObjectVtbl testinlineobjvtbl2 = {
    testinlineobj_QI,
    testinlineobj_AddRef,
    testinlineobj_Release,
    testinlineobj_Draw,
    testinlineobj_GetMetrics,
    testinlineobj_GetOverhangMetrics,
    testinlineobj2_GetBreakConditions
};

static IDWriteInlineObject testinlineobj = { &testinlineobjvtbl };
static IDWriteInlineObject testinlineobj2 = { &testinlineobjvtbl };
static IDWriteInlineObject testinlineobj3 = { &testinlineobjvtbl2 };

struct test_inline_obj
{
    IDWriteInlineObject IDWriteInlineObject_iface;
    DWRITE_INLINE_OBJECT_METRICS metrics;
    DWRITE_OVERHANG_METRICS overhangs;
};

static inline struct test_inline_obj *impl_from_IDWriteInlineObject(IDWriteInlineObject *iface)
{
    return CONTAINING_RECORD(iface, struct test_inline_obj, IDWriteInlineObject_iface);
}

static HRESULT WINAPI testinlineobj3_GetMetrics(IDWriteInlineObject *iface, DWRITE_INLINE_OBJECT_METRICS *metrics)
{
    struct test_inline_obj *obj = impl_from_IDWriteInlineObject(iface);
    *metrics = obj->metrics;
    return S_OK;
}

static HRESULT WINAPI testinlineobj3_GetOverhangMetrics(IDWriteInlineObject *iface, DWRITE_OVERHANG_METRICS *overhangs)
{
    struct test_inline_obj *obj = impl_from_IDWriteInlineObject(iface);
    *overhangs = obj->overhangs;
    /* Return value is ignored. */
    return E_NOTIMPL;
}

static const IDWriteInlineObjectVtbl testinlineobjvtbl3 = {
    testinlineobj_QI,
    testinlineobj_AddRef,
    testinlineobj_Release,
    testinlineobj_Draw,
    testinlineobj3_GetMetrics,
    testinlineobj3_GetOverhangMetrics,
    testinlineobj_GetBreakConditions,
};

static void test_inline_obj_init(struct test_inline_obj *obj, const DWRITE_INLINE_OBJECT_METRICS *metrics,
        const DWRITE_OVERHANG_METRICS *overhangs)
{
    obj->IDWriteInlineObject_iface.lpVtbl = &testinlineobjvtbl3;
    obj->metrics = *metrics;
    obj->overhangs = *overhangs;
}

struct test_effect
{
    IUnknown IUnknown_iface;
    LONG ref;
};

static inline struct test_effect *test_effect_from_IUnknown(IUnknown *iface)
{
    return CONTAINING_RECORD(iface, struct test_effect, IUnknown_iface);
}

static HRESULT WINAPI testeffect_QI(IUnknown *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IUnknown)) {
        *obj = iface;
        IUnknown_AddRef(iface);
        return S_OK;
    }

    ok(0, "Unexpected riid %s.\n", wine_dbgstr_guid(riid));
    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI testeffect_AddRef(IUnknown *iface)
{
    struct test_effect *effect = test_effect_from_IUnknown(iface);
    return InterlockedIncrement(&effect->ref);
}

static ULONG WINAPI testeffect_Release(IUnknown *iface)
{
    struct test_effect *effect = test_effect_from_IUnknown(iface);
    LONG ref = InterlockedDecrement(&effect->ref);

    if (!ref)
        HeapFree(GetProcessHeap(), 0, effect);

    return ref;
}

static const IUnknownVtbl testeffectvtbl = {
    testeffect_QI,
    testeffect_AddRef,
    testeffect_Release
};

static IUnknown *create_test_effect(void)
{
    struct test_effect *effect;

    effect = HeapAlloc(GetProcessHeap(), 0, sizeof(*effect));
    effect->IUnknown_iface.lpVtbl = &testeffectvtbl;
    effect->ref = 1;

    return &effect->IUnknown_iface;
}

static void test_CreateTextLayout(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    IDWriteTextLayout4 *layout4;
    IDWriteTextLayout2 *layout2 = NULL;
    IDWriteTextLayout *layout;
    IDWriteTextFormat3 *format3;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, NULL, 0, NULL, 0.0, 0.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, NULL, 0.0, 0.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, NULL, 1.0, 0.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, NULL, 0.0, 1.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, NULL, 1000.0, 1000.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, NULL, 0, format, 100.0f, 100.0f, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 0.0f, 0.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    EXPECT_REF(format, 1);
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(format, 1);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    if (hr == S_OK) {
        IDWriteTextLayout1 *layout1;
        IDWriteTextFormat1 *format1;
        IDWriteTextFormat *format;

        hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextLayout1, (void**)&layout1);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout1_Release(layout1);

        EXPECT_REF(layout2, 2);
        hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextFormat1, (void**)&format1);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        EXPECT_REF(layout2, 3);

        hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextFormat, (void**)&format);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(format == (IDWriteTextFormat*)format1, "got %p, %p\n", format, format1);
        ok(format != (IDWriteTextFormat*)layout2, "got %p, %p\n", format, layout2);
        EXPECT_REF(layout2, 4);

        hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextLayout1, (void**)&layout1);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout1_Release(layout1);

        IDWriteTextFormat1_Release(format1);
        IDWriteTextFormat_Release(format);

        hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat1, (void**)&format1);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        EXPECT_REF(layout2, 3);

        hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat, (void**)&format);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(format == (IDWriteTextFormat*)format1, "got %p, %p\n", format, format1);
        EXPECT_REF(layout2, 4);

        IDWriteTextFormat1_Release(format1);
        IDWriteTextFormat_Release(format);
    }
    else
        win_skip("IDWriteTextLayout2 is not supported.\n");

    if (layout2 && SUCCEEDED(IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextLayout4, (void **)&layout4)))
    {
        hr = IDWriteTextLayout4_QueryInterface(layout4, &IID_IDWriteTextFormat3, (void **)&format3);
        ok(hr == S_OK, "Failed to get text format, hr %#x.\n", hr);
        IDWriteTextFormat3_Release(format3);
    }
    else
        win_skip("IDWriteTextLayout4 is not supported.\n");

    if (layout2)
        IDWriteTextLayout2_Release(layout2);
    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static DWRITE_MATRIX layoutcreate_transforms[] = {
    { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 },
    { 1.0, 0.0, 0.0, 1.0, 0.3, 0.2 },
    { 1.0, 0.0, 0.0, 1.0,-0.3,-0.2 },

    { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
    { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
    { 1.0, 2.0, 0.5, 1.0, 0.0, 0.0 },
};

static void test_CreateGdiCompatibleTextLayout(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    FLOAT dimension;
    HRESULT hr;
    int i;

    factory = create_factory();

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, NULL, 0, NULL, 0.0, 0.0, 0.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, NULL, 0.0, 0.0, 0.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, NULL, 1.0, 0.0, 0.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, NULL, 1.0, 0.0, 1.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, NULL, 1000.0, 1000.0, 1.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    /* create with text format */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(format, 1);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, NULL, 0, format, 100.0f, 100.0f, 1.0f, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(format, 1);
    EXPECT_REF(layout, 1);

    IDWriteTextLayout_AddRef(layout);
    EXPECT_REF(format, 1);
    EXPECT_REF(layout, 2);
    IDWriteTextLayout_Release(layout);
    IDWriteTextLayout_Release(layout);

    /* zero length string is okay */
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 0, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    dimension = IDWriteTextLayout_GetMaxWidth(layout);
    ok(dimension == 100.0, "got %f\n", dimension);

    dimension = IDWriteTextLayout_GetMaxHeight(layout);
    ok(dimension == 100.0, "got %f\n", dimension);

    IDWriteTextLayout_Release(layout);

    /* negative, zero ppdip */
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 1, format, 100.0, 100.0, -1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 1, format, 100.0, 100.0, 0.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    /* transforms */
    for (i = 0; i < ARRAY_SIZE(layoutcreate_transforms); i++) {
        hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 1, format, 100.0, 100.0, 1.0,
            &layoutcreate_transforms[i], FALSE, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout_Release(layout);
    }

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_CreateTextFormat(void)
{
    static const WCHAR emptyW[] = {0};
    IDWriteFontCollection *collection, *syscoll;
    DWRITE_PARAGRAPH_ALIGNMENT paralign;
    DWRITE_READING_DIRECTION readdir;
    DWRITE_WORD_WRAPPING wrapping;
    DWRITE_TEXT_ALIGNMENT align;
    DWRITE_FLOW_DIRECTION flow;
    DWRITE_LINE_SPACING_METHOD method;
    DWRITE_TRIMMING trimming;
    IDWriteTextFormat *format;
    FLOAT spacing, baseline;
    IDWriteInlineObject *trimmingsign;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    /* zero/negative font size */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 0.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, -10.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* invalid font properties */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, 1000, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_ITALIC + 1,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_ITALIC,
        10, 10.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* empty family name */
    hr = IDWriteFactory_CreateTextFormat(factory, emptyW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    if (0) /* crashes on native */
        hr = IDWriteTextFormat_GetFontCollection(format, NULL);

    collection = NULL;
    hr = IDWriteTextFormat_GetFontCollection(format, &collection);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(collection != NULL, "got %p\n", collection);

    hr = IDWriteFactory_GetSystemFontCollection(factory, &syscoll, FALSE);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(collection == syscoll, "got %p, was %p\n", syscoll, collection);
    IDWriteFontCollection_Release(syscoll);
    IDWriteFontCollection_Release(collection);

    /* default format properties */
    align = IDWriteTextFormat_GetTextAlignment(format);
    ok(align == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", align);

    paralign = IDWriteTextFormat_GetParagraphAlignment(format);
    ok(paralign == DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "got %d\n", paralign);

    wrapping = IDWriteTextFormat_GetWordWrapping(format);
    ok(wrapping == DWRITE_WORD_WRAPPING_WRAP, "got %d\n", wrapping);

    readdir = IDWriteTextFormat_GetReadingDirection(format);
    ok(readdir == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", readdir);

    flow = IDWriteTextFormat_GetFlowDirection(format);
    ok(flow == DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM, "got %d\n", flow);

    hr = IDWriteTextFormat_GetLineSpacing(format, &method, &spacing, &baseline);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(spacing == 0.0, "got %f\n", spacing);
    ok(baseline == 0.0, "got %f\n", baseline);
    ok(method == DWRITE_LINE_SPACING_METHOD_DEFAULT, "got %d\n", method);

    trimming.granularity = DWRITE_TRIMMING_GRANULARITY_WORD;
    trimming.delimiter = 10;
    trimming.delimiterCount = 10;
    trimmingsign = (void*)0xdeadbeef;
    hr = IDWriteTextFormat_GetTrimming(format, &trimming, &trimmingsign);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(trimming.granularity == DWRITE_TRIMMING_GRANULARITY_NONE, "got %d\n", trimming.granularity);
    ok(trimming.delimiter == 0, "got %d\n", trimming.delimiter);
    ok(trimming.delimiterCount == 0, "got %d\n", trimming.delimiterCount);
    ok(trimmingsign == NULL, "got %p\n", trimmingsign);

    /* setters */
    hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_LEADING);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_JUSTIFIED+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetParagraphAlignment(format, DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetParagraphAlignment(format, DWRITE_PARAGRAPH_ALIGNMENT_CENTER+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetWordWrapping(format, DWRITE_WORD_WRAPPING_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetWordWrapping(format, DWRITE_WORD_WRAPPING_CHARACTER+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetReadingDirection(format, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetFlowDirection(format, DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM);
    ok(hr == S_OK, "got 0x%08x\n", hr);


    hr = IDWriteTextFormat_SetTrimming(format, &trimming, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* invalid granularity */
    trimming.granularity = 10;
    trimming.delimiter = 0;
    trimming.delimiterCount = 0;
    hr = IDWriteTextFormat_SetTrimming(format, &trimming, NULL);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_GetLocaleName(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    static const WCHAR ruW[] = {'r','u',0};
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format, *format2;
    IDWriteFactory *factory;
    WCHAR buff[10];
    UINT32 len;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, ruW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 0, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat, (void**)&format2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    len = IDWriteTextFormat_GetLocaleNameLength(format2);
    ok(len == 2, "got %u\n", len);
    len = IDWriteTextFormat_GetLocaleNameLength(format);
    ok(len == 2, "got %u\n", len);
    hr = IDWriteTextFormat_GetLocaleName(format2, buff, len);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    hr = IDWriteTextFormat_GetLocaleName(format2, buff, len+1);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buff, ruW), "got %s\n", wine_dbgstr_w(buff));
    hr = IDWriteTextFormat_GetLocaleName(format, buff, len);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    hr = IDWriteTextFormat_GetLocaleName(format, buff, len+1);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buff, ruW), "got %s\n", wine_dbgstr_w(buff));

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteTextFormat_Release(format2);
    IDWriteFactory_Release(factory);
}

static const struct drawcall_entry drawellipsis_seq[] = {
    { DRAW_GLYPHRUN, {0x2026, 0}, {'e','n','-','g','b',0}, 1 },
    { DRAW_LAST_KIND }
};

static void test_CreateEllipsisTrimmingSign(void)
{
    static const WCHAR engbW[] = {'e','n','-','G','B',0};
    DWRITE_INLINE_OBJECT_METRICS metrics;
    DWRITE_BREAK_CONDITION before, after;
    struct renderer_context ctxt;
    IDWriteTextFormat *format;
    IDWriteInlineObject *sign;
    IDWriteFactory *factory;
    IUnknown *unk, *effect;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, engbW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(format, 1);
    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &sign);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(format, 1);

    hr = IDWriteInlineObject_QueryInterface(sign, &IID_IDWriteTextLayout, (void**)&unk);
    ok(hr == E_NOINTERFACE, "got 0x%08x\n", hr);

if (0) {/* crashes on native */
    hr = IDWriteInlineObject_GetBreakConditions(sign, NULL, NULL);
    hr = IDWriteInlineObject_GetMetrics(sign, NULL);
}
    metrics.width = 0.0;
    metrics.height = 123.0;
    metrics.baseline = 123.0;
    metrics.supportsSideways = TRUE;
    hr = IDWriteInlineObject_GetMetrics(sign, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics.width > 0.0, "got %.2f\n", metrics.width);
    ok(metrics.height == 0.0, "got %.2f\n", metrics.height);
    ok(metrics.baseline == 0.0, "got %.2f\n", metrics.baseline);
    ok(!metrics.supportsSideways, "got %d\n", metrics.supportsSideways);

    before = after = DWRITE_BREAK_CONDITION_CAN_BREAK;
    hr = IDWriteInlineObject_GetBreakConditions(sign, &before, &after);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(before == DWRITE_BREAK_CONDITION_NEUTRAL, "got %d\n", before);
    ok(after == DWRITE_BREAK_CONDITION_NEUTRAL, "got %d\n", after);

    /* Draw tests */
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteInlineObject_Draw(sign, NULL, &testrenderer, 0.0, 0.0, FALSE, FALSE, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawellipsis_seq, "ellipsis sign draw test", FALSE);

    effect = create_test_effect();

    EXPECT_REF(effect, 1);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteInlineObject_Draw(sign, NULL, &testrenderer, 0.0f, 0.0f, FALSE, FALSE, effect);
    ok(hr == S_OK, "Failed to draw trimming sign, hr %#x.\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawellipsis_seq, "ellipsis sign draw with effect test", FALSE);
    EXPECT_REF(effect, 1);

    IUnknown_Release(effect);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteInlineObject_Draw(sign, NULL, &testrenderer, 0.0f, 0.0f, FALSE, FALSE, (void *)0xdeadbeef);
    ok(hr == S_OK, "Failed to draw trimming sign, hr %#x.\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawellipsis_seq, "ellipsis sign draw with effect test", FALSE);

    memset(&ctxt, 0, sizeof(ctxt));
    hr = IDWriteInlineObject_Draw(sign, &ctxt, &testrenderer, 123.0f, 456.0f, FALSE, FALSE, NULL);
    ok(hr == S_OK, "Failed to draw trimming sign, hr %#x.\n", hr);
    ok(ctxt.originX == 123.0f && ctxt.originY == 456.0f, "Unexpected drawing origin\n");

    IDWriteInlineObject_Release(sign);

    /* Centered format */
    hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_CENTER);
    ok(hr == S_OK, "Failed to set text alignment, hr %#x.\n", hr);

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &sign);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(&ctxt, 0, sizeof(ctxt));
    hr = IDWriteInlineObject_Draw(sign, &ctxt, &testrenderer, 123.0f, 456.0f, FALSE, FALSE, NULL);
    ok(hr == S_OK, "Failed to draw trimming sign, hr %#x.\n", hr);
    ok(ctxt.originX == 123.0f && ctxt.originY == 456.0f, "Unexpected drawing origin\n");

    IDWriteInlineObject_Release(sign);

    /* non-orthogonal flow/reading combination */
    hr = IDWriteTextFormat_SetReadingDirection(format, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetFlowDirection(format, DWRITE_FLOW_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK || broken(hr == E_INVALIDARG) /* vista, win7 */, "got 0x%08x\n", hr);
    if (hr == S_OK) {
        hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &sign);
        ok(hr == DWRITE_E_FLOWDIRECTIONCONFLICTS, "got 0x%08x\n", hr);
    }

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_fontweight(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    static const WCHAR ruW[] = {'r','u',0};
    IDWriteTextFormat *format, *fmt2;
    IDWriteTextLayout *layout;
    DWRITE_FONT_WEIGHT weight;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    FLOAT size;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, ruW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat, (void**)&fmt2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    weight = IDWriteTextFormat_GetFontWeight(fmt2);
    ok(weight == DWRITE_FONT_WEIGHT_BOLD, "got %u\n", weight);

    range.startPosition = range.length = 0;
    hr = IDWriteTextLayout_GetFontWeight(layout, 0, &weight, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(range.startPosition == 0 && range.length == ~0u, "got %u, %u\n", range.startPosition, range.length);

    range.startPosition = 0;
    range.length = 6;
    hr = IDWriteTextLayout_SetFontWeight(layout, DWRITE_FONT_WEIGHT_NORMAL, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = range.length = 0;
    hr = IDWriteTextLayout_GetFontWeight(layout, 0, &weight, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(range.startPosition == 0 && range.length == 6, "got %u, %u\n", range.startPosition, range.length);

    /* IDWriteTextFormat methods output doesn't reflect layout changes */
    weight = IDWriteTextFormat_GetFontWeight(fmt2);
    ok(weight == DWRITE_FONT_WEIGHT_BOLD, "got %u\n", weight);

    range.length = 0;
    weight = DWRITE_FONT_WEIGHT_BOLD;
    hr = IDWriteTextLayout_GetFontWeight(layout, 0, &weight, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(weight == DWRITE_FONT_WEIGHT_NORMAL, "got %d\n", weight);
    ok(range.length == 6, "got %d\n", range.length);

    range.startPosition = 0;
    range.length = 6;
    hr = IDWriteTextLayout_SetFontWeight(layout, 1000, range);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxWidth(layout);
    ok(size == 100.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxWidth(layout, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxWidth(layout);
    ok(size == 0.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxWidth(layout, -1.0);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxWidth(layout);
    ok(size == 0.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxWidth(layout, 100.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxWidth(layout);
    ok(size == 100.0, "got %.2f\n", size);

    size = IDWriteTextLayout_GetMaxHeight(layout);
    ok(size == 100.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxHeight(layout, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxHeight(layout);
    ok(size == 0.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxHeight(layout, -1.0);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxHeight(layout);
    ok(size == 0.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxHeight(layout, 100.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxHeight(layout);
    ok(size == 100.0, "got %.2f\n", size);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(fmt2);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetInlineObject(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    static const WCHAR ruW[] = {'r','u',0};

    IDWriteInlineObject *inlineobj, *inlineobj2, *inlinetest;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range, r2;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, ruW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &inlineobj);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &inlineobj2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(inlineobj, 1);
    EXPECT_REF(inlineobj2, 1);

    inlinetest = (void*)0x1;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == NULL, "got %p\n", inlinetest);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(inlineobj, 2);

    inlinetest = (void*)0x1;
    hr = IDWriteTextLayout_GetInlineObject(layout, 2, &inlinetest, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == NULL, "got %p\n", inlinetest);

    inlinetest = NULL;
    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 2, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);

    /* get from somewhere inside a range */
    inlinetest = NULL;
    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 1, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 2, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj2, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    inlinetest = NULL;
    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 1, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj2, "got %p\n", inlinetest);
    ok(r2.startPosition == 1 && r2.length == 1, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);
    EXPECT_REF(inlineobj2, 2);

    inlinetest = NULL;
    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 1, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 2, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);

    range.startPosition = 1;
    range.length = 2;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(inlineobj, 2);

    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 3, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);
    EXPECT_REF(inlineobj2, 1);

    IDWriteTextLayout_Release(layout);

    EXPECT_REF(inlineobj, 1);

    IDWriteInlineObject_Release(inlineobj);
    IDWriteInlineObject_Release(inlineobj2);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

/* drawing calls sequence doesn't depend on run order, instead all runs are
   drawn first, inline objects next and then underline/strikes */
static const struct drawcall_entry draw_seq[] = {
    { DRAW_GLYPHRUN, {'s',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'r','i',0}, {'r','u',0}, 2 },
    { DRAW_GLYPHRUN|DRAW_EFFECT, {'n',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'g',0}, {'r','u',0}, 1 },
    { DRAW_INLINE },
    { DRAW_UNDERLINE, {0}, {'r','u',0} },
    { DRAW_STRIKETHROUGH },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_trimmed_seq[] = {
    { DRAW_GLYPHRUN, {'a',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_seq2[] = {
    { DRAW_GLYPHRUN, {'s',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'t',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'r',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'i',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'n',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'g',0}, {'r','u',0}, 1 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_seq3[] = {
    { DRAW_GLYPHRUN, {0x202a,0x202c,0}, {'r','u',0}, 0 },
    { DRAW_GLYPHRUN, {'a','b',0}, {'r','u',0}, 2 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_seq4[] = {
    { DRAW_GLYPHRUN, {'s','t','r',0}, {'r','u',0}, 3 },
    { DRAW_GLYPHRUN, {'i','n','g',0}, {'r','u',0}, 3  },
    { DRAW_STRIKETHROUGH },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_seq5[] = {
    { DRAW_GLYPHRUN, {'s','t',0}, {'r','u',0}, 2 },
    { DRAW_GLYPHRUN, {'r','i',0}, {'r','u',0}, 2 },
    { DRAW_GLYPHRUN, {'n','g',0}, {'r','u',0}, 2 },
    { DRAW_STRIKETHROUGH },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry empty_seq[] = {
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_single_run_seq[] = {
    { DRAW_GLYPHRUN, {'s','t','r','i','n','g',0}, {'r','u',0}, 6 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_ltr_reordered_run_seq[] = {
    { DRAW_GLYPHRUN, {'1','2','3','-','5','2',0}, {'r','u',0}, 6 },
    { DRAW_GLYPHRUN, {0x64a,0x64f,0x633,0x627,0x648,0x650,0x64a,0}, {'r','u',0}, 7, 1 },
    { DRAW_GLYPHRUN, {'7','1',0}, {'r','u',0}, 2, 2 },
    { DRAW_GLYPHRUN, {'.',0}, {'r','u',0}, 1 },
    { DRAW_LAST_KIND }
};

static void test_Draw(void)
{
    static const WCHAR str3W[] = {'1','2','3','-','5','2',0x64a,0x64f,0x633,0x627,0x648,0x650,
        0x64a,'7','1','.',0};
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    static const WCHAR str2W[] = {0x202a,0x202c,'a','b',0};
    static const WCHAR ruW[] = {'r','u',0};
    IDWriteInlineObject *inlineobj;
    struct renderer_context ctxt;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    DWRITE_TEXT_METRICS tm;
    DWRITE_MATRIX m;
    HRESULT hr;

    factory = create_factory();

    memset(&ctxt, 0, sizeof(ctxt));
    ctxt.snapping_disabled = TRUE;

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, ruW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 100.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &inlineobj);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 5;
    range.length = 1;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 4;
    range.length = 1;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, (IUnknown*)inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq, "draw test", FALSE);
    IDWriteTextLayout_Release(layout);

    /* with reduced width DrawGlyphRun() is called for every line */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 5.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq2, "draw test 2", TRUE);
    hr = IDWriteTextLayout_GetMetrics(layout, &tm);
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(tm.lineCount == 6, "got %u\n", tm.lineCount);
    IDWriteTextLayout_Release(layout);

    /* string with control characters */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 4, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq3, "draw test 3", FALSE);
    IDWriteTextLayout_Release(layout);

    /* strikethrough splits ranges from renderer point of view, but doesn't break
       shaping */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);

    range.startPosition = 0;
    range.length = 3;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq4, "draw test 4", FALSE);
    IDWriteTextLayout_Release(layout);

    /* strikethrough somewhere in the middle */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);

    range.startPosition = 2;
    range.length = 2;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq5, "draw test 5", FALSE);
    IDWriteTextLayout_Release(layout);

    /* empty string */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, empty_seq, "draw test 6", FALSE);
    IDWriteTextLayout_Release(layout);

    ctxt.gdicompat = TRUE;
    ctxt.use_gdi_natural = TRUE;

    /* different parameter combinations with gdi-compatible layout */
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, TRUE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 7", FALSE);

    /* text alignment keeps pixel-aligned origin */
    hr = IDWriteTextLayout_GetMetrics(layout, &tm);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(tm.width == floorf(tm.width), "got %f\n", tm.width);

    hr = IDWriteTextLayout_SetMaxWidth(layout, tm.width + 3.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_CENTER);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ctxt.originX = ctxt.originY = 0.0;
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 7", FALSE);
    ok(ctxt.originX != 0.0 && ctxt.originX == floorf(ctxt.originX), "got %f\n", ctxt.originX);

    IDWriteTextLayout_Release(layout);

    ctxt.gdicompat = TRUE;
    ctxt.use_gdi_natural = FALSE;

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 8", FALSE);
    IDWriteTextLayout_Release(layout);

    ctxt.gdicompat = TRUE;
    ctxt.use_gdi_natural = TRUE;

    m.m11 = m.m22 = 2.0;
    m.m12 = m.m21 = m.dx = m.dy = 0.0;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, &m, TRUE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 9", FALSE);
    IDWriteTextLayout_Release(layout);

    ctxt.gdicompat = TRUE;
    ctxt.use_gdi_natural = FALSE;

    m.m11 = m.m22 = 2.0;
    m.m12 = m.m21 = m.dx = m.dy = 0.0;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, &m, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 10", FALSE);
    IDWriteTextLayout_Release(layout);

    IDWriteInlineObject_Release(inlineobj);

    /* text that triggers bidi run reordering */
    hr = IDWriteFactory_CreateTextLayout(factory, str3W, lstrlenW(str3W), format, 1000.0f, 100.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ctxt.gdicompat = FALSE;
    ctxt.use_gdi_natural = FALSE;
    ctxt.snapping_disabled = TRUE;

    hr = IDWriteTextLayout_SetReadingDirection(layout, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK, "Failed to set reading direction, hr %#x.\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_ltr_reordered_run_seq, "draw test 11", FALSE);

    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_typography(void)
{
    DWRITE_FONT_FEATURE feature;
    IDWriteTypography *typography;
    IDWriteFactory *factory;
    UINT32 count;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTypography(factory, &typography);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    feature.nameTag = DWRITE_FONT_FEATURE_TAG_KERNING;
    feature.parameter = 1;
    hr = IDWriteTypography_AddFontFeature(typography, feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = IDWriteTypography_GetFontFeatureCount(typography);
    ok(count == 1, "got %u\n", count);

    /* duplicated features work just fine */
    feature.nameTag = DWRITE_FONT_FEATURE_TAG_KERNING;
    feature.parameter = 0;
    hr = IDWriteTypography_AddFontFeature(typography, feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = IDWriteTypography_GetFontFeatureCount(typography);
    ok(count == 2, "got %u\n", count);

    memset(&feature, 0xcc, sizeof(feature));
    hr = IDWriteTypography_GetFontFeature(typography, 0, &feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(feature.nameTag == DWRITE_FONT_FEATURE_TAG_KERNING, "got tag %x\n", feature.nameTag);
    ok(feature.parameter == 1, "got %u\n", feature.parameter);

    memset(&feature, 0xcc, sizeof(feature));
    hr = IDWriteTypography_GetFontFeature(typography, 1, &feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(feature.nameTag == DWRITE_FONT_FEATURE_TAG_KERNING, "got tag %x\n", feature.nameTag);
    ok(feature.parameter == 0, "got %u\n", feature.parameter);

    hr = IDWriteTypography_GetFontFeature(typography, 2, &feature);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* duplicated with same parameter value */
    feature.nameTag = DWRITE_FONT_FEATURE_TAG_KERNING;
    feature.parameter = 0;
    hr = IDWriteTypography_AddFontFeature(typography, feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = IDWriteTypography_GetFontFeatureCount(typography);
    ok(count == 3, "got %u\n", count);

    memset(&feature, 0xcc, sizeof(feature));
    hr = IDWriteTypography_GetFontFeature(typography, 2, &feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(feature.nameTag == DWRITE_FONT_FEATURE_TAG_KERNING, "got tag %x\n", feature.nameTag);
    ok(feature.parameter == 0, "got %u\n", feature.parameter);

    IDWriteTypography_Release(typography);
    IDWriteFactory_Release(factory);
}

static void test_GetClusterMetrics(void)
{
    static const WCHAR str_white_spaceW[] = {
        /* BK - FORM FEED, LINE TABULATION, LINE SEP, PARA SEP */ 0xc, 0xb, 0x2028, 0x2029,
        /* ZW - ZERO WIDTH SPACE */ 0x200b,
        /* SP - SPACE  */ 0x20
    };
    static const WCHAR str5W[] = {'a','\r','b','\n','c','\n','\r','d','\r','\n','e',0xb,'f',0xc,
        'g',0x0085,'h',0x2028,'i',0x2029,0xad,0xa,0};
    static const WCHAR str3W[] = {0x2066,')',')',0x661,'(',0x627,')',0};
    static const WCHAR str2W[] = {0x202a,0x202c,'a',0};
    static const WCHAR strW[] = {'a','b','c','d',0};
    static const WCHAR str4W[] = {'a',' ',0};
    static const WCHAR str6W[] = {'a',' ','b',0};
    DWRITE_INLINE_OBJECT_METRICS inline_metrics;
    DWRITE_CLUSTER_METRICS metrics[22];
    DWRITE_TEXT_METRICS text_metrics;
    DWRITE_TRIMMING trimming_options;
    IDWriteTextLayout1 *layout1;
    IDWriteInlineObject *trimm;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_LINE_METRICS line;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    UINT32 count, i;
    FLOAT width;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, str3W, 7, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    hr = IDWriteTextLayout_GetClusterMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 7, "got %u\n", count);
    IDWriteTextLayout_Release(layout);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);

    /* check every cluster width */
    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);
    for (i = 0; i < count; i++) {
        ok(metrics[i].width > 0.0, "%u: got width %.2f\n", i, metrics[i].width);
        ok(metrics[i].length == 1, "%u: got length %u\n", i, metrics[i].length);
    }

    /* apply spacing and check widths again */
    if (IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout1, (void**)&layout1) == S_OK) {
        DWRITE_CLUSTER_METRICS metrics2[4];
        FLOAT leading, trailing, min_advance;
        DWRITE_TEXT_RANGE r;

        leading = trailing = min_advance = 2.0;
        hr = IDWriteTextLayout1_GetCharacterSpacing(layout1, 500, &leading, &trailing,
            &min_advance, &r);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(leading == 0.0 && trailing == 0.0 && min_advance == 0.0,
            "got %.2f, %.2f, %.2f\n", leading, trailing, min_advance);
        ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);

        leading = trailing = min_advance = 2.0;
        hr = IDWriteTextLayout1_GetCharacterSpacing(layout1, 0, &leading, &trailing,
            &min_advance, NULL);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(leading == 0.0 && trailing == 0.0 && min_advance == 0.0,
            "got %.2f, %.2f, %.2f\n", leading, trailing, min_advance);

        r.startPosition = 0;
        r.length = 4;
        hr = IDWriteTextLayout1_SetCharacterSpacing(layout1, 10.0, 15.0, 0.0, r);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        count = 0;
        hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics2, ARRAY_SIZE(metrics2), &count);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(count == 4, "got %u\n", count);
        for (i = 0; i < count; i++) {
todo_wine
            ok(metrics2[i].width > metrics[i].width, "%u: got width %.2f, was %.2f\n", i, metrics2[i].width,
                metrics[i].width);
            ok(metrics2[i].length == 1, "%u: got length %u\n", i, metrics2[i].length);
        }

        /* back to defaults */
        r.startPosition = 0;
        r.length = 4;
        hr = IDWriteTextLayout1_SetCharacterSpacing(layout1, 0.0, 0.0, 0.0, r);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        /* negative advance limit */
        r.startPosition = 0;
        r.length = 4;
        hr = IDWriteTextLayout1_SetCharacterSpacing(layout1, 0.0, 0.0, -10.0, r);
        ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

        IDWriteTextLayout1_Release(layout1);
    }
    else
        win_skip("IDWriteTextLayout1 is not supported, cluster spacing test skipped.\n");

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &trimm);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetInlineObject(layout, trimm, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* inline object takes a separate cluster, replaced codepoints number doesn't matter */
    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 3, "got %u\n", count);

    count = 0;
    memset(&metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 3, "got %u\n", count);
    ok(metrics[0].length == 2, "got %u\n", metrics[0].length);

    hr = IDWriteInlineObject_GetMetrics(trimm, &inline_metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inline_metrics.width > 0.0 && inline_metrics.width == metrics[0].width, "got %.2f, expected %.2f\n",
        inline_metrics.width, metrics[0].width);

    IDWriteTextLayout_Release(layout);

    /* text with non-visual control codes */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 3, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* bidi control codes take a separate cluster */
    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 3, "got %u\n", count);

    ok(metrics[0].width == 0.0, "got %.2f\n", metrics[0].width);
    ok(metrics[0].length == 1, "got %d\n", metrics[0].length);
    ok(metrics[0].canWrapLineAfter == 0, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[0].isWhitespace == 0, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[0].isNewline == 0, "got %d\n", metrics[0].isNewline);
    ok(metrics[0].isSoftHyphen == 0, "got %d\n", metrics[0].isSoftHyphen);
    ok(metrics[0].isRightToLeft == 0, "got %d\n", metrics[0].isRightToLeft);

    ok(metrics[1].width == 0.0, "got %.2f\n", metrics[1].width);
    ok(metrics[1].length == 1, "got %d\n", metrics[1].length);
    ok(metrics[1].canWrapLineAfter == 0, "got %d\n", metrics[1].canWrapLineAfter);
    ok(metrics[1].isWhitespace == 0, "got %d\n", metrics[1].isWhitespace);
    ok(metrics[1].isNewline == 0, "got %d\n", metrics[1].isNewline);
    ok(metrics[1].isSoftHyphen == 0, "got %d\n", metrics[1].isSoftHyphen);
    ok(metrics[1].isRightToLeft == 0, "got %d\n", metrics[1].isRightToLeft);

    ok(metrics[2].width > 0.0, "got %.2f\n", metrics[2].width);
    ok(metrics[2].length == 1, "got %d\n", metrics[2].length);
    ok(metrics[2].canWrapLineAfter == 1, "got %d\n", metrics[2].canWrapLineAfter);
    ok(metrics[2].isWhitespace == 0, "got %d\n", metrics[2].isWhitespace);
    ok(metrics[2].isNewline == 0, "got %d\n", metrics[2].isNewline);
    ok(metrics[2].isSoftHyphen == 0, "got %d\n", metrics[2].isSoftHyphen);
    ok(metrics[2].isRightToLeft == 0, "got %d\n", metrics[2].isRightToLeft);

    IDWriteTextLayout_Release(layout);

    /* single inline object that fails to report its metrics */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 4;
    hr = IDWriteTextLayout_SetInlineObject(layout, &testinlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    /* object sets a width to 123.0, but returns failure from GetMetrics() */
    ok(metrics[0].width == 0.0, "got %.2f\n", metrics[0].width);
    ok(metrics[0].length == 4, "got %d\n", metrics[0].length);
    ok(metrics[0].canWrapLineAfter == 1, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[0].isWhitespace == 0, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[0].isNewline == 0, "got %d\n", metrics[0].isNewline);
    ok(metrics[0].isSoftHyphen == 0, "got %d\n", metrics[0].isSoftHyphen);
    ok(metrics[0].isRightToLeft == 0, "got %d\n", metrics[0].isRightToLeft);

    /* now set two inline object for [0,1] and [2,3], both fail to report break conditions */
    range.startPosition = 2;
    range.length = 2;
    hr = IDWriteTextLayout_SetInlineObject(layout, &testinlineobj2, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);

    ok(metrics[0].width == 0.0, "got %.2f\n", metrics[0].width);
    ok(metrics[0].length == 2, "got %d\n", metrics[0].length);
    ok(metrics[0].canWrapLineAfter == 0, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[0].isWhitespace == 0, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[0].isNewline == 0, "got %d\n", metrics[0].isNewline);
    ok(metrics[0].isSoftHyphen == 0, "got %d\n", metrics[0].isSoftHyphen);
    ok(metrics[0].isRightToLeft == 0, "got %d\n", metrics[0].isRightToLeft);

    ok(metrics[1].width == 0.0, "got %.2f\n", metrics[1].width);
    ok(metrics[1].length == 2, "got %d\n", metrics[1].length);
    ok(metrics[1].canWrapLineAfter == 1, "got %d\n", metrics[1].canWrapLineAfter);
    ok(metrics[1].isWhitespace == 0, "got %d\n", metrics[1].isWhitespace);
    ok(metrics[1].isNewline == 0, "got %d\n", metrics[1].isNewline);
    ok(metrics[1].isSoftHyphen == 0, "got %d\n", metrics[1].isSoftHyphen);
    ok(metrics[1].isRightToLeft == 0, "got %d\n", metrics[1].isRightToLeft);

    IDWriteTextLayout_Release(layout);

    /* zero length string */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 1;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 0, "got %u\n", count);
    IDWriteTextLayout_Release(layout);

    /* whitespace */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 2, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 2, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    ok(metrics[0].isWhitespace == 0, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[0].canWrapLineAfter == 0, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[1].isWhitespace == 1, "got %d\n", metrics[1].isWhitespace);
    ok(metrics[1].canWrapLineAfter == 1, "got %d\n", metrics[1].canWrapLineAfter);
    IDWriteTextLayout_Release(layout);

    /* layout is fully covered by inline object with after condition DWRITE_BREAK_CONDITION_MAY_NOT_BREAK */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 2, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = ~0u;
    hr = IDWriteTextLayout_SetInlineObject(layout, &testinlineobj3, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 2, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[0].canWrapLineAfter == 1, "got %d\n", metrics[0].canWrapLineAfter);

    IDWriteTextLayout_Release(layout);

    /* compare natural cluster width with gdi layout */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 1, format, 100.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[0].width != floorf(metrics[0].width), "got %f\n", metrics[0].width);

    IDWriteTextLayout_Release(layout);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, str4W, 1, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[0].width == floorf(metrics[0].width), "got %f\n", metrics[0].width);

    IDWriteTextLayout_Release(layout);

    /* isNewline tests */
    hr = IDWriteFactory_CreateTextLayout(factory, str5W, lstrlenW(str5W), format, 100.0f, 200.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 22, "got %u\n", count);

    ok(metrics[1].isNewline == 1, "got %d\n", metrics[1].isNewline);
    ok(metrics[3].isNewline == 1, "got %d\n", metrics[3].isNewline);
    ok(metrics[5].isNewline == 1, "got %d\n", metrics[5].isNewline);
    ok(metrics[6].isNewline == 1, "got %d\n", metrics[6].isNewline);
    ok(metrics[9].isNewline == 1, "got %d\n", metrics[9].isNewline);
    ok(metrics[11].isNewline == 1, "got %d\n", metrics[11].isNewline);
    ok(metrics[13].isNewline == 1, "got %d\n", metrics[13].isNewline);
    ok(metrics[15].isNewline == 1, "got %d\n", metrics[15].isNewline);
    ok(metrics[17].isNewline == 1, "got %d\n", metrics[17].isNewline);
    ok(metrics[19].isNewline == 1, "got %d\n", metrics[19].isNewline);
    ok(metrics[21].isNewline == 1, "got %d\n", metrics[21].isNewline);

    ok(metrics[0].isNewline == 0, "got %d\n", metrics[0].isNewline);
    ok(metrics[2].isNewline == 0, "got %d\n", metrics[2].isNewline);
    ok(metrics[4].isNewline == 0, "got %d\n", metrics[4].isNewline);
    ok(metrics[7].isNewline == 0, "got %d\n", metrics[7].isNewline);
    ok(metrics[8].isNewline == 0, "got %d\n", metrics[8].isNewline);
    ok(metrics[10].isNewline == 0, "got %d\n", metrics[10].isNewline);
    ok(metrics[12].isNewline == 0, "got %d\n", metrics[12].isNewline);
    ok(metrics[14].isNewline == 0, "got %d\n", metrics[14].isNewline);
    ok(metrics[16].isNewline == 0, "got %d\n", metrics[16].isNewline);
    ok(metrics[18].isNewline == 0, "got %d\n", metrics[18].isNewline);
    ok(metrics[20].isNewline == 0, "got %d\n", metrics[20].isNewline);

    for (i = 0; i < count; i++) {
        ok(metrics[i].length == 1, "%d: got %d\n", i, metrics[i].length);
        ok(metrics[i].isSoftHyphen == (i == count - 2), "%d: got %d\n", i, metrics[i].isSoftHyphen);
        if (metrics[i].isSoftHyphen)
            ok(!metrics[i].isWhitespace, "%u: got %d\n", i, metrics[i].isWhitespace);
        if (metrics[i].isNewline) {
            ok(metrics[i].width == 0.0f, "%u: got width %f\n", i, metrics[i].width);
            ok(metrics[i].isWhitespace == 1, "%u: got %d\n", i, metrics[i].isWhitespace);
            ok(metrics[i].canWrapLineAfter == 1, "%u: got %d\n", i, metrics[i].canWrapLineAfter);
        }
    }

    IDWriteTextLayout_Release(layout);

    /* Test whitespace resolution from linebreaking classes BK, ZW, and SP */
    hr = IDWriteFactory_CreateTextLayout(factory, str_white_spaceW, ARRAY_SIZE(str_white_spaceW), format,
        100.0f, 200.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 20, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 6, "got %u\n", count);

    ok(metrics[0].isWhitespace == 1, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[1].isWhitespace == 1, "got %d\n", metrics[1].isWhitespace);
    ok(metrics[2].isWhitespace == 1, "got %d\n", metrics[2].isWhitespace);
    ok(metrics[3].isWhitespace == 1, "got %d\n", metrics[3].isWhitespace);
    ok(metrics[4].isWhitespace == 0, "got %d\n", metrics[4].isWhitespace);
    ok(metrics[5].isWhitespace == 1, "got %d\n", metrics[5].isWhitespace);

    IDWriteTextLayout_Release(layout);

    /* trigger line trimming */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, lstrlenW(strW), format, 100.0f, 200.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);

    hr = IDWriteTextLayout_GetMetrics(layout, &text_metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    width = metrics[0].width + inline_metrics.width;
    ok(width < text_metrics.width, "unexpected trimming sign width\n");

    /* enable trimming, reduce layout width so only first cluster and trimming sign fits */
    trimming_options.granularity = DWRITE_TRIMMING_GRANULARITY_CHARACTER;
    trimming_options.delimiter = 0;
    trimming_options.delimiterCount = 0;
    hr = IDWriteTextLayout_SetTrimming(layout, &trimming_options, trimm);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetMaxWidth(layout, width);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);

    hr = IDWriteTextLayout_GetLineMetrics(layout, &line, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(line.length == 4, "got %u\n", line.length);
    ok(line.isTrimmed, "got %d\n", line.isTrimmed);

    IDWriteTextLayout_Release(layout);

    /* NO_WRAP, check cluster wrapping attribute. */
    hr = IDWriteTextFormat_SetWordWrapping(format, DWRITE_WORD_WRAPPING_NO_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, str6W, lstrlenW(str6W), format, 1000.0f, 200.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 3, "got %u\n", count);

    ok(metrics[0].canWrapLineAfter == 0, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[1].canWrapLineAfter == 1, "got %d\n", metrics[1].canWrapLineAfter);
    ok(metrics[2].canWrapLineAfter == 1, "got %d\n", metrics[2].canWrapLineAfter);

    IDWriteTextLayout_Release(layout);

    /* Single cluster layout, trigger trimming. */
    hr = IDWriteFactory_CreateTextLayout(factory, str6W, 1, format, 1000.0f, 200.0f, &layout);
    ok(hr == S_OK, "Failed to create layout, hr %#x.\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(count == 1, "Unexpected cluster count %u.\n", count);

    hr = IDWriteTextLayout_SetMaxWidth(layout, metrics[0].width / 2.0f);
    ok(hr == S_OK, "Failed to set layout width, hr %#x.\n", hr);

    trimming_options.granularity = DWRITE_TRIMMING_GRANULARITY_CHARACTER;
    trimming_options.delimiter = 0;
    trimming_options.delimiterCount = 0;
    hr = IDWriteTextLayout_SetTrimming(layout, &trimming_options, trimm);
    ok(hr == S_OK, "Failed to set trimming options, hr %#x.\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(count == 1, "Unexpected cluster count %u.\n", count);

    hr = IDWriteTextLayout_GetLineMetrics(layout, &line, 1, &count);
    ok(hr == S_OK, "Failed to get line metrics, hr %#x.\n", hr);
    ok(count == 1, "Unexpected line count %u.\n", count);
    ok(line.length == 1, "Unexpected line length %u.\n", line.length);
    ok(line.isTrimmed, "Unexpected trimming flag %x.\n", line.isTrimmed);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "Draw() failed, hr %#x.\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_trimmed_seq, "Trimmed draw test", FALSE);

    IDWriteTextLayout_Release(layout);

    IDWriteInlineObject_Release(trimm);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetLocaleName(void)
{
    static const WCHAR eNuSW[] = {'e','N','-','u','S',0};
    static const WCHAR strW[] = {'a','b','c','d',0};
    WCHAR buffW[LOCALE_NAME_MAX_LENGTH + ARRAY_SIZE(strW)];
    IDWriteTextFormat *format, *format2;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    /* create format with mixed case locale name, get it back */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, eNuSW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_GetLocaleName(format, buffW, ARRAY_SIZE(buffW));
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat, (void**)&format2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_GetLocaleName(format2, buffW, ARRAY_SIZE(buffW));
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));

    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, ARRAY_SIZE(buffW), NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));

    IDWriteTextFormat_Release(format2);
    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetLocaleName(layout, enusW, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLocaleName(layout, NULL, range);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* invalid locale name is allowed */
    hr = IDWriteTextLayout_SetLocaleName(layout, strW, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetLocaleName(layout, 0, NULL, 0, NULL);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    if (0) /* crashes on native */
        hr = IDWriteTextLayout_GetLocaleName(layout, 0, NULL, 1, NULL);

    buffW[0] = 0;
    range.length = 0;
    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, ARRAY_SIZE(buffW), &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, strW), "got %s\n", wine_dbgstr_w(buffW));
    ok(range.startPosition == 0 && range.length == 1, "got %u,%u\n", range.startPosition, range.length);

    /* get with a shorter buffer */
    buffW[0] = 0xa;
    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, 1, NULL);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(buffW[0] == 0, "got %x\n", buffW[0]);

    /* name is too long */
    lstrcpyW(buffW, strW);
    while (lstrlenW(buffW) <= LOCALE_NAME_MAX_LENGTH)
        lstrcatW(buffW, strW);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetLocaleName(layout, buffW, range);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    buffW[0] = 0;
    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, ARRAY_SIZE(buffW), NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, strW), "got %s\n", wine_dbgstr_w(buffW));

    /* set initial locale name for whole text, except with a different casing */
    range.startPosition = 0;
    range.length = 4;
    hr = IDWriteTextLayout_SetLocaleName(layout, eNuSW, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    buffW[0] = 0;
    range.length = 0;
    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, ARRAY_SIZE(buffW), &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));
    ok((range.startPosition == 0 && range.length == ~0u) ||
        broken(range.startPosition == 0 && range.length == 4) /* vista/win7 */, "got %u,%u\n", range.startPosition, range.length);

    /* check what's returned for positions after the text */
    buffW[0] = 0;
    range.length = 0;
    hr = IDWriteTextLayout_GetLocaleName(layout, 100, buffW, ARRAY_SIZE(buffW), &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));
    ok((range.startPosition == 0 && range.length == ~0u) ||
        broken(range.startPosition == 4 && range.length == ~0u-4) /* vista/win7 */, "got %u,%u\n", range.startPosition, range.length);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetPairKerning(void)
{
    static const WCHAR strW[] = {'a','e',0x0300,'d',0}; /* accent grave */
    DWRITE_CLUSTER_METRICS clusters[4];
    IDWriteTextLayout1 *layout1;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    BOOL kerning;
    UINT32 count;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout1, (void**)&layout1);
    IDWriteTextLayout_Release(layout);

    if (hr != S_OK) {
        win_skip("SetPairKerning() is not supported.\n");
        IDWriteFactory_Release(factory);
        return;
    }

if (0) { /* crashes on native */
    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, NULL, NULL);
    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, NULL, &range);
}

    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, &kerning, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 0;
    kerning = TRUE;
    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, &kerning, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!kerning, "got %d\n", kerning);
    ok(range.length == ~0u, "got %u\n", range.length);

    count = 0;
    hr = IDWriteTextLayout1_GetClusterMetrics(layout1, clusters, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(count == 3, "got %u\n", count);
if (count == 3) {
    ok(clusters[0].length == 1, "got %u\n", clusters[0].length);
    ok(clusters[1].length == 2, "got %u\n", clusters[1].length);
    ok(clusters[2].length == 1, "got %u\n", clusters[2].length);
}
    /* pair kerning flag participates in itemization - combining characters
       breaks */
    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout1_SetPairKerning(layout1, 2, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    kerning = FALSE;
    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, &kerning, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(kerning == TRUE, "got %d\n", kerning);

    count = 0;
    hr = IDWriteTextLayout1_GetClusterMetrics(layout1, clusters, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);
    ok(clusters[0].length == 1, "got %u\n", clusters[0].length);
    ok(clusters[1].length == 1, "got %u\n", clusters[1].length);
    ok(clusters[2].length == 1, "got %u\n", clusters[2].length);
    ok(clusters[3].length == 1, "got %u\n", clusters[3].length);

    IDWriteTextLayout1_Release(layout1);
    IDWriteFactory_Release(factory);
}

static void test_SetVerticalGlyphOrientation(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    DWRITE_VERTICAL_GLYPH_ORIENTATION orientation;
    IDWriteTextLayout2 *layout2;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    IDWriteTextLayout_Release(layout);

    if (hr != S_OK) {
        win_skip("SetVerticalGlyphOrientation() is not supported.\n");
        IDWriteFactory_Release(factory);
        return;
    }

    orientation = IDWriteTextLayout2_GetVerticalGlyphOrientation(layout2);
    ok(orientation == DWRITE_VERTICAL_GLYPH_ORIENTATION_DEFAULT, "got %d\n", orientation);

    hr = IDWriteTextLayout2_SetVerticalGlyphOrientation(layout2, DWRITE_VERTICAL_GLYPH_ORIENTATION_STACKED+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    IDWriteTextLayout2_Release(layout2);
    IDWriteFactory_Release(factory);
}

static void test_DetermineMinWidth(void)
{
    struct minwidth_test {
        const WCHAR text[10];    /* text to create a layout for */
        const WCHAR mintext[10]; /* text that represents sequence of minimal width */
    } minwidth_tests[] = {
        { {' ','a','b',' ',0}, {'a','b',0} },
        { {'a','\n',' ',' ',0}, {'a',0} },
        { {'a','\n',' ',' ','b',0}, {'b',0} },
        { {'a','b','c','\n',' ',' ','b',0}, {'a','b','c',0} },
    };
    static const WCHAR strW[] = {'a','b','c','d',0};
    DWRITE_CLUSTER_METRICS metrics[10];
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    UINT32 count, i, j;
    FLOAT minwidth;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, lstrlenW(strW), format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_DetermineMinWidth(layout, NULL);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    /* empty string */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 100.0f, 100.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    minwidth = 1.0f;
    hr = IDWriteTextLayout_DetermineMinWidth(layout, &minwidth);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(minwidth == 0.0f, "got %f\n", minwidth);
    IDWriteTextLayout_Release(layout);

    for (i = 0; i < ARRAY_SIZE(minwidth_tests); i++) {
        FLOAT width = 0.0f;

        /* measure expected width */
        hr = IDWriteFactory_CreateTextLayout(factory, minwidth_tests[i].mintext, lstrlenW(minwidth_tests[i].mintext), format, 1000.0f, 1000.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        for (j = 0; j < count; j++)
            width += metrics[j].width;

        IDWriteTextLayout_Release(layout);

        hr = IDWriteFactory_CreateTextLayout(factory, minwidth_tests[i].text, lstrlenW(minwidth_tests[i].text), format, 1000.0f, 1000.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        minwidth = 0.0f;
        hr = IDWriteTextLayout_DetermineMinWidth(layout, &minwidth);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(minwidth == width, "test %u: expected width %f, got %f\n", i, width, minwidth);

        IDWriteTextLayout_Release(layout);
    }

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFontSize(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE r;
    FLOAT size;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* negative/zero size */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, -15.0, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetFontSize(layout, 0.0, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    size = 0.0;
    hr = IDWriteTextLayout_GetFontSize(layout, 0, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);
    ok(size == 10.0, "got %.2f\n", size);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 15.0, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* zero length range */
    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_SetFontSize(layout, 123.0, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = 0.0;
    hr = IDWriteTextLayout_GetFontSize(layout, 1, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(size == 15.0, "got %.2f\n", size);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontSize(layout, 15.0, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = 0.0;
    hr = IDWriteTextLayout_GetFontSize(layout, 1, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(size == 15.0, "got %.2f\n", size);

    size = 0.0;
    hr = IDWriteTextLayout_GetFontSize(layout, 0, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(size == 15.0, "got %.2f\n", size);

    size = 15.0;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontSize(layout, 20, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 4 && r.length == ~0u-4, "got %u, %u\n", r.startPosition, r.length);
    ok(size == 10.0, "got %.2f\n", size);

    r.startPosition = 100;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontSize(layout, 25.0, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = 15.0;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontSize(layout, 100, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(size == 25.0, "got %.2f\n", size);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFontFamilyName(void)
{
    static const WCHAR taHomaW[] = {'T','a','H','o','m','a',0};
    static const WCHAR arialW[] = {'A','r','i','a','l',0};
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE r;
    WCHAR nameW[50];
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* NULL name */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, NULL, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    nameW[0] = 0;
    hr = IDWriteTextLayout_GetFontFamilyName(layout, 1, nameW, ARRAY_SIZE(nameW), &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);

    /* set name only different in casing */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, taHomaW, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* zero length range */
    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, arialW, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 0;
    r.length = 0;
    nameW[0] = 0;
    hr = IDWriteTextLayout_GetFontFamilyName(layout, 1, nameW, ARRAY_SIZE(nameW), &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(nameW, taHomaW), "got %s\n", wine_dbgstr_w(nameW));
    ok(r.startPosition == 1 && r.length == 1, "got %u, %u\n", r.startPosition, r.length);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, arialW, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_GetFontFamilyName(layout, 1, nameW, ARRAY_SIZE(nameW), &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 1 && r.length == 1, "got %u, %u\n", r.startPosition, r.length);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, arialW, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    nameW[0] = 0;
    hr = IDWriteTextLayout_GetFontFamilyName(layout, 1, nameW, ARRAY_SIZE(nameW), &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(!lstrcmpW(nameW, arialW), "got name %s\n", wine_dbgstr_w(nameW));

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFontStyle(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_FONT_STYLE style;
    DWRITE_TEXT_RANGE r;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* invalid style value */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_ITALIC+1, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_GetFontStyle(layout, 0, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);
    ok(style == DWRITE_FONT_STYLE_NORMAL, "got %d\n", style);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_ITALIC, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* zero length range */
    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_NORMAL, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    style = DWRITE_FONT_STYLE_NORMAL;
    hr = IDWriteTextLayout_GetFontStyle(layout, 1, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(style == DWRITE_FONT_STYLE_ITALIC, "got %d\n", style);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_OBLIQUE, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    style = DWRITE_FONT_STYLE_ITALIC;
    hr = IDWriteTextLayout_GetFontStyle(layout, 1, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(style == DWRITE_FONT_STYLE_OBLIQUE, "got %d\n", style);

    style = DWRITE_FONT_STYLE_ITALIC;
    hr = IDWriteTextLayout_GetFontStyle(layout, 0, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(style == DWRITE_FONT_STYLE_OBLIQUE, "got %d\n", style);

    style = DWRITE_FONT_STYLE_ITALIC;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontStyle(layout, 20, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 4 && r.length == ~0u-4, "got %u, %u\n", r.startPosition, r.length);
    ok(style == DWRITE_FONT_STYLE_NORMAL, "got %d\n", style);

    r.startPosition = 100;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_OBLIQUE, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    style = DWRITE_FONT_STYLE_NORMAL;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontStyle(layout, 100, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(style == DWRITE_FONT_STYLE_OBLIQUE, "got %d\n", style);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFontStretch(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    DWRITE_FONT_STRETCH stretch;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE r;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* invalid stretch value */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_ULTRA_EXPANDED+1, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    hr = IDWriteTextLayout_GetFontStretch(layout, 0, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);
    ok(stretch == DWRITE_FONT_STRETCH_NORMAL, "got %d\n", stretch);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_CONDENSED, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* zero length range */
    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_NORMAL, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    hr = IDWriteTextLayout_GetFontStretch(layout, 1, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(stretch == DWRITE_FONT_STRETCH_CONDENSED, "got %d\n", stretch);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_EXPANDED, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    hr = IDWriteTextLayout_GetFontStretch(layout, 1, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(stretch == DWRITE_FONT_STRETCH_EXPANDED, "got %d\n", stretch);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    hr = IDWriteTextLayout_GetFontStretch(layout, 0, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(stretch == DWRITE_FONT_STRETCH_EXPANDED, "got %d\n", stretch);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontStretch(layout, 20, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 4 && r.length == ~0u-4, "got %u, %u\n", r.startPosition, r.length);
    ok(stretch == DWRITE_FONT_STRETCH_NORMAL, "got %d\n", stretch);

    r.startPosition = 100;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_EXPANDED, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontStretch(layout, 100, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(stretch == DWRITE_FONT_STRETCH_EXPANDED, "got %d\n", stretch);

    /* trying to set undefined value */
    r.startPosition = 0;
    r.length = 2;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_UNDEFINED, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetStrikethrough(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE r;
    BOOL value;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    value = TRUE;
    hr = IDWriteTextLayout_GetStrikethrough(layout, 0, &value, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);
    ok(value == FALSE, "got %d\n", value);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    value = FALSE;
    hr = IDWriteTextLayout_GetStrikethrough(layout, 1, &value, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(value == TRUE, "got %d\n", value);
    ok(r.startPosition == 1 && r.length == 1, "got %u, %u\n", r.startPosition, r.length);

    value = TRUE;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetStrikethrough(layout, 20, &value, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 2 && r.length == ~0u-2, "got %u, %u\n", r.startPosition, r.length);
    ok(value == FALSE, "got %d\n", value);

    r.startPosition = 100;
    r.length = 4;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    value = FALSE;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetStrikethrough(layout, 100, &value, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(value == TRUE, "got %d\n", value);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_GetMetrics(void)
{
    static const WCHAR str2W[] = {0x2066,')',')',0x661,'(',0x627,')',0};
    static const WCHAR strW[] = {'a','b','c','d',0};
    static const WCHAR str3W[] = {'a',0};
    static const WCHAR str4W[] = {' ',0};
    DWRITE_CLUSTER_METRICS clusters[4];
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    UINT32 count, i;
    FLOAT width;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);
    for (i = 0, width = 0.0; i < count; i++)
        width += clusters[i].width;

    memset(&metrics, 0xcc, sizeof(metrics));
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics.left == 0.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == width, "got %.2f, expected %.2f\n", metrics.width, width);
    ok(metrics.widthIncludingTrailingWhitespace == width, "got %.2f, expected %.2f\n",
        metrics.widthIncludingTrailingWhitespace, width);
    ok(metrics.height > 0.0, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 1000.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.maxBidiReorderingDepth == 1, "got %u\n", metrics.maxBidiReorderingDepth);
    ok(metrics.lineCount == 1, "got %u\n", metrics.lineCount);

    IDWriteTextLayout_Release(layout);

    /* a string with more complex bidi sequence */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 7, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(&metrics, 0xcc, sizeof(metrics));
    metrics.maxBidiReorderingDepth = 0;
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics.left == 0.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width > 0.0, "got %.2f\n", metrics.width);
    ok(metrics.widthIncludingTrailingWhitespace > 0.0, "got %.2f\n", metrics.widthIncludingTrailingWhitespace);
    ok(metrics.height > 0.0, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 1000.0, "got %.2f\n", metrics.layoutHeight);
todo_wine
    ok(metrics.maxBidiReorderingDepth > 1, "got %u\n", metrics.maxBidiReorderingDepth);
    ok(metrics.lineCount == 1, "got %u\n", metrics.lineCount);

    IDWriteTextLayout_Release(layout);

    /* single cluster layout */
    hr = IDWriteFactory_CreateTextLayout(factory, str3W, 1, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    memset(&metrics, 0xcc, sizeof(metrics));
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics.left == 0.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == clusters[0].width, "got %.2f, expected %.2f\n", metrics.width, clusters[0].width);
    ok(metrics.widthIncludingTrailingWhitespace == clusters[0].width, "got %.2f\n", metrics.widthIncludingTrailingWhitespace);
    ok(metrics.height > 0.0, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 1000.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.maxBidiReorderingDepth == 1, "got %u\n", metrics.maxBidiReorderingDepth);
    ok(metrics.lineCount == 1, "got %u\n", metrics.lineCount);
    IDWriteTextLayout_Release(layout);

    /* Whitespace only. */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 1, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "Failed to create text layout, hr %#x.\n", hr);

    memset(&metrics, 0xcc, sizeof(metrics));
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "Failed to get layout metrics, hr %#x.\n", hr);
    ok(metrics.left == 0.0f, "Unexpected value for left %f.\n", metrics.left);
    ok(metrics.top == 0.0f, "Unexpected value for top %f.\n", metrics.top);
    ok(metrics.width == 0.0f, "Unexpected width %f.\n", metrics.width);
    ok(metrics.widthIncludingTrailingWhitespace > 0.0f, "Unexpected full width %f.\n",
            metrics.widthIncludingTrailingWhitespace);
    ok(metrics.height > 0.0, "Unexpected height %f.\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "Unexpected box width %f.\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 1000.0, "Unexpected box height %f.\n", metrics.layoutHeight);
    ok(metrics.maxBidiReorderingDepth == 1, "Unexpected reordering depth %u.\n", metrics.maxBidiReorderingDepth);
    ok(metrics.lineCount == 1, "Unexpected line count %u.\n", metrics.lineCount);
    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFlowDirection(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    DWRITE_READING_DIRECTION reading;
    DWRITE_FLOW_DIRECTION flow;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flow = IDWriteTextFormat_GetFlowDirection(format);
    ok(flow == DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM, "got %d\n", flow);

    reading = IDWriteTextFormat_GetReadingDirection(format);
    ok(reading == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", reading);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    hr = IDWriteTextFormat_SetFlowDirection(format, DWRITE_FLOW_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK || broken(hr == E_INVALIDARG) /* vista,win7 */, "got 0x%08x\n", hr);
    if (hr == S_OK) {
        hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout_Release(layout);

        hr = IDWriteTextFormat_SetReadingDirection(format, DWRITE_READING_DIRECTION_TOP_TO_BOTTOM);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextFormat_SetFlowDirection(format, DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout_Release(layout);
    }
    else
        win_skip("DWRITE_FLOW_DIRECTION_LEFT_TO_RIGHT is not supported\n");

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static const struct drawcall_entry draweffect_seq[] = {
    { DRAW_GLYPHRUN|DRAW_EFFECT, {'a','e',0x0300,0}, {'e','n','-','u','s',0}, 2 },
    { DRAW_GLYPHRUN, {'d',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draweffect2_seq[] = {
    { DRAW_GLYPHRUN|DRAW_EFFECT, {'a','e',0}, {'e','n','-','u','s',0}, 2 },
    { DRAW_GLYPHRUN, {'c','d',0}, {'e','n','-','u','s',0}, 2 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draweffect3_seq[] = {
    { DRAW_INLINE|DRAW_EFFECT },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draweffect4_seq[] = {
    { DRAW_INLINE },
    { DRAW_LAST_KIND }
};

static void test_SetDrawingEffect(void)
{
    static const WCHAR strW[] = {'a','e',0x0300,'d',0}; /* accent grave */
    static const WCHAR str2W[] = {'a','e','c','d',0};
    IDWriteInlineObject *sign;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    IUnknown *unk, *effect;
    DWRITE_TEXT_RANGE r;
    HRESULT hr;
    LONG ref;

    factory = create_factory();

    effect = create_test_effect();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* string with combining mark */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* set effect past the end of text */
    r.startPosition = 100;
    r.length = 10;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetDrawingEffect(layout, 101, &unk, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 10, "got %u, %u\n", r.startPosition, r.length);
    IUnknown_Release(unk);

    r.startPosition = r.length = 0;
    unk = (void*)0xdeadbeef;
    hr = IDWriteTextLayout_GetDrawingEffect(layout, 1000, &unk, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 110 && r.length == ~0u-110, "got %u, %u\n", r.startPosition, r.length);
    ok(unk == NULL, "got %p\n", unk);

    /* effect is applied to clusters, not individual text positions */
    r.startPosition = 0;
    r.length = 2;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect_seq, "effect draw test", TRUE);
    IDWriteTextLayout_Release(layout);

    /* simple string */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 0;
    r.length = 2;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect2_seq, "effect draw test 2", FALSE);
    IDWriteTextLayout_Release(layout);

    /* Inline object - effect set for same range */
    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &sign);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetInlineObject(layout, sign, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect3_seq, "effect draw test 3", FALSE);

    /* now set effect somewhere inside a range replaced by inline object */
    hr = IDWriteTextLayout_SetDrawingEffect(layout, NULL, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* no effect is reported in this case */
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect4_seq, "effect draw test 4", FALSE);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, NULL, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 0;
    r.length = 1;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* first range position is all that matters for inline ranges */
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect3_seq, "effect draw test 5", FALSE);

    IDWriteTextLayout_Release(layout);

    ref = IUnknown_Release(effect);
    ok(ref == 0, "Unexpected effect refcount %u\n", ref);
    IDWriteInlineObject_Release(sign);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static BOOL get_enus_string(IDWriteLocalizedStrings *strings, WCHAR *buff, UINT32 size)
{
    UINT32 index;
    BOOL exists = FALSE;
    HRESULT hr;

    hr = IDWriteLocalizedStrings_FindLocaleName(strings, enusW, &index, &exists);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    if (exists) {
        hr = IDWriteLocalizedStrings_GetString(strings, index, buff, size);
        ok(hr == S_OK, "got 0x%08x\n", hr);
    }
    else
        *buff = 0;

    return exists;
}

static void test_GetLineMetrics(void)
{
    static const WCHAR str3W[] = {'a','\r','b','\n','c','\n','\r','d','\r','\n',0};
    static const WCHAR strW[] = {'a','b','c','d',' ',0};
    static const WCHAR str2W[] = {'a','b','\r','c','d',0};
    static const WCHAR str4W[] = {'a','\r',0};
    static const WCHAR emptyW[] = {0};
    IDWriteFontCollection *syscollection;
    DWRITE_FONT_METRICS fontmetrics;
    DWRITE_LINE_METRICS metrics[6];
    UINT32 count, i, familycount;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFontFace *fontface;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE range;
    WCHAR nameW[256];
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 2048.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 5, format, 30000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 1, "got count %u\n", count);

    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics[0].length == 5, "got %u\n", metrics[0].length);
    ok(metrics[0].trailingWhitespaceLength == 1, "got %u\n", metrics[0].trailingWhitespaceLength);

    ok(metrics[0].newlineLength == 0, "got %u\n", metrics[0].newlineLength);
    ok(metrics[0].isTrimmed == FALSE, "got %d\n", metrics[0].isTrimmed);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);

    /* Test line height and baseline calculation */
    hr = IDWriteFactory_GetSystemFontCollection(factory, &syscollection, FALSE);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    familycount = IDWriteFontCollection_GetFontFamilyCount(syscollection);

    for (i = 0; i < familycount; i++) {
        IDWriteLocalizedStrings *names;
        IDWriteFontFamily *family;
        IDWriteFont *font;
        BOOL exists;

        format = NULL;
        layout = NULL;

        hr = IDWriteFontCollection_GetFontFamily(syscollection, i, &family);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontFamily_GetFirstMatchingFont(family, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
            DWRITE_FONT_STYLE_NORMAL, &font);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFont_CreateFontFace(font, &fontface);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontFamily_GetFamilyNames(family, &names);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        if (!(exists = get_enus_string(names, nameW, ARRAY_SIZE(nameW)))) {
            IDWriteLocalFontFileLoader *localloader;
            IDWriteFontFileLoader *loader;
            IDWriteFontFile *file;
            const void *key;
            UINT32 keysize;
            UINT32 count;

            count = 1;
            hr = IDWriteFontFace_GetFiles(fontface, &count, &file);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFontFile_GetLoader(file, &loader);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFontFileLoader_QueryInterface(loader, &IID_IDWriteLocalFontFileLoader, (void**)&localloader);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            IDWriteFontFileLoader_Release(loader);

            hr = IDWriteFontFile_GetReferenceKey(file, &key, &keysize);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteLocalFontFileLoader_GetFilePathFromKey(localloader, key, keysize, nameW, ARRAY_SIZE(nameW));
            ok(hr == S_OK, "got 0x%08x\n", hr);

            skip("Failed to get English family name, font file %s\n", wine_dbgstr_w(nameW));

            IDWriteLocalFontFileLoader_Release(localloader);
            IDWriteFontFile_Release(file);
        }

        IDWriteLocalizedStrings_Release(names);
        IDWriteFont_Release(font);

        if (!exists)
            goto cleanup;

        IDWriteFontFace_GetMetrics(fontface, &fontmetrics);
        hr = IDWriteFactory_CreateTextFormat(factory, nameW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL, fontmetrics.designUnitsPerEm, enusW, &format);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, emptyW, 1, format, 30000.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        memset(metrics, 0, sizeof(metrics));
        count = 0;
        hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(count == 1, "got %u\n", count);

        ok(metrics[0].baseline == fontmetrics.ascent + fontmetrics.lineGap, "%s: got %.2f, expected %d, "
            "linegap %d\n", wine_dbgstr_w(nameW), metrics[0].baseline, fontmetrics.ascent + fontmetrics.lineGap,
            fontmetrics.lineGap);
        ok(metrics[0].height == fontmetrics.ascent + fontmetrics.descent + fontmetrics.lineGap,
            "%s: got %.2f, expected %d, linegap %d\n", wine_dbgstr_w(nameW), metrics[0].height,
            fontmetrics.ascent + fontmetrics.descent + fontmetrics.lineGap, fontmetrics.lineGap);

    cleanup:
        if (layout)
            IDWriteTextLayout_Release(layout);
        if (format)
            IDWriteTextFormat_Release(format);
        IDWriteFontFace_Release(fontface);
        IDWriteFontFamily_Release(family);
    }
    IDWriteFontCollection_Release(syscollection);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 2048.0f, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fontface = get_fontface_from_format(format);
    ok(fontface != NULL, "got %p\n", fontface);

    /* force 2 lines */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 5, format, 10000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(metrics, 0, sizeof(metrics));
    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    /* baseline is relative to a line, and is not accumulated */
    ok(metrics[0].baseline == metrics[1].baseline, "got %.2f, %.2f\n", metrics[0].baseline,
        metrics[1].baseline);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);

    /* line breaks */
    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);

    hr = IDWriteFactory_CreateTextLayout(factory, str3W, 10, format, 100.0, 300.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(metrics, 0xcc, sizeof(metrics));
    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 6, "got %u\n", count);

    ok(metrics[0].length == 2, "got %u\n", metrics[0].length);
    ok(metrics[1].length == 2, "got %u\n", metrics[1].length);
    ok(metrics[2].length == 2, "got %u\n", metrics[2].length);
    ok(metrics[3].length == 1, "got %u\n", metrics[3].length);
    ok(metrics[4].length == 3, "got %u\n", metrics[4].length);
    ok(metrics[5].length == 0, "got %u\n", metrics[5].length);

    ok(metrics[0].newlineLength == 1, "got %u\n", metrics[0].newlineLength);
    ok(metrics[1].newlineLength == 1, "got %u\n", metrics[1].newlineLength);
    ok(metrics[2].newlineLength == 1, "got %u\n", metrics[2].newlineLength);
    ok(metrics[3].newlineLength == 1, "got %u\n", metrics[3].newlineLength);
    ok(metrics[4].newlineLength == 2, "got %u\n", metrics[4].newlineLength);
    ok(metrics[5].newlineLength == 0, "got %u\n", metrics[5].newlineLength);

    ok(metrics[0].trailingWhitespaceLength == 1, "got %u\n", metrics[0].newlineLength);
    ok(metrics[1].trailingWhitespaceLength == 1, "got %u\n", metrics[1].newlineLength);
    ok(metrics[2].trailingWhitespaceLength == 1, "got %u\n", metrics[2].newlineLength);
    ok(metrics[3].trailingWhitespaceLength == 1, "got %u\n", metrics[3].newlineLength);
    ok(metrics[4].trailingWhitespaceLength == 2, "got %u\n", metrics[4].newlineLength);
    ok(metrics[5].trailingWhitespaceLength == 0, "got %u\n", metrics[5].newlineLength);

    IDWriteTextLayout_Release(layout);

    /* empty text layout */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 100.0f, 300.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[0].length == 0, "got %u\n", metrics[0].length);
    ok(metrics[0].trailingWhitespaceLength == 0, "got %u\n", metrics[0].trailingWhitespaceLength);
    ok(metrics[0].newlineLength == 0, "got %u\n", metrics[0].newlineLength);
    ok(metrics[0].height > 0.0f, "got %f\n", metrics[0].height);
    ok(metrics[0].baseline > 0.0f, "got %f\n", metrics[0].baseline);
    ok(!metrics[0].isTrimmed, "got %d\n", metrics[0].isTrimmed);

    /* change font size at first position, see if metrics changed */
    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 80.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 1, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[1].height > metrics[0].height, "got %f\n", metrics[1].height);
    ok(metrics[1].baseline > metrics[0].baseline, "got %f\n", metrics[1].baseline);

    /* revert font size back to format value, set different size for position 1 */
    hr = IDWriteTextLayout_SetFontSize(layout, 12.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 80.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(metrics + 1, 0, sizeof(*metrics));
    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 1, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[1].height == metrics[0].height, "got %f\n", metrics[1].height);
    ok(metrics[1].baseline == metrics[0].baseline, "got %f\n", metrics[1].baseline);

    IDWriteTextLayout_Release(layout);

    /* text is "a\r" */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 2, format, 100.0f, 300.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    ok(metrics[0].length == 2, "got %u\n", metrics[0].length);
    ok(metrics[0].newlineLength == 1, "got %u\n", metrics[0].newlineLength);
    ok(metrics[0].height > 0.0f, "got %f\n", metrics[0].height);
    ok(metrics[0].baseline > 0.0f, "got %f\n", metrics[0].baseline);
    ok(metrics[1].length == 0, "got %u\n", metrics[1].length);
    ok(metrics[1].newlineLength == 0, "got %u\n", metrics[1].newlineLength);
    ok(metrics[1].height > 0.0f, "got %f\n", metrics[1].height);
    ok(metrics[1].baseline > 0.0f, "got %f\n", metrics[1].baseline);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 80.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 2, 2, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    ok(metrics[3].height > metrics[1].height, "got %f, old %f\n", metrics[3].height, metrics[1].height);
    ok(metrics[3].baseline > metrics[1].baseline, "got %f, old %f\n", metrics[3].baseline, metrics[1].baseline);

    /* revert to original format */
    hr = IDWriteTextLayout_SetFontSize(layout, 12.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 2, 2, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    ok(metrics[3].height == metrics[1].height, "got %f, old %f\n", metrics[3].height, metrics[1].height);
    ok(metrics[3].baseline == metrics[1].baseline, "got %f, old %f\n", metrics[3].baseline, metrics[1].baseline);

    /* Switch to uniform spacing */
    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_UNIFORM, 456.0f, 123.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);

    for (i = 0; i < count; i++) {
        ok(metrics[i].height == 456.0f, "%u: got line height %f\n", i, metrics[i].height);
        ok(metrics[i].baseline == 123.0f, "%u: got line baseline %f\n", i, metrics[i].baseline);
    }

    IDWriteTextLayout_Release(layout);

    /* Switch to proportional */
    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_PROPORTIONAL, 2.0f, 4.0f);
    if (hr == S_OK) {
        hr = IDWriteFactory_CreateTextLayout(factory, str4W, 1, format, 100.0f, 300.0f, &layout);
        ok(hr == S_OK, "Failed to create layout, hr %#x.\n", hr);

        hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
        ok(hr == S_OK, "Failed to get line metrics, hr %#x.\n", hr);
        ok(count == 1, "Unexpected line count %u\n", count);

        /* Back to default mode. */
        hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, 0.0f);
        ok(hr == S_OK, "Failed to set spacing method, hr %#x.\n", hr);

        hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 1, 1, &count);
        ok(hr == S_OK, "Failed to get line metrics, hr %#x.\n", hr);
        ok(count == 1, "Unexpected line count %u\n", count);

        /* Proportional spacing applies multipliers to default, content based spacing. */
        ok(metrics[0].height == 2.0f * metrics[1].height, "Unexpected line height %f.\n", metrics[0].height);
        ok(metrics[0].baseline == 4.0f * metrics[1].baseline, "Unexpected line baseline %f.\n", metrics[0].baseline);

        IDWriteTextLayout_Release(layout);
    }
    else
        win_skip("Proportional spacing is not supported.\n");

    IDWriteTextFormat_Release(format);
    IDWriteFontFace_Release(fontface);
    IDWriteFactory_Release(factory);
}

static void test_SetTextAlignment(void)
{
    static const WCHAR strW[] = {'a',0};

    static const WCHAR stringsW[][10] = {
        {'a',0},
        {0}
    };

    DWRITE_CLUSTER_METRICS clusters[10];
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextFormat1 *format1;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_ALIGNMENT v;
    UINT32 count, i;
    HRESULT hr;

    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);

    v = IDWriteTextFormat_GetTextAlignment(format);
    ok(v == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", v);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetTextAlignment(layout);
    ok(v == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", v);

    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_TRAILING);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_TRAILING);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetTextAlignment(format);
    ok(v == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", v);

    v = IDWriteTextLayout_GetTextAlignment(layout);
    ok(v == DWRITE_TEXT_ALIGNMENT_TRAILING, "got %d\n", v);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat1, (void**)&format1);
    if (hr == S_OK) {
        hr = IDWriteTextFormat1_SetTextAlignment(format1, DWRITE_TEXT_ALIGNMENT_CENTER);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        v = IDWriteTextFormat_GetTextAlignment(format);
        ok(v == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", v);

        v = IDWriteTextLayout_GetTextAlignment(layout);
        ok(v == DWRITE_TEXT_ALIGNMENT_CENTER, "got %d\n", v);

        v = IDWriteTextFormat1_GetTextAlignment(format1);
        ok(v == DWRITE_TEXT_ALIGNMENT_CENTER, "got %d\n", v);

        IDWriteTextFormat1_Release(format1);
    }
    else
        win_skip("IDWriteTextFormat1 is not supported\n");

    IDWriteTextLayout_Release(layout);

    for (i = 0; i < ARRAY_SIZE(stringsW); i++) {
        FLOAT text_width;

        hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_LEADING);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, stringsW[i], lstrlenW(stringsW[i]), format, 500.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_NO_WRAP);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        count = 0;
        hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, ARRAY_SIZE(clusters), &count);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        if (stringsW[i][0])
            ok(count > 0, "got %u\n", count);
        else
            ok(count == 0, "got %u\n", count);

        text_width = 0.0f;
        while (count)
            text_width += clusters[--count].width;

        /* maxwidth is 500, leading alignment */
        hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_LEADING);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        ok(metrics.left == 0.0f, "got %.2f\n", metrics.left);
        ok(metrics.width == text_width, "got %.2f\n", metrics.width);
        ok(metrics.layoutWidth == 500.0f, "got %.2f\n", metrics.layoutWidth);
        ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

        /* maxwidth is 500, trailing alignment */
        hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_TRAILING);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        ok(metrics.left == metrics.layoutWidth - metrics.width, "got %.2f\n", metrics.left);
        ok(metrics.width == text_width, "got %.2f\n", metrics.width);
        ok(metrics.layoutWidth == 500.0f, "got %.2f\n", metrics.layoutWidth);
        ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
        IDWriteTextLayout_Release(layout);

        /* initially created with trailing alignment */
        hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_TRAILING);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, stringsW[i], lstrlenW(stringsW[i]), format, 500.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        ok(metrics.left == metrics.layoutWidth - metrics.width, "got %.2f\n", metrics.left);
        ok(metrics.width == text_width, "got %.2f\n", metrics.width);
        ok(metrics.layoutWidth == 500.0f, "got %.2f\n", metrics.layoutWidth);
        ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
        IDWriteTextLayout_Release(layout);

        if (stringsW[i][0]) {
            /* max width less than total run width, trailing alignment */
            hr = IDWriteTextFormat_SetWordWrapping(format, DWRITE_WORD_WRAPPING_NO_WRAP);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFactory_CreateTextLayout(factory, stringsW[i], lstrlenW(stringsW[i]), format, clusters[0].width, 100.0f, &layout);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            ok(metrics.left == metrics.layoutWidth - metrics.width, "got %.2f\n", metrics.left);
            ok(metrics.width == text_width, "got %.2f\n", metrics.width);
            ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
            IDWriteTextLayout_Release(layout);
        }

        /* maxwidth is 500, centered */
        hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_CENTER);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, stringsW[i], lstrlenW(stringsW[i]), format, 500.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(metrics.left == (metrics.layoutWidth - metrics.width) / 2.0f, "got %.2f\n", metrics.left);
        ok(metrics.width == text_width, "got %.2f\n", metrics.width);
        ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

        IDWriteTextLayout_Release(layout);
    }

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetParagraphAlignment(void)
{
    static const WCHAR strW[] = {'a',0};
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_PARAGRAPH_ALIGNMENT v;
    DWRITE_LINE_METRICS lines[1];
    UINT32 count;
    HRESULT hr;

    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);

    v = IDWriteTextFormat_GetParagraphAlignment(format);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "got %d\n", v);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetParagraphAlignment(layout);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "got %d\n", v);

    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_FAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_FAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetParagraphAlignment(format);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "got %d\n", v);

    v = IDWriteTextLayout_GetParagraphAlignment(layout);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_FAR, "got %d\n", v);

    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetParagraphAlignment(layout);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_CENTER, "got %d\n", v);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, lines, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    /* maxheight is 100, near alignment */
    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

    /* maxwidth is 100, far alignment */
    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_FAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.top == metrics.layoutHeight - metrics.height, "got %.2f\n", metrics.top);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
    IDWriteTextLayout_Release(layout);

    /* initially created with centered alignment */
    hr = IDWriteTextFormat_SetParagraphAlignment(format, DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.top == (metrics.layoutHeight - lines[0].height) / 2, "got %.2f\n", metrics.top);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetReadingDirection(void)
{
    static const WCHAR strW[] = {'a',0};
    DWRITE_CLUSTER_METRICS clusters[1];
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_READING_DIRECTION v;
    DWRITE_LINE_METRICS lines[1];
    UINT32 count;
    HRESULT hr;

    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);

    v = IDWriteTextFormat_GetReadingDirection(format);
    ok(v == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", v);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetReadingDirection(layout);
    ok(v == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", v);

    v = IDWriteTextFormat_GetReadingDirection(format);
    ok(v == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", v);

    hr = IDWriteTextLayout_SetReadingDirection(layout, DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, lines, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    /* leading alignment, RTL */
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.left == metrics.layoutWidth - clusters[0].width, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == clusters[0].width, "got %.2f\n", metrics.width);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

    /* trailing alignment, RTL */
    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_TRAILING);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.left == 0.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == clusters[0].width, "got %.2f\n", metrics.width);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

    /* centered alignment, RTL */
    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_CENTER);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.left == (metrics.layoutWidth - clusters[0].width) / 2.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == clusters[0].width, "got %.2f\n", metrics.width);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);
    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 * ppdip;

        vec2[0] = m->m11 * vec[0] + m->m21 * vec[1] + m->dx;
        vec2[1] = m->m12 * vec[0] + m->m22 * vec[1] + m->dy;

        /* snap */
        vec2[0] = floorf(vec2[0] + 0.5f);
        vec2[1] = floorf(vec2[1] + 0.5f);

        /* apply inverted transform */
        vec[1] = (-m->m12 * vec2[0] + m->m11 * vec2[1] - (m->m11 * m->dy - m->m12 * m->dx)) / det;
        vec[1] /= ppdip;
    }
    else
        vec[1] = floorf(coord * ppdip + 0.5f) / ppdip;
    return vec[1];
}

static inline BOOL float_eq(FLOAT left, FLOAT right)
{
    int x = *(int *)&left;
    int y = *(int *)&right;

    if (x < 0)
        x = INT_MIN - x;
    if (y < 0)
        y = INT_MIN - y;

    return abs(x - y) <= 16;
}

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 */
    { {  1.0,  0.0,  0.0,  1.0,-0.1, 0.2 },   1.0 },
    { {  0.0,  1.0, -1.0,  0.0,-0.2,-0.3 },   1.0 }, /*  90 degrees rotation */
    { { -1.0,  0.0,  0.0, -1.0,-0.3,-1.6 },   1.0 }, /* 180 degrees rotation */
    { {  0.0, -1.0,  1.0,  0.0,-0.7, 0.6 },  10.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 },
    { {  3.0,  0.0,  0.0,  5.0, 0.2,-0.3 },  10.0 },
    { {  0.0, -3.0,  5.0,  0.0,-0.1, 0.7 },  10.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.gdicompat = FALSE;
    ctxt.use_gdi_natural = FALSE;
    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 < ARRAY_SIZE(snapping_tests); 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 < ARRAY_SIZE(compattransforms); 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);
}

static void test_SetWordWrapping(void)
{
    static const WCHAR strW[] = {'a',' ','s','o','m','e',' ','t','e','x','t',' ','a','n','d',
        ' ','a',' ','b','i','t',' ','m','o','r','e','\n','b'};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_WORD_WRAPPING v;
    UINT32 count;
    HRESULT hr;

    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);

    v = IDWriteTextFormat_GetWordWrapping(format);
    ok(v == DWRITE_WORD_WRAPPING_WRAP, "got %d\n", v);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, ARRAY_SIZE(strW), format, 10.0f, 100.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetWordWrapping(layout);
    ok(v == DWRITE_WORD_WRAPPING_WRAP, "got %d\n", v);

    hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_NO_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_NO_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetWordWrapping(format);
    ok(v == DWRITE_WORD_WRAPPING_WRAP, "got %d\n", v);

    /* disable wrapping, text has explicit newline */
    hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_NO_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);

    hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count > 2, "got %u\n", count);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

/* Collection dedicated to fallback testing */

static const WCHAR g_blahfontW[] = {'B','l','a','h',0};
static HRESULT WINAPI fontcollection_QI(IDWriteFontCollection *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteFontCollection) || IsEqualIID(riid, &IID_IUnknown)) {
        *obj = iface;
        IDWriteFontCollection_AddRef(iface);
        return S_OK;
    }

    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI fontcollection_AddRef(IDWriteFontCollection *iface)
{
    return 2;
}

static ULONG WINAPI fontcollection_Release(IDWriteFontCollection *iface)
{
    return 1;
}

static UINT32 WINAPI fontcollection_GetFontFamilyCount(IDWriteFontCollection *iface)
{
    ok(0, "unexpected call\n");
    return 0;
}

static HRESULT WINAPI fontcollection_GetFontFamily(IDWriteFontCollection *iface, UINT32 index, IDWriteFontFamily **family)
{
    if (index == 123456) {
        IDWriteFactory *factory = create_factory();
        IDWriteFontCollection *syscollection;
        BOOL exists;
        HRESULT hr;

        hr = IDWriteFactory_GetSystemFontCollection(factory, &syscollection, FALSE);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontCollection_FindFamilyName(syscollection, tahomaW, &index, &exists);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontCollection_GetFontFamily(syscollection, index, family);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        IDWriteFontCollection_Release(syscollection);
        IDWriteFactory_Release(factory);
        return S_OK;
    }
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI fontcollection_FindFamilyName(IDWriteFontCollection *iface, WCHAR const *name, UINT32 *index, BOOL *exists)
{
    if (!lstrcmpW(name, g_blahfontW)) {
        *index = 123456;
        *exists = TRUE;
        return S_OK;
    }
    ok(0, "unexpected call, name %s\n", wine_dbgstr_w(name));
    return E_NOTIMPL;
}

static HRESULT WINAPI fontcollection_GetFontFromFontFace(IDWriteFontCollection *iface, IDWriteFontFace *face, IDWriteFont **font)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static const IDWriteFontCollectionVtbl fallbackcollectionvtbl = {
    fontcollection_QI,
    fontcollection_AddRef,
    fontcollection_Release,
    fontcollection_GetFontFamilyCount,
    fontcollection_GetFontFamily,
    fontcollection_FindFamilyName,
    fontcollection_GetFontFromFontFace
};

static IDWriteFontCollection fallbackcollection = { &fallbackcollectionvtbl };

static void test_MapCharacters(void)
{
    static const WCHAR strW[] = {'a','b','c',0};
    static const WCHAR str2W[] = {'a',0x3058,'b',0};
    IDWriteLocalizedStrings *strings;
    IDWriteFontFallback *fallback;
    IDWriteFactory2 *factory2;
    IDWriteFactory *factory;
    UINT32 mappedlength;
    IDWriteFont *font;
    WCHAR buffW[50];
    BOOL exists;
    FLOAT scale;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_QueryInterface(factory, &IID_IDWriteFactory2, (void**)&factory2);
    IDWriteFactory_Release(factory);
    if (hr != S_OK) {
        win_skip("MapCharacters() is not supported\n");
        return;
    }

    fallback = NULL;
    hr = IDWriteFactory2_GetSystemFontFallback(factory2, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback != NULL, "got %p\n", fallback);

    mappedlength = 1;
    scale = 0.0f;
    font = (void*)0xdeadbeef;
    hr = IDWriteFontFallback_MapCharacters(fallback, NULL, 0, 0, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(mappedlength == 0, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font == NULL, "got %p\n", font);

    /* zero length source */
    g_source = strW;
    mappedlength = 1;
    scale = 0.0f;
    font = (void*)0xdeadbeef;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 0, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 0, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font == NULL, "got %p\n", font);

    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
}
    ok(scale == 1.0f, "got %f\n", scale);
todo_wine
    ok(font != NULL, "got %p\n", font);
if (font) {
    IDWriteFont_Release(font);
}
    /* same latin text, full length */
    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 3, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 3, "got %u\n", mappedlength);
}
    ok(scale == 1.0f, "got %f\n", scale);
todo_wine
    ok(font != NULL, "got %p\n", font);
if (font) {
    IDWriteFont_Release(font);
}
    /* string 'a\x3058b' */
    g_source = str2W;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 3, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
}
    ok(scale == 1.0f, "got %f\n", scale);
todo_wine
    ok(font != NULL, "got %p\n", font);
if (font) {
    IDWriteFont_Release(font);
}
    g_source = str2W;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 1, 2, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
}
    ok(scale == 1.0f, "got %f\n", scale);
todo_wine
    ok(font != NULL, "got %p\n", font);
if (font) {
    /* font returned for Hiragana character, check if it supports Latin too */
    exists = FALSE;
    hr = IDWriteFont_HasCharacter(font, 'b', &exists);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(exists, "got %d\n", exists);

    IDWriteFont_Release(font);
}
    /* Try with explicit collection, Tahoma will be forced. */
    /* 1. Latin part */
    g_source = str2W;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 3, &fallbackcollection, g_blahfontW, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);

    exists = FALSE;
    hr = IDWriteFont_GetInformationalStrings(font, DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, &strings, &exists);
    ok(hr == S_OK && exists, "got 0x%08x, exists %d\n", hr, exists);
    hr = IDWriteLocalizedStrings_GetString(strings, 0, buffW, ARRAY_SIZE(buffW));
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, tahomaW), "%s\n", wine_dbgstr_w(buffW));
    IDWriteLocalizedStrings_Release(strings);
    IDWriteFont_Release(font);

    /* 2. Hiragana character, force Tahoma font does not support Japanese */
    g_source = str2W;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 1, 1, &fallbackcollection, g_blahfontW, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);

    exists = FALSE;
    hr = IDWriteFont_GetInformationalStrings(font, DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, &strings, &exists);
    ok(hr == S_OK && exists, "got 0x%08x, exists %d\n", hr, exists);
    hr = IDWriteLocalizedStrings_GetString(strings, 0, buffW, ARRAY_SIZE(buffW));
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(lstrcmpW(buffW, tahomaW), "%s\n", wine_dbgstr_w(buffW));
    IDWriteLocalizedStrings_Release(strings);
    IDWriteFont_Release(font);

    IDWriteFontFallback_Release(fallback);
    IDWriteFactory2_Release(factory2);
}

static void test_FontFallbackBuilder(void)
{
    static const WCHAR localeW[] = {'l','o','c','a','l','e',0};
    static const WCHAR strW[] = {'A',0};
    IDWriteFontFallback *fallback, *fallback2;
    IDWriteFontFallbackBuilder *builder;
    IDWriteFontFallback1 *fallback1;
    DWRITE_UNICODE_RANGE range;
    IDWriteFactory2 *factory2;
    IDWriteFactory *factory;
    const WCHAR *familyW;
    UINT32 mappedlength;
    IDWriteFont *font;
    FLOAT scale;
    HRESULT hr;
    ULONG ref;

    factory = create_factory();

    hr = IDWriteFactory_QueryInterface(factory, &IID_IDWriteFactory2, (void**)&factory2);
    IDWriteFactory_Release(factory);

    if (hr != S_OK) {
        win_skip("IDWriteFontFallbackBuilder is not supported\n");
        return;
    }

    EXPECT_REF(factory2, 1);
    hr = IDWriteFactory2_CreateFontFallbackBuilder(factory2, &builder);
    EXPECT_REF(factory2, 2);

    fallback = NULL;
    EXPECT_REF(factory2, 2);
    EXPECT_REF(builder, 1);
    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(factory2, 3);
    EXPECT_REF(fallback, 1);
    EXPECT_REF(builder, 1);

    IDWriteFontFallback_AddRef(fallback);
    EXPECT_REF(builder, 1);
    EXPECT_REF(fallback, 2);
    EXPECT_REF(factory2, 3);
    IDWriteFontFallback_Release(fallback);

    /* New instance is created every time, even if mappings have not changed. */
    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback2);
    ok(hr == S_OK, "Failed to create fallback object, hr %#x.\n", hr);
    ok(fallback != fallback2, "Unexpected fallback instance.\n");
    IDWriteFontFallback_Release(fallback2);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, NULL, 0, NULL, 0, NULL, NULL, NULL, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    range.first = 'A';
    range.last = 'B';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 0, NULL, 0, NULL, NULL, NULL, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 0, NULL, 0, NULL, NULL, NULL, 1.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 0, &familyW, 1, NULL, NULL, NULL, 1.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, NULL, 0, &familyW, 1, NULL, NULL, NULL, 1.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* negative scaling factor */
    range.first = range.last = 0;
    familyW = g_blahfontW;
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, -1.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 0.0f);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);

    /* empty range */
    range.first = range.last = 0;
    familyW = g_blahfontW;
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 1.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.first = range.last = 0;
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 2.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.first = range.last = 'A';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 3.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.first = 'B';
    range.last = 'A';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 4.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    IDWriteFontFallback_Release(fallback);

    if (0) /* crashes on native */
        hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, NULL);

    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* fallback font missing from system collection */
    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = (void*)0xdeadbeef;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font == NULL, "got %p\n", font);
}
    IDWriteFontFallback_Release(fallback);

    /* remap with custom collection */
    range.first = range.last = 'A';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, &fallbackcollection, NULL, NULL, 5.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 5.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);
}
    if (font)
        IDWriteFont_Release(font);

    IDWriteFontFallback_Release(fallback);

    range.first = 'B';
    range.last = 'A';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, &fallbackcollection, NULL, NULL, 6.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 5.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);
}
    if (font)
        IDWriteFont_Release(font);

    IDWriteFontFallback_Release(fallback);

    /* explicit locale */
    range.first = 'A';
    range.last = 'B';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, &fallbackcollection, localeW, NULL, 6.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 5.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);
}
    if (font)
        IDWriteFont_Release(font);

    if (SUCCEEDED(IDWriteFontFallback_QueryInterface(fallback, &IID_IDWriteFontFallback1, (void **)&fallback1)))
    {
        IDWriteFontFallback1_Release(fallback1);
    }
    else
        win_skip("IDWriteFontFallback1 is not supported.\n");

    IDWriteFontFallback_Release(fallback);

    IDWriteFontFallbackBuilder_Release(builder);
    ref = IDWriteFactory2_Release(factory2);
    ok(ref == 0, "Factory is not released, ref %u.\n", ref);
}

static void test_fallback(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteFontFallback *fallback, *fallback2;
    IDWriteFontFallback1 *fallback1;
    DWRITE_CLUSTER_METRICS clusters[4];
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextLayout2 *layout2;
    IDWriteTextFormat1 *format1;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory2 *factory2;
    IDWriteFactory *factory;
    UINT32 count, i;
    FLOAT width;
    HRESULT hr;

    factory = create_factory();

    /* Font does not exist in system collection. */
    hr = IDWriteFactory_CreateTextFormat(factory, g_blahfontW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "Failed to create text format, hr %#x.\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "Failed to create text layout, hr %#x.\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 4, &count);
todo_wine {
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(count == 4, "Unexpected count %u.\n", count);
}
    for (i = 0, width = 0.0; i < count; i++)
        width += clusters[i].width;

    memset(&metrics, 0xcc, sizeof(metrics));
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "Failed to get layout metrics, hr %#x.\n", hr);
todo_wine {
    ok(metrics.width > 0.0 && metrics.width == width, "Unexpected width %.2f, expected %.2f.\n", metrics.width, width);
    ok(metrics.height > 0.0, "Unexpected height %.2f.\n", metrics.height);
    ok(metrics.lineCount == 1, "Unexpected line count %u.\n", metrics.lineCount);
}
    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* Existing font. */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    IDWriteTextLayout_Release(layout);

    if (hr != S_OK) {
        win_skip("GetFontFallback() is not supported.\n");
        IDWriteFactory_Release(factory);
        return;
    }

    if (0) /* crashes on native */
        hr = IDWriteTextLayout2_GetFontFallback(layout2, NULL);

    fallback = (void*)0xdeadbeef;
    hr = IDWriteTextLayout2_GetFontFallback(layout2, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback == NULL, "got %p\n", fallback);

    hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextFormat1, (void**)&format1);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fallback = (void*)0xdeadbeef;
    hr = IDWriteTextFormat1_GetFontFallback(format1, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback == NULL, "got %p\n", fallback);

    hr = IDWriteFactory_QueryInterface(factory, &IID_IDWriteFactory2, (void**)&factory2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fallback = NULL;
    hr = IDWriteFactory2_GetSystemFontFallback(factory2, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback != NULL, "got %p\n", fallback);

    hr = IDWriteTextFormat1_SetFontFallback(format1, fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fallback2 = (void*)0xdeadbeef;
    hr = IDWriteTextLayout2_GetFontFallback(layout2, &fallback2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback2 == fallback, "got %p\n", fallback2);

    hr = IDWriteTextLayout2_SetFontFallback(layout2, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fallback2 = (void*)0xdeadbeef;
    hr = IDWriteTextFormat1_GetFontFallback(format1, &fallback2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback2 == NULL, "got %p\n", fallback2);

    if (SUCCEEDED(IDWriteFontFallback_QueryInterface(fallback, &IID_IDWriteFontFallback1, (void **)&fallback1)))
    {
        IDWriteFontFallback1_Release(fallback1);
    }
    else
        win_skip("IDWriteFontFallback1 is not supported.\n");

    IDWriteFontFallback_Release(fallback);
    IDWriteTextFormat1_Release(format1);
    IDWriteTextLayout2_Release(layout2);
    IDWriteFactory_Release(factory);
}

static void test_SetTypography(void)
{
    static const WCHAR strW[] = {'a','f','i','b',0};
    IDWriteTypography *typography, *typography2;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteFactory_CreateTypography(factory, &typography);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(typography, 1);
    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetTypography(layout, typography, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(typography, 2);

    hr = IDWriteTextLayout_GetTypography(layout, 0, &typography2, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(typography2 == typography, "got %p, expected %p\n", typography2, typography);
    IDWriteTypography_Release(typography2);
    IDWriteTypography_Release(typography);

    hr = IDWriteFactory_CreateTypography(factory, &typography2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetTypography(layout, typography2, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(typography2, 2);
    IDWriteTypography_Release(typography2);

    hr = IDWriteTextLayout_GetTypography(layout, 0, &typography, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(range.length == 1, "got %u\n", range.length);

    IDWriteTypography_Release(typography);

    IDWriteTextLayout_Release(layout);
    IDWriteFactory_Release(factory);
}

static void test_SetLastLineWrapping(void)
{
    static const WCHAR strW[] = {'a',0};
    IDWriteTextLayout2 *layout2;
    IDWriteTextFormat1 *format1;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;
    BOOL ret;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat1, (void**)&format1);
    IDWriteTextFormat_Release(format);
    if (hr != S_OK) {
        win_skip("SetLastLineWrapping() is not supported\n");
        IDWriteFactory_Release(factory);
        return;
    }

    ret = IDWriteTextFormat1_GetLastLineWrapping(format1);
    ok(ret, "got %d\n", ret);

    hr = IDWriteTextFormat1_SetLastLineWrapping(format1, FALSE);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, (IDWriteTextFormat*)format1, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    ret = IDWriteTextLayout2_GetLastLineWrapping(layout2);
    ok(!ret, "got %d\n", ret);

    hr = IDWriteTextLayout2_SetLastLineWrapping(layout2, TRUE);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    IDWriteTextLayout2_Release(layout2);
    IDWriteTextFormat1_Release(format1);
    IDWriteFactory_Release(factory);
}

static void test_SetOpticalAlignment(void)
{
    static const WCHAR strW[] = {'a',0};
    DWRITE_OPTICAL_ALIGNMENT alignment;
    IDWriteTextLayout2 *layout2;
    IDWriteTextFormat1 *format1;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat1, (void**)&format1);
    IDWriteTextFormat_Release(format);
    if (hr != S_OK) {
        win_skip("SetOpticalAlignment() is not supported\n");
        IDWriteFactory_Release(factory);
        return;
    }

    alignment = IDWriteTextFormat1_GetOpticalAlignment(format1);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NONE, "got %d\n", alignment);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, (IDWriteTextFormat*)format1, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat1_Release(format1);

    alignment = IDWriteTextLayout2_GetOpticalAlignment(layout2);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NONE, "got %d\n", alignment);

    hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextFormat1, (void**)&format1);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    alignment = IDWriteTextFormat1_GetOpticalAlignment(format1);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NONE, "got %d\n", alignment);

    hr = IDWriteTextLayout2_SetOpticalAlignment(layout2, DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout2_SetOpticalAlignment(layout2, DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    alignment = IDWriteTextFormat1_GetOpticalAlignment(format1);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS, "got %d\n", alignment);

    hr = IDWriteTextFormat1_SetOpticalAlignment(format1, DWRITE_OPTICAL_ALIGNMENT_NONE);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat1_SetOpticalAlignment(format1, DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    alignment = IDWriteTextLayout2_GetOpticalAlignment(layout2);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NONE, "got %d\n", alignment);

    IDWriteTextLayout2_Release(layout2);
    IDWriteTextFormat1_Release(format1);
    IDWriteFactory_Release(factory);
}

static const struct drawcall_entry drawunderline_seq[] = {
    { DRAW_GLYPHRUN, {'a','e',0x0300,0}, {'e','n','-','u','s',0}, 2 }, /* reported runs can't mix different underline values */
    { DRAW_GLYPHRUN, {'d',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry drawunderline2_seq[] = {
    { DRAW_GLYPHRUN, {'a',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_GLYPHRUN, {'e',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry drawunderline3_seq[] = {
    { DRAW_GLYPHRUN, {'a',0}, {'e','n','-','c','a',0}, 1 },
    { DRAW_GLYPHRUN, {'e',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_UNDERLINE, {0}, {'e','n','-','c','a',0} },
    { DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry drawunderline4_seq[] = {
    { DRAW_GLYPHRUN, {'a',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_GLYPHRUN, {'e',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
    { DRAW_STRIKETHROUGH },
    { DRAW_LAST_KIND }
};

static void test_SetUnderline(void)
{
    static const WCHAR encaW[] = {'e','n','-','C','A',0};
    static const WCHAR strW[] = {'a','e',0x0300,'d',0}; /* accent grave */
    IDWriteFontCollection *syscollection;
    DWRITE_CLUSTER_METRICS clusters[4];
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    UINT32 count, i;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, ARRAY_SIZE(clusters), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(count == 3, "got %u\n", count);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, ARRAY_SIZE(clusters), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(count == 3, "got %u\n", count);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawunderline_seq, "draw underline test", TRUE);

    IDWriteTextLayout_Release(layout);

    /* 2 characters, same font, significantly different font size. Set underline for both, see how many
       underline drawing calls is there. */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 2, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 100.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawunderline2_seq, "draw underline test 2", FALSE);

    /* now set different locale for second char, draw again */
    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetLocaleName(layout, encaW, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawunderline3_seq, "draw underline test 2", FALSE);

    IDWriteTextLayout_Release(layout);

    /* 2 characters, same font properties, first with strikethrough, both underlined */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 2, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawunderline4_seq, "draw underline test 4", FALSE);

    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);

    /* Test runHeight value with all available fonts */
    hr = IDWriteFactory_GetSystemFontCollection(factory, &syscollection, FALSE);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    count = IDWriteFontCollection_GetFontFamilyCount(syscollection);

    for (i = 0; i < count; ++i)
    {
        DWRITE_FONT_METRICS fontmetrics;
        IDWriteLocalizedStrings *names;
        struct renderer_context ctxt;
        IDWriteFontFamily *family;
        IDWriteFontFace *fontface;
        WCHAR nameW[256], str[1];
        IDWriteFont *font;
        UINT32 codepoint;
        UINT16 glyph;
        BOOL exists;

        format = NULL;
        layout = NULL;

        hr = IDWriteFontCollection_GetFontFamily(syscollection, i, &family);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontFamily_GetFirstMatchingFont(family, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
            DWRITE_FONT_STYLE_NORMAL, &font);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFont_CreateFontFace(font, &fontface);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontFamily_GetFamilyNames(family, &names);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        if (!(exists = get_enus_string(names, nameW, ARRAY_SIZE(nameW)))) {
            IDWriteLocalFontFileLoader *localloader;
            IDWriteFontFileLoader *loader;
            IDWriteFontFile *file;
            const void *key;
            UINT32 keysize;
            UINT32 count;

            count = 1;
            hr = IDWriteFontFace_GetFiles(fontface, &count, &file);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFontFile_GetLoader(file, &loader);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFontFileLoader_QueryInterface(loader, &IID_IDWriteLocalFontFileLoader, (void**)&localloader);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            IDWriteFontFileLoader_Release(loader);

            hr = IDWriteFontFile_GetReferenceKey(file, &key, &keysize);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteLocalFontFileLoader_GetFilePathFromKey(localloader, key, keysize, nameW, ARRAY_SIZE(nameW));
            ok(hr == S_OK, "got 0x%08x\n", hr);

            skip("Failed to get English family name, font file %s\n", wine_dbgstr_w(nameW));

            IDWriteLocalFontFileLoader_Release(localloader);
            IDWriteFontFile_Release(file);
        }

        IDWriteLocalizedStrings_Release(names);
        IDWriteFont_Release(font);

        if (!exists)
            goto cleanup;

        IDWriteFontFace_GetMetrics(fontface, &fontmetrics);
        hr = IDWriteFactory_CreateTextFormat(factory, nameW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL, fontmetrics.designUnitsPerEm, enusW, &format);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        /* Look for first supported character to avoid triggering fallback path. With fallback it's harder to test
           DrawUnderline() metrics, because actual resolved fontface is not passed to it. Grabbing fontface instance
           from corresponding DrawGlyphRun() call is not straightforward. */
        for (codepoint = ' '; codepoint < 0xffff; ++codepoint)
        {
            glyph = 0;
            hr = IDWriteFontFace_GetGlyphIndices(fontface, &codepoint, 1, &glyph);
            ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
            if (glyph)
                break;
        }

        if (!glyph)
        {
            skip("Couldn't find reasonable test string.\n");
            goto cleanup;
        }

        str[0] = codepoint;
        hr = IDWriteFactory_CreateTextLayout(factory, str, 1, format, 30000.0f, 100.0f, &layout);
        ok(hr == S_OK, "Unexpected hr %#x.\n", hr);

        range.startPosition = 0;
        range.length = 2;
        hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
        ok(hr == S_OK, "Unexpected hr %#x.\n", hr);

        memset(&ctxt, 0, sizeof(ctxt));
        ctxt.format = format;
        ctxt.familyW = nameW;
        hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0f, 0.0f);
        ok(hr == S_OK, "Unexpected hr %#x.\n", hr);

    cleanup:
        if (layout)
            IDWriteTextLayout_Release(layout);
        if (format)
            IDWriteTextFormat_Release(format);
        IDWriteFontFace_Release(fontface);
        IDWriteFontFamily_Release(family);
    }
    IDWriteFontCollection_Release(syscollection);

    IDWriteFactory_Release(factory);
}

static void test_InvalidateLayout(void)
{
    static const WCHAR strW[] = {'a',0};
    IDWriteTextLayout3 *layout3;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout3, (void**)&layout3);
    if (hr == S_OK) {
        IDWriteTextFormat1 *format1;
        IDWriteTextFormat2 *format2;

        hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat2, (void**)&format2);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextFormat2_Release(format2);

        hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat2, (void**)&format2);
        ok(hr == S_OK || broken(hr == E_NOINTERFACE), "Unexpected hr %#x.\n", hr);
        if (hr == S_OK) {
            ok(format != (IDWriteTextFormat *)format2, "Unexpected interface pointer.\n");
            IDWriteTextFormat2_Release(format2);
        }

        hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat1, (void**)&format1);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextFormat1_QueryInterface(format1, &IID_IDWriteTextFormat2, (void**)&format2);
        ok(hr == S_OK || broken(hr == E_NOINTERFACE), "Unexpected hr %#x.\n", hr);
        if (hr == S_OK)
            IDWriteTextFormat2_Release(format2);
        IDWriteTextFormat1_Release(format1);

        hr = IDWriteTextLayout3_QueryInterface(layout3, &IID_IDWriteTextFormat2, (void**)&format2);
        ok(hr == S_OK || broken(hr == E_NOINTERFACE), "got 0x%08x\n", hr);
        if (hr == S_OK)
            IDWriteTextFormat2_Release(format2);

        hr = IDWriteTextLayout3_InvalidateLayout(layout3);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout3_Release(layout3);
    }
    else
        win_skip("IDWriteTextLayout3::InvalidateLayout() is not supported.\n");

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_line_spacing(void)
{
    static const WCHAR strW[] = {'a',0};
    IDWriteTextFormat2 *format2;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, -10.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_DEFAULT, -10.0f, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_PROPORTIONAL+1, 0.0f, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, -10.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_DEFAULT, -10.0f, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_PROPORTIONAL+1, 0.0f, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    if (IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat2, (void**)&format2) == S_OK) {
        DWRITE_LINE_SPACING spacing;

        hr = IDWriteTextFormat2_GetLineSpacing(format2, &spacing);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        ok(spacing.method == DWRITE_LINE_SPACING_METHOD_DEFAULT, "got method %d\n", spacing.method);
        ok(spacing.height == 0.0f, "got %f\n", spacing.height);
        ok(spacing.baseline == -10.0f, "got %f\n", spacing.baseline);
        ok(spacing.leadingBefore == 0.0f, "got %f\n", spacing.leadingBefore);
        ok(spacing.fontLineGapUsage == DWRITE_FONT_LINE_GAP_USAGE_DEFAULT, "got %f\n", spacing.leadingBefore);

        spacing.leadingBefore = -1.0f;

        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

        spacing.leadingBefore = 1.1f;

        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

        spacing.leadingBefore = 1.0f;

        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        spacing.method = DWRITE_LINE_SPACING_METHOD_PROPORTIONAL + 1;
        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

        spacing.method = DWRITE_LINE_SPACING_METHOD_PROPORTIONAL;
        spacing.fontLineGapUsage = DWRITE_FONT_LINE_GAP_USAGE_ENABLED + 1;
        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextFormat2_GetLineSpacing(format2, &spacing);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(spacing.fontLineGapUsage == DWRITE_FONT_LINE_GAP_USAGE_ENABLED + 1, "got %d\n", spacing.fontLineGapUsage);

        IDWriteTextFormat2_Release(format2);
    }

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_GetOverhangMetrics(void)
{
    static const struct overhangs_test
    {
        FLOAT uniform_baseline;
        DWRITE_INLINE_OBJECT_METRICS metrics;
        DWRITE_OVERHANG_METRICS overhang_metrics;
        DWRITE_OVERHANG_METRICS expected;
    } overhangs_tests[] = {
        { 16.0f, { 10.0f, 50.0f, 20.0f }, { 1.0f, 2.0f, 3.0f, 4.0f }, { 1.0f, 6.0f, 3.0f, 0.0f } },
        { 15.0f, { 10.0f, 50.0f, 20.0f }, { 1.0f, 2.0f, 3.0f, 4.0f }, { 1.0f, 7.0f, 3.0f, -1.0f } },
        { 16.0f, { 10.0f, 50.0f, 20.0f }, { -1.0f, 0.0f, -3.0f, 4.0f }, { -1.0f, 4.0f, -3.0f, 0.0f } },
        { 15.0f, { 10.0f, 50.0f, 20.0f }, { -1.0f, 10.0f, 3.0f, -4.0f }, { -1.0f, 15.0f, 3.0f, -9.0f } },
    };
    static const WCHAR strW[] = {'A',0};
    IDWriteFactory *factory;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    HRESULT hr;
    UINT32 i;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL, 100.0f, enusW, &format);
    ok(hr == S_OK, "Failed to create text format, hr %#x.\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "Failed to create text layout, hr %x.\n", hr);

    for (i = 0; i < ARRAY_SIZE(overhangs_tests); i++) {
        const struct overhangs_test *test = &overhangs_tests[i];
        DWRITE_OVERHANG_METRICS overhang_metrics;
        DWRITE_TEXT_RANGE range = { 0, 1 };
        DWRITE_TEXT_METRICS metrics;
        struct test_inline_obj obj;

        test_inline_obj_init(&obj, &test->metrics, &test->overhang_metrics);

        hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_UNIFORM, test->metrics.height * 2.0f,
                test->uniform_baseline);
        ok(hr == S_OK, "Failed to set line spacing, hr %#x.\n", hr);

        hr = IDWriteTextLayout_SetInlineObject(layout, NULL, range);
        ok(hr == S_OK, "Failed to reset inline object, hr %#x.\n", hr);

        hr = IDWriteTextLayout_SetInlineObject(layout, &obj.IDWriteInlineObject_iface, range);
        ok(hr == S_OK, "Failed to set inline object, hr %#x.\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "Failed to get layout metrics, hr %#x.\n", hr);

        ok(metrics.width == test->metrics.width, "%u: unexpected formatted width.\n", i);
        ok(metrics.height == test->metrics.height * 2.0f, "%u: unexpected formatted height.\n", i);

        hr = IDWriteTextLayout_SetMaxWidth(layout, metrics.width);
        hr = IDWriteTextLayout_SetMaxHeight(layout, test->metrics.height);

        hr = IDWriteTextLayout_GetOverhangMetrics(layout, &overhang_metrics);
        ok(hr == S_OK, "Failed to get overhang metrics, hr %#x.\n", hr);

        ok(!memcmp(&overhang_metrics, &test->expected, sizeof(overhang_metrics)),
                "%u: unexpected overhang metrics (%f, %f, %f, %f).\n", i, overhang_metrics.left, overhang_metrics.top,
                overhang_metrics.right, overhang_metrics.bottom);
    }

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_tab_stops(void)
{
    static const WCHAR strW[] = {'\t','a','\t','b'};
    DWRITE_CLUSTER_METRICS clusters[4];
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE range;
    FLOAT tabstop, size;
    ULONG count;
    HRESULT hr;

    factory = create_factory();

    /* Default tab stop value. */
    for (size = 1.0f; size < 25.0f; size += 5.0f)
    {
        hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL,
                DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, size, enusW, &format);
        ok(hr == S_OK, "Failed to create text format, hr %#x.\n", hr);

        tabstop = IDWriteTextFormat_GetIncrementalTabStop(format);
        ok(tabstop == 4.0f * size, "Unexpected tab stop %f.\n", tabstop);

        IDWriteTextFormat_Release(format);
    }

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL,
            DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == S_OK, "Failed to create text format, hr %#x.\n", hr);

    hr = IDWriteTextFormat_SetIncrementalTabStop(format, 0.0f);
    ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);

    hr = IDWriteTextFormat_SetIncrementalTabStop(format, -10.0f);
    ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);

    tabstop = IDWriteTextFormat_GetIncrementalTabStop(format);
    ok(tabstop == 40.0f, "Unexpected tab stop %f.\n", tabstop);

    hr = IDWriteTextFormat_SetIncrementalTabStop(format, 100.0f);
    ok(hr == S_OK, "Failed to set tab stop value, hr %#x.\n", hr);

    tabstop = IDWriteTextFormat_GetIncrementalTabStop(format);
    ok(tabstop == 100.0f, "Unexpected tab stop %f.\n", tabstop);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "Failed to create text layout, hr %x.\n", hr);

    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 4, &count);
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(clusters[0].isWhitespace, "Unexpected isWhitespace.\n");
    ok(!clusters[1].isWhitespace, "Unexpected isWhitespace.\n");
    ok(clusters[2].isWhitespace, "Unexpected isWhitespace.\n");
    ok(!clusters[3].isWhitespace, "Unexpected isWhitespace.\n");
todo_wine {
    ok(clusters[0].width == tabstop, "Unexpected tab width.\n");
    ok(clusters[1].width + clusters[2].width == tabstop, "Unexpected tab width.\n");
}
    range.startPosition = 0;
    range.length = ~0u;
    hr = IDWriteTextLayout_SetFontSize(layout, 20.0f, range);
    ok(hr == S_OK, "Failed to set font size, hr %#x.\n", hr);

    tabstop = IDWriteTextLayout_GetIncrementalTabStop(layout);
    ok(tabstop == 100.0f, "Unexpected tab stop %f.\n", tabstop);

    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 4, &count);
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(clusters[0].isWhitespace, "Unexpected isWhitespace.\n");
    ok(!clusters[1].isWhitespace, "Unexpected isWhitespace.\n");
    ok(clusters[2].isWhitespace, "Unexpected isWhitespace.\n");
    ok(!clusters[3].isWhitespace, "Unexpected isWhitespace.\n");
todo_wine {
    ok(clusters[0].width == tabstop, "Unexpected tab width.\n");
    ok(clusters[1].width + clusters[2].width == tabstop, "Unexpected tab width.\n");
}
    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);

    IDWriteFactory_Release(factory);
}

START_TEST(layout)
{
    IDWriteFactory *factory;

    if (!(factory = create_factory())) {
        win_skip("failed to create factory\n");
        return;
    }

    init_call_sequences(sequences, NUM_CALL_SEQUENCES);
    init_call_sequences(expected_seq, 1);

    test_CreateTextLayout();
    test_CreateGdiCompatibleTextLayout();
    test_CreateTextFormat();
    test_GetLocaleName();
    test_CreateEllipsisTrimmingSign();
    test_fontweight();
    test_SetInlineObject();
    test_Draw();
    test_typography();
    test_GetClusterMetrics();
    test_SetLocaleName();
    test_SetPairKerning();
    test_SetVerticalGlyphOrientation();
    test_fallback();
    test_DetermineMinWidth();
    test_SetFontSize();
    test_SetFontFamilyName();
    test_SetFontStyle();
    test_SetFontStretch();
    test_SetStrikethrough();
    test_GetMetrics();
    test_SetFlowDirection();
    test_SetDrawingEffect();
    test_GetLineMetrics();
    test_SetTextAlignment();
    test_SetParagraphAlignment();
    test_SetReadingDirection();
    test_pixelsnapping();
    test_SetWordWrapping();
    test_MapCharacters();
    test_FontFallbackBuilder();
    test_SetTypography();
    test_SetLastLineWrapping();
    test_SetOpticalAlignment();
    test_SetUnderline();
    test_InvalidateLayout();
    test_line_spacing();
    test_GetOverhangMetrics();
    test_tab_stops();

    IDWriteFactory_Release(factory);
}
