text: /*
   *    Text analyzing tests
   *
   * Copyright 2012-2020 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 <stdio.h>
  #include <limits.h>
  #include <math.h>
  
  #include "initguid.h"
  #include "windows.h"
  #include "winternl.h"
  #include "dwrite_3.h"
  
  #include "wine/test.h"
  
  static IDWriteFactory *factory;
  
  #define LRE 0x202a
  #define RLE 0x202b
  #define PDF 0x202c
  #define LRO 0x202d
  #define RLO 0x202e
  #define LRI 0x2066
  #define RLI 0x2067
  #define FSI 0x2068
  #define PDI 0x2069
  
  #define MS_GDEF_TAG DWRITE_MAKE_OPENTYPE_TAG('G','D','E','F')
  
  #ifdef WORDS_BIGENDIAN
  #define GET_BE_WORD(x) (x)
  #define GET_BE_DWORD(x) (x)
  #define GET_LE_WORD(x) RtlUshortByteSwap(x)
  #define GET_LE_DWORD(x) RtlUlongByteSwap(x)
  #else
  #define GET_BE_WORD(x) RtlUshortByteSwap(x)
  #define GET_BE_DWORD(x) RtlUlongByteSwap(x)
  #define GET_LE_WORD(x) (x)
  #define GET_LE_DWORD(x) (x)
  #endif
  
  struct ot_gdef_classdef_format1
  {
      WORD format;
      WORD start_glyph;
      WORD glyph_count;
      WORD classes[1];
  };
  
  struct ot_gdef_class_range
  {
      WORD start_glyph;
      WORD end_glyph;
      WORD glyph_class;
  };
  
  struct ot_gdef_classdef_format2
  {
      WORD format;
      WORD range_count;
      struct ot_gdef_class_range ranges[1];
  };
  
  enum analysis_kind {
      ScriptAnalysis,
      LastKind
  };
  
  static const char *get_analysis_kind_name(enum analysis_kind kind)
  {
      switch (kind)
      {
      case ScriptAnalysis:
          return "ScriptAnalysis";
      default:
          return "unknown";
      }
  }
  
  struct script_analysis {
      UINT32 pos;
      UINT32 len;
      DWRITE_SCRIPT_SHAPES shapes;
  };
  
  struct call_entry {
      enum analysis_kind kind;
      struct script_analysis sa;
  };
  
  struct testcontext {
      enum analysis_kind kind;
      BOOL todo;
      int *failcount;
      const char *file;
      int line;
  };
  
  struct call_sequence
  {
      int count;
      int size;
      struct call_entry *sequence;
  };
  
  #define NUM_CALL_SEQUENCES    1
  #define ANALYZER_ID 0
  static struct call_sequence *sequences[NUM_CALL_SEQUENCES];
  static struct call_sequence *expected_seq[1];
  
  static void add_call(struct call_sequence **seq, int sequence_index, const struct call_entry *call)
  {
      struct call_sequence *call_seq = seq[sequence_index];
  
      if (!call_seq->sequence)
      {
          call_seq->size = 10;
          call_seq->sequence = malloc(call_seq->size * sizeof(*call_seq->sequence));
      }
  
      if (call_seq->count == call_seq->size)
      {
          call_seq->size *= 2;
          call_seq->sequence = realloc(call_seq->sequence, call_seq->size * sizeof(*call_seq->sequence));
      }
  
      assert(call_seq->sequence);
  
      call_seq->sequence[call_seq->count++] = *call;
  }
  
  static inline void flush_sequence(struct call_sequence **seg, int sequence_index)
  {
      struct call_sequence *call_seq = seg[sequence_index];
  
      free(call_seq->sequence);
      call_seq->sequence = NULL;
      call_seq->count = call_seq->size = 0;
  }
  
  static void init_call_sequences(struct call_sequence **seq, int n)
  {
      int i;
  
      for (i = 0; i < n; i++)
          seq[i] = calloc(1, sizeof(*seq[i]));
  }
  
  static void test_uint(UINT32 actual, UINT32 expected, const char *name, const struct testcontext *ctxt)
  {
      if (expected != actual && ctxt->todo)
      {
          (*ctxt->failcount)++;
          ok_(ctxt->file, ctxt->line) (0, "%s: \"%s\" expecting %u, got %u\n", get_analysis_kind_name(ctxt->kind), name, expected, actual);
      }
      else
          ok_(ctxt->file, ctxt->line) (expected == actual, "%s: \"%s\" expecting %u, got %u\n", get_analysis_kind_name(ctxt->kind), name,
              expected, actual);
  }
  
  static void ok_sequence_(struct call_sequence **seq, int sequence_index,
      const struct call_entry *expected, const char *context, BOOL todo,
      const char *file, int line)
  {
      struct call_sequence *call_seq = seq[sequence_index];
      static const struct call_entry end_of_sequence = { LastKind };
      const struct call_entry *actual, *sequence;
      int failcount = 0;
      struct testcontext ctxt;
  
      add_call(seq, sequence_index, &end_of_sequence);
  
      sequence = call_seq->sequence;
      actual = sequence;
  
      ctxt.failcount = &failcount;
      ctxt.todo = todo;
      ctxt.file = file;
      ctxt.line = line;
  
      while (expected->kind != LastKind && actual->kind != LastKind)
      {
          if (expected->kind == actual->kind)
          {
              ctxt.kind = expected->kind;
  
              switch (actual->kind)
              {
              case ScriptAnalysis:
                  test_uint(actual->sa.pos, expected->sa.pos, "position", &ctxt);
                  test_uint(actual->sa.len, expected->sa.len, "length", &ctxt);
                  test_uint(actual->sa.shapes, expected->sa.shapes, "shapes", &ctxt);
                  break;
              default:
                  ok(0, "%s: callback not handled, %s\n", context, get_analysis_kind_name(actual->kind));
              }
              expected++;
              actual++;
          }
          else if (todo)
          {
              failcount++;
              todo_wine
              {
                  ok_(file, line) (0, "%s: call %s was expected, but got call %s instead\n",
                      context, get_analysis_kind_name(expected->kind), get_analysis_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_analysis_kind_name(expected->kind), get_analysis_kind_name(actual->kind));
              expected++;
              actual++;
          }
      }
  
      if (todo)
      {
          todo_wine
          {
              if (expected->kind != LastKind || actual->kind != LastKind)
              {
                  failcount++;
                  ok_(file, line) (0, "%s: the call sequence is not complete: expected %s - actual %s\n",
                      context, get_analysis_kind_name(expected->kind), get_analysis_kind_name(actual->kind));
              }
          }
      }
      else if (expected->kind != LastKind || actual->kind != LastKind)
      {
          ok_(file, line) (0, "%s: the call sequence is not complete: expected %s - actual %s\n",
              context, get_analysis_kind_name(expected->kind), get_analysis_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 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 call_entry entry;
      entry.kind = ScriptAnalysis;
      entry.sa.pos = position;
      entry.sa.len = length;
      entry.sa.shapes = sa->shapes;
      add_call(sequences, ANALYZER_ID, &entry);
      return S_OK;
  }
  
  static DWRITE_SCRIPT_ANALYSIS g_sa;
  static HRESULT WINAPI analysissink_SetScriptAnalysis2(IDWriteTextAnalysisSink *iface,
      UINT32 position, UINT32 length, DWRITE_SCRIPT_ANALYSIS const* sa)
  {
      g_sa = *sa;
      return S_OK;
  }
  
  #define BREAKPOINT_COUNT 20
  static DWRITE_LINE_BREAKPOINT g_actual_bp[BREAKPOINT_COUNT];
  
  static HRESULT WINAPI analysissink_SetLineBreakpoints(IDWriteTextAnalysisSink *iface,
          UINT32 position,
          UINT32 length,
          DWRITE_LINE_BREAKPOINT const* breakpoints)
  {
      if (position + length > BREAKPOINT_COUNT) {
          ok(0, "SetLineBreakpoints: reported pos=%u, len=%u overflows expected length %d\n", position, length, BREAKPOINT_COUNT);
          return E_FAIL;
      }
      memcpy(&g_actual_bp[position], breakpoints, length*sizeof(DWRITE_LINE_BREAKPOINT));
      return S_OK;
  }
  
  #define BIDI_LEVELS_COUNT 10
  static UINT8 g_explicit_levels[BIDI_LEVELS_COUNT];
  static UINT8 g_resolved_levels[BIDI_LEVELS_COUNT];
  static HRESULT WINAPI analysissink_SetBidiLevel(IDWriteTextAnalysisSink *iface,
          UINT32 position,
          UINT32 length,
          UINT8 explicitLevel,
          UINT8 resolvedLevel)
  {
      if (position + length > BIDI_LEVELS_COUNT) {
          ok(0, "SetBidiLevel: reported pos=%u, len=%u overflows expected length %d\n", position, length, BIDI_LEVELS_COUNT);
          return E_FAIL;
      }
      memset(g_explicit_levels + position, explicitLevel, length);
      memset(g_resolved_levels + position, resolvedLevel, length);
      return S_OK;
  }
  
  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 IDWriteTextAnalysisSinkVtbl analysissinkvtbl2 = {
      analysissink_QueryInterface,
      analysissink_AddRef,
      analysissink_Release,
      analysissink_SetScriptAnalysis2,
      analysissink_SetLineBreakpoints,
      analysissink_SetBidiLevel,
      analysissink_SetNumberSubstitution
  };
  
  static IDWriteTextAnalysisSink analysissink = { &analysissinkvtbl };
  static IDWriteTextAnalysisSink analysissink2 = { &analysissinkvtbl2 };
  
  static HRESULT WINAPI analysissource_QueryInterface(IDWriteTextAnalysisSource *iface,
      REFIID riid, void **obj)
  {
      ok(0, "QueryInterface not expected\n");
      return E_NOTIMPL;
  }
  
  static ULONG WINAPI analysissource_AddRef(IDWriteTextAnalysisSource *iface)
  {
      ok(0, "AddRef not expected\n");
      return 2;
  }
  
  static ULONG WINAPI analysissource_Release(IDWriteTextAnalysisSource *iface)
  {
      ok(0, "Release not expected\n");
      return 1;
  }
  
  struct testanalysissource
  {
      IDWriteTextAnalysisSource IDWriteTextAnalysisSource_iface;
      const WCHAR *text;
      UINT32 text_length;
      DWRITE_READING_DIRECTION direction;
  };
  
  static void init_textsource(struct testanalysissource *source, const WCHAR *text,
          DWRITE_READING_DIRECTION direction)
  {
      source->text = text;
      source->text_length = lstrlenW(text);
      source->direction = direction;
  };
  
  static inline struct testanalysissource *impl_from_IDWriteTextAnalysisSource(IDWriteTextAnalysisSource *iface)
  {
      return CONTAINING_RECORD(iface, struct testanalysissource, IDWriteTextAnalysisSource_iface);
  }
  
  static HRESULT WINAPI analysissource_GetTextAtPosition(IDWriteTextAnalysisSource *iface,
      UINT32 position, WCHAR const** text, UINT32* text_len)
  {
      struct testanalysissource *source = impl_from_IDWriteTextAnalysisSource(iface);
  
      if (position >= source->text_length)
      {
          *text = NULL;
          *text_len = 0;
      }
      else
      {
          *text = source->text + position;
          *text_len = source->text_length - 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)
  {
      struct testanalysissource *source = impl_from_IDWriteTextAnalysisSource(iface);
      return source->direction;
  }
  
  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 struct testanalysissource analysissource = { { &analysissourcevtbl } };
  
  static IDWriteFontFace *create_fontface(void)
  {
      IDWriteGdiInterop *interop;
      IDWriteFontFace *fontface;
      IDWriteFont *font;
      LOGFONTW logfont;
      HRESULT hr;
  
      hr = IDWriteFactory_GetGdiInterop(factory, &interop);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      memset(&logfont, 0, sizeof(logfont));
      logfont.lfHeight = 12;
      logfont.lfWidth  = 12;
      logfont.lfWeight = FW_NORMAL;
      logfont.lfItalic = 1;
      lstrcpyW(logfont.lfFaceName, L"Tahoma");
  
      hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &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);
      IDWriteGdiInterop_Release(interop);
  
      return fontface;
  }
  
  static WCHAR *create_testfontfile(const WCHAR *filename)
  {
      static WCHAR pathW[MAX_PATH];
      DWORD written;
      HANDLE file;
      HRSRC res;
      void *ptr;
  
      GetTempPathW(ARRAY_SIZE(pathW), pathW);
      lstrcatW(pathW, filename);
  
      file = CreateFileW(pathW, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
      ok(file != INVALID_HANDLE_VALUE, "file creation failed, at %s, error %d\n", wine_dbgstr_w(pathW),
          GetLastError());
  
      res = FindResourceA(GetModuleHandleA(NULL), (LPCSTR)MAKEINTRESOURCE(1), (LPCSTR)RT_RCDATA);
      ok(res != 0, "couldn't find resource\n");
      ptr = LockResource(LoadResource(GetModuleHandleA(NULL), res));
      WriteFile(file, ptr, SizeofResource(GetModuleHandleA(NULL), res), &written, NULL);
      ok(written == SizeofResource(GetModuleHandleA(NULL), res), "couldn't write resource\n");
      CloseHandle(file);
  
      return pathW;
  }
  
  #define DELETE_FONTFILE(filename) _delete_testfontfile(filename, __LINE__)
  static void _delete_testfontfile(const WCHAR *filename, int line)
  {
      BOOL ret = DeleteFileW(filename);
      ok_(__FILE__,line)(ret, "failed to delete file %s, error %d\n", wine_dbgstr_w(filename), GetLastError());
  }
  
  static IDWriteFontFace *create_testfontface(const WCHAR *filename)
  {
      IDWriteFontFace *face;
      IDWriteFontFile *file;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateFontFileReference(factory, filename, NULL, &file);
      ok(hr == S_OK, "got 0x%08x\n",hr);
  
      hr = IDWriteFactory_CreateFontFace(factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &file, 0,
          DWRITE_FONT_SIMULATIONS_NONE, &face);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      IDWriteFontFile_Release(file);
  
      return face;
  }
  
  struct sa_test {
      const WCHAR string[50];
      int item_count;
      struct script_analysis sa[10];
  };
  
  static struct sa_test sa_tests[] = {
      {
        /* just 1 char string */
        {'t',0}, 1,
            { { 0, 1, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        {'t','e','s','t',0}, 1,
            { { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        {' ',' ',' ',' ','!','$','[','^','{','~',0}, 1,
            { { 0, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        {' ',' ',' ','1','2',' ',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* digits only */
        {'1','2',0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Arabic */
        {0x064a,0x064f,0x0633,0x0627,0x0648,0x0650,0x064a,0x0661,0}, 1,
            { { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Arabic */
        {0x0627,0x0644,0x0635,0x0651,0x0650,0x062d,0x0629,0x064f,' ',0x062a,0x064e,
         0x0627,0x062c,0x064c,' ',0x0639,0x064e,0x0644,0x0649,' ',
         0x0631,0x064f,0x0624,0x0648,0x0633,0x0650,' ',0x0627,0x0644,
         0x0623,0x0635,0x0650,0x062d,0x0651,0x064e,0x0627,0x0621,0x0650,0x06f0,0x06f5,0}, 1,
            { { 0, 40, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Arabic, Latin */
        {'1','2','3','-','5','2',0x064a,0x064f,0x0633,0x0627,0x0648,0x0650,0x064a,'7','1','.',0}, 1,
            { { 0, 16, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Arabic, English */
        {'A','B','C','-','D','E','F',' ',0x0621,0x0623,0x0624,0}, 2,
            { { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 8, 3, DWRITE_SCRIPT_SHAPES_DEFAULT },
            }
      },
      {
        /* leading space, Arabic, English */
        {' ',0x0621,0x0623,0x0624,'A','B','C','-','D','E','F',0}, 2,
            { { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 4, 7, DWRITE_SCRIPT_SHAPES_DEFAULT },
            }
      },
      {
        /* English, Arabic, trailing space */
        {'A','B','C','-','D','E','F',0x0621,0x0623,0x0624,' ',0}, 2,
            { { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 7, 4, DWRITE_SCRIPT_SHAPES_DEFAULT },
            }
      },
      {
        /* C1 Controls, Latin-1 Supplement */
        {0x80,0x90,0x9f,0xa0,0xc0,0xb8,0xbf,0xc0,0xff,0}, 2,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_NO_VISUAL },
              { 3, 6, DWRITE_SCRIPT_SHAPES_DEFAULT },
            }
      },
      {
        /* Latin Extended-A */
        {0x100,0x120,0x130,0x140,0x150,0x160,0x170,0x17f,0}, 1,
            { { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Latin Extended-B */
        {0x180,0x190,0x1bf,0x1c0,0x1c3,0x1c4,0x1cc,0x1dc,0x1ff,0x217,0x21b,0x24f,0}, 1,
            { { 0, 12, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* IPA Extensions */
        {0x250,0x260,0x270,0x290,0x2af,0}, 1,
            { { 0, 5, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Spacing Modifier Letters */
        {0x2b0,0x2ba,0x2d7,0x2dd,0x2ef,0x2ff,0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Combining Diacritical Marks */
        {0x300,0x320,0x340,0x345,0x350,0x36f,0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Greek and Coptic */
        {0x370,0x388,0x3d8,0x3e1,0x3e2,0x3fa,0x3ff,0}, 3,
            { { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 4, 1, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 5, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }
            }
      },
      {
        /* Cyrillic and Cyrillic Supplement */
        {0x400,0x40f,0x410,0x44f,0x450,0x45f,0x460,0x481,0x48a,0x4f0,0x4fa,0x4ff,0x500,0x510,0x520,0}, 1,
            { { 0, 15, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Armenian */
        {0x531,0x540,0x559,0x55f,0x570,0x589,0x58a,0}, 1,
            { { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Hebrew */
        {0x5e9,0x5dc,0x5d5,0x5dd,0}, 1,
            { { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Latin, Hebrew, Latin */
        {'p','a','r','t',' ','o','n','e',' ',0x5d7,0x5dc,0x5e7,' ',0x5e9,0x5ea,0x5d9,0x5d9,0x5dd,' ','p','a','r','t',' ','t','h','r','e','e',0}, 3,
            { { 0, 9, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 9, 10, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 19, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Syriac */
        {0x710,0x712,0x712,0x714,'.',0}, 1,
            { { 0, 5, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Arabic Supplement */
        {0x750,0x760,0x76d,'.',0}, 1,
            { { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Thaana */
        {0x780,0x78e,0x798,0x7a6,0x7b0,'.',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* N'Ko */
        {0x7c0,0x7ca,0x7e8,0x7eb,0x7f6,'.',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Thaana */
        {0x780,0x798,0x7a5,0x7a6,0x7b0,'.',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Devanagari */
        {0x926,0x947,0x935,0x928,0x93e,0x917,0x930,0x940,'.',0}, 1,
            { { 0, 9, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Bengali */
        {0x9ac,0x9be,0x982,0x9b2,0x9be,'.',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Gurmukhi */
        {0xa17,0xa41,0xa30,0xa2e,0xa41,0xa16,0xa40,'.',0}, 1,
            { { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Gujarati */
        {0xa97,0xac1,0xa9c,0xab0,0xabe,0xaa4,0xac0,'.',0}, 1,
            { { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Oriya */
        {0xb13,0xb21,0xb3c,0xb3f,0xb06,'.',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Tamil */
        {0xba4,0xbae,0xbbf,0xbb4,0xbcd,'.',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Telugu */
        {0xc24,0xc46,0xc32,0xc41,0xc17,0xc41,'.',0}, 1,
            { { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Kannada */
        {0xc95,0xca8,0xccd,0xca8,0xca1,'.',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Malayalam */
        {0xd2e,0xd32,0xd2f,0xd3e,0xd33,0xd02,'.',0}, 1,
            { { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Sinhala */
        {0xd82,0xd85,0xd9a,0xdcf,'.',0}, 1,
            { { 0, 5, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Thai */
        {0x0e04,0x0e27,0x0e32,0x0e21,0x0e1e,0x0e22,0x0e32,0x0e22,0x0e32,0x0e21,
         0x0e2d,0x0e22,0x0e39,0x0e48,0x0e17,0x0e35,0x0e48,0x0e44,0x0e2b,0x0e19,
         0x0e04,0x0e27,0x0e32,0x0e21,0x0e2a, 0x0e33,0x0e40,0x0e23,0x0e47,0x0e08,
         0x0e2d,0x0e22,0x0e39,0x0e48,0x0e17,0x0e35,0x0e48,0x0e19,0x0e31,0x0e48,0x0e19,'.',0}, 1,
            { { 0, 42, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Lao */
        {0xead,0xeb1,0xe81,0xeaa,0xead,0xe99,0xea5,0xeb2,0xea7,'.',0}, 1,
            { { 0, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Tibetan */
        {0xf04,0xf05,0xf0e,0x020,0xf51,0xf7c,0xf53,0xf0b,0xf5a,0xf53,0xf0b,
         0xf51,0xf44,0xf0b,0xf54,0xf7c,0xf0d,'.',0}, 1,
            { { 0, 18, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Myanmar */
        {0x1019,0x103c,0x1014,0x103a,0x1019,0x102c,0x1021,0x1000,0x1039,0x1001,0x101b,0x102c,'.',0}, 1,
            { { 0, 13, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Georgian */
        {0x10a0,0x10d0,0x10da,0x10f1,0x10fb,0x2d00,'.',0}, 1,
            { { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Hangul */
        {0x1100,0x1110,0x1160,0x1170,0x11a8,'.',0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Ethiopic */
        {0x130d,0x12d5,0x12dd,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Cherokee */
        {0x13e3,0x13b3,0x13a9,0x0020,0x13a6,0x13ec,0x13c2,0x13af,0x13cd,0x13d7,0}, 1,
            { { 0, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Canadian */
        {0x1403,0x14c4,0x1483,0x144e,0x1450,0x1466,0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Ogham */
        {0x169b,0x1691,0x168c,0x1690,0x168b,0x169c,0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Runic */
        {0x16a0,0x16a1,0x16a2,0x16a3,0x16a4,0x16a5,0}, 1,
            { { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Khmer */
        {0x1781,0x17c1,0x1798,0x179a,0x1797,0x17b6,0x179f,0x17b6,0x19e0,0}, 1,
            { { 0, 9, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Mongolian */
        {0x182e,0x1823,0x1829,0x182d,0x1823,0x182f,0x0020,0x182a,0x1822,0x1834,0x1822,0x182d,0x180c,0}, 1,
            { { 0, 13, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Limbu */
        {0x1900,0x1910,0x1920,0x1930,0}, 1,
            { { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Tai Le */
        {0x1956,0x196d,0x1970,0x1956,0x196c,0x1973,0x1951,0x1968,0x1952,0x1970,0}, 1,
            { { 0, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* New Tai Lue */
        {0x1992,0x19c4,0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Buginese */
        {0x1a00,0x1a10,0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Tai Tham */
        {0x1a20,0x1a40,0x1a50,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Balinese */
        {0x1b00,0x1b05,0x1b20,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Sundanese */
        {0x1b80,0x1b85,0x1ba0,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Batak */
        {0x1bc0,0x1be5,0x1bfc,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Lepcha */
        {0x1c00,0x1c20,0x1c40,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Ol Chiki */
        {0x1c50,0x1c5a,0x1c77,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Sundanese Supplement */
        {0x1cc0,0x1cc5,0x1cc7,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Phonetic Extensions */
        {0x1d00,0x1d40,0x1d70,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Combining diacritical marks */
        {0x1dc0,0x300,0x1ddf,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Latin Extended Additional, Extended-C */
        {0x1e00,0x1d00,0x2c60,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Greek Extended */
        {0x3f0,0x1f00,0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* General Punctuation */
        {0x1dc0,0x2000,0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Superscripts and Subscripts */
        {0x2070,0x2086,0x2000,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Currency, Combining Diacritical Marks for Symbols. Letterlike Symbols.. */
        {0x20a0,0x20b8,0x2000,0x20d0,0x2100,0x2150,0x2190,0x2200,0x2300,0x2400,0x2440,0x2460,0x2500,0x2580,0x25a0,0x2600,
         0x2700,0x27c0,0x27f0,0x2900,0x2980,0x2a00,0x2b00,0}, 1,
            { { 0, 23, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Braille */
        {0x2800,0x2070,0x2000,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Glagolitic */
        {0x2c00,0x2c12,0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* Coptic */
        {0x2c80,0x3e2,0x1f00,0}, 2,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 2, 1, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      {
        /* Tifinagh */
        {0x2d30,0x2d4a,0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
      },
      {
        /* LRE/PDF */
        {LRE,PDF,'a','b','c','\r',0}, 3,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_NO_VISUAL },
              { 2, 3, DWRITE_SCRIPT_SHAPES_DEFAULT   },
              { 5, 1, DWRITE_SCRIPT_SHAPES_NO_VISUAL } }
      },
      {
        /* LRE/PDF and other visual and non-visual codes from Common script range */
        {LRE,PDF,'r','!',0x200b,'\r',0}, 3,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_NO_VISUAL },
              { 2, 2, DWRITE_SCRIPT_SHAPES_DEFAULT   },
              { 4, 2, DWRITE_SCRIPT_SHAPES_NO_VISUAL } }
      },
      {
        /* Inherited on its own */
        {0x300,0x300,0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      {
        /* Inherited followed by Latin */
        {0x300,0x300,'a',0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      {
        /* Inherited mixed with Arabic and Latin */
        {0x300,'+',0x627,0x300,'a',0}, 2,
            { { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 4, 1, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      {
        {'a',0x300,'+',0x627,0x300,')','a',0}, 3,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 3, 3, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 6, 1, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      /* Paired punctuation */
      {
        {0x627,'(','a',')','a',0}, 2,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 2, 3, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      {
        {0x627,'[','a',']',0x627,0}, 3,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 2, 2, DWRITE_SCRIPT_SHAPES_DEFAULT },
              { 4, 1, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      /* Combining marks */
      {
        /* dotted circle - Common, followed by accent - Inherited */
        {0x25cc,0x300,0}, 1,
            { { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      {
        /* combining mark with explicit script value */
        {0x25cc,0x300,0x5c4,0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      {
        /* inherited merges with following explicit script */
        {0x25cc,0x300,'a',0}, 1,
            { { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT } }
      },
      /* keep this as end test data marker */
      { {0} }
  };
  
  static void init_expected_sa(struct call_sequence **seq, const struct sa_test *test)
  {
      static const struct call_entry end_of_sequence = { LastKind };
      int i;
  
      flush_sequence(seq, ANALYZER_ID);
  
      /* add expected calls */
      for (i = 0; i < test->item_count; i++)
      {
          struct call_entry call;
  
          call.kind = ScriptAnalysis;
          call.sa.pos = test->sa[i].pos;
          call.sa.len = test->sa[i].len;
          call.sa.shapes = test->sa[i].shapes;
          add_call(seq, 0, &call);
      }
  
      /* and stop marker */
      add_call(seq, 0, &end_of_sequence);
  }
  
  static void get_script_analysis(const WCHAR *str, DWRITE_SCRIPT_ANALYSIS *sa)
  {
      IDWriteTextAnalyzer *analyzer;
      HRESULT hr;
  
      init_textsource(&analysissource, str, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      hr = IDWriteTextAnalyzer_AnalyzeScript(analyzer, &analysissource.IDWriteTextAnalysisSource_iface, 0,
          lstrlenW(analysissource.text), &analysissink2);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      *sa = g_sa;
  }
  
  static void test_AnalyzeScript(void)
  {
      const struct sa_test *ptr = sa_tests;
      IDWriteTextAnalyzer *analyzer;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      while (*ptr->string)
      {
          init_textsource(&analysissource, ptr->string, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
  
          init_expected_sa(expected_seq, ptr);
          hr = IDWriteTextAnalyzer_AnalyzeScript(analyzer, &analysissource.IDWriteTextAnalysisSource_iface, 0,
              lstrlenW(ptr->string), &analysissink);
          ok(hr == S_OK, "got 0x%08x\n", hr);
          ok_sequence(sequences, ANALYZER_ID, expected_seq[0]->sequence, wine_dbgstr_w(ptr->string), FALSE);
          ptr++;
      }
  
      IDWriteTextAnalyzer_Release(analyzer);
  }
  
  struct linebreaks_test {
      const WCHAR text[BREAKPOINT_COUNT+1];
      DWRITE_LINE_BREAKPOINT bp[BREAKPOINT_COUNT];
  };
  
  static const struct linebreaks_test linebreaks_tests[] =
  {
      { {'A','-','B',' ','C',0x58a,'D',0x2010,'E',0x2012,'F',0x2013,'\t',0xc,0xb,0x2028,0x2029,0x200b,0},
        {
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     1, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 1, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MUST_BREAK,    1, 0 },
            { DWRITE_BREAK_CONDITION_MUST_BREAK,    DWRITE_BREAK_CONDITION_MUST_BREAK,    1, 0 },
            { DWRITE_BREAK_CONDITION_MUST_BREAK,    DWRITE_BREAK_CONDITION_MUST_BREAK,    1, 0 },
            { DWRITE_BREAK_CONDITION_MUST_BREAK,    DWRITE_BREAK_CONDITION_MUST_BREAK,    1, 0 },
            { DWRITE_BREAK_CONDITION_MUST_BREAK,    DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
        }
      },
      /* Soft hyphen, visible word dividers */
      { {'A',0xad,'B',0x5be,'C',0xf0b,'D',0x1361,'E',0x17d8,'F',0x17da,'G',0},
        {
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 1 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_CAN_BREAK,     0, 0 },
        }
      },
      /* LB30 changes in Unicode 13 regarding East Asian parentheses */
      { {0x5f35,'G',0x300c,0},
        {
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK     },
            { DWRITE_BREAK_CONDITION_CAN_BREAK,     DWRITE_BREAK_CONDITION_MAY_NOT_BREAK },
            { DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK     },
        }
      },
      { { 0 } }
  };
  
  static void compare_breakpoints(const struct linebreaks_test *test, DWRITE_LINE_BREAKPOINT *actual)
  {
      static const char *conditions[] = {"N","CB","NB","B"};
      const WCHAR *text = test->text;
      int cmp = memcmp(test->bp, actual, sizeof(*actual)*BREAKPOINT_COUNT);
      ok(!cmp, "%s: got wrong breakpoint data\n", wine_dbgstr_w(test->text));
      if (cmp) {
          int i = 0;
          while (*text) {
              ok(!memcmp(&test->bp[i], &actual[i], sizeof(*actual)),
                  "%s: got [%s, %s] (%s, %s), expected [%s, %s] (%s, %s)\n",
                  wine_dbgstr_wn(&test->text[i], 1),
                  conditions[g_actual_bp[i].breakConditionBefore],
                  conditions[g_actual_bp[i].breakConditionAfter],
                  g_actual_bp[i].isWhitespace ? "WS"  : "0",
                  g_actual_bp[i].isSoftHyphen ? "SHY" : "0",
                  conditions[test->bp[i].breakConditionBefore],
                  conditions[test->bp[i].breakConditionAfter],
                  test->bp[i].isWhitespace ? "WS"  : "0",
                  test->bp[i].isSoftHyphen ? "SHY" : "0");
              if (g_actual_bp[i].isSoftHyphen)
                  ok(!g_actual_bp[i].isWhitespace, "%s: soft hyphen marked as a whitespace\n",
                      wine_dbgstr_wn(&test->text[i], 1));
              text++;
              i++;
          }
      }
  }
  
  static void test_AnalyzeLineBreakpoints(void)
  {
      const struct linebreaks_test *ptr = linebreaks_tests;
      IDWriteTextAnalyzer *analyzer;
      UINT32 i = 0;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      init_textsource(&analysissource, L"", DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
      hr = IDWriteTextAnalyzer_AnalyzeLineBreakpoints(analyzer, &analysissource.IDWriteTextAnalysisSource_iface, 0, 0,
          &analysissink);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      while (*ptr->text)
      {
          UINT32 len;
  
          init_textsource(&analysissource, ptr->text, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
  
          len = lstrlenW(ptr->text);
          if (len > BREAKPOINT_COUNT) {
              ok(0, "test %u: increase BREAKPOINT_COUNT to at least %u\n", i, len);
              i++;
              ptr++;
              continue;
          }
  
          memset(g_actual_bp, 0, sizeof(g_actual_bp));
          hr = IDWriteTextAnalyzer_AnalyzeLineBreakpoints(analyzer, &analysissource.IDWriteTextAnalysisSource_iface,
              0, len, &analysissink);
          ok(hr == S_OK, "got 0x%08x\n", hr);
          compare_breakpoints(ptr, g_actual_bp);
  
          i++;
          ptr++;
      }
  
      IDWriteTextAnalyzer_Release(analyzer);
  }
  
  static void test_GetScriptProperties(void)
  {
      IDWriteTextAnalyzer1 *analyzer1;
      IDWriteTextAnalyzer *analyzer;
      DWRITE_SCRIPT_ANALYSIS sa;
      DWRITE_SCRIPT_PROPERTIES props;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
      IDWriteTextAnalyzer_Release(analyzer);
      if (hr != S_OK) {
          win_skip("GetScriptProperties() is not supported.\n");
          return;
      }
  
      sa.script = 1000;
      hr = IDWriteTextAnalyzer1_GetScriptProperties(analyzer1, sa, &props);
      ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
  
      if (0) /* crashes on native */
          hr = IDWriteTextAnalyzer1_GetScriptProperties(analyzer1, sa, NULL);
  
      sa.script = 0;
      hr = IDWriteTextAnalyzer1_GetScriptProperties(analyzer1, sa, &props);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      IDWriteTextAnalyzer1_Release(analyzer1);
  }
  
  struct textcomplexity_test {
      const WCHAR text[5];
      UINT32 length;
      BOOL simple;
      UINT32 len_read;
  };
  
  static const struct textcomplexity_test textcomplexity_tests[] = {
      { {0},                     1, FALSE, 1 },
      { {0},                     0,  TRUE, 0 },
      { {0x610,0},               0,  TRUE, 0 },
      { {'A','B','C','D',0},     3,  TRUE, 3 },
      { {'A','B','C','D',0},     5,  TRUE, 4 },
      { {'A','B','C','D',0},    10,  TRUE, 4 },
      { {'A',0x610,'C','D',0},   1,  TRUE, 1 },
      { {'A',0x610,'C','D',0},   2, FALSE, 2 },
      { {0x610,'A','C','D',0},   1, FALSE, 1 },
      { {0x610,'A','C','D',0},   2, FALSE, 1 },
      { {0x610,0x610,'C','D',0}, 2, FALSE, 2 },
      { {0xd800,'A','B',0},      1, FALSE, 1 },
      { {0xd800,'A','B',0},      2, FALSE, 1 },
      { {0xdc00,'A','B',0},      2, FALSE, 1 },
      { {0x202a,'A',0x202c,0},   3, FALSE, 1 },
      { {0x200e,'A',0},          2, FALSE, 1 },
      { {0x200f,'A',0},          2, FALSE, 1 },
      { {0x202d,'A',0},          2, FALSE, 1 },
      { {0x202e,'A',0},          2, FALSE, 1 },
  
  };
  
  static void test_GetTextComplexity(void)
  {
      IDWriteTextAnalyzer1 *analyzer1;
      IDWriteTextAnalyzer *analyzer;
      IDWriteFontFace *fontface;
      UINT16 indices[10];
      BOOL simple;
      HRESULT hr;
      UINT32 len;
      int i;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
      IDWriteTextAnalyzer_Release(analyzer);
      if (hr != S_OK) {
          win_skip("GetTextComplexity() is not supported.\n");
          return;
      }
  
  if (0) { /* crashes on native */
      hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, NULL, 0, NULL, NULL, NULL, NULL);
      hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, NULL, 0, NULL, NULL, &len, NULL);
      hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, L"ABC", 3, NULL, NULL, NULL, NULL);
      hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, L"ABC", 3, NULL, NULL, &len, NULL);
      hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, L"ABC", 3, NULL, &simple, NULL, NULL);
  }
  
      len = 1;
      simple = TRUE;
      hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, NULL, 0, NULL, &simple, &len, NULL);
      ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
      ok(len == 0, "got %d\n", len);
      ok(simple == FALSE, "got %d\n", simple);
  
      len = 1;
      simple = TRUE;
      indices[0] = 1;
      hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, L"ABC", 3, NULL, &simple, &len, NULL);
      ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
      ok(len == 0, "got %d\n", len);
      ok(simple == FALSE, "got %d\n", simple);
      ok(indices[0] == 1, "got %d\n", indices[0]);
  
      fontface = create_fontface();
  
      for (i = 0; i < ARRAY_SIZE(textcomplexity_tests); i++) {
         const struct textcomplexity_test *ptr = &textcomplexity_tests[i];
         len = 1;
         simple = !ptr->simple;
         indices[0] = 0;
         hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, ptr->text, ptr->length, fontface, &simple, &len, indices);
         ok(hr == S_OK, "%d: got 0x%08x\n", i, hr);
         ok(len == ptr->len_read, "%d: read length: got %d, expected %d\n", i, len, ptr->len_read);
         ok(simple == ptr->simple, "%d: simple: got %d, expected %d\n", i, simple, ptr->simple);
         if (simple && ptr->length)
             ok(indices[0] > 0, "%d: got %d\n", i, indices[0]);
         else
             ok(indices[0] == 0, "%d: got %d\n", i, indices[0]);
      }
  
      IDWriteFontFace_Release(fontface);
      IDWriteTextAnalyzer1_Release(analyzer1);
  }
  
  static void test_numbersubstitution(void)
  {
      IDWriteNumberSubstitution *substitution;
      HRESULT hr;
  
      /* locale is not specified, method does not require it */
      hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, NULL, FALSE, &substitution);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      IDWriteNumberSubstitution_Release(substitution);
  
      /* invalid locale name, method does not require it */
      hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, L"dummy",
              FALSE, &substitution);
      ok(hr == S_OK, "Failed to create number substitution, hr %#x.\n", hr);
      IDWriteNumberSubstitution_Release(substitution);
  
      /* invalid method */
      hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_TRADITIONAL+1, NULL, FALSE, &substitution);
      ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
  
      /* invalid method */
      hr = IDWriteFactory_CreateNumberSubstitution(factory, -1, NULL, FALSE, &substitution);
      ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
  
      /* invalid locale */
      hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_TRADITIONAL, NULL, FALSE, &substitution);
      ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
  
      hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_TRADITIONAL, L"dummy",
              FALSE, &substitution);
      ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);
  
      hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_CONTEXTUAL, L"dummy",
              FALSE, &substitution);
      ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);
  
      hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_NATIONAL, L"dummy",
              FALSE, &substitution);
      ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);
  
      /* invalid locale, but it's not needed for this method */
      hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, L"dummy", FALSE,
              &substitution);
      ok(hr == S_OK, "Failed to create number substitution, hr %#x.\n", hr);
      IDWriteNumberSubstitution_Release(substitution);
  }
  
  static void get_fontface_glyphs(IDWriteFontFace *fontface, const WCHAR *str, UINT16 *glyphs)
  {
      while (*str) {
          UINT32 codepoint = *str;
          HRESULT hr;
  
          hr = IDWriteFontFace_GetGlyphIndices(fontface, &codepoint, 1, glyphs++);
          ok(hr == S_OK, "got 0x%08x\n", hr);
          str++;
      }
  }
  
  static void get_fontface_advances(IDWriteFontFace *fontface, FLOAT emsize, const UINT16 *glyphs, FLOAT *advances, UINT32 count)
  {
      DWRITE_FONT_METRICS fontmetrics;
      UINT32 i;
  
      IDWriteFontFace_GetMetrics(fontface, &fontmetrics);
      for (i = 0; i < count; i++) {
          DWRITE_GLYPH_METRICS metrics;
          HRESULT hr;
  
          hr = IDWriteFontFace_GetDesignGlyphMetrics(fontface, glyphs + i, 1, &metrics, FALSE);
          ok(hr == S_OK, "got 0x%08x\n", hr);
  
          advances[i] = (FLOAT)metrics.advanceWidth * emsize / (FLOAT)fontmetrics.designUnitsPerEm;
      }
  }
  
  enum ot_gdef_class
  {
      GDEF_CLASS_UNCLASSIFIED = 0,
      GDEF_CLASS_BASE = 1,
      GDEF_CLASS_LIGATURE = 2,
      GDEF_CLASS_MARK = 3,
      GDEF_CLASS_COMPONENT = 4,
      GDEF_CLASS_MAX = GDEF_CLASS_COMPONENT,
  };
  
  struct dwrite_fonttable
  {
      BYTE *data;
      void *context;
      unsigned int size;
  };
  
  static const void *table_read_ensure(const struct dwrite_fonttable *table, unsigned int offset, unsigned int size)
  {
      if (size > table->size || offset > table->size - size)
          return NULL;
  
      return table->data + offset;
  }
  
  static WORD table_read_be_word(const struct dwrite_fonttable *table, unsigned int offset)
  {
      const WORD *ptr = table_read_ensure(table, offset, sizeof(*ptr));
      return ptr ? GET_BE_WORD(*ptr) : 0;
  }
  
  static int gdef_class_compare_format2(const void *g, const void *r)
  {
      const struct ot_gdef_class_range *range = r;
      UINT16 glyph = *(UINT16 *)g;
  
      if (glyph < GET_BE_WORD(range->start_glyph))
          return -1;
      else if (glyph > GET_BE_WORD(range->end_glyph))
          return 1;
      else
          return 0;
  }
  
  static unsigned int get_glyph_class(const struct dwrite_fonttable *table, UINT16 glyph)
  {
      unsigned int glyph_class = GDEF_CLASS_UNCLASSIFIED, offset;
      WORD format, count;
  
      offset = table_read_be_word(table, 4);
  
      format = table_read_be_word(table, offset);
  
      if (format == 1)
      {
          const struct ot_gdef_classdef_format1 *format1;
  
          count = table_read_be_word(table, offset + FIELD_OFFSET(struct ot_gdef_classdef_format1, glyph_count));
          format1 = table_read_ensure(table, offset, FIELD_OFFSET(struct ot_gdef_classdef_format1, classes[count]));
          if (format1)
          {
              WORD start_glyph = GET_BE_WORD(format1->start_glyph);
              if (glyph >= start_glyph && (glyph - start_glyph) < count)
              {
                  glyph_class = GET_BE_WORD(format1->classes[glyph - start_glyph]);
                  if (glyph_class > GDEF_CLASS_MAX)
                       glyph_class = GDEF_CLASS_UNCLASSIFIED;
              }
          }
      }
      else if (format == 2)
      {
          const struct ot_gdef_classdef_format2 *format2;
  
          count = table_read_be_word(table, offset + FIELD_OFFSET(struct ot_gdef_classdef_format2, range_count));
          format2 = table_read_ensure(table, offset, FIELD_OFFSET(struct ot_gdef_classdef_format2, ranges[count]));
          if (format2)
          {
              const struct ot_gdef_class_range *range = bsearch(&glyph, format2->ranges, count,
                      sizeof(struct ot_gdef_class_range), gdef_class_compare_format2);
              glyph_class = range && glyph <= GET_BE_WORD(range->end_glyph) ?
                      GET_BE_WORD(range->glyph_class) : GDEF_CLASS_UNCLASSIFIED;
              if (glyph_class > GDEF_CLASS_MAX)
                   glyph_class = GDEF_CLASS_UNCLASSIFIED;
          }
      }
  
      return glyph_class;
  }
  
  static void get_enus_string(IDWriteLocalizedStrings *strings, WCHAR *buff, unsigned int size)
  {
      BOOL exists = FALSE;
      unsigned int index;
      HRESULT hr;
  
      hr = IDWriteLocalizedStrings_FindLocaleName(strings, L"en-us", &index, &exists);
      ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
  
      /* Not all fonts have an en-us name! */
      if (!exists)
          index = 0;
  
      hr = IDWriteLocalizedStrings_GetString(strings, index, buff, size);
      ok(hr == S_OK, "Failed to get name string, hr %#x.\n", hr);
  }
  
  static void test_glyph_props(IDWriteTextAnalyzer *analyzer, const WCHAR *family, const WCHAR *face,
          IDWriteFontFace *fontface)
  {
      unsigned int i, ch, count, offset;
      struct dwrite_fonttable gdef;
      DWRITE_UNICODE_RANGE *ranges;
      IDWriteFontFace1 *fontface1;
      BOOL exists = FALSE;
      HRESULT hr;
  
      hr = IDWriteFontFace_TryGetFontTable(fontface, MS_GDEF_TAG, (const void **)&gdef.data, &gdef.size,
              &gdef.context, &exists);
      ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
  
      if (!exists)
          return;
  
      offset = table_read_be_word(&gdef, 4);
      if (!offset)
      {
          IDWriteFontFace_ReleaseFontTable(fontface, gdef.context);
          return;
      }
  
      if (FAILED(IDWriteFontFace_QueryInterface(fontface, &IID_IDWriteFontFace1, (void **)&fontface1)))
      {
          IDWriteFontFace_ReleaseFontTable(fontface, gdef.context);
          return;
      }
  
      hr = IDWriteFontFace1_GetUnicodeRanges(fontface1, 0, NULL, &count);
      ok(hr == E_NOT_SUFFICIENT_BUFFER, "Unexpected hr %#x.\n", hr);
  
      ranges = malloc(count * sizeof(*ranges));
  
      hr = IDWriteFontFace1_GetUnicodeRanges(fontface1, count, ranges, &count);
      ok(hr == S_OK, "Failed to get ranges, hr %#x.\n", hr);
  
      for (i = 0; i < count; ++i)
      {
          if (ranges[i].first > 0xffff)
              break;
  
          for (ch = ranges[i].first; ch <= ranges[i].last; ch++)
          {
              DWRITE_SHAPING_TEXT_PROPERTIES text_props[10];
              DWRITE_SHAPING_GLYPH_PROPERTIES glyph_props[10];
              UINT16 glyphs[10], clustermap[10], glyph;
              unsigned int actual_count, glyph_class;
              DWRITE_SCRIPT_ANALYSIS sa;
              WCHAR text[1];
  
              hr = IDWriteFontFace1_GetGlyphIndices(fontface1, &ch, 1, &glyph);
              ok(hr == S_OK, "Failed to get glyph index, hr %#x.\n", hr);
  
              if (!glyph)
                  continue;
  
              sa.script = 999;
              sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT;
              text[0] = (WCHAR)ch;
              memset(glyph_props, 0, sizeof(glyph_props));
              hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, text, 1, fontface, FALSE, FALSE, &sa, NULL,
                      NULL, NULL, NULL, 0, ARRAY_SIZE(glyphs), clustermap, text_props, glyphs, glyph_props, &actual_count);
              ok(hr == S_OK, "Failed to shape, hr %#x.\n", hr);
              if (actual_count > 1)
                  continue;
  
              glyph_class = get_glyph_class(&gdef, glyphs[0]);
  
              switch (glyph_class)
              {
                  case GDEF_CLASS_MARK:
                      ok(glyph_props[0].isDiacritic && glyph_props[0].isZeroWidthSpace,
                              "%#x -> %u: unexpected glyph properties %u/%u. Class %u. Font %s - %s.\n",
                              text[0], glyphs[0], glyph_props[0].isDiacritic, glyph_props[0].isZeroWidthSpace,
                              glyph_class, wine_dbgstr_w(family), wine_dbgstr_w(face));
                      break;
                  default:
                      break;
              }
  
              if (glyph_props[0].isDiacritic)
                  ok(glyph_props[0].isZeroWidthSpace,
                          "%#x -> %u: unexpected glyph properties %u/%u. Class %u. Font %s - %s.\n", text[0], glyphs[0],
                          glyph_props[0].isDiacritic, glyph_props[0].isZeroWidthSpace, glyph_class,
                          wine_dbgstr_w(family), wine_dbgstr_w(face));
          }
      }
  
      free(ranges);
  
      IDWriteFontFace_ReleaseFontTable(fontface, gdef.context);
      IDWriteFontFace1_Release(fontface1);
  }
  
  static void test_GetGlyphs(void)
  {
      static const WCHAR test1W[] = {'<','B',' ','C',0};
      static const WCHAR test2W[] = {'<','B','\t','C',0};
      static const WCHAR test3W[] = {0x202a,0x202c,0};
      DWRITE_SHAPING_GLYPH_PROPERTIES shapingprops[20];
      DWRITE_SHAPING_TEXT_PROPERTIES props[20];
      UINT32 maxglyphcount, actual_count;
      FLOAT advances[10], advances2[10];
      IDWriteFontCollection *syscoll;
      IDWriteTextAnalyzer *analyzer;
      IDWriteFontFace *fontface;
      DWRITE_SCRIPT_ANALYSIS sa;
      DWRITE_GLYPH_OFFSET offsets[10];
      UINT16 clustermap[10];
      UINT16 glyphs1[10];
      UINT16 glyphs2[10];
      unsigned int i, j;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      fontface = create_fontface();
  
      maxglyphcount = 1;
      sa.script = 0;
      sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
      ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
  
  if (0) {
      /* NULL fontface - crashes on Windows */
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), NULL, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
  }
  
      /* invalid script id */
      maxglyphcount = 10;
      actual_count = 0;
      sa.script = 999;
      sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(actual_count == 4, "got %d\n", actual_count);
      ok(sa.script == 999, "got %u\n", sa.script);
  
      /* no '\t' -> ' ' replacement */
      maxglyphcount = 10;
      actual_count = 0;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(actual_count == 4, "got %d\n", actual_count);
  
      actual_count = 0;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test2W, lstrlenW(test2W), fontface, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs2, shapingprops, &actual_count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(actual_count == 4, "got %d\n", actual_count);
      ok(glyphs1[2] != glyphs2[2], "got %d\n", glyphs1[2]);
  
      /* check that mirroring works */
      maxglyphcount = 10;
      actual_count = 0;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(actual_count == 4, "got %d\n", actual_count);
  
      actual_count = 0;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, TRUE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs2, shapingprops, &actual_count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(actual_count == 4, "got %d\n", actual_count);
      ok(glyphs1[0] != glyphs2[0], "got %d\n", glyphs1[0]);
  
      /* embedded control codes, with unknown script id 0 */
      get_fontface_glyphs(fontface, test3W, glyphs2);
      get_fontface_advances(fontface, 10.0, glyphs2, advances2, 2);
  
      actual_count = 0;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test3W, lstrlenW(test3W), fontface, FALSE, TRUE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(actual_count == 2, "got %d\n", actual_count);
      ok(glyphs1[0] == glyphs2[0], "got %u, expected %u\n", glyphs1[0], glyphs2[0]);
      ok(glyphs1[1] == glyphs2[1], "got %u, expected %u\n", glyphs1[1], glyphs2[1]);
      ok(shapingprops[0].isClusterStart == 1, "got %d\n", shapingprops[0].isClusterStart);
      ok(shapingprops[0].isZeroWidthSpace == 0, "got %d\n", shapingprops[0].isZeroWidthSpace);
      ok(shapingprops[1].isClusterStart == 1, "got %d\n", shapingprops[1].isClusterStart);
      ok(shapingprops[1].isZeroWidthSpace == 0, "got %d\n", shapingprops[1].isZeroWidthSpace);
      ok(clustermap[0] == 0, "got %d\n", clustermap[0]);
      ok(clustermap[1] == 1, "got %d\n", clustermap[1]);
  
      memset(advances, 0, sizeof(advances));
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, test3W, clustermap, props, lstrlenW(test3W),
          glyphs1, shapingprops, actual_count, fontface, 10.0, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == advances2[0], "got %.2f, expected %.2f\n", advances[0], advances2[0]);
      ok(advances[1] == advances2[1], "got %.2f, expected %.2f\n", advances[1], advances2[1]);
  
      /* embedded control codes with proper script */
      sa.script = 0;
      get_script_analysis(test3W, &sa);
      ok(sa.script != 0, "got %d\n", sa.script);
      actual_count = 0;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test3W, lstrlenW(test3W), fontface, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(actual_count == 2, "got %d\n", actual_count);
      ok(glyphs1[0] == glyphs2[0], "got %u, expected %u\n", glyphs1[0], glyphs2[0]);
      ok(glyphs1[1] == glyphs2[1], "got %u, expected %u\n", glyphs1[1], glyphs2[1]);
      ok(shapingprops[0].isClusterStart == 1, "got %d\n", shapingprops[0].isClusterStart);
      ok(shapingprops[0].isZeroWidthSpace == 0, "got %d\n", shapingprops[0].isZeroWidthSpace);
      ok(shapingprops[1].isClusterStart == 1, "got %d\n", shapingprops[1].isClusterStart);
      ok(shapingprops[1].isZeroWidthSpace == 0, "got %d\n", shapingprops[1].isZeroWidthSpace);
      ok(clustermap[0] == 0, "got %d\n", clustermap[0]);
      ok(clustermap[1] == 1, "got %d\n", clustermap[1]);
  
      memset(advances, 0, sizeof(advances));
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, test3W, clustermap, props, lstrlenW(test3W),
          glyphs1, shapingprops, actual_count, fontface, 10.0, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == advances2[0], "got %.2f, expected %.2f\n", advances[0], advances2[0]);
      ok(advances[1] == advances2[1], "got %.2f, expected %.2f\n", advances[1], advances2[1]);
  
      /* DWRITE_SCRIPT_SHAPES_NO_VISUAL run */
      maxglyphcount = 10;
      actual_count = 0;
      sa.script = 0;
      sa.shapes = DWRITE_SCRIPT_SHAPES_NO_VISUAL;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(actual_count == 4, "got %d\n", actual_count);
      ok(sa.script == 0, "got %u\n", sa.script);
      ok(!shapingprops[0].isZeroWidthSpace, "got %d\n", shapingprops[0].isZeroWidthSpace);
  
      IDWriteFontFace_Release(fontface);
  
      /* Test setting glyph properties from GDEF. */
      hr = IDWriteFactory_GetSystemFontCollection(factory, &syscoll, FALSE);
      ok(hr == S_OK, "Failed to get system collection, hr %#x.\n", hr);
  
      for (i = 0; i < IDWriteFontCollection_GetFontFamilyCount(syscoll); ++i)
      {
          IDWriteLocalizedStrings *names;
          IDWriteFontFamily *family;
          WCHAR familyW[256];
  
          hr = IDWriteFontCollection_GetFontFamily(syscoll, i, &family);
          ok(hr == S_OK, "Failed to get font family, hr %#x.\n", hr);
  
          hr = IDWriteFontFamily_GetFamilyNames(family, &names);
          ok(hr == S_OK, "Failed to get family names, hr %#x.\n", hr);
          get_enus_string(names, familyW, ARRAY_SIZE(familyW));
          IDWriteLocalizedStrings_Release(names);
  
          for (j = 0; j < IDWriteFontFamily_GetFontCount(family); ++j)
          {
              IDWriteFont *font;
              WCHAR faceW[256];
  
              hr = IDWriteFontFamily_GetFont(family, j, &font);
              ok(hr == S_OK, "Failed to get font instance, hr %#x.\n", hr);
  
              hr = IDWriteFont_CreateFontFace(font, &fontface);
              ok(hr == S_OK, "Failed to create fontface, hr %#x.\n", hr);
  
              hr = IDWriteFont_GetFaceNames(font, &names);
              ok(hr == S_OK, "Failed to get face names, hr %#x.\n", hr);
              get_enus_string(names, faceW, ARRAY_SIZE(faceW));
              IDWriteLocalizedStrings_Release(names);
  
              test_glyph_props(analyzer, familyW, faceW, fontface);
  
              IDWriteFontFace_Release(fontface);
              IDWriteFont_Release(font);
          }
  
          IDWriteFontFamily_Release(family);
      }
  
      IDWriteFontCollection_Release(syscoll);
  
      IDWriteTextAnalyzer_Release(analyzer);
  }
  
  static void test_GetTypographicFeatures(void)
  {
      static const WCHAR arabicW[] = {0x064a,0x064f,0x0633,0};
      DWRITE_FONT_FEATURE_TAG tags[20];
      IDWriteTextAnalyzer2 *analyzer2;
      IDWriteTextAnalyzer *analyzer;
      IDWriteFontFace *fontface;
      DWRITE_SCRIPT_ANALYSIS sa;
      UINT32 count;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer2, (void**)&analyzer2);
      IDWriteTextAnalyzer_Release(analyzer);
      if (hr != S_OK) {
          win_skip("GetTypographicFeatures() is not supported.\n");
          return;
      }
  
      fontface = create_fontface();
  
      get_script_analysis(L"abc", &sa);
      count = 0;
      hr = IDWriteTextAnalyzer2_GetTypographicFeatures(analyzer2, fontface, sa, NULL, 0, &count, NULL);
      ok(hr == E_NOT_SUFFICIENT_BUFFER, "Unexpected hr %#x.\n", hr);
      ok(!!count, "Unexpected count %u.\n", count);
  
      /* invalid locale name is ignored */
      get_script_analysis(L"abc", &sa);
      count = 0;
      hr = IDWriteTextAnalyzer2_GetTypographicFeatures(analyzer2, fontface, sa, L"cadabra", 0, &count, NULL);
      ok(hr == E_NOT_SUFFICIENT_BUFFER, "Unexpected hr %#x.\n", hr);
      ok(!!count, "Unexpected count %u.\n", count);
  
      /* Make some calls for different scripts. */
  
      get_script_analysis(arabicW, &sa);
      memset(tags, 0, sizeof(tags));
      count = 0;
      hr = IDWriteTextAnalyzer2_GetTypographicFeatures(analyzer2, fontface, sa, NULL, ARRAY_SIZE(tags), &count, tags);
      ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
      ok(!!count, "Unexpected count %u.\n", count);
  
      get_script_analysis(L"abc", &sa);
      memset(tags, 0, sizeof(tags));
      count = 0;
      hr = IDWriteTextAnalyzer2_GetTypographicFeatures(analyzer2, fontface, sa, NULL, ARRAY_SIZE(tags), &count, tags);
      ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
      ok(!!count, "Unexpected count %u.\n", count);
  
      IDWriteFontFace_Release(fontface);
      IDWriteTextAnalyzer2_Release(analyzer2);
  }
  
  static void test_GetGlyphPlacements(void)
  {
      DWRITE_SHAPING_GLYPH_PROPERTIES glyphprops[2];
      DWRITE_SHAPING_TEXT_PROPERTIES textprops[2];
      static const WCHAR aW[] = {'A','D',0};
      UINT16 clustermap[2], glyphs[2];
      DWRITE_GLYPH_OFFSET offsets[2];
      IDWriteTextAnalyzer *analyzer;
      IDWriteFontFace *fontface;
      DWRITE_SCRIPT_ANALYSIS sa;
      FLOAT advances[2];
      UINT32 count, len;
      WCHAR *path;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      path = create_testfontfile(L"wine_test_font.ttf");
      fontface = create_testfontface(path);
  
      get_script_analysis(aW, &sa);
      count = 0;
      len = lstrlenW(aW);
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, aW, len, fontface, FALSE, FALSE, &sa, NULL,
          NULL, NULL, NULL, 0, len, clustermap, textprops, glyphs, glyphprops, &count);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(count == 2, "got %u\n", count);
  
      /* just return on zero glyphs */
      advances[0] = advances[1] = 1.0;
      offsets[0].advanceOffset = offsets[0].ascenderOffset = 2.0;
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
          len, glyphs, glyphprops, 0, fontface, 0.0, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == 1.0, "got %.2f\n", advances[0]);
      ok(offsets[0].advanceOffset == 2.0 && offsets[0].ascenderOffset == 2.0, "got %.2f,%.2f\n",
          offsets[0].advanceOffset, offsets[0].ascenderOffset);
  
      /* advances/offsets are scaled with provided font emSize and designed eM box size */
      advances[0] = advances[1] = 1.0;
      memset(offsets, 0xcc, sizeof(offsets));
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
          len, glyphs, glyphprops, len, fontface, 0.0, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == 0.0, "got %.2f\n", advances[0]);
      ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
          offsets[0].advanceOffset, offsets[0].ascenderOffset);
  
      advances[0] = advances[1] = 1.0;
      memset(offsets, 0xcc, sizeof(offsets));
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
          len, glyphs, glyphprops, len, fontface, 2048.0, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == 1000.0, "got %.2f\n", advances[0]);
      ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
          offsets[0].advanceOffset, offsets[0].ascenderOffset);
  
      advances[0] = advances[1] = 1.0;
      memset(offsets, 0xcc, sizeof(offsets));
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
          len, glyphs, glyphprops, len, fontface, 1024.0, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == 500.0, "got %.2f\n", advances[0]);
      ok(advances[1] == 500.0, "got %.2f\n", advances[1]);
      ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
          offsets[0].advanceOffset, offsets[0].ascenderOffset);
  
      advances[0] = advances[1] = 1.0;
      memset(offsets, 0xcc, sizeof(offsets));
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
          len, glyphs, glyphprops, len, fontface, 20.48, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == 10.0, "got %.2f\n", advances[0]);
      ok(advances[1] == 10.0, "got %.2f\n", advances[1]);
      ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
          offsets[0].advanceOffset, offsets[0].ascenderOffset);
  
      /* without clustermap */
      advances[0] = advances[1] = 1.0;
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, NULL, textprops,
          len, glyphs, glyphprops, len, fontface, 1024.0, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == 500.0, "got %.2f\n", advances[0]);
      ok(advances[1] == 500.0, "got %.2f\n", advances[1]);
  
      /* it's happy to use negative size too */
      advances[0] = advances[1] = 1.0;
      memset(offsets, 0xcc, sizeof(offsets));
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
          len, glyphs, glyphprops, len, fontface, -10.24, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == -5.0, "got %.2f\n", advances[0]);
      ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
          offsets[0].advanceOffset, offsets[0].ascenderOffset);
  
      /* DWRITE_SCRIPT_SHAPES_NO_VISUAL has no effect on placement */
      sa.shapes = DWRITE_SCRIPT_SHAPES_NO_VISUAL;
      advances[0] = advances[1] = 1.0f;
      memset(offsets, 0xcc, sizeof(offsets));
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
          len, glyphs, glyphprops, len, fontface, 2048.0f, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == 1000.0f, "got %.2f\n", advances[0]);
      ok(advances[1] == 1000.0f, "got %.2f\n", advances[1]);
      ok(offsets[0].advanceOffset == 0.0f && offsets[0].ascenderOffset == 0.0f, "got %.2f,%.2f\n",
          offsets[0].advanceOffset, offsets[0].ascenderOffset);
  
      /* isZeroWidthSpace */
      sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT;
      advances[0] = advances[1] = 1.0f;
      memset(offsets, 0xcc, sizeof(offsets));
      glyphprops[0].isZeroWidthSpace = 1;
      hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
          len, glyphs, glyphprops, len, fontface, 2048.0f, FALSE, FALSE, &sa, NULL, NULL,
          NULL, 0, advances, offsets);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      ok(advances[0] == 0.0f, "got %.2f\n", advances[0]);
      ok(advances[1] == 1000.0f, "got %.2f\n", advances[1]);
      ok(offsets[0].advanceOffset == 0.0f && offsets[0].ascenderOffset == 0.0f, "got %.2f,%.2f\n",
          offsets[0].advanceOffset, offsets[0].ascenderOffset);
  
      IDWriteTextAnalyzer_Release(analyzer);
      IDWriteFontFace_Release(fontface);
      DELETE_FONTFILE(path);
  }
  
  struct spacing_test {
      FLOAT leading;
      FLOAT trailing;
      FLOAT min_advance;
      FLOAT advances[3];
      FLOAT offsets[3];
      FLOAT modified_advances[3];
      FLOAT modified_offsets[3];
      BOOL  single_cluster;
      DWRITE_SHAPING_GLYPH_PROPERTIES props[3];
  };
  
  static const struct spacing_test spacing_tests[] =
  {
  /* Default spacing glyph properties. */
  #define P_S { 0 }
  /* isZeroWidthSpace */
  #define P_Z { 0, 0, 0, 1, 0 }
  /* isDiacritic */
  #define P_D { 0, 0, 1, 0, 0 }
  /* isDiacritic + isZeroWidthSpace, that's how diacritics are shaped. */
  #define P_D_Z { 0, 0, 1, 1, 0 }
  
      {   0.0,   0.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 } }, /* 0 */
      {   0.0,   0.0,  2.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 } },
      {   1.0,   0.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 12.0 }, {  3.0,  4.0 } },
      {   1.0,   1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 12.0, 13.0 }, {  3.0,  4.0 } },
      {   1.0,  -1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  3.0,  4.0 } },
      {   0.0, -10.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  1.0 }, {  2.0,  3.0 } }, /* 5 */
      {  -5.0,  -4.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0,  5.0 }, { -1.0, -0.5 } },
      {  -5.0,  -5.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0,  5.0 }, { -0.5,  0.0 } },
      {   2.0,   0.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  7.0,  7.0 }, {  6.0,  6.5 } },
      {   2.0,   1.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  8.0,  8.0 }, {  6.0,  6.5 } },
      {   2.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  7.0,  7.0 }, {  4.0,  5.0 } }, /* 10 */
      {   1.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  6.0,  6.0 }, {  3.0,  4.0 } },
      { -10.0,   1.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  6.0,  6.0 }, { -3.0, -3.0 } },
      {   0.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0,  5.0 }, {  2.0,  3.0 } },
      {   1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  0.0 }, {  2.0,  3.0 } },
      { -10.0,   1.0,  5.0, {  8.0, 11.0 }, { 2.0, 3.0 }, {  6.0,  6.0 }, { -1.0, -3.0 } }, /* 15 */
      /* cluster of more than 1 glyph */
      {  0.0f,   0.0f,  0.0f, { 10.0f, 11.0f }, { 2.0f, 3.0f }, { 10.0f, 11.0f }, {  2.0f, 3.0f }, TRUE },
      {  1.0f,   0.0f,  0.0f, { 10.0f, 11.0f }, { 2.0f, 3.5f }, { 11.0f, 11.0f }, {  3.0f, 3.5f }, TRUE },
      {  1.0f,   1.0f,  0.0f, { 10.0f, 11.0f }, { 2.0f, 3.0f }, { 11.0f, 12.0f }, {  3.0f, 3.0f }, TRUE },
      {   1.0,  -1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 10.0 }, {  3.0, 3.0 }, TRUE },
      {   0.0, -10.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0,  1.0 }, {  2.0, 3.0 }, TRUE }, /* 20 */
      {   0.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0,  1.0 }, {  2.0, 3.0 }, TRUE },
      {   1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  0.0 }, {  2.0, 3.0 }, TRUE },
      {  -5.0, -10.0,  4.0, { 10.0, 11.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 5.0, 11.0, 2.0 }, { -3.0, 3.0, 4.0 }, TRUE },
      { -10.0, -10.0,  4.0, { 10.0, 11.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 0.0, 11.0, 2.0 }, { -8.0, 3.0, 4.0 }, TRUE },
      { -10.0, -10.0,  5.0, { 10.0,  1.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 1.0,  1.0, 3.0 }, { -7.0, 3.0, 4.0 }, TRUE }, /* 25 */
      { -10.0,   1.0,  5.0, { 10.0,  1.0,  2.0 }, { 2.0, 3.0, 4.0 }, { 2.0,  1.0, 3.0 }, { -6.0, 3.0, 4.0 }, TRUE },
      {   1.0, -10.0,  5.0, {  2.0,  1.0, 10.0 }, { 2.0, 3.0, 4.0 }, { 3.0,  1.0, 2.0 }, {  3.0, 3.0, 4.0 }, TRUE },
      { -10.0, -10.0,  5.0, { 11.0,  1.0, 11.0 }, { 2.0, 3.0, 4.0 }, { 2.0,  1.0, 2.0 }, { -7.0, 3.0, 4.0 }, TRUE },
      /* isZeroWidthSpace set */
      {   1.0,   0.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 12.0 }, {  2.0,  4.0 }, FALSE, { P_Z, P_S } },
      {   1.0,   1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 13.0 }, {  2.0,  4.0 }, FALSE, { P_Z, P_S } }, /* 30 */
      {   1.0,  -1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  3.0,  3.0 }, FALSE, { P_S, P_Z } },
      {   0.0, -10.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0,  1.0 }, {  2.0,  3.0 }, FALSE, { P_Z, P_S } },
      {  -5.0,  -4.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0, 11.0 }, { -1.0,  3.0 }, FALSE, { P_S, P_Z } },
      {  -5.0,  -5.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, FALSE, { P_Z, P_Z } },
      {   2.0,   0.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  7.0,  2.0 }, {  6.0,  3.0 }, FALSE, { P_S, P_Z } }, /* 35 */
      {   2.0,   1.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  8.0,  2.0 }, {  6.0,  3.0 }, FALSE, { P_S, P_Z } },
      {   2.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, FALSE, { P_Z, P_Z } },
      {   1.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  6.0, 11.0 }, {  3.0,  3.0 }, FALSE, { P_S, P_Z } },
      { -10.0,   1.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, FALSE, { P_Z, P_Z } },
      {   0.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0, 11.0 }, {  2.0,  3.0 }, FALSE, { P_S, P_Z } }, /* 40 */
      {   1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  0.0 }, {  2.0,  3.0 }, FALSE, { P_Z, P_S } },
      { -10.0,   1.0,  5.0, {  8.0, 11.0 }, { 2.0, 3.0 }, {  6.0, 11.0 }, { -1.0,  3.0 }, FALSE, { P_S, P_Z } },
      /* isDiacritic */
      {   1.0,   0.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 12.0 }, {  3.0,  4.0 }, FALSE, { P_D, P_S } },
      {   1.0,   1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 12.0, 13.0 }, {  3.0,  4.0 }, FALSE, { P_D, P_S } },
      {   1.0,  -1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  3.0,  4.0 }, FALSE, { P_S, P_D } }, /* 45 */
      {   0.0, -10.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  1.0 }, {  2.0,  3.0 }, FALSE, { P_D, P_S } },
      {  -5.0,  -4.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0,  5.0 }, { -1.0, -0.5 }, FALSE, { P_S, P_D } },
      {  -5.0,  -5.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0,  5.0 }, { -0.5,  0.0 }, FALSE, { P_D, P_D } },
      {   2.0,   0.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  7.0,  7.0 }, {  6.0,  6.5 }, FALSE, { P_S, P_D } },
      {   2.0,   1.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  8.0,  8.0 }, {  6.0,  6.5 }, FALSE, { P_S, P_D } }, /* 50 */
      {   2.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  7.0,  7.0 }, {  4.0,  5.0 }, FALSE, { P_D, P_D } },
      {   1.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  6.0,  6.0 }, {  3.0,  4.0 }, FALSE, { P_S, P_D } },
      { -10.0,   1.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  6.0,  6.0 }, { -3.0, -3.0 }, FALSE, { P_D, P_D } },
      {   0.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0,  5.0 }, {  2.0,  3.0 }, FALSE, { P_S, P_D } },
      {   1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  0.0 }, {  2.0,  3.0 }, FALSE, { P_D, P_S } }, /* 55 */
      { -10.0,   1.0,  5.0, {  8.0, 11.0 }, { 2.0, 3.0 }, {  6.0,  6.0 }, { -1.0, -3.0 }, FALSE, { P_S, P_D } },
      /* isZeroWidthSpace in a cluster */
      {   1.0,   0.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 12.0 }, {  3.0,  4.0 }, TRUE, { P_Z, P_S } },
      {   1.0,   1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 13.0 }, {  3.0,  4.0 }, TRUE, { P_Z, P_S } },
      {   1.0,  -1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  3.0,  4.0 }, TRUE, { P_S, P_Z } },
      {   0.0, -10.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0,  1.0 }, {  2.0,  3.0 }, TRUE, { P_Z, P_S } }, /* 60 */
      {  -5.0,  -4.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  1.0, 11.0 }, { -3.0,  7.0 }, TRUE, { P_S, P_Z } },
      {  -5.0,  -5.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, TRUE, { P_Z, P_Z } },
      {   0.0,   0.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  3.0,  2.0 }, {  3.0,  2.0 }, TRUE, { P_S, P_Z } },
      {   0.0,   0.0,  5.0, {  1.0,  2.0, 1.0 }, { 2.0, 3.0, 5.0 }, { 1.5, 2.0, 1.5 }, { 2.5, 3.0, 5.0 }, TRUE, { P_S, P_Z, P_S } },
      {   0.0,   0.0,  5.0, {  1.0,  2.0, 1.0 }, { 2.0, 3.0, 5.0 }, { 1.5, 2.5, 1.0 }, { 2.5, 3.0, 4.5 }, TRUE, { P_S, P_S, P_Z } },
      {   0.0,   0.0,  5.0, {  1.0,  2.0, 1.0 }, { 2.0, 3.0, 5.0 }, { 2.0, 2.0, 1.0 }, { 2.5, 2.5, 4.5 }, TRUE, { P_S, P_Z, P_Z } },
      {   0.0,   0.0,  5.0, {  1.0,  2.0, 1.0 }, { 2.0, 3.0, 5.0 }, { 1.0, 2.0, 1.0 }, { 2.0, 3.0, 5.0 }, TRUE, { P_Z, P_Z, P_Z } },
      {   2.0,   1.0,  1.0, {  1.0,  2.0, 3.0 }, { 2.0, 3.0, 4.0 }, {  3.0,  2.0, 4.0 }, {  4.0,  3.0, 4.0 }, TRUE, { P_S, P_Z, P_S } },
      {   0.0,   0.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, { 3.0, 2.0 }, { 3.0, 2.0 }, TRUE, { P_S, P_Z } },
      {   0.0,   0.0,  5.0, {  1.0,  2.0, 6.0 }, { 2.0, 3.0, 4.0 }, { 1.0, 2.0, 6.0 }, { 2.0, 3.0, 4.0 }, TRUE, { P_S, P_Z, P_S } },
      {   2.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, TRUE, { P_Z, P_Z } }, /* 65 */
      {   1.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  1.0, 11.0 }, {  3.0, 13.0 }, TRUE, { P_S, P_Z } },
      { -10.0,   1.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, TRUE, { P_Z, P_Z } },
      {   0.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0, 11.0 }, {  2.0, 13.0 }, TRUE, { P_S, P_Z } },
      {   1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  0.0 }, {  2.0,  3.0 }, TRUE, { P_Z, P_S } },
      { -10.0,   1.0,  5.0, {  8.0, 11.0 }, { 2.0, 3.0 }, { -1.0, 11.0 }, { -8.0,  2.0 }, TRUE, { P_S, P_Z } }, /* 70 */
      /* isDiacritic in a cluster */
      {   1.0,   0.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 11.0 }, {  3.0,  3.0 }, TRUE, { P_D, P_S } },
      {   1.0,   1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 12.0 }, {  3.0,  3.0 }, TRUE, { P_D, P_S } },
      {   1.0,  -1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 10.0 }, {  3.0,  3.0 }, TRUE, { P_S, P_D } },
      {   0.0, -10.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0,  1.0 }, {  2.0,  3.0 }, TRUE, { P_D, P_S } },
      {  -5.0,  -4.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0,  7.0 }, { -3.0,  3.0 }, TRUE, { P_S, P_D } }, /* 75 */
      {  -5.0,  -5.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0,  6.0 }, { -3.0,  3.0 }, TRUE, { P_D, P_D } },
      {   2.0,   0.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  4.0,  3.0 }, {  5.0,  3.0 }, TRUE, { P_S, P_D } },
      {   2.0,   1.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  4.0,  4.0 }, {  5.0,  3.0 }, TRUE, { P_S, P_D } },
      {   2.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 12.0,  1.0 }, {  4.0,  3.0 }, TRUE, { P_D, P_D } },
      {   1.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0,  1.0 }, {  3.0,  3.0 }, TRUE, { P_S, P_D } }, /* 80 */
      { -10.0,   1.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0, 12.0 }, { -8.0,  3.0 }, TRUE, { P_D, P_D } },
      {   0.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0,  1.0 }, {  2.0,  3.0 }, TRUE, { P_S, P_D } },
      {   1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  0.0 }, {  2.0,  3.0 }, TRUE, { P_D, P_S } },
      { -10.0,   1.0,  5.0, {  8.0, 11.0 }, { 2.0, 3.0 }, { -2.0, 12.0 }, { -8.0,  3.0 }, TRUE, { P_S, P_D } },
      /* isZeroWidthSpace + isDiacritic */
      {   1.0,   0.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 12.0 }, {  2.0,  4.0 }, FALSE, { P_D_Z, P_S } }, /* 85 */
      {   1.0,   1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 13.0 }, {  2.0,  4.0 }, FALSE, { P_D_Z, P_S } },
      {   1.0,  -1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  3.0,  3.0 }, FALSE, { P_S, P_D_Z } },
      {   0.0, -10.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0,  1.0 }, {  2.0,  3.0 }, FALSE, { P_D_Z, P_S } },
      {  -5.0,  -4.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0, 11.0 }, { -1.0,  3.0 }, FALSE, { P_S, P_D_Z } },
      {  -5.0,  -5.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, FALSE, { P_D_Z, P_D_Z } }, /* 90 */
      {   2.0,   0.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  7.0,  2.0 }, {  6.0,  3.0 }, FALSE, { P_S, P_D_Z } },
      {   2.0,   1.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  8.0,  2.0 }, {  6.0,  3.0 }, FALSE, { P_S, P_D_Z } },
      {   2.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, FALSE, { P_D_Z, P_D_Z } },
      {   1.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  6.0, 11.0 }, {  3.0,  3.0 }, FALSE, { P_S, P_D_Z } },
      { -10.0,   1.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, FALSE, { P_D_Z, P_D_Z } }, /* 95 */
      {   0.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  5.0, 11.0 }, {  2.0,  3.0 }, FALSE, { P_S, P_D_Z } },
      {   1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  0.0 }, {  2.0,  3.0 }, FALSE, { P_D_Z, P_S } },
      { -10.0,   1.0,  5.0, {  8.0, 11.0 }, { 2.0, 3.0 }, {  6.0, 11.0 }, { -1.0,  3.0 }, FALSE, { P_S, P_D_Z } },
      /* isZeroWidthSpace + isDiacritic in a cluster */
      {   1.0,   0.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 12.0 }, {  3.0,  4.0 }, TRUE, { P_D_Z, P_S } },
      {   1.0,   1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 13.0 }, {  3.0,  4.0 }, TRUE, { P_D_Z, P_S } }, /* 100 */
      {   1.0,  -1.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  3.0,  4.0 }, TRUE, { P_S, P_D_Z } },
      {   0.0, -10.0,  0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0,  1.0 }, {  2.0,  3.0 }, TRUE, { P_D_Z, P_S } },
      {  -5.0,  -4.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  1.0, 11.0 }, { -3.0,  7.0 }, TRUE, { P_S, P_D_Z } },
      {  -5.0,  -5.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, TRUE, { P_D_Z, P_D_Z } },
      {   2.0,   0.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  5.0,  2.0 }, {  5.0,  2.0 }, TRUE, { P_S, P_D_Z } }, /* 105 */
      {   2.0,   1.0,  5.0, {  1.0,  2.0 }, { 2.0, 3.0 }, {  6.0,  2.0 }, {  5.0,  1.0 }, TRUE, { P_S, P_D_Z } },
      {   2.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, TRUE, { P_D_Z, P_D_Z } },
      {   1.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  1.0, 11.0 }, {  3.0, 13.0 }, TRUE, { P_S, P_D_Z } },
      { -10.0,   1.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, {  2.0,  3.0 }, TRUE, { P_D_Z, P_D_Z } },
      {   0.0, -10.0,  5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0, 11.0 }, {  2.0, 13.0 }, TRUE, { P_S, P_D_Z } }, /* 110 */
      {   1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, {  0.0,  0.0 }, {  2.0,  3.0 }, TRUE, { P_D_Z, P_S } },
      { -10.0,   1.0,  5.0, {  8.0, 11.0 }, { 2.0, 3.0 }, { -1.0, 11.0 }, { -8.0,  2.0 }, TRUE, { P_S, P_D_Z } },
  
  #undef P_S
  #undef P_D
  #undef P_Z
  #undef P_D_Z
  };
  
  static void test_ApplyCharacterSpacing(void)
  {
      DWRITE_SHAPING_GLYPH_PROPERTIES props[3];
      IDWriteTextAnalyzer1 *analyzer1;
      IDWriteTextAnalyzer *analyzer;
      UINT16 clustermap[2];
      HRESULT hr;
      int i;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
      IDWriteTextAnalyzer_Release(analyzer);
      if (hr != S_OK) {
          win_skip("ApplyCharacterSpacing() is not supported.\n");
          return;
      }
  
      for (i = 0; i < ARRAY_SIZE(spacing_tests); ++i)
      {
          const struct spacing_test *ptr = spacing_tests + i;
          DWRITE_GLYPH_OFFSET offsets[3];
          UINT32 glyph_count;
          FLOAT advances[3];
  
          offsets[0].advanceOffset = ptr->offsets[0];
          offsets[1].advanceOffset = ptr->offsets[1];
          offsets[2].advanceOffset = ptr->offsets[2];
          /* Ascender offsets are never touched as spacing applies in reading direction only,
             we'll only test them to see if they are not changed */
          offsets[0].ascenderOffset = 23.0;
          offsets[1].ascenderOffset = 32.0;
          offsets[2].ascenderOffset = 31.0;
  
          advances[0] = advances[1] = 123.45f;
          memcpy(props, ptr->props, sizeof(props));
          glyph_count = ptr->advances[2] > 0.0 ? 3 : 2;
          if (ptr->single_cluster)
          {
              clustermap[0] = 0;
              clustermap[1] = 0;
              props[0].isClusterStart = 1;
          }
          else
          {
              /* trivial case with one glyph per cluster */
              clustermap[0] = 0;
              clustermap[1] = 1;
              props[0].isClusterStart = props[1].isClusterStart = 1;
          }
  
          winetest_push_context("Test %u", i);
  
          hr = IDWriteTextAnalyzer1_ApplyCharacterSpacing(analyzer1,
              ptr->leading,
              ptr->trailing,
              ptr->min_advance,
              ARRAY_SIZE(clustermap),
              glyph_count,
              clustermap,
              ptr->advances,
              offsets,
              props,
              advances,
              offsets);
          ok(hr == (ptr->min_advance < 0.0f ? E_INVALIDARG : S_OK), "Unexpected hr %#x.\n", hr);
  
          if (hr == S_OK) {
              ok(ptr->modified_advances[0] == advances[0], "Got advance[0] %.2f, expected %.2f.\n", advances[0], ptr->modified_advances[0]);
              ok(ptr->modified_advances[1] == advances[1], "Got advance[1] %.2f, expected %.2f.\n", advances[1], ptr->modified_advances[1]);
              if (glyph_count > 2)
                  ok(ptr->modified_advances[2] == advances[2], "Got advance[2] %.2f, expected %.2f.\n", advances[2], ptr->modified_advances[2]);
  
              ok(ptr->modified_offsets[0] == offsets[0].advanceOffset, "Got offset[0] %.2f, expected %.2f.\n",
                  offsets[0].advanceOffset, ptr->modified_offsets[0]);
              ok(ptr->modified_offsets[1] == offsets[1].advanceOffset, "Got offset[1] %.2f, expected %.2f.\n",
                  offsets[1].advanceOffset, ptr->modified_offsets[1]);
              if (glyph_count > 2)
                  ok(ptr->modified_offsets[2] == offsets[2].advanceOffset, "Got offset[2] %.2f, expected %.2f.\n",
                      offsets[2].advanceOffset, ptr->modified_offsets[2]);
  
              ok(offsets[0].ascenderOffset == 23.0, "Unexpected ascenderOffset %.2f.\n", offsets[0].ascenderOffset);
              ok(offsets[1].ascenderOffset == 32.0, "Unexpected ascenderOffset %.2f.\n", offsets[1].ascenderOffset);
              ok(offsets[2].ascenderOffset == 31.0, "Unexpected ascenderOffset %.2f.\n", offsets[2].ascenderOffset);
          }
          else {
              ok(ptr->modified_advances[0] == advances[0], "Got advance[0] %.2f, expected %.2f.\n", advances[0], ptr->modified_advances[0]);
              ok(ptr->modified_advances[1] == advances[1], "Got advance[1] %.2f, expected %.2f.\n", advances[1], ptr->modified_advances[1]);
              ok(ptr->offsets[0] == offsets[0].advanceOffset, "Got offset[0] %.2f, expected %.2f.\n",
                  offsets[0].advanceOffset, ptr->modified_offsets[0]);
              ok(ptr->offsets[1] == offsets[1].advanceOffset, "Got offset[1] %.2f, expected %.2f.\n",
                  offsets[1].advanceOffset, ptr->modified_offsets[1]);
              ok(offsets[0].ascenderOffset == 23.0, "Unexpected ascenderOffset %.2f.\n", offsets[0].ascenderOffset);
              ok(offsets[1].ascenderOffset == 32.0, "Unexpected ascenderOffset %.2f.\n", offsets[1].ascenderOffset);
          }
  
          /* same, with argument aliasing */
          memcpy(advances, ptr->advances, glyph_count * sizeof(*advances));
          offsets[0].advanceOffset = ptr->offsets[0];
          offsets[1].advanceOffset = ptr->offsets[1];
          offsets[2].advanceOffset = ptr->offsets[2];
          /* Ascender offsets are never touched as spacing applies in reading direction only,
             we'll only test them to see if they are not changed */
          offsets[0].ascenderOffset = 23.0f;
          offsets[1].ascenderOffset = 32.0f;
          offsets[2].ascenderOffset = 31.0f;
  
          hr = IDWriteTextAnalyzer1_ApplyCharacterSpacing(analyzer1,
              ptr->leading,
              ptr->trailing,
              ptr->min_advance,
              ARRAY_SIZE(clustermap),
              glyph_count,
              clustermap,
              advances,
              offsets,
              props,
              advances,
              offsets);
          ok(hr == (ptr->min_advance < 0.0f ? E_INVALIDARG : S_OK), "Unexpected hr %#x.\n", hr);
  
          if (hr == S_OK)
          {
              ok(ptr->modified_advances[0] == advances[0], "Got advance[0] %.2f, expected %.2f.\n", advances[0], ptr->modified_advances[0]);
              ok(ptr->modified_advances[1] == advances[1], "Got advance[1] %.2f, expected %.2f.\n", advances[1], ptr->modified_advances[1]);
              if (glyph_count > 2)
                  ok(ptr->modified_advances[2] == advances[2], "Got advance[2] %.2f, expected %.2f.\n", advances[2], ptr->modified_advances[2]);
  
              ok(ptr->modified_offsets[0] == offsets[0].advanceOffset, "Got offset[0] %.2f, expected %.2f.\n",
                  offsets[0].advanceOffset, ptr->modified_offsets[0]);
              ok(ptr->modified_offsets[1] == offsets[1].advanceOffset, "Got offset[1] %.2f, expected %.2f.\n",
                  offsets[1].advanceOffset, ptr->modified_offsets[1]);
              if (glyph_count > 2)
                  ok(ptr->modified_offsets[2] == offsets[2].advanceOffset, "Got offset[2] %.2f, expected %.2f.\n",
                      offsets[2].advanceOffset, ptr->modified_offsets[2]);
  
              ok(offsets[0].ascenderOffset == 23.0f, "Unexpected ascenderOffset %.2f.\n", offsets[0].ascenderOffset);
              ok(offsets[1].ascenderOffset == 32.0f, "Unexpected ascenderOffset %.2f.\n", offsets[1].ascenderOffset);
              ok(offsets[2].ascenderOffset == 31.0f, "Unexpected ascenderOffset %.2f.\n", offsets[2].ascenderOffset);
          }
          else
          {
              /* with aliased advances original values are retained */
              ok(ptr->advances[0] == advances[0], "Got advance[0] %.2f, expected %.2f.\n", advances[0], ptr->advances[0]);
              ok(ptr->advances[1] == advances[1], "Got advance[1] %.2f, expected %.2f.\n", advances[1], ptr->advances[1]);
              ok(ptr->offsets[0] == offsets[0].advanceOffset, "Got offset[0] %.2f, expected %.2f.\n",
                  offsets[0].advanceOffset, ptr->modified_offsets[0]);
              ok(ptr->offsets[1] == offsets[1].advanceOffset, "Got offset[1] %.2f, expected %.2f.\n",
                  offsets[1].advanceOffset, ptr->modified_offsets[1]);
              ok(offsets[0].ascenderOffset == 23.0f, "Unexpected ascenderOffset %.2f.\n", offsets[0].ascenderOffset);
              ok(offsets[1].ascenderOffset == 32.0f, "Unexpected ascenderOffset %.2f.\n", offsets[1].ascenderOffset);
          }
  
          winetest_pop_context();
      }
  
      IDWriteTextAnalyzer1_Release(analyzer1);
  }
  
  struct orientation_transf_test {
      DWRITE_GLYPH_ORIENTATION_ANGLE angle;
      BOOL is_sideways;
      DWRITE_MATRIX m;
  };
  
  static const struct orientation_transf_test ot_tests[] = {
      { DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES,   FALSE, {  1.0,  0.0,  0.0,  1.0, 0.0, 0.0 } },
      { DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES,  FALSE, {  0.0,  1.0, -1.0,  0.0, 0.0, 0.0 } },
      { DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES, FALSE, { -1.0,  0.0,  0.0, -1.0, 0.0, 0.0 } },
      { DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES, FALSE, {  0.0, -1.0,  1.0,  0.0, 0.0, 0.0 } },
      { DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES,   TRUE,  {  0.0,  1.0, -1.0,  0.0, 0.0, 0.0 } },
      { DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES,  TRUE,  { -1.0,  0.0,  0.0, -1.0, 0.0, 0.0 } },
      { DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES, TRUE,  {  0.0, -1.0,  1.0,  0.0, 0.0, 0.0 } },
      { DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES, TRUE,  {  1.0,  0.0,  0.0,  1.0, 0.0, 0.0 } }
  };
  
  static inline const char *dbgstr_matrix(const DWRITE_MATRIX *m)
  {
      static char buff[64];
      sprintf(buff, "{%.2f, %.2f, %.2f, %.2f, %.2f, %.2f}", m->m11, m->m12,
          m->m21, m->m22, m->dx, m->dy);
      return buff;
  }
  
  static void test_GetGlyphOrientationTransform(void)
  {
      IDWriteTextAnalyzer2 *analyzer2;
      IDWriteTextAnalyzer1 *analyzer1;
      IDWriteTextAnalyzer *analyzer;
      FLOAT originx, originy;
      DWRITE_MATRIX m;
      HRESULT hr;
      int i;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
      IDWriteTextAnalyzer_Release(analyzer);
      if (hr != S_OK) {
          win_skip("GetGlyphOrientationTransform() is not supported.\n");
          return;
      }
  
      /* invalid angle value */
      memset(&m, 0xcc, sizeof(m));
      hr = IDWriteTextAnalyzer1_GetGlyphOrientationTransform(analyzer1,
          DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES + 1, FALSE, &m);
      ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
      ok(m.m11 == 0.0, "got %.2f\n", m.m11);
  
      for (i = 0; i < ARRAY_SIZE(ot_tests); i++) {
          memset(&m, 0, sizeof(m));
          hr = IDWriteTextAnalyzer1_GetGlyphOrientationTransform(analyzer1, ot_tests[i].angle,
              ot_tests[i].is_sideways, &m);
          ok(hr == S_OK, "got 0x%08x\n", hr);
          ok(!memcmp(&ot_tests[i].m, &m, sizeof(m)), "%d: wrong matrix %s\n", i, dbgstr_matrix(&m));
      }
  
      hr = IDWriteTextAnalyzer1_QueryInterface(analyzer1, &IID_IDWriteTextAnalyzer2, (void**)&analyzer2);
      IDWriteTextAnalyzer1_Release(analyzer1);
      if (hr != S_OK) {
          win_skip("IDWriteTextAnalyzer2::GetGlyphOrientationTransform() is not supported.\n");
          return;
      }
  
      /* invalid angle value */
      memset(&m, 0xcc, sizeof(m));
      hr = IDWriteTextAnalyzer2_GetGlyphOrientationTransform(analyzer2,
          DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES + 1, FALSE, 0.0, 0.0, &m);
      ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
      ok(m.m11 == 0.0, "got %.2f\n", m.m11);
  
      originx = 50.0;
      originy = 60.0;
      for (i = 0; i < ARRAY_SIZE(ot_tests); i++) {
          DWRITE_GLYPH_ORIENTATION_ANGLE angle = DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES;
          DWRITE_MATRIX m_exp;
  
          memset(&m, 0, sizeof(m));
  
          /* zero offset gives same result as a call from IDWriteTextAnalyzer1 */
          hr = IDWriteTextAnalyzer2_GetGlyphOrientationTransform(analyzer2, ot_tests[i].angle,
              ot_tests[i].is_sideways, 0.0, 0.0, &m);
          ok(hr == S_OK, "got 0x%08x\n", hr);
          ok(!memcmp(&ot_tests[i].m, &m, sizeof(m)), "%d: wrong matrix %s\n", i, dbgstr_matrix(&m));
  
          m_exp = ot_tests[i].m;
          hr = IDWriteTextAnalyzer2_GetGlyphOrientationTransform(analyzer2, ot_tests[i].angle,
              ot_tests[i].is_sideways, originx, originy, &m);
          ok(hr == S_OK, "got 0x%08x\n", hr);
  
          /* 90 degrees more for sideways */
          if (ot_tests[i].is_sideways) {
              switch (ot_tests[i].angle)
              {
              case DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES:
                  angle = DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES;
                  break;
              case DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES:
                  angle = DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES;
                  break;
              case DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES:
                  angle = DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES;
                  break;
              case DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES:
                  angle = DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES;
                  break;
              default:
                  ;
              }
          }
          else
              angle = ot_tests[i].angle;
  
          /* set expected offsets */
          switch (angle)
          {
          case DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES:
              break;
          case DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES:
              m_exp.dx = originx + originy;
              m_exp.dy = originy - originx;
              break;
          case DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES:
              m_exp.dx = originx + originx;
              m_exp.dy = originy + originy;
              break;
          case DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES:
              m_exp.dx = originx - originy;
              m_exp.dy = originy + originx;
              break;
          default:
              ;
          }
  
          ok(!memcmp(&m_exp, &m, sizeof(m)), "%d: wrong matrix %s\n", i, dbgstr_matrix(&m));
      }
  
      IDWriteTextAnalyzer2_Release(analyzer2);
  }
  
  static void test_GetBaseline(void)
  {
      DWRITE_SCRIPT_ANALYSIS sa = { 0 };
      IDWriteTextAnalyzer1 *analyzer1;
      IDWriteTextAnalyzer *analyzer;
      IDWriteFontFace *fontface;
      INT32 baseline;
      BOOL exists;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
      IDWriteTextAnalyzer_Release(analyzer);
      if (hr != S_OK) {
          win_skip("GetBaseline() is not supported.\n");
          return;
      }
  
      fontface = create_fontface();
  
      /* Tahoma does not have a BASE table. */
  
      exists = TRUE;
      baseline = 456;
      hr = IDWriteTextAnalyzer1_GetBaseline(analyzer1, fontface, DWRITE_BASELINE_DEFAULT, FALSE,
             TRUE, sa, NULL, &baseline, &exists);
      ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
      ok(!baseline, "Unexpected baseline %d.\n", baseline);
      ok(!exists, "Unexpected flag %d.\n", exists);
  
      exists = TRUE;
      baseline = 456;
      hr = IDWriteTextAnalyzer1_GetBaseline(analyzer1, fontface, DWRITE_BASELINE_DEFAULT, FALSE,
             FALSE, sa, NULL, &baseline, &exists);
      ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
      ok(!baseline, "Unexpected baseline %d.\n", baseline);
      ok(!exists, "Unexpected flag %d.\n", exists);
  
      exists = TRUE;
      baseline = 0;
      hr = IDWriteTextAnalyzer1_GetBaseline(analyzer1, fontface, DWRITE_BASELINE_CENTRAL, FALSE,
             TRUE, sa, NULL, &baseline, &exists);
      ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
      ok(baseline != 0, "Unexpected baseline %d.\n", baseline);
      ok(!exists, "Unexpected flag %d.\n", exists);
  
      exists = TRUE;
      baseline = 0;
      hr = IDWriteTextAnalyzer1_GetBaseline(analyzer1, fontface, DWRITE_BASELINE_CENTRAL, FALSE,
             FALSE, sa, NULL, &baseline, &exists);
      ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
      ok(!baseline, "Unexpected baseline %d.\n", baseline);
      ok(!exists, "Unexpected flag %d.\n", exists);
  
      exists = TRUE;
      baseline = 456;
      hr = IDWriteTextAnalyzer1_GetBaseline(analyzer1, fontface, DWRITE_BASELINE_DEFAULT + 100, FALSE,
             TRUE, sa, NULL, &baseline, &exists);
      ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);
      ok(!baseline, "Unexpected baseline %d.\n", baseline);
      ok(!exists, "Unexpected flag %d.\n", exists);
  
      IDWriteFontFace_Release(fontface);
      IDWriteTextAnalyzer1_Release(analyzer1);
  }
  
  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) <= 8;
  }
  
  static void test_GetGdiCompatibleGlyphPlacements(void)
  {
      DWRITE_SHAPING_GLYPH_PROPERTIES glyphprops[1];
      DWRITE_SHAPING_TEXT_PROPERTIES textprops[1];
      DWRITE_SCRIPT_ANALYSIS sa = { 0 };
      IDWriteTextAnalyzer *analyzer;
      IDWriteFontFace *fontface;
      UINT16 clustermap[1];
      HRESULT hr;
      UINT32 count;
      UINT16 glyphs[1];
      FLOAT advance;
      DWRITE_GLYPH_OFFSET offsets[1];
      DWRITE_FONT_METRICS fontmetrics;
      float emsize;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      fontface = create_fontface();
  
      IDWriteFontFace_GetMetrics(fontface, &fontmetrics);
  
      count = 0;
      hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, L"A", 1, fontface, FALSE, FALSE, &sa, NULL, NULL, NULL, NULL, 0, 1,
              clustermap, textprops, glyphs, glyphprops, &count);
      ok(hr == S_OK, "Failed to get glyphs, hr %#x.\n", hr);
      ok(count == 1, "got %u\n", count);
  
      for (emsize = 12.0f; emsize <= 20.0f; emsize += 1.0f)
      {
          FLOAT compatadvance, expected, ppdip;
          DWRITE_GLYPH_METRICS metrics;
  
          hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, L"A", clustermap, textprops, 1, glyphs, glyphprops,
                  count, fontface, emsize, FALSE, FALSE, &sa, NULL, NULL, NULL, 0, &advance, offsets);
          ok(hr == S_OK, "Failed to get glyph placements, hr %#x.\n", hr);
          ok(advance > 0.0f, "Unexpected advance %f.\n", advance);
  
          /* 1 ppdip, no transform */
          ppdip = 1.0;
          hr = IDWriteFontFace_GetGdiCompatibleGlyphMetrics(fontface, emsize, ppdip, NULL, FALSE,
              glyphs, 1, &metrics, FALSE);
          ok(hr == S_OK, "got 0x%08x\n", hr);
  
          expected = floorf(metrics.advanceWidth * emsize * ppdip / fontmetrics.designUnitsPerEm + 0.5f) / ppdip;
          hr = IDWriteTextAnalyzer_GetGdiCompatibleGlyphPlacements(analyzer, L"A", clustermap, textprops, 1, glyphs,
                  glyphprops, count, fontface, emsize, ppdip, NULL, FALSE, FALSE, FALSE, &sa, NULL, NULL, NULL, 0,
                  &compatadvance, offsets);
          ok(hr == S_OK, "Failed to get glyph placements, hr %#x.\n", hr);
          ok(compatadvance == expected, "%.0f: got advance %f, expected %f, natural %f\n", emsize,
              compatadvance, expected, advance);
  
          /* 1.2 ppdip, no transform */
          ppdip = 1.2f;
          hr = IDWriteFontFace_GetGdiCompatibleGlyphMetrics(fontface, emsize, ppdip, NULL, FALSE,
              glyphs, 1, &metrics, FALSE);
          ok(hr == S_OK, "got 0x%08x\n", hr);
  
          expected = floorf(metrics.advanceWidth * emsize * ppdip / fontmetrics.designUnitsPerEm + 0.5f) / ppdip;
          hr = IDWriteTextAnalyzer_GetGdiCompatibleGlyphPlacements(analyzer, L"A", clustermap, textprops, 1, glyphs,
                  glyphprops, count, fontface, emsize, ppdip, NULL, FALSE, FALSE, FALSE, &sa, NULL, NULL, NULL, 0,
                  &compatadvance, offsets);
          ok(hr == S_OK, "Failed to get glyph placements, hr %#x.\n", hr);
          ok(float_eq(compatadvance, expected), "%.0f: got advance %f, expected %f, natural %f\n", emsize,
              compatadvance, expected, advance);
      }
  
      IDWriteFontFace_Release(fontface);
      IDWriteTextAnalyzer_Release(analyzer);
  }
  
  struct bidi_test
  {
      const WCHAR text[BIDI_LEVELS_COUNT];
      DWRITE_READING_DIRECTION direction;
      UINT8 explicit[BIDI_LEVELS_COUNT];
      UINT8 resolved[BIDI_LEVELS_COUNT];
      BOOL todo;
  };
  
  static const struct bidi_test bidi_tests[] = {
      {
        { 0x645, 0x6cc, 0x200c, 0x6a9, 0x646, 0x645, 0 },
        DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
        { 1, 1, 1, 1, 1, 1 },
        { 1, 1, 1, 1, 1, 1 }
      },
      {
        { 0x645, 0x6cc, 0x200c, 0x6a9, 0x646, 0x645, 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 0, 0, 0, 0, 0, 0 },
        { 1, 1, 1, 1, 1, 1 }
      },
      {
        { 0x200c, 0x645, 0x6cc, 0x6a9, 0x646, 0x645, 0 },
        DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
        { 1, 1, 1, 1, 1, 1 },
        { 1, 1, 1, 1, 1, 1 }
      },
      {
        { 0x200c, 0x645, 0x6cc, 0x6a9, 0x646, 0x645, 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 0, 0, 0, 0, 0, 0 },
        { 0, 1, 1, 1, 1, 1 }
      },
      {
        { 'A', 0x200c, 'B', 0 },
        DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
        { 1, 1, 1 },
        { 2, 2, 2 }
      },
      {
        { 'A', 0x200c, 'B', 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 0, 0, 0 },
        { 0, 0, 0 }
      },
      {
        { LRE, PDF, 'a', 'b', 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 2, 2, 0, 0 },
        { 0, 0, 0, 0 },
      },
      {
        { 'a', LRE, PDF, 'b', 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 0, 2, 2, 0 },
        { 0, 0, 0, 0 },
      },
      {
        { RLE, PDF, 'a', 'b', 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 1, 1, 0, 0 },
        { 0, 0, 0, 0 },
      },
      {
        { 'a', RLE, PDF, 'b', 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 0, 1, 1, 0 },
        { 0, 0, 0, 0 },
      },
      {
        { 'a', RLE, PDF, 'b', 0 },
        DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
        { 1, 3, 3, 1 },
        { 2, 2, 2, 2 },
      },
      {
        { LRE, PDF, 'a', 'b', 0 },
        DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
        { 2, 2, 1, 1 },
        { 1, 1, 2, 2 },
      },
      {
        { PDF, 'a', 'b', 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 0, 0, 0, 0 },
        { 0, 0, 0, 0 }
      },
      {
        { LRE, 'a', 'b', PDF, 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 2, 2, 2, 2 },
        { 0, 2, 2, 2 },
        TRUE
      },
      {
        { LRI, 'a', 'b', PDI, 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 0, 0, 0, 0 },
        { 0, 0, 0, 0 },
        TRUE
      },
      {
        { RLI, 'a', 'b', PDI, 0 },
        DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
        { 0, 0, 0, 0 },
        { 0, 0, 0, 0 },
        TRUE
      },
      {
        { 0 }
      }
  };
  
  static void compare_bidi_levels(unsigned int seq, const struct bidi_test *test, UINT32 len, UINT8 *explicit, UINT8 *resolved)
  {
      unsigned int i, failcount = 0;
      BOOL match;
  
      match = !memcmp(explicit, test->explicit, len);
      if (!match) {
          if (test->todo) {
              failcount++;
          todo_wine
              ok(0, "test %u: %s wrong explicit levels:\n", seq, wine_dbgstr_w(test->text));
          }
          else
              ok(0, "test %u: %s wrong explicit levels:\n", seq, wine_dbgstr_w(test->text));
  
          for (i = 0; i < len; i++) {
              if (test->explicit[i] != explicit[i]) {
                 if (test->todo) {
                     failcount++;
                 todo_wine
                     ok(0, "\tat %u, explicit level %u, expected %u\n", i, explicit[i], test->explicit[i]);
                 }
                 else
                     ok(0, "\tat %u, explicit level %u, expected %u\n", i, explicit[i], test->explicit[i]);
              }
          }
      }
  
      match = !memcmp(resolved, test->resolved, len);
      if (!match) {
          if (test->todo) {
              failcount++;
          todo_wine
              ok(0, "test %u: %s wrong resolved levels:\n", seq, wine_dbgstr_w(test->text));
          }
          else
              ok(0, "test %u: %s wrong resolved levels:\n", seq, wine_dbgstr_w(test->text));
  
          for (i = 0; i < len; i++) {
              if (test->resolved[i] != resolved[i]) {
                 if (test->todo) {
                     failcount++;
                 todo_wine
                     ok(0, "\tat %u, resolved level %u, expected %u\n", i, resolved[i], test->resolved[i]);
                 }
                 else
                     ok(0, "\tat %u, resolved level %u, expected %u\n", i, resolved[i], test->resolved[i]);
              }
          }
      }
  
      todo_wine_if(test->todo && failcount == 0)
          ok(1, "test %u: marked as \"todo_wine\" but succeeds\n", seq);
  }
  
  static void test_AnalyzeBidi(void)
  {
      const struct bidi_test *ptr = bidi_tests;
      IDWriteTextAnalyzer *analyzer;
      UINT32 i = 0;
      HRESULT hr;
  
      hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
      ok(hr == S_OK, "got 0x%08x\n", hr);
  
      while (*ptr->text)
      {
          UINT32 len;
  
          init_textsource(&analysissource, ptr->text, ptr->direction);
  
          len = lstrlenW(ptr->text);
          if (len > BIDI_LEVELS_COUNT) {
              ok(0, "test %u: increase BIDI_LEVELS_COUNT to at least %u\n", i, len);
              i++;
              ptr++;
              continue;
          }
  
          memset(g_explicit_levels, 0, sizeof(g_explicit_levels));
          memset(g_resolved_levels, 0, sizeof(g_resolved_levels));
          hr = IDWriteTextAnalyzer_AnalyzeBidi(analyzer, &analysissource.IDWriteTextAnalysisSource_iface, 0,
              len, &analysissink);
          ok(hr == S_OK, "%u: got 0x%08x\n", i, hr);
          compare_bidi_levels(i, ptr, len, g_explicit_levels, g_resolved_levels);
  
          i++;
          ptr++;
      }
  
      IDWriteTextAnalyzer_Release(analyzer);
  }
  
  START_TEST(analyzer)
  {
      HRESULT hr;
  
      hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_ISOLATED, &IID_IDWriteFactory, (IUnknown**)&factory);
      ok(hr == S_OK, "got 0x%08x\n", hr);
      if (hr != S_OK)
      {
          win_skip("failed to create factory\n");
          return;
      }
  
      init_call_sequences(sequences, NUM_CALL_SEQUENCES);
      init_call_sequences(expected_seq, 1);
  
      test_AnalyzeScript();
      test_AnalyzeLineBreakpoints();
      test_AnalyzeBidi();
      test_GetScriptProperties();
      test_GetTextComplexity();
      test_GetGlyphs();
      test_numbersubstitution();
      test_GetTypographicFeatures();
      test_GetGlyphPlacements();
      test_ApplyCharacterSpacing();
      test_GetGlyphOrientationTransform();
      test_GetBaseline();
      test_GetGdiCompatibleGlyphPlacements();
  
      IDWriteFactory_Release(factory);
  }
  