text: /*
   *    Text format and layout
   *
   * Copyright 2012, 2014-2021 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 <stdarg.h>
  #include <math.h>
  
  #include "windef.h"
  #include "winbase.h"
  #include "wingdi.h"
  #include "dwrite_private.h"
  #include "scripts.h"
  
  WINE_DEFAULT_DEBUG_CHANNEL(dwrite);
  
  struct dwrite_textformat_data
  {
      WCHAR *family_name;
      UINT32 family_len;
      WCHAR *locale;
      UINT32 locale_len;
  
      DWRITE_FONT_WEIGHT weight;
      DWRITE_FONT_STYLE style;
      DWRITE_FONT_STRETCH stretch;
  
      DWRITE_PARAGRAPH_ALIGNMENT paralign;
      DWRITE_READING_DIRECTION readingdir;
      DWRITE_WORD_WRAPPING wrapping;
      BOOL last_line_wrapping;
      DWRITE_TEXT_ALIGNMENT textalignment;
      DWRITE_FLOW_DIRECTION flow;
      DWRITE_VERTICAL_GLYPH_ORIENTATION vertical_orientation;
      DWRITE_OPTICAL_ALIGNMENT optical_alignment;
      DWRITE_LINE_SPACING spacing;
      DWRITE_AUTOMATIC_FONT_AXES automatic_axes;
  
      FLOAT fontsize;
      FLOAT tabstop;
  
      DWRITE_TRIMMING trimming;
      IDWriteInlineObject *trimmingsign;
  
      IDWriteFontCollection *collection;
      IDWriteFontFallback *fallback;
  
      DWRITE_FONT_AXIS_VALUE *axis_values;
      unsigned int axis_values_count;
  };
  
  enum layout_range_attr_kind {
      LAYOUT_RANGE_ATTR_WEIGHT,
      LAYOUT_RANGE_ATTR_STYLE,
      LAYOUT_RANGE_ATTR_STRETCH,
      LAYOUT_RANGE_ATTR_FONTSIZE,
      LAYOUT_RANGE_ATTR_EFFECT,
      LAYOUT_RANGE_ATTR_INLINE,
      LAYOUT_RANGE_ATTR_UNDERLINE,
      LAYOUT_RANGE_ATTR_STRIKETHROUGH,
      LAYOUT_RANGE_ATTR_PAIR_KERNING,
      LAYOUT_RANGE_ATTR_FONTCOLL,
      LAYOUT_RANGE_ATTR_LOCALE,
      LAYOUT_RANGE_ATTR_FONTFAMILY,
      LAYOUT_RANGE_ATTR_SPACING,
      LAYOUT_RANGE_ATTR_TYPOGRAPHY
  };
  
  struct layout_range_attr_value {
      DWRITE_TEXT_RANGE range;
      union {
          DWRITE_FONT_WEIGHT weight;
          DWRITE_FONT_STYLE style;
          DWRITE_FONT_STRETCH stretch;
          FLOAT fontsize;
          IDWriteInlineObject *object;
          IUnknown *effect;
          BOOL underline;
          BOOL strikethrough;
          BOOL pair_kerning;
          IDWriteFontCollection *collection;
          const WCHAR *locale;
          const WCHAR *fontfamily;
          struct {
              FLOAT leading;
              FLOAT trailing;
              FLOAT min_advance;
          } spacing;
          IDWriteTypography *typography;
      } u;
  };
  
  enum layout_range_kind {
      LAYOUT_RANGE_REGULAR,
      LAYOUT_RANGE_UNDERLINE,
      LAYOUT_RANGE_STRIKETHROUGH,
      LAYOUT_RANGE_EFFECT,
      LAYOUT_RANGE_SPACING,
      LAYOUT_RANGE_TYPOGRAPHY
  };
  
  struct layout_range_header {
      struct list entry;
      enum layout_range_kind kind;
      DWRITE_TEXT_RANGE range;
  };
  
  struct layout_range {
      struct layout_range_header h;
      DWRITE_FONT_WEIGHT weight;
      DWRITE_FONT_STYLE style;
      FLOAT fontsize;
      DWRITE_FONT_STRETCH stretch;
      IDWriteInlineObject *object;
      BOOL pair_kerning;
      IDWriteFontCollection *collection;
      WCHAR locale[LOCALE_NAME_MAX_LENGTH];
      WCHAR *fontfamily;
  };
  
  struct layout_range_bool {
      struct layout_range_header h;
      BOOL value;
  };
  
  struct layout_range_iface {
      struct layout_range_header h;
      IUnknown *iface;
  };
  
  struct layout_range_spacing {
      struct layout_range_header h;
      FLOAT leading;
      FLOAT trailing;
      FLOAT min_advance;
  };
  
  enum layout_run_kind {
      LAYOUT_RUN_REGULAR,
      LAYOUT_RUN_INLINE
  };
  
  struct inline_object_run {
      IDWriteInlineObject *object;
      UINT16 length;
  };
  
  struct regular_layout_run {
      DWRITE_GLYPH_RUN_DESCRIPTION descr;
      DWRITE_GLYPH_RUN run;
      DWRITE_SCRIPT_ANALYSIS sa;
      UINT16 *glyphs;
      UINT16 *clustermap;
      FLOAT  *advances;
      DWRITE_GLYPH_OFFSET *offsets;
      UINT32 glyphcount; /* actual glyph count after shaping, not necessarily the same as reported to Draw() */
  };
  
  struct layout_run
  {
      struct list entry;
      enum layout_run_kind kind;
      union
      {
          struct inline_object_run object;
          struct regular_layout_run regular;
      } u;
      float baseline;
      float height;
      unsigned int start_position; /* run text position in range [0, layout-text-length) */
  };
  
  struct layout_effective_run {
      struct list entry;
      const struct layout_run *run; /* nominal run this one is based on */
      UINT32 start;           /* relative text position, 0 means first text position of a nominal run */
      UINT32 length;          /* length in codepoints that this run covers */
      UINT32 glyphcount;      /* total glyph count in this run */
      IUnknown *effect;       /* original reference is kept only at range level */
      D2D1_POINT_2F origin;   /* baseline origin */
      FLOAT align_dx;         /* adjustment from text alignment */
      FLOAT width;            /* run width */
      UINT16 *clustermap;     /* effective clustermap, allocated separately, is not reused from nominal map */
      UINT32 line;            /* 0-based line index in line metrics array */
      BOOL underlined;        /* set if this run is underlined */
      D2D1_RECT_F bbox;       /* ink run box, top == bottom means it wasn't estimated yet */
  };
  
  struct layout_effective_inline {
      struct list entry;
      IDWriteInlineObject *object;  /* inline object, set explicitly or added when trimming a line */
      IUnknown *effect;             /* original reference is kept only at range level */
      FLOAT baseline;
      D2D1_POINT_2F origin;         /* left top corner */
      FLOAT align_dx;               /* adjustment from text alignment */
      FLOAT width;                  /* object width as it's reported it */
      BOOL  is_sideways;            /* vertical flow direction flag passed to Draw */
      BOOL  is_rtl;                 /* bidi flag passed to Draw */
      UINT32 line;                  /* 0-based line index in line metrics array */
  };
  
  struct layout_underline {
      struct list entry;
      const struct layout_effective_run *run;
      DWRITE_UNDERLINE u;
  };
  
  struct layout_strikethrough {
      struct list entry;
      const struct layout_effective_run *run;
      DWRITE_STRIKETHROUGH s;
  };
  
  struct layout_cluster {
      const struct layout_run *run; /* link to nominal run this cluster belongs to */
      UINT32 position;        /* relative to run, first cluster has 0 position */
  };
  
  struct layout_line
  {
      float height;   /* height based on content */
      float baseline; /* baseline based on content */
      DWRITE_LINE_METRICS1 metrics;
  };
  
  enum layout_recompute_mask {
      RECOMPUTE_CLUSTERS            = 1 << 0,
      RECOMPUTE_MINIMAL_WIDTH       = 1 << 1,
      RECOMPUTE_LINES               = 1 << 2,
      RECOMPUTE_OVERHANGS           = 1 << 3,
      RECOMPUTE_LINES_AND_OVERHANGS = RECOMPUTE_LINES | RECOMPUTE_OVERHANGS,
      RECOMPUTE_EVERYTHING          = 0xffff
  };
  
  struct dwrite_textlayout
  {
      IDWriteTextLayout4 IDWriteTextLayout4_iface;
      IDWriteTextFormat3 IDWriteTextFormat3_iface;
      IDWriteTextAnalysisSink1 IDWriteTextAnalysisSink1_iface;
      IDWriteTextAnalysisSource1 IDWriteTextAnalysisSource1_iface;
      LONG refcount;
  
      IDWriteFactory7 *factory;
  
      WCHAR *str;
      UINT32 len;
      struct dwrite_textformat_data format;
      struct list strike_ranges;
      struct list underline_ranges;
      struct list typographies;
      struct list effects;
      struct list spacing;
      struct list ranges;
      struct list runs;
      /* lists ready to use by Draw() */
      struct list eruns;
      struct list inlineobjects;
      struct list underlines;
      struct list strikethrough;
      USHORT recompute;
  
      DWRITE_LINE_BREAKPOINT *nominal_breakpoints;
      DWRITE_LINE_BREAKPOINT *actual_breakpoints;
  
      struct layout_cluster *clusters;
      DWRITE_CLUSTER_METRICS *clustermetrics;
      UINT32 cluster_count;
      FLOAT  minwidth;
  
      struct layout_line *lines;
      size_t lines_size;
  
      DWRITE_TEXT_METRICS1 metrics;
      DWRITE_OVERHANG_METRICS overhangs;
  
      DWRITE_MEASURING_MODE measuringmode;
  
      /* gdi-compatible layout specifics */
      FLOAT ppdip;
      DWRITE_MATRIX transform;
  };
  
  struct dwrite_textformat
  {
      IDWriteTextFormat3 IDWriteTextFormat3_iface;
      LONG refcount;
      struct dwrite_textformat_data format;
  };
  
  struct dwrite_trimmingsign
  {
      IDWriteInlineObject IDWriteInlineObject_iface;
      LONG refcount;
  
      IDWriteTextLayout *layout;
  };
  
  struct dwrite_typography {
      IDWriteTypography IDWriteTypography_iface;
      LONG refcount;
  
      DWRITE_FONT_FEATURE *features;
      size_t capacity;
      size_t count;
  };
  
  static const IDWriteTextFormat3Vtbl dwritetextformatvtbl;
  
  static void release_format_data(struct dwrite_textformat_data *data)
  {
      if (data->collection) IDWriteFontCollection_Release(data->collection);
      if (data->fallback) IDWriteFontFallback_Release(data->fallback);
      if (data->trimmingsign) IDWriteInlineObject_Release(data->trimmingsign);
      free(data->family_name);
      free(data->locale);
      free(data->axis_values);
  }
  
  static inline struct dwrite_textlayout *impl_from_IDWriteTextLayout4(IDWriteTextLayout4 *iface)
  {
      return CONTAINING_RECORD(iface, struct dwrite_textlayout, IDWriteTextLayout4_iface);
  }
  
  static inline struct dwrite_textlayout *impl_layout_from_IDWriteTextFormat3(IDWriteTextFormat3 *iface)
  {
      return CONTAINING_RECORD(iface, struct dwrite_textlayout, IDWriteTextFormat3_iface);
  }
  
  static inline struct dwrite_textlayout *impl_from_IDWriteTextAnalysisSink1(IDWriteTextAnalysisSink1 *iface)
  {
      return CONTAINING_RECORD(iface, struct dwrite_textlayout, IDWriteTextAnalysisSink1_iface);
  }
  
  static inline struct dwrite_textlayout *impl_from_IDWriteTextAnalysisSource1(IDWriteTextAnalysisSource1 *iface)
  {
      return CONTAINING_RECORD(iface, struct dwrite_textlayout, IDWriteTextAnalysisSource1_iface);
  }
  
  static inline struct dwrite_textformat *impl_from_IDWriteTextFormat3(IDWriteTextFormat3 *iface)
  {
      return CONTAINING_RECORD(iface, struct dwrite_textformat, IDWriteTextFormat3_iface);
  }
  
  static struct dwrite_textformat *unsafe_impl_from_IDWriteTextFormat(IDWriteTextFormat*);
  
  static inline struct dwrite_trimmingsign *impl_from_IDWriteInlineObject(IDWriteInlineObject *iface)
  {
      return CONTAINING_RECORD(iface, struct dwrite_trimmingsign, IDWriteInlineObject_iface);
  }
  
  static inline struct dwrite_typography *impl_from_IDWriteTypography(IDWriteTypography *iface)
  {
      return CONTAINING_RECORD(iface, struct dwrite_typography, IDWriteTypography_iface);
  }
  
  static inline const char *debugstr_rundescr(const DWRITE_GLYPH_RUN_DESCRIPTION *descr)
  {
      return wine_dbg_sprintf("[%u,%u)", descr->textPosition, descr->textPosition + descr->stringLength);
  }
  
  static inline BOOL is_layout_gdi_compatible(struct dwrite_textlayout *layout)
  {
      return layout->measuringmode != DWRITE_MEASURING_MODE_NATURAL;
  }
  
  static inline HRESULT format_set_textalignment(struct dwrite_textformat_data *format, DWRITE_TEXT_ALIGNMENT alignment,
      BOOL *changed)
  {
      if ((UINT32)alignment > DWRITE_TEXT_ALIGNMENT_JUSTIFIED)
          return E_INVALIDARG;
      if (changed) *changed = format->textalignment != alignment;
      format->textalignment = alignment;
      return S_OK;
  }
  
  static inline HRESULT format_set_paralignment(struct dwrite_textformat_data *format,
      DWRITE_PARAGRAPH_ALIGNMENT alignment, BOOL *changed)
  {
      if ((UINT32)alignment > DWRITE_PARAGRAPH_ALIGNMENT_CENTER)
          return E_INVALIDARG;
      if (changed) *changed = format->paralign != alignment;
      format->paralign = alignment;
      return S_OK;
  }
  
  static inline HRESULT format_set_readingdirection(struct dwrite_textformat_data *format,
      DWRITE_READING_DIRECTION direction, BOOL *changed)
  {
      if ((UINT32)direction > DWRITE_READING_DIRECTION_BOTTOM_TO_TOP)
          return E_INVALIDARG;
      if (changed) *changed = format->readingdir != direction;
      format->readingdir = direction;
      return S_OK;
  }
  
  static inline HRESULT format_set_wordwrapping(struct dwrite_textformat_data *format,
      DWRITE_WORD_WRAPPING wrapping, BOOL *changed)
  {
      if ((UINT32)wrapping > DWRITE_WORD_WRAPPING_CHARACTER)
          return E_INVALIDARG;
      if (changed) *changed = format->wrapping != wrapping;
      format->wrapping = wrapping;
      return S_OK;
  }
  
  static inline HRESULT format_set_flowdirection(struct dwrite_textformat_data *format,
      DWRITE_FLOW_DIRECTION direction, BOOL *changed)
  {
      if ((UINT32)direction > DWRITE_FLOW_DIRECTION_RIGHT_TO_LEFT)
          return E_INVALIDARG;
      if (changed) *changed = format->flow != direction;
      format->flow = direction;
      return S_OK;
  }
  
  static inline HRESULT format_set_trimming(struct dwrite_textformat_data *format,
      DWRITE_TRIMMING const *trimming, IDWriteInlineObject *trimming_sign, BOOL *changed)
  {
      if (changed)
          *changed = FALSE;
  
      if ((UINT32)trimming->granularity > DWRITE_TRIMMING_GRANULARITY_WORD)
          return E_INVALIDARG;
  
      if (changed) {
          *changed = !!memcmp(&format->trimming, trimming, sizeof(*trimming));
          if (format->trimmingsign != trimming_sign)
              *changed = TRUE;
      }
  
      format->trimming = *trimming;
      if (format->trimmingsign)
          IDWriteInlineObject_Release(format->trimmingsign);
      format->trimmingsign = trimming_sign;
      if (format->trimmingsign)
          IDWriteInlineObject_AddRef(format->trimmingsign);
      return S_OK;
  }
  
  static inline HRESULT format_set_linespacing(struct dwrite_textformat_data *format,
      DWRITE_LINE_SPACING const *spacing, BOOL *changed)
  {
      if (spacing->height < 0.0f || spacing->leadingBefore < 0.0f || spacing->leadingBefore > 1.0f ||
          (UINT32)spacing->method > DWRITE_LINE_SPACING_METHOD_PROPORTIONAL)
          return E_INVALIDARG;
  
      if (changed)
          *changed = memcmp(spacing, &format->spacing, sizeof(*spacing));
  
      format->spacing = *spacing;
      return S_OK;
  }
  
  static HRESULT format_set_font_axisvalues(struct dwrite_textformat_data *format,
          DWRITE_FONT_AXIS_VALUE const *axis_values, unsigned int num_values)
  {
      free(format->axis_values);
      format->axis_values = NULL;
      format->axis_values_count = 0;
  
      if (num_values)
      {
          if (!(format->axis_values = calloc(num_values, sizeof(*axis_values))))
              return E_OUTOFMEMORY;
          memcpy(format->axis_values, axis_values, num_values * sizeof(*axis_values));
          format->axis_values_count = num_values;
      }
  
      return S_OK;
  }
  
  static HRESULT format_get_font_axisvalues(struct dwrite_textformat_data *format,
          DWRITE_FONT_AXIS_VALUE *axis_values, unsigned int num_values)
  {
      if (!format->axis_values_count)
      {
          if (num_values) memset(axis_values, 0, num_values * sizeof(*axis_values));
          return S_OK;
      }
  
      if (num_values < format->axis_values_count)
          return E_NOT_SUFFICIENT_BUFFER;
  
      memcpy(axis_values, format->axis_values, min(num_values, format->axis_values_count) * sizeof(*axis_values));
  
      return S_OK;
  }
  
  static HRESULT get_fontfallback_from_format(const struct dwrite_textformat_data *format, IDWriteFontFallback **fallback)
  {
      *fallback = format->fallback;
      if (*fallback)
          IDWriteFontFallback_AddRef(*fallback);
      return S_OK;
  }
  
  static HRESULT set_fontfallback_for_format(struct dwrite_textformat_data *format, IDWriteFontFallback *fallback)
  {
      if (format->fallback)
          IDWriteFontFallback_Release(format->fallback);
      format->fallback = fallback;
      if (fallback)
          IDWriteFontFallback_AddRef(fallback);
      return S_OK;
  }
  
  static HRESULT format_set_optical_alignment(struct dwrite_textformat_data *format,
      DWRITE_OPTICAL_ALIGNMENT alignment)
  {
      if ((UINT32)alignment > DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS)
          return E_INVALIDARG;
      format->optical_alignment = alignment;
      return S_OK;
  }
  
  static HRESULT format_set_vertical_orientation(struct dwrite_textformat_data *format,
          DWRITE_VERTICAL_GLYPH_ORIENTATION orientation, BOOL *changed)
  {
      if ((UINT32)orientation > DWRITE_VERTICAL_GLYPH_ORIENTATION_STACKED)
          return E_INVALIDARG;
  
      if (changed)
          *changed = format->vertical_orientation != orientation;
  
      format->vertical_orientation = orientation;
      return S_OK;
  }
  
  static BOOL is_run_rtl(const struct layout_effective_run *run)
  {
      return run->run->u.regular.run.bidiLevel & 1;
  }
  
  static HRESULT alloc_layout_run(enum layout_run_kind kind, unsigned int start_position,
          struct layout_run **run)
  {
      if (!(*run = calloc(1, sizeof(**run))))
          return E_OUTOFMEMORY;
  
      (*run)->kind = kind;
      (*run)->start_position = start_position;
  
      return S_OK;
  }
  
  static void free_layout_runs(struct dwrite_textlayout *layout)
  {
      struct layout_run *cur, *cur2;
      LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->runs, struct layout_run, entry)
      {
          list_remove(&cur->entry);
          if (cur->kind == LAYOUT_RUN_REGULAR)
          {
              if (cur->u.regular.run.fontFace)
                  IDWriteFontFace_Release(cur->u.regular.run.fontFace);
              free(cur->u.regular.glyphs);
              free(cur->u.regular.clustermap);
              free(cur->u.regular.advances);
              free(cur->u.regular.offsets);
          }
          free(cur);
      }
  }
  
  static void free_layout_eruns(struct dwrite_textlayout *layout)
  {
      struct layout_effective_inline *in, *in2;
      struct layout_effective_run *cur, *cur2;
      struct layout_strikethrough *s, *s2;
      struct layout_underline *u, *u2;
  
      LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->eruns, struct layout_effective_run, entry)
      {
          list_remove(&cur->entry);
          free(cur->clustermap);
          free(cur);
      }
  
      LIST_FOR_EACH_ENTRY_SAFE(in, in2, &layout->inlineobjects, struct layout_effective_inline, entry)
      {
          list_remove(&in->entry);
          free(in);
      }
  
      LIST_FOR_EACH_ENTRY_SAFE(u, u2, &layout->underlines, struct layout_underline, entry)
      {
          list_remove(&u->entry);
          free(u);
      }
  
      LIST_FOR_EACH_ENTRY_SAFE(s, s2, &layout->strikethrough, struct layout_strikethrough, entry)
      {
          list_remove(&s->entry);
          free(s);
      }
  }
  
  /* Used to resolve break condition by forcing stronger condition over weaker. */
  static inline DWRITE_BREAK_CONDITION override_break_condition(DWRITE_BREAK_CONDITION existingbreak, DWRITE_BREAK_CONDITION newbreak)
  {
      switch (existingbreak) {
      case DWRITE_BREAK_CONDITION_NEUTRAL:
          return newbreak;
      case DWRITE_BREAK_CONDITION_CAN_BREAK:
          return newbreak == DWRITE_BREAK_CONDITION_NEUTRAL ? existingbreak : newbreak;
      /* let's keep stronger conditions as is */
      case DWRITE_BREAK_CONDITION_MAY_NOT_BREAK:
      case DWRITE_BREAK_CONDITION_MUST_BREAK:
          break;
      default:
          ERR("unknown break condition %d\n", existingbreak);
      }
  
      return existingbreak;
  }
  
  /* This helper should be used to get effective range length, in other words it returns number of text
     positions from range starting point to the end of the range, limited by layout text length */
  static inline UINT32 get_clipped_range_length(const struct dwrite_textlayout *layout, const struct layout_range *range)
  {
      if (range->h.range.startPosition + range->h.range.length <= layout->len)
          return range->h.range.length;
      return layout->len - range->h.range.startPosition;
  }
  
  /* Actual breakpoint data gets updated with break condition required by inline object set for range 'cur'. */
  static HRESULT layout_update_breakpoints_range(struct dwrite_textlayout *layout, const struct layout_range *cur)
  {
      DWRITE_BREAK_CONDITION before, after;
      UINT32 i, length;
      HRESULT hr;
  
      /* ignore returned conditions if failed */
      hr = IDWriteInlineObject_GetBreakConditions(cur->object, &before, &after);
      if (FAILED(hr))
          after = before = DWRITE_BREAK_CONDITION_NEUTRAL;
  
      if (!layout->actual_breakpoints)
      {
          if (!(layout->actual_breakpoints = calloc(layout->len, sizeof(*layout->actual_breakpoints))))
              return E_OUTOFMEMORY;
          memcpy(layout->actual_breakpoints, layout->nominal_breakpoints, sizeof(DWRITE_LINE_BREAKPOINT)*layout->len);
      }
  
      length = get_clipped_range_length(layout, cur);
      for (i = cur->h.range.startPosition; i < length + cur->h.range.startPosition; i++) {
          /* for first codepoint check if there's anything before it and update accordingly */
          if (i == cur->h.range.startPosition) {
              if (i > 0)
                  layout->actual_breakpoints[i].breakConditionBefore = layout->actual_breakpoints[i-1].breakConditionAfter =
                      override_break_condition(layout->actual_breakpoints[i-1].breakConditionAfter, before);
              else
                  layout->actual_breakpoints[i].breakConditionBefore = before;
              layout->actual_breakpoints[i].breakConditionAfter = DWRITE_BREAK_CONDITION_MAY_NOT_BREAK;
          }
          /* similar check for last codepoint */
          else if (i == cur->h.range.startPosition + length - 1) {
              if (i == layout->len - 1)
                  layout->actual_breakpoints[i].breakConditionAfter = after;
              else
                  layout->actual_breakpoints[i].breakConditionAfter = layout->actual_breakpoints[i+1].breakConditionBefore =
                      override_break_condition(layout->actual_breakpoints[i+1].breakConditionBefore, after);
              layout->actual_breakpoints[i].breakConditionBefore = DWRITE_BREAK_CONDITION_MAY_NOT_BREAK;
          }
          /* for all positions within a range disable breaks */
          else {
              layout->actual_breakpoints[i].breakConditionBefore = DWRITE_BREAK_CONDITION_MAY_NOT_BREAK;
              layout->actual_breakpoints[i].breakConditionAfter = DWRITE_BREAK_CONDITION_MAY_NOT_BREAK;
          }
  
          layout->actual_breakpoints[i].isWhitespace = 0;
          layout->actual_breakpoints[i].isSoftHyphen = 0;
      }
  
      return S_OK;
  }
  
  static struct layout_range *get_layout_range_by_pos(struct dwrite_textlayout *layout, UINT32 pos)
  {
      struct layout_range *cur;
  
      LIST_FOR_EACH_ENTRY(cur, &layout->ranges, struct layout_range, h.entry)
      {
          DWRITE_TEXT_RANGE *r = &cur->h.range;
          if (r->startPosition <= pos && pos < r->startPosition + r->length)
              return cur;
      }
  
      return NULL;
  }
  
  static struct layout_range_header *get_layout_range_header_by_pos(struct list *ranges, UINT32 pos)
  {
      struct layout_range_header *cur;
  
      LIST_FOR_EACH_ENTRY(cur, ranges, struct layout_range_header, entry)
      {
          DWRITE_TEXT_RANGE *r = &cur->range;
          if (r->startPosition <= pos && pos < r->startPosition + r->length)
              return cur;
      }
  
      return NULL;
  }
  
  static inline DWRITE_LINE_BREAKPOINT get_effective_breakpoint(const struct dwrite_textlayout *layout, UINT32 pos)
  {
      if (layout->actual_breakpoints)
          return layout->actual_breakpoints[pos];
      return layout->nominal_breakpoints[pos];
  }
  
  static inline void init_cluster_metrics(const struct dwrite_textlayout *layout, const struct regular_layout_run *run,
      UINT16 start_glyph, UINT16 stop_glyph, UINT32 stop_position, UINT16 length, DWRITE_CLUSTER_METRICS *metrics)
  {
      UINT8 breakcondition;
      UINT32 position;
      UINT16 j;
  
      /* For clusters made of control chars we report zero glyphs, and we need zero cluster
         width as well; advances are already computed at this point and are not necessary zero. */
      metrics->width = 0.0f;
      if (run->run.glyphCount) {
          for (j = start_glyph; j < stop_glyph; j++)
              metrics->width += run->run.glyphAdvances[j];
      }
      metrics->length = length;
  
      position = run->descr.textPosition + stop_position;
      if (stop_glyph == run->glyphcount)
          breakcondition = get_effective_breakpoint(layout, position).breakConditionAfter;
      else {
          breakcondition = get_effective_breakpoint(layout, position).breakConditionBefore;
          if (stop_position) position -= 1;
      }
  
      metrics->canWrapLineAfter = breakcondition == DWRITE_BREAK_CONDITION_CAN_BREAK ||
                                  breakcondition == DWRITE_BREAK_CONDITION_MUST_BREAK;
      if (metrics->length == 1) {
          DWRITE_LINE_BREAKPOINT bp = get_effective_breakpoint(layout, position);
          metrics->isWhitespace = bp.isWhitespace;
          metrics->isNewline = metrics->canWrapLineAfter && lb_is_newline_char(layout->str[position]);
          metrics->isSoftHyphen = bp.isSoftHyphen;
      }
      else {
          metrics->isWhitespace = 0;
          metrics->isNewline = 0;
          metrics->isSoftHyphen = 0;
      }
      metrics->isRightToLeft = run->run.bidiLevel & 1;
      metrics->padding = 0;
  }
  
  /*
  
    All clusters in a 'run' will be added to 'layout' data, starting at index pointed to by 'cluster'.
    On return 'cluster' is updated to point to next metrics struct to be filled in on next call.
    Note that there's no need to reallocate anything at this point as we allocate one cluster per
    codepoint initially.
  
  */
  static void layout_set_cluster_metrics(struct dwrite_textlayout *layout, const struct layout_run *r, UINT32 *cluster)
  {
      DWRITE_CLUSTER_METRICS *metrics = &layout->clustermetrics[*cluster];
      struct layout_cluster *c = &layout->clusters[*cluster];
      const struct regular_layout_run *run = &r->u.regular;
      UINT32 i, start = 0;
  
      assert(r->kind == LAYOUT_RUN_REGULAR);
  
      for (i = 0; i < run->descr.stringLength; i++) {
          BOOL end = i == run->descr.stringLength - 1;
  
          if (run->descr.clusterMap[start] != run->descr.clusterMap[i]) {
              init_cluster_metrics(layout, run, run->descr.clusterMap[start], run->descr.clusterMap[i], i,
                  i - start, metrics);
              c->position = start;
              c->run = r;
  
              *cluster += 1;
              metrics++;
              c++;
              start = i;
          }
  
          if (end) {
              init_cluster_metrics(layout, run, run->descr.clusterMap[start], run->glyphcount, i,
                  i - start + 1, metrics);
              c->position = start;
              c->run = r;
  
              *cluster += 1;
              return;
          }
      }
  }
  
  #define SCALE_FONT_METRIC(metric, emSize, metrics) ((FLOAT)(metric) * (emSize) / (FLOAT)(metrics)->designUnitsPerEm)
  
  static void layout_get_font_metrics(struct dwrite_textlayout *layout, IDWriteFontFace *fontface, FLOAT emsize,
      DWRITE_FONT_METRICS *fontmetrics)
  {
      if (is_layout_gdi_compatible(layout)) {
          HRESULT hr = IDWriteFontFace_GetGdiCompatibleMetrics(fontface, emsize, layout->ppdip, &layout->transform, fontmetrics);
          if (FAILED(hr))
              WARN("failed to get compat metrics, 0x%08x\n", hr);
      }
      else
          IDWriteFontFace_GetMetrics(fontface, fontmetrics);
  }
  
  static void layout_get_font_height(FLOAT emsize, DWRITE_FONT_METRICS *fontmetrics, FLOAT *baseline, FLOAT *height)
  {
      *baseline = SCALE_FONT_METRIC(fontmetrics->ascent + fontmetrics->lineGap, emsize, fontmetrics);
      *height = SCALE_FONT_METRIC(fontmetrics->ascent + fontmetrics->descent + fontmetrics->lineGap, emsize, fontmetrics);
  }
  
  static HRESULT layout_itemize(struct dwrite_textlayout *layout)
  {
      IDWriteTextAnalyzer2 *analyzer;
      struct layout_range *range;
      struct layout_run *r;
      HRESULT hr = S_OK;
  
      analyzer = get_text_analyzer();
  
      LIST_FOR_EACH_ENTRY(range, &layout->ranges, struct layout_range, h.entry) {
          /* We don't care about ranges that don't contain any text. */
          if (range->h.range.startPosition >= layout->len)
              break;
  
          /* Inline objects override actual text in range. */
          if (range->object) {
              hr = layout_update_breakpoints_range(layout, range);
              if (FAILED(hr))
                  return hr;
  
              if (FAILED(hr = alloc_layout_run(LAYOUT_RUN_INLINE, range->h.range.startPosition, &r)))
                  return hr;
  
              r->u.object.object = range->object;
              r->u.object.length = get_clipped_range_length(layout, range);
              list_add_tail(&layout->runs, &r->entry);
              continue;
          }
  
          /* Initial splitting by script. */
          hr = IDWriteTextAnalyzer2_AnalyzeScript(analyzer, (IDWriteTextAnalysisSource *)&layout->IDWriteTextAnalysisSource1_iface,
                  range->h.range.startPosition, get_clipped_range_length(layout, range),
                  (IDWriteTextAnalysisSink *)&layout->IDWriteTextAnalysisSink1_iface);
          if (FAILED(hr))
              break;
  
          /* Splitting further by bidi levels. */
          hr = IDWriteTextAnalyzer2_AnalyzeBidi(analyzer, (IDWriteTextAnalysisSource *)&layout->IDWriteTextAnalysisSource1_iface,
                  range->h.range.startPosition, get_clipped_range_length(layout, range),
                  (IDWriteTextAnalysisSink *)&layout->IDWriteTextAnalysisSink1_iface);
          if (FAILED(hr))
              break;
      }
  
      return hr;
  }
  
  static HRESULT layout_resolve_fonts(struct dwrite_textlayout *layout)
  {
      IDWriteFontCollection *sys_collection;
      IDWriteFontFallback *fallback = NULL;
      struct layout_range *range;
      struct layout_run *r;
      HRESULT hr;
  
      if (FAILED(hr = IDWriteFactory5_GetSystemFontCollection((IDWriteFactory5 *)layout->factory, FALSE,
              (IDWriteFontCollection1 **)&sys_collection, FALSE))) {
          WARN("Failed to get system collection, hr %#x.\n", hr);
          return hr;
      }
  
      if (layout->format.fallback) {
          fallback = layout->format.fallback;
          IDWriteFontFallback_AddRef(fallback);
      }
      else {
          if (FAILED(hr = IDWriteFactory7_GetSystemFontFallback(layout->factory, &fallback))) {
              WARN("Failed to get system fallback, hr %#x.\n", hr);
              goto fatal;
          }
      }
  
      LIST_FOR_EACH_ENTRY(r, &layout->runs, struct layout_run, entry) {
          struct regular_layout_run *run = &r->u.regular;
          IDWriteFont *font;
          UINT32 length;
  
          if (r->kind == LAYOUT_RUN_INLINE)
              continue;
  
          range = get_layout_range_by_pos(layout, run->descr.textPosition);
  
          if (run->sa.shapes == DWRITE_SCRIPT_SHAPES_NO_VISUAL) {
              IDWriteFontCollection *collection;
  
              collection = range->collection ? range->collection : sys_collection;
  
              if (FAILED(hr = create_matching_font(collection, range->fontfamily, range->weight, range->style,
                      range->stretch, &font))) {
                  WARN("%s: failed to create matching font for non visual run, family %s, collection %p\n",
                          debugstr_rundescr(&run->descr), debugstr_w(range->fontfamily), range->collection);
                  break;
              }
  
              hr = IDWriteFont_CreateFontFace(font, &run->run.fontFace);
              IDWriteFont_Release(font);
              if (FAILED(hr)) {
                  WARN("Failed to create font face, hr %#x.\n", hr);
                  break;
              }
  
              run->run.fontEmSize = range->fontsize;
              continue;
          }
  
          length = run->descr.stringLength;
  
          while (length) {
              UINT32 mapped_length;
              FLOAT scale;
  
              run = &r->u.regular;
  
              hr = IDWriteFontFallback_MapCharacters(fallback,
                  (IDWriteTextAnalysisSource *)&layout->IDWriteTextAnalysisSource1_iface,
                  run->descr.textPosition,
                  run->descr.stringLength,
                  range->collection,
                  range->fontfamily,
                  range->weight,
                  range->style,
                  range->stretch,
                  &mapped_length,
                  &font,
                  &scale);
              if (FAILED(hr)) {
                  WARN("%s: failed to map family %s, collection %p, hr %#x.\n", debugstr_rundescr(&run->descr),
                          debugstr_w(range->fontfamily), range->collection, hr);
                  goto fatal;
              }
  
              hr = IDWriteFont_CreateFontFace(font, &run->run.fontFace);
              IDWriteFont_Release(font);
              if (FAILED(hr)) {
                  WARN("Failed to create font face, hr %#x.\n", hr);
                  goto fatal;
              }
  
              run->run.fontEmSize = range->fontsize * scale;
  
              if (mapped_length < length)
              {
                  struct regular_layout_run *nextrun;
                  struct layout_run *nextr;
  
                  /* keep mapped part for current run, add another run for the rest */
                  if (FAILED(hr = alloc_layout_run(LAYOUT_RUN_REGULAR, 0, &nextr)))
                      goto fatal;
  
                  *nextr = *r;
                  nextr->start_position = run->descr.textPosition + mapped_length;
                  nextrun = &nextr->u.regular;
                  nextrun->descr.textPosition = nextr->start_position;
                  nextrun->descr.stringLength = run->descr.stringLength - mapped_length;
                  nextrun->descr.string = &layout->str[nextrun->descr.textPosition];
                  run->descr.stringLength = mapped_length;
                  list_add_after(&r->entry, &nextr->entry);
                  r = nextr;
              }
  
              length -= mapped_length;
          }
      }
  
  fatal:
      IDWriteFontCollection_Release(sys_collection);
      if (fallback)
          IDWriteFontFallback_Release(fallback);
  
      return hr;
  }
  
  struct shaping_context
  {
      IDWriteTextAnalyzer2 *analyzer;
      struct regular_layout_run *run;
      DWRITE_SHAPING_GLYPH_PROPERTIES *glyph_props;
      DWRITE_SHAPING_TEXT_PROPERTIES *text_props;
  
      struct
      {
          DWRITE_TYPOGRAPHIC_FEATURES **features;
          unsigned int *range_lengths;
          unsigned int range_count;
      } user_features;
  };
  
  static void layout_shape_clear_user_features_context(struct shaping_context *context)
  {
      unsigned int i;
  
      for (i = 0; i < context->user_features.range_count; ++i)
      {
          free(context->user_features.features[i]->features);
          free(context->user_features.features[i]);
      }
      free(context->user_features.features);
      memset(&context->user_features, 0, sizeof(context->user_features));
  }
  
  static void layout_shape_clear_context(struct shaping_context *context)
  {
      layout_shape_clear_user_features_context(context);
      free(context->glyph_props);
      free(context->text_props);
  }
  
  static HRESULT layout_shape_add_empty_user_features_range(struct shaping_context *context, unsigned int length)
  {
      DWRITE_TYPOGRAPHIC_FEATURES *features;
      unsigned int r = context->user_features.range_count;
  
      if (!(context->user_features.features[r] = calloc(1, sizeof(*features))))
          return E_OUTOFMEMORY;
  
      context->user_features.range_lengths[r] = length;
      context->user_features.range_count++;
  
      return S_OK;
  }
  
  static HRESULT layout_shape_get_user_features(struct dwrite_textlayout *layout, struct shaping_context *context)
  {
      unsigned int i, f, start = 0, r, covered_length = 0, length, feature_count;
      struct regular_layout_run *run = context->run;
      DWRITE_TYPOGRAPHIC_FEATURES *features;
      struct layout_range_iface *range;
      IDWriteTypography *typography;
      HRESULT hr = E_OUTOFMEMORY;
  
      range = (struct layout_range_iface *)get_layout_range_header_by_pos(&layout->typographies, 0);
      if (range->h.range.length >= run->descr.stringLength && !range->iface)
          return S_OK;
  
      if (!(context->user_features.features = calloc(run->descr.stringLength, sizeof(*context->user_features.features))))
          goto failed;
      if (!(context->user_features.range_lengths = calloc(run->descr.stringLength, sizeof(*context->user_features.range_lengths))))
          goto failed;
  
      for (i = run->descr.textPosition; i < run->descr.textPosition + run->descr.stringLength; ++i)
      {
          range = (struct layout_range_iface *)get_layout_range_header_by_pos(&layout->typographies, i);
          if (!range || !range->iface) continue;
  
          typography = (IDWriteTypography *)range->iface;
          feature_count = IDWriteTypography_GetFontFeatureCount(typography);
          if (!feature_count)
          {
              i = range->h.range.length - i + 1;
              continue;
          }
  
          if (start != i)
          {
              if (FAILED(hr = layout_shape_add_empty_user_features_range(context, i - start))) goto failed;
              covered_length += i - start;
              start += range->h.range.length;
          }
  
          r = context->user_features.range_count;
          if (!(features = context->user_features.features[r] = malloc(sizeof(*features))))
              goto failed;
  
          context->user_features.range_lengths[r] = length = min(run->descr.textPosition + run->descr.stringLength,
                  range->h.range.startPosition + range->h.range.length) - i;
          features->featureCount = feature_count;
          if (!(features->features = calloc(feature_count, sizeof(*features->features))))
              goto failed;
  
          for (f = 0; f < feature_count; ++f)
          {
              IDWriteTypography_GetFontFeature(typography, f, &features->features[f]);
          }
  
          i += length;
          covered_length += length;
          context->user_features.range_count++;
      }
  
      if (context->user_features.range_count && covered_length < run->descr.stringLength)
      {
          if (FAILED(hr = layout_shape_add_empty_user_features_range(context, run->descr.stringLength - covered_length)))
              goto failed;
      }
  
      hr = S_OK;
  
  failed:
  
      if (!context->user_features.range_count || FAILED(hr))
          layout_shape_clear_user_features_context(context);
  
      return hr;
  }
  
  static HRESULT layout_shape_get_glyphs(struct dwrite_textlayout *layout, struct shaping_context *context)
  {
      struct regular_layout_run *run = context->run;
      unsigned int max_count;
      HRESULT hr;
  
      run->descr.localeName = get_layout_range_by_pos(layout, run->descr.textPosition)->locale;
      run->clustermap = calloc(run->descr.stringLength, sizeof(*run->clustermap));
      if (!run->clustermap)
          return E_OUTOFMEMORY;
  
      max_count = 3 * run->descr.stringLength / 2 + 16;
      run->glyphs = calloc(max_count, sizeof(*run->glyphs));
      if (!run->glyphs)
          return E_OUTOFMEMORY;
  
      context->text_props = calloc(run->descr.stringLength, sizeof(*context->text_props));
      context->glyph_props = calloc(max_count, sizeof(*context->glyph_props));
      if (!context->text_props || !context->glyph_props)
          return E_OUTOFMEMORY;
  
      if (FAILED(hr = layout_shape_get_user_features(layout, context)))
          return hr;
  
      for (;;)
      {
          hr = IDWriteTextAnalyzer2_GetGlyphs(context->analyzer, run->descr.string, run->descr.stringLength, run->run.fontFace,
                  run->run.isSideways, run->run.bidiLevel & 1, &run->sa, run->descr.localeName, NULL /* FIXME */,
                  (const DWRITE_TYPOGRAPHIC_FEATURES **)context->user_features.features, context->user_features.range_lengths,
                  context->user_features.range_count, max_count, run->clustermap, context->text_props, run->glyphs,
                  context->glyph_props, &run->glyphcount);
          if (hr == E_NOT_SUFFICIENT_BUFFER)
          {
              free(run->glyphs);
              free(context->glyph_props);
  
              max_count *= 2;
  
              run->glyphs = calloc(max_count, sizeof(*run->glyphs));
              context->glyph_props = calloc(max_count, sizeof(*context->glyph_props));
              if (!run->glyphs || !context->glyph_props)
              {
                  hr = E_OUTOFMEMORY;
                  break;
              }
  
              continue;
          }
  
          break;
      }
  
      if (FAILED(hr))
          WARN("%s: shaping failed, hr %#x.\n", debugstr_rundescr(&run->descr), hr);
  
      run->run.glyphIndices = run->glyphs;
      run->descr.clusterMap = run->clustermap;
  
      return hr;
  }
  
  static struct layout_range_spacing *layout_get_next_spacing_range(struct dwrite_textlayout *layout,
          struct layout_range_spacing *cur)
  {
      return (struct layout_range_spacing *)LIST_ENTRY(list_next(&layout->spacing, &cur->h.entry),
              struct layout_range_header, entry);
  }
  
  static HRESULT layout_shape_apply_character_spacing(struct dwrite_textlayout *layout, struct shaping_context *context)
  {
      struct regular_layout_run *run = context->run;
      struct layout_range_spacing *first = NULL, *last = NULL, *cur;
      unsigned int i, length, pos, start, end, g0, glyph_count;
      struct layout_range_header *h;
      UINT16 *clustermap;
  
      LIST_FOR_EACH_ENTRY(h, &layout->spacing, struct layout_range_header, entry)
      {
          if ((h->range.startPosition >= run->descr.textPosition &&
                  h->range.startPosition <= run->descr.textPosition + run->descr.stringLength) ||
              (run->descr.textPosition >= h->range.startPosition &&
                  run->descr.textPosition <= h->range.startPosition + h->range.length))
          {
              if (!first) first = last = (struct layout_range_spacing *)h;
          }
          else if (last) break;
      }
      if (!first) return S_OK;
  
      if (!(clustermap = calloc(run->descr.stringLength, sizeof(*clustermap))))
          return E_OUTOFMEMORY;
  
      pos = run->descr.textPosition;
  
      for (cur = first;; cur = layout_get_next_spacing_range(layout, cur))
      {
          float leading, trailing;
  
          /* The range current spacing settings apply to. */
          start = max(pos, cur->h.range.startPosition);
          pos = end = min(pos + run->descr.stringLength, cur->h.range.startPosition + cur->h.range.length);
  
          /* Back to run-relative index. */
          start -= run->descr.textPosition;
          end -= run->descr.textPosition;
  
          length = end - start;
  
          g0 = run->descr.clusterMap[start];
  
          for (i = 0; i < length; ++i)
              clustermap[i] = run->descr.clusterMap[start + i] - run->descr.clusterMap[start];
  
          glyph_count = (end < run->descr.stringLength ? run->descr.clusterMap[end] + 1 : run->glyphcount) - g0;
  
          /* There is no direction argument for spacing interface, we have to swap arguments here to get desired output. */
          if (run->run.bidiLevel & 1)
          {
              leading = cur->trailing;
              trailing = cur->leading;
          }
          else
          {
              leading = cur->leading;
              trailing = cur->trailing;
          }
          IDWriteTextAnalyzer2_ApplyCharacterSpacing(context->analyzer, leading, trailing, cur->min_advance,
                  length, glyph_count, clustermap, &run->advances[g0], &run->offsets[g0], &context->glyph_props[g0],
                  &run->advances[g0], &run->offsets[g0]);
  
          if (cur == last) break;
      }
  
      free(clustermap);
  
      return S_OK;
  }
  
  static HRESULT layout_shape_get_positions(struct dwrite_textlayout *layout, struct shaping_context *context)
  {
      struct regular_layout_run *run = context->run;
      HRESULT hr;
  
      run->advances = calloc(run->glyphcount, sizeof(*run->advances));
      run->offsets = calloc(run->glyphcount, sizeof(*run->offsets));
      if (!run->advances || !run->offsets)
          return E_OUTOFMEMORY;
  
      /* Get advances and offsets. */
      if (is_layout_gdi_compatible(layout))
          hr = IDWriteTextAnalyzer2_GetGdiCompatibleGlyphPlacements(context->analyzer, run->descr.string, run->descr.clusterMap,
                  context->text_props, run->descr.stringLength, run->run.glyphIndices, context->glyph_props, run->glyphcount,
                  run->run.fontFace, run->run.fontEmSize, layout->ppdip, &layout->transform,
                  layout->measuringmode == DWRITE_MEASURING_MODE_GDI_NATURAL, run->run.isSideways, run->run.bidiLevel & 1,
                  &run->sa, run->descr.localeName, (const DWRITE_TYPOGRAPHIC_FEATURES **)context->user_features.features,
                  context->user_features.range_lengths, context->user_features.range_count, run->advances, run->offsets);
      else
          hr = IDWriteTextAnalyzer2_GetGlyphPlacements(context->analyzer, run->descr.string, run->descr.clusterMap,
                  context->text_props, run->descr.stringLength, run->run.glyphIndices, context->glyph_props, run->glyphcount,
                  run->run.fontFace, run->run.fontEmSize, run->run.isSideways, run->run.bidiLevel & 1, &run->sa,
                  run->descr.localeName, (const DWRITE_TYPOGRAPHIC_FEATURES **)context->user_features.features,
                  context->user_features.range_lengths, context->user_features.range_count, run->advances, run->offsets);
  
      if (FAILED(hr))
      {
          memset(run->advances, 0, run->glyphcount * sizeof(*run->advances));
          memset(run->offsets, 0, run->glyphcount * sizeof(*run->offsets));
          WARN("%s: failed to get glyph placement info, hr %#x.\n", debugstr_rundescr(&run->descr), hr);
      }
  
      if (SUCCEEDED(hr))
          hr = layout_shape_apply_character_spacing(layout, context);
  
      run->run.glyphAdvances = run->advances;
      run->run.glyphOffsets = run->offsets;
  
      return hr;
  }
  
  static HRESULT layout_shape_run(struct dwrite_textlayout *layout, struct regular_layout_run *run)
  {
      struct shaping_context context = { 0 };
      HRESULT hr;
  
      context.analyzer = get_text_analyzer();
      context.run = run;
  
      if (SUCCEEDED(hr = layout_shape_get_glyphs(layout, &context)))
          hr = layout_shape_get_positions(layout, &context);
  
      layout_shape_clear_context(&context);
  
      /* Special treatment for runs that don't produce visual output, shaping code adds normal glyphs for them,
         with valid cluster map and potentially with non-zero advances; layout code exposes those as zero
         width clusters. */
      if (run->sa.shapes == DWRITE_SCRIPT_SHAPES_NO_VISUAL)
          run->run.glyphCount = 0;
      else
          run->run.glyphCount = run->glyphcount;
  
      return hr;
  }
  
  static HRESULT layout_compute_runs(struct dwrite_textlayout *layout)
  {
      struct layout_run *r;
      UINT32 cluster = 0;
      HRESULT hr;
  
      free_layout_eruns(layout);
      free_layout_runs(layout);
  
      /* Cluster data arrays are allocated once, assuming one text position per cluster. */
      if (!layout->clustermetrics && layout->len)
      {
          layout->clustermetrics = calloc(layout->len, sizeof(*layout->clustermetrics));
          layout->clusters = calloc(layout->len, sizeof(*layout->clusters));
          if (!layout->clustermetrics || !layout->clusters)
          {
              free(layout->clustermetrics);
              free(layout->clusters);
              return E_OUTOFMEMORY;
          }
      }
      layout->cluster_count = 0;
  
      if (FAILED(hr = layout_itemize(layout))) {
          WARN("Itemization failed, hr %#x.\n", hr);
          return hr;
      }
  
      if (FAILED(hr = layout_resolve_fonts(layout))) {
          WARN("Failed to resolve layout fonts, hr %#x.\n", hr);
          return hr;
      }
  
      /* fill run info */
      LIST_FOR_EACH_ENTRY(r, &layout->runs, struct layout_run, entry) {
          struct regular_layout_run *run = &r->u.regular;
          DWRITE_FONT_METRICS fontmetrics = { 0 };
  
          /* we need to do very little in case of inline objects */
          if (r->kind == LAYOUT_RUN_INLINE) {
              DWRITE_CLUSTER_METRICS *metrics = &layout->clustermetrics[cluster];
              struct layout_cluster *c = &layout->clusters[cluster];
              DWRITE_INLINE_OBJECT_METRICS inlinemetrics;
  
              metrics->width = 0.0f;
              metrics->length = r->u.object.length;
              metrics->canWrapLineAfter = 0;
              metrics->isWhitespace = 0;
              metrics->isNewline = 0;
              metrics->isSoftHyphen = 0;
              metrics->isRightToLeft = 0;
              metrics->padding = 0;
              c->run = r;
              c->position = 0; /* there's always one cluster per inline object, so 0 is valid value */
              cluster++;
  
              /* it's not fatal if GetMetrics() fails, all returned metrics are ignored */
              hr = IDWriteInlineObject_GetMetrics(r->u.object.object, &inlinemetrics);
              if (FAILED(hr)) {
                  memset(&inlinemetrics, 0, sizeof(inlinemetrics));
                  hr = S_OK;
              }
              metrics->width = inlinemetrics.width;
              r->baseline = inlinemetrics.baseline;
              r->height = inlinemetrics.height;
  
              /* FIXME: use resolved breakpoints in this case too */
  
              continue;
          }
  
          if (FAILED(hr = layout_shape_run(layout, run)))
              WARN("%s: shaping failed, hr %#x.\n", debugstr_rundescr(&run->descr), hr);
  
          /* baseline derived from font metrics */
          layout_get_font_metrics(layout, run->run.fontFace, run->run.fontEmSize, &fontmetrics);
          layout_get_font_height(run->run.fontEmSize, &fontmetrics, &r->baseline, &r->height);
  
          layout_set_cluster_metrics(layout, r, &cluster);
      }
  
      if (hr == S_OK) {
          layout->cluster_count = cluster;
          if (cluster)
              layout->clustermetrics[cluster-1].canWrapLineAfter = 1;
      }
  
      return hr;
  }
  
  static HRESULT layout_compute(struct dwrite_textlayout *layout)
  {
      HRESULT hr;
  
      if (!(layout->recompute & RECOMPUTE_CLUSTERS))
          return S_OK;
  
      /* nominal breakpoints are evaluated only once, because string never changes */
      if (!layout->nominal_breakpoints)
      {
          IDWriteTextAnalyzer2 *analyzer;
  
          if (!(layout->nominal_breakpoints = calloc(layout->len, sizeof(*layout->nominal_breakpoints))))
              return E_OUTOFMEMORY;
  
          analyzer = get_text_analyzer();
  
          if (FAILED(hr = IDWriteTextAnalyzer2_AnalyzeLineBreakpoints(analyzer,
                  (IDWriteTextAnalysisSource *)&layout->IDWriteTextAnalysisSource1_iface,
                  0, layout->len, (IDWriteTextAnalysisSink *)&layout->IDWriteTextAnalysisSink1_iface)))
              WARN("Line breakpoints analysis failed, hr %#x.\n", hr);
      }
  
      free(layout->actual_breakpoints);
      layout->actual_breakpoints = NULL;
  
      hr = layout_compute_runs(layout);
  
      if (TRACE_ON(dwrite)) {
          struct layout_run *cur;
  
          LIST_FOR_EACH_ENTRY(cur, &layout->runs, struct layout_run, entry) {
              if (cur->kind == LAYOUT_RUN_INLINE)
                  TRACE("run inline object %p, len %u\n", cur->u.object.object, cur->u.object.length);
              else
                  TRACE("run [%u,%u], len %u, bidilevel %u\n", cur->u.regular.descr.textPosition, cur->u.regular.descr.textPosition +
                      cur->u.regular.descr.stringLength-1, cur->u.regular.descr.stringLength, cur->u.regular.run.bidiLevel);
          }
      }
  
      layout->recompute &= ~RECOMPUTE_CLUSTERS;
      return hr;
  }
  
  static inline FLOAT get_cluster_range_width(struct dwrite_textlayout *layout, UINT32 start, UINT32 end)
  {
      FLOAT width = 0.0f;
      for (; start < end; start++)
          width += layout->clustermetrics[start].width;
      return width;
  }
  
  static inline IUnknown *layout_get_effect_from_pos(struct dwrite_textlayout *layout, UINT32 pos)
  {
      struct layout_range_header *h = get_layout_range_header_by_pos(&layout->effects, pos);
      return ((struct layout_range_iface*)h)->iface;
  }
  
  /* A set of parameters that additionally splits resulting runs. It happens after shaping and all text processing,
     no glyph changes are possible. It's understandable for drawing effects, because DrawGlyphRun() reports them as
     one of the arguments, but it also happens for decorations, so every effective run has uniform
     underline/strikethough/effect tuple. */
  struct layout_final_splitting_params {
      BOOL strikethrough;
      BOOL underline;
      IUnknown *effect;
  };
  
  static inline BOOL layout_get_strikethrough_from_pos(struct dwrite_textlayout *layout, UINT32 pos)
  {
      struct layout_range_header *h = get_layout_range_header_by_pos(&layout->strike_ranges, pos);
      return ((struct layout_range_bool*)h)->value;
  }
  
  static inline BOOL layout_get_underline_from_pos(struct dwrite_textlayout *layout, UINT32 pos)
  {
      struct layout_range_header *h = get_layout_range_header_by_pos(&layout->underline_ranges, pos);
      return ((struct layout_range_bool*)h)->value;
  }
  
  static void layout_splitting_params_from_pos(struct dwrite_textlayout *layout, UINT32 pos,
      struct layout_final_splitting_params *params)
  {
      params->strikethrough = layout_get_strikethrough_from_pos(layout, pos);
      params->underline = layout_get_underline_from_pos(layout, pos);
      params->effect = layout_get_effect_from_pos(layout, pos);
  }
  
  static BOOL is_same_splitting_params(const struct layout_final_splitting_params *left,
      const struct layout_final_splitting_params *right)
  {
      return left->strikethrough == right->strikethrough &&
             left->underline == right->underline &&
             left->effect == right->effect;
  }
  
  static void layout_get_erun_font_metrics(struct dwrite_textlayout *layout, struct layout_effective_run *erun,
      DWRITE_FONT_METRICS *metrics)
  {
      memset(metrics, 0, sizeof(*metrics));
      if (is_layout_gdi_compatible(layout)) {
          HRESULT hr = IDWriteFontFace_GetGdiCompatibleMetrics(
              erun->run->u.regular.run.fontFace,
              erun->run->u.regular.run.fontEmSize,
              layout->ppdip,
              &layout->transform,
              metrics);
          if (FAILED(hr))
              WARN("failed to get font metrics, 0x%08x\n", hr);
      }
      else
          IDWriteFontFace_GetMetrics(erun->run->u.regular.run.fontFace, metrics);
  }
  
  /* Effective run is built from consecutive clusters of a single nominal run, 'first_cluster' is 0 based cluster index,
     'cluster_count' indicates how many clusters to add, including first one. */
  static HRESULT layout_add_effective_run(struct dwrite_textlayout *layout, const struct layout_run *r, UINT32 first_cluster,
      UINT32 cluster_count, UINT32 line, FLOAT origin_x, struct layout_final_splitting_params *params)
  {
      BOOL is_rtl = layout->format.readingdir == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
      UINT32 i, start, length, last_cluster;
      struct layout_effective_run *run;
  
      if (r->kind == LAYOUT_RUN_INLINE)
      {
          struct layout_effective_inline *inlineobject;
  
          if (!(inlineobject = malloc(sizeof(*inlineobject))))
              return E_OUTOFMEMORY;
  
          inlineobject->object = r->u.object.object;
          inlineobject->width = get_cluster_range_width(layout, first_cluster, first_cluster + cluster_count);
          inlineobject->origin.x = is_rtl ? origin_x - inlineobject->width : origin_x;
          inlineobject->origin.y = 0.0f; /* set after line is built */
          inlineobject->align_dx = 0.0f;
          inlineobject->baseline = r->baseline;
  
          /* It's not clear how these two are set, possibly directionality
             is derived from surrounding text (replaced text could have
             different ranges which differ in reading direction). */
          inlineobject->is_sideways = FALSE;
          inlineobject->is_rtl = FALSE;
          inlineobject->line = line;
  
          /* effect assigned from start position and on is used for inline objects */
          inlineobject->effect = layout_get_effect_from_pos(layout, layout->clusters[first_cluster].position +
                  layout->clusters[first_cluster].run->start_position);
  
          list_add_tail(&layout->inlineobjects, &inlineobject->entry);
          return S_OK;
      }
  
      if (!(run = malloc(sizeof(*run))))
          return E_OUTOFMEMORY;
  
      /* No need to iterate for that, use simple fact that:
         <last cluster position> = <first cluster position> + <sum of cluster lengths not including last one> */
      last_cluster = first_cluster + cluster_count - 1;
      length = layout->clusters[last_cluster].position - layout->clusters[first_cluster].position +
          layout->clustermetrics[last_cluster].length;
  
      if (!(run->clustermap = calloc(length, sizeof(*run->clustermap))))
      {
          free(run);
          return E_OUTOFMEMORY;
      }
  
      run->run = r;
      run->start = start = layout->clusters[first_cluster].position;
      run->length = length;
      run->width = get_cluster_range_width(layout, first_cluster, first_cluster + cluster_count);
      memset(&run->bbox, 0, sizeof(run->bbox));
  
      /* Check if run direction matches paragraph direction, if it doesn't adjust by
         run width */
      if (is_run_rtl(run) ^ is_rtl)
          run->origin.x = is_rtl ? origin_x - run->width : origin_x + run->width;
      else
          run->origin.x = origin_x;
  
      run->origin.y = 0.0f; /* set after line is built */
      run->align_dx = 0.0f;
      run->line = line;
  
      if (r->u.regular.run.glyphCount) {
          /* Trim leading and trailing clusters. */
          run->glyphcount = r->u.regular.run.glyphCount - r->u.regular.clustermap[start];
          if (start + length < r->u.regular.descr.stringLength)
              run->glyphcount -= r->u.regular.run.glyphCount - r->u.regular.clustermap[start + length];
      }
      else
          run->glyphcount = 0;
  
      /* cluster map needs to be shifted */
      for (i = 0; i < length; i++)
          run->clustermap[i] = r->u.regular.clustermap[start + i] - r->u.regular.clustermap[start];
  
      run->effect = params->effect;
      run->underlined = params->underline;
      list_add_tail(&layout->eruns, &run->entry);
  
      /* Strikethrough style is guaranteed to be consistent within effective run,
         its width equals to run width, thickness and offset are derived from
         font metrics, rest of the values are from layout or run itself */
      if (params->strikethrough)
      {
          struct layout_strikethrough *s;
          DWRITE_FONT_METRICS metrics;
  
          if (!(s = malloc(sizeof(*s))))
              return E_OUTOFMEMORY;
  
          layout_get_erun_font_metrics(layout, run, &metrics);
          s->s.width = get_cluster_range_width(layout, first_cluster, first_cluster + cluster_count);
          s->s.thickness = SCALE_FONT_METRIC(metrics.strikethroughThickness, r->u.regular.run.fontEmSize, &metrics);
          /* Negative offset moves it above baseline as Y coordinate grows downward. */
          s->s.offset = -SCALE_FONT_METRIC(metrics.strikethroughPosition, r->u.regular.run.fontEmSize, &metrics);
          s->s.readingDirection = layout->format.readingdir;
          s->s.flowDirection = layout->format.flow;
          s->s.localeName = r->u.regular.descr.localeName;
          s->s.measuringMode = layout->measuringmode;
          s->run = run;
  
          list_add_tail(&layout->strikethrough, &s->entry);
      }
  
      return S_OK;
  }
  
  static void layout_apply_line_spacing(struct dwrite_textlayout *layout, UINT32 line)
  {
      switch (layout->format.spacing.method)
      {
      case DWRITE_LINE_SPACING_METHOD_DEFAULT:
          layout->lines[line].metrics.height = layout->lines[line].height;
          layout->lines[line].metrics.baseline = layout->lines[line].baseline;
          break;
      case DWRITE_LINE_SPACING_METHOD_UNIFORM:
          layout->lines[line].metrics.height = layout->format.spacing.height;
          layout->lines[line].metrics.baseline = layout->format.spacing.baseline;
          break;
      case DWRITE_LINE_SPACING_METHOD_PROPORTIONAL:
          layout->lines[line].metrics.height = layout->lines[line].height * layout->format.spacing.height;
          layout->lines[line].metrics.baseline = layout->lines[line].baseline * layout->format.spacing.baseline;
          break;
      default:
          ERR("Unknown spacing method %u\n", layout->format.spacing.method);
      }
  }
  
  static HRESULT layout_set_line_metrics(struct dwrite_textlayout *layout, DWRITE_LINE_METRICS1 *metrics)
  {
      size_t i = layout->metrics.lineCount;
  
      if (!dwrite_array_reserve((void **)&layout->lines, &layout->lines_size, layout->metrics.lineCount + 1,
              sizeof(*layout->lines)))
      {
          return E_OUTOFMEMORY;
      }
  
      layout->lines[i].metrics = *metrics;
      layout->lines[i].height = metrics->height;
      layout->lines[i].baseline = metrics->baseline;
  
      if (layout->format.spacing.method != DWRITE_LINE_SPACING_METHOD_DEFAULT)
          layout_apply_line_spacing(layout, i);
  
      layout->metrics.lineCount++;
      return S_OK;
  }
  
  static inline struct layout_effective_run *layout_get_next_erun(struct dwrite_textlayout *layout,
      const struct layout_effective_run *cur)
  {
      struct list *e;
  
      if (!cur)
          e = list_head(&layout->eruns);
      else
          e = list_next(&layout->eruns, &cur->entry);
      if (!e)
          return NULL;
      return LIST_ENTRY(e, struct layout_effective_run, entry);
  }
  
  static inline struct layout_effective_run *layout_get_prev_erun(struct dwrite_textlayout *layout,
      const struct layout_effective_run *cur)
  {
      struct list *e;
  
      if (!cur)
          e = list_tail(&layout->eruns);
      else
          e = list_prev(&layout->eruns, &cur->entry);
      if (!e)
          return NULL;
      return LIST_ENTRY(e, struct layout_effective_run, entry);
  }
  
  static inline struct layout_effective_inline *layout_get_next_inline_run(struct dwrite_textlayout *layout,
      const struct layout_effective_inline *cur)
  {
      struct list *e;
  
      if (!cur)
          e = list_head(&layout->inlineobjects);
      else
          e = list_next(&layout->inlineobjects, &cur->entry);
      if (!e)
          return NULL;
      return LIST_ENTRY(e, struct layout_effective_inline, entry);
  }
  
  static FLOAT layout_get_line_width(struct dwrite_textlayout *layout,
      struct layout_effective_run *erun, struct layout_effective_inline *inrun, UINT32 line)
  {
      FLOAT width = 0.0f;
  
      while (erun && erun->line == line) {
          width += erun->width;
          erun = layout_get_next_erun(layout, erun);
          if (!erun)
              break;
      }
  
      while (inrun && inrun->line == line) {
          width += inrun->width;
          inrun = layout_get_next_inline_run(layout, inrun);
          if (!inrun)
              break;
      }
  
      return width;
  }
  
  static inline BOOL should_skip_transform(const DWRITE_MATRIX *m, FLOAT *det)
  {
      *det = m->m11 * m->m22 - m->m12 * m->m21;
      /* on certain conditions we can skip transform */
      return (!memcmp(m, &identity, sizeof(*m)) || fabsf(*det) <= 1e-10f);
  }
  
  static inline void layout_apply_snapping(D2D1_POINT_2F *vec, BOOL skiptransform, FLOAT ppdip,
      const DWRITE_MATRIX *m, FLOAT det)
  {
      if (!skiptransform) {
          D2D1_POINT_2F vec2;
  
          /* apply transform */
          vec->x *= ppdip;
          vec->y *= ppdip;
  
          vec2.x = m->m11 * vec->x + m->m21 * vec->y + m->dx;
          vec2.y = m->m12 * vec->x + m->m22 * vec->y + m->dy;
  
          /* snap */
          vec2.x = floorf(vec2.x + 0.5f);
          vec2.y = floorf(vec2.y + 0.5f);
  
          /* apply inverted transform, we don't care about X component at this point */
          vec->x = (m->m22 * vec2.x - m->m21 * vec2.y + m->m21 * m->dy - m->m22 * m->dx) / det;
          vec->x /= ppdip;
  
          vec->y = (-m->m12 * vec2.x + m->m11 * vec2.y - (m->m11 * m->dy - m->m12 * m->dx)) / det;
          vec->y /= ppdip;
      }
      else {
          vec->x = floorf(vec->x * ppdip + 0.5f) / ppdip;
          vec->y = floorf(vec->y * ppdip + 0.5f) / ppdip;
      }
  }
  
  static void layout_apply_leading_alignment(struct dwrite_textlayout *layout)
  {
      BOOL is_rtl = layout->format.readingdir == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
      struct layout_effective_inline *inrun;
      struct layout_effective_run *erun;
  
      erun = layout_get_next_erun(layout, NULL);
      inrun = layout_get_next_inline_run(layout, NULL);
  
      while (erun) {
          erun->align_dx = 0.0f;
          erun = layout_get_next_erun(layout, erun);
      }
  
      while (inrun) {
          inrun->align_dx = 0.0f;
          inrun = layout_get_next_inline_run(layout, inrun);
      }
  
      layout->metrics.left = is_rtl ? layout->metrics.layoutWidth - layout->metrics.width : 0.0f;
  }
  
  static void layout_apply_trailing_alignment(struct dwrite_textlayout *layout)
  {
      BOOL is_rtl = layout->format.readingdir == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
      struct layout_effective_inline *inrun;
      struct layout_effective_run *erun;
      UINT32 line;
  
      erun = layout_get_next_erun(layout, NULL);
      inrun = layout_get_next_inline_run(layout, NULL);
  
      for (line = 0; line < layout->metrics.lineCount; line++) {
          FLOAT width = layout_get_line_width(layout, erun, inrun, line);
          FLOAT shift = layout->metrics.layoutWidth - width;
  
          if (is_rtl)
              shift *= -1.0f;
  
          while (erun && erun->line == line) {
              erun->align_dx = shift;
              erun = layout_get_next_erun(layout, erun);
          }
  
          while (inrun && inrun->line == line) {
              inrun->align_dx = shift;
              inrun = layout_get_next_inline_run(layout, inrun);
          }
      }
  
      layout->metrics.left = is_rtl ? 0.0f : layout->metrics.layoutWidth - layout->metrics.width;
  }
  
  static inline FLOAT layout_get_centered_shift(struct dwrite_textlayout *layout, BOOL skiptransform,
      FLOAT width, FLOAT det)
  {
      if (is_layout_gdi_compatible(layout)) {
          D2D1_POINT_2F vec = { layout->metrics.layoutWidth - width, 0.0f};
          layout_apply_snapping(&vec, skiptransform, layout->ppdip, &layout->transform, det);
          return floorf(vec.x / 2.0f);
      }
      else
          return (layout->metrics.layoutWidth - width) / 2.0f;
  }
  
  static void layout_apply_centered_alignment(struct dwrite_textlayout *layout)
  {
      BOOL is_rtl = layout->format.readingdir == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
      struct layout_effective_inline *inrun;
      struct layout_effective_run *erun;
      BOOL skiptransform;
      UINT32 line;
      FLOAT det;
  
      erun = layout_get_next_erun(layout, NULL);
      inrun = layout_get_next_inline_run(layout, NULL);
  
      skiptransform = should_skip_transform(&layout->transform, &det);
  
      for (line = 0; line < layout->metrics.lineCount; line++) {
          FLOAT width = layout_get_line_width(layout, erun, inrun, line);
          FLOAT shift = layout_get_centered_shift(layout, skiptransform, width, det);
  
          if (is_rtl)
              shift *= -1.0f;
  
          while (erun && erun->line == line) {
              erun->align_dx = shift;
              erun = layout_get_next_erun(layout, erun);
          }
  
          while (inrun && inrun->line == line) {
              inrun->align_dx = shift;
              inrun = layout_get_next_inline_run(layout, inrun);
          }
      }
  
      layout->metrics.left = (layout->metrics.layoutWidth - layout->metrics.width) / 2.0f;
  }
  
  static void layout_apply_text_alignment(struct dwrite_textlayout *layout)
  {
      switch (layout->format.textalignment)
      {
      case DWRITE_TEXT_ALIGNMENT_LEADING:
          layout_apply_leading_alignment(layout);
          break;
      case DWRITE_TEXT_ALIGNMENT_TRAILING:
          layout_apply_trailing_alignment(layout);
          break;
      case DWRITE_TEXT_ALIGNMENT_CENTER:
          layout_apply_centered_alignment(layout);
          break;
      case DWRITE_TEXT_ALIGNMENT_JUSTIFIED:
          FIXME("alignment %d not implemented\n", layout->format.textalignment);
          break;
      default:
          ;
      }
  }
  
  static void layout_apply_par_alignment(struct dwrite_textlayout *layout)
  {
      struct layout_effective_inline *inrun;
      struct layout_effective_run *erun;
      FLOAT origin_y = 0.0f;
      UINT32 line;
  
      /* alignment mode defines origin, after that all run origins are updated
         the same way */
  
      switch (layout->format.paralign)
      {
      case DWRITE_PARAGRAPH_ALIGNMENT_NEAR:
          origin_y = 0.0f;
          break;
      case DWRITE_PARAGRAPH_ALIGNMENT_FAR:
          origin_y = layout->metrics.layoutHeight - layout->metrics.height;
          break;
      case DWRITE_PARAGRAPH_ALIGNMENT_CENTER:
          origin_y = (layout->metrics.layoutHeight - layout->metrics.height) / 2.0f;
          break;
      default:
          ;
      }
  
      layout->metrics.top = origin_y;
  
      erun = layout_get_next_erun(layout, NULL);
      inrun = layout_get_next_inline_run(layout, NULL);
      for (line = 0; line < layout->metrics.lineCount; line++)
      {
          float pos_y = origin_y + layout->lines[line].metrics.baseline;
  
          while (erun && erun->line == line) {
              erun->origin.y = pos_y;
              erun = layout_get_next_erun(layout, erun);
          }
  
          while (inrun && inrun->line == line) {
              inrun->origin.y = pos_y - inrun->baseline;
              inrun = layout_get_next_inline_run(layout, inrun);
          }
  
          origin_y += layout->lines[line].metrics.height;
      }
  }
  
  struct layout_underline_splitting_params {
      const WCHAR *locale; /* points to range data, no additional allocation */
      IUnknown *effect;    /* does not hold another reference */
  };
  
  static void init_u_splitting_params_from_erun(struct layout_effective_run *erun,
      struct layout_underline_splitting_params *params)
  {
      params->locale = erun->run->u.regular.descr.localeName;
      params->effect = erun->effect;
  }
  
  static BOOL is_same_u_splitting(struct layout_underline_splitting_params *left,
      struct layout_underline_splitting_params *right)
  {
      return left->effect == right->effect && !wcsicmp(left->locale, right->locale);
  }
  
  static HRESULT layout_add_underline(struct dwrite_textlayout *layout, struct layout_effective_run *first,
      struct layout_effective_run *last)
  {
      FLOAT thickness, offset, runheight;
      struct layout_effective_run *cur;
      DWRITE_FONT_METRICS metrics;
  
      if (first == layout_get_prev_erun(layout, last)) {
          layout_get_erun_font_metrics(layout, first, &metrics);
          thickness = SCALE_FONT_METRIC(metrics.underlineThickness, first->run->u.regular.run.fontEmSize, &metrics);
          offset = SCALE_FONT_METRIC(metrics.underlinePosition, first->run->u.regular.run.fontEmSize, &metrics);
          runheight = SCALE_FONT_METRIC(metrics.capHeight, first->run->u.regular.run.fontEmSize, &metrics);
      }
      else {
          FLOAT width = 0.0f;
  
          /* Single underline is added for consecutive underlined runs. In this case underline parameters are
             calculated as weighted average, where run width acts as a weight. */
          thickness = offset = runheight = 0.0f;
          cur = first;
          do {
              layout_get_erun_font_metrics(layout, cur, &metrics);
  
              thickness += SCALE_FONT_METRIC(metrics.underlineThickness, cur->run->u.regular.run.fontEmSize, &metrics) * cur->width;
              offset += SCALE_FONT_METRIC(metrics.underlinePosition, cur->run->u.regular.run.fontEmSize, &metrics) * cur->width;
              runheight = max(SCALE_FONT_METRIC(metrics.capHeight, cur->run->u.regular.run.fontEmSize, &metrics), runheight);
              width += cur->width;
  
              cur = layout_get_next_erun(layout, cur);
          } while (cur != last);
  
          thickness /= width;
          offset /= width;
      }
  
      cur = first;
      do {
          struct layout_underline_splitting_params params, prev_params;
          struct layout_effective_run *next, *w;
          struct layout_underline *u;
  
          init_u_splitting_params_from_erun(cur, &prev_params);
          while ((next = layout_get_next_erun(layout, cur)) != last) {
              init_u_splitting_params_from_erun(next, &params);
              if (!is_same_u_splitting(&prev_params, &params))
                  break;
              cur = next;
          }
  
          if (!(u = malloc(sizeof(*u))))
              return E_OUTOFMEMORY;
  
          w = cur;
          u->u.width = 0.0f;
          while (w != next) {
              u->u.width += w->width;
              w = layout_get_next_erun(layout, w);
          }
  
          u->u.thickness = thickness;
          /* Font metrics convention is to have it negative when below baseline, for rendering
             however Y grows from baseline down for horizontal baseline. */
          u->u.offset = -offset;
          u->u.runHeight = runheight;
          u->u.readingDirection = is_run_rtl(cur) ? DWRITE_READING_DIRECTION_RIGHT_TO_LEFT :
              DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
          u->u.flowDirection = layout->format.flow;
          u->u.localeName = cur->run->u.regular.descr.localeName;
          u->u.measuringMode = layout->measuringmode;
          u->run = cur;
          list_add_tail(&layout->underlines, &u->entry);
  
          cur = next;
      } while (cur != last);
  
      return S_OK;
  }
  
  /* Adds zero width line, metrics are derived from font at specified text position. */
  static HRESULT layout_set_dummy_line_metrics(struct dwrite_textlayout *layout, UINT32 pos)
  {
      DWRITE_LINE_METRICS1 metrics = { 0 };
      DWRITE_FONT_METRICS fontmetrics;
      struct layout_range *range;
      IDWriteFontFace *fontface;
      IDWriteFont *font;
      HRESULT hr;
  
      range = get_layout_range_by_pos(layout, pos);
      hr = create_matching_font(range->collection,
          range->fontfamily,
          range->weight,
          range->style,
          range->stretch,
          &font);
      if (FAILED(hr))
          return hr;
      hr = IDWriteFont_CreateFontFace(font, &fontface);
      IDWriteFont_Release(font);
      if (FAILED(hr))
          return hr;
  
      layout_get_font_metrics(layout, fontface, range->fontsize, &fontmetrics);
      layout_get_font_height(range->fontsize, &fontmetrics, &metrics.baseline, &metrics.height);
      IDWriteFontFace_Release(fontface);
  
      return layout_set_line_metrics(layout, &metrics);
  }
  
  static void layout_add_line(struct dwrite_textlayout *layout, UINT32 first_cluster, UINT32 last_cluster,
          UINT32 *textpos)
  {
      BOOL is_rtl = layout->format.readingdir == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
      struct layout_final_splitting_params params, prev_params;
      DWRITE_INLINE_OBJECT_METRICS sign_metrics = { 0 };
      UINT32 line = layout->metrics.lineCount, i;
      DWRITE_LINE_METRICS1 metrics = { 0 };
      UINT32 index, start, pos = *textpos;
      FLOAT descent, trailingspacewidth;
      BOOL append_trimming_run = FALSE;
      const struct layout_run *run;
      float width = 0.0f, origin_x;
      HRESULT hr;
  
      /* Take a look at clusters we got for this line in reverse order to set trailing properties for current line */
      for (index = last_cluster, trailingspacewidth = 0.0f; index >= first_cluster; index--) {
          DWRITE_CLUSTER_METRICS *cluster = &layout->clustermetrics[index];
          struct layout_cluster *lc = &layout->clusters[index];
          WCHAR ch;
  
          /* This also filters out clusters added from inline objects, those are never
             treated as a white space. */
          if (!cluster->isWhitespace)
              break;
  
          /* Every isNewline cluster is also isWhitespace, but not every
             newline character cluster has isNewline set, so go back to original string. */
          ch = lc->run->u.regular.descr.string[lc->position];
          if (cluster->length == 1 && lb_is_newline_char(ch))
              metrics.newlineLength += cluster->length;
  
          metrics.trailingWhitespaceLength += cluster->length;
          trailingspacewidth += cluster->width;
  
          if (index == 0)
              break;
      }
  
      /* Line metrics length includes trailing whitespace length too */
      for (i = first_cluster; i <= last_cluster; i++)
          metrics.length += layout->clustermetrics[i].length;
  
      /* Ignore trailing whitespaces */
      while (last_cluster > first_cluster) {
          if (!layout->clustermetrics[last_cluster].isWhitespace)
              break;
  
          last_cluster--;
      }
  
      /* Does not include trailing space width */
      if (!layout->clustermetrics[last_cluster].isWhitespace)
          width = get_cluster_range_width(layout, first_cluster, last_cluster + 1);
  
      /* Append trimming run if necessary */
      if (width > layout->metrics.layoutWidth && layout->format.trimmingsign != NULL &&
              layout->format.trimming.granularity != DWRITE_TRIMMING_GRANULARITY_NONE) {
          FLOAT trimmed_width = width;
  
          hr = IDWriteInlineObject_GetMetrics(layout->format.trimmingsign, &sign_metrics);
          if (SUCCEEDED(hr)) {
              while (last_cluster > first_cluster) {
                  if (trimmed_width + sign_metrics.width <= layout->metrics.layoutWidth)
                      break;
                  if (layout->format.trimming.granularity == DWRITE_TRIMMING_GRANULARITY_CHARACTER)
                      trimmed_width -= layout->clustermetrics[last_cluster--].width;
                  else {
                      while (last_cluster > first_cluster) {
                          trimmed_width -= layout->clustermetrics[last_cluster].width;
                          if (layout->clustermetrics[last_cluster--].canWrapLineAfter)
                              break;
                      }
                  }
              }
              append_trimming_run = TRUE;
          }
          else
              WARN("Failed to get trimming sign metrics, lines won't be trimmed, hr %#x.\n", hr);
  
          width = trimmed_width + sign_metrics.width;
      }
  
      layout_splitting_params_from_pos(layout, pos, &params);
      prev_params = params;
      run = layout->clusters[first_cluster].run;
  
      /* Form runs from a range of clusters; this is what will be reported with DrawGlyphRun() */
      origin_x = is_rtl ? layout->metrics.layoutWidth : 0.0f;
      for (start = first_cluster, i = first_cluster; i <= last_cluster; i++) {
          layout_splitting_params_from_pos(layout, pos, &params);
  
          if (run != layout->clusters[i].run || !is_same_splitting_params(&prev_params, &params)) {
              hr = layout_add_effective_run(layout, run, start, i - start, line, origin_x, &prev_params);
              if (FAILED(hr))
                  return;
  
              origin_x += is_rtl ? -get_cluster_range_width(layout, start, i) :
                  get_cluster_range_width(layout, start, i);
              run = layout->clusters[i].run;
              start = i;
          }
  
          prev_params = params;
          pos += layout->clustermetrics[i].length;
      }
  
      /* Final run from what's left from cluster range */
      hr = layout_add_effective_run(layout, run, start, i - start, line, origin_x, &prev_params);
      if (FAILED(hr))
          return;
  
      if (get_cluster_range_width(layout, start, i) + sign_metrics.width > layout->metrics.layoutWidth)
          append_trimming_run = FALSE;
  
      if (append_trimming_run) {
          struct layout_effective_inline *trimming_sign;
  
          if (!(trimming_sign = calloc(1, sizeof(*trimming_sign))))
              return;
  
          trimming_sign->object = layout->format.trimmingsign;
          trimming_sign->width = sign_metrics.width;
          origin_x += is_rtl ? -get_cluster_range_width(layout, start, i) : get_cluster_range_width(layout, start, i);
          trimming_sign->origin.x = is_rtl ? origin_x - trimming_sign->width : origin_x;
          trimming_sign->origin.y = 0.0f; /* set after line is built */
          trimming_sign->align_dx = 0.0f;
          trimming_sign->baseline = sign_metrics.baseline;
  
          trimming_sign->is_sideways = FALSE;
          trimming_sign->is_rtl = FALSE;
          trimming_sign->line = line;
  
          trimming_sign->effect = layout_get_effect_from_pos(layout, layout->clusters[i].position +
                  layout->clusters[i].run->start_position);
  
          list_add_tail(&layout->inlineobjects, &trimming_sign->entry);
      }
  
      /* Look for max baseline and descent for this line */
      for (index = first_cluster, metrics.baseline = 0.0f, descent = 0.0f; index <= last_cluster; index++) {
          const struct layout_run *cur = layout->clusters[index].run;
          FLOAT cur_descent = cur->height - cur->baseline;
  
          if (cur->baseline > metrics.baseline)
              metrics.baseline = cur->baseline;
          if (cur_descent > descent)
              descent = cur_descent;
      }
  
      layout->metrics.width = max(width, layout->metrics.width);
      layout->metrics.widthIncludingTrailingWhitespace = max(width + trailingspacewidth,
          layout->metrics.widthIncludingTrailingWhitespace);
  
      metrics.height = descent + metrics.baseline;
      metrics.isTrimmed = append_trimming_run || width > layout->metrics.layoutWidth;
      layout_set_line_metrics(layout, &metrics);
  
      *textpos += metrics.length;
  }
  
  static void layout_set_line_positions(struct dwrite_textlayout *layout)
  {
      struct layout_effective_inline *inrun;
      struct layout_effective_run *erun;
      FLOAT origin_y;
      UINT32 line;
  
      /* Now all line info is here, update effective runs positions in flow direction */
      erun = layout_get_next_erun(layout, NULL);
      inrun = layout_get_next_inline_run(layout, NULL);
  
      for (line = 0, origin_y = 0.0f; line < layout->metrics.lineCount; line++)
      {
          float pos_y = origin_y + layout->lines[line].metrics.baseline;
  
          /* For all runs on this line */
          while (erun && erun->line == line) {
              erun->origin.y = pos_y;
              erun = layout_get_next_erun(layout, erun);
          }
  
          /* Same for inline runs */
          while (inrun && inrun->line == line) {
              inrun->origin.y = pos_y - inrun->baseline;
              inrun = layout_get_next_inline_run(layout, inrun);
          }
  
          origin_y += layout->lines[line].metrics.height;
      }
  
      layout->metrics.height = origin_y;
  
      /* Initial paragraph alignment is always near */
      if (layout->format.paralign != DWRITE_PARAGRAPH_ALIGNMENT_NEAR)
          layout_apply_par_alignment(layout);
  }
  
  static BOOL layout_can_wrap_after(const struct dwrite_textlayout *layout, UINT32 cluster)
  {
      if (layout->format.wrapping == DWRITE_WORD_WRAPPING_CHARACTER)
          return TRUE;
  
      return layout->clustermetrics[cluster].canWrapLineAfter;
  }
  
  static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
  {
      BOOL is_rtl = layout->format.readingdir == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
      struct layout_effective_run *erun, *first_underlined;
      UINT32 i, start, textpos, last_breaking_point;
      DWRITE_LINE_METRICS1 metrics;
      FLOAT width;
      UINT32 line;
      HRESULT hr;
  
      if (!(layout->recompute & RECOMPUTE_LINES))
          return S_OK;
  
      free_layout_eruns(layout);
  
      hr = layout_compute(layout);
      if (FAILED(hr))
          return hr;
  
      layout->metrics.lineCount = 0;
      memset(&metrics, 0, sizeof(metrics));
  
      layout->metrics.height = 0.0f;
      layout->metrics.width = 0.0f;
      layout->metrics.widthIncludingTrailingWhitespace = 0.0f;
  
      last_breaking_point = ~0u;
  
      for (i = 0, start = 0, width = 0.0f, textpos = 0; i < layout->cluster_count; i++) {
          BOOL overflow = FALSE;
  
          while (i < layout->cluster_count && !layout->clustermetrics[i].isNewline) {
              /* Check for overflow */
              overflow = ((width + layout->clustermetrics[i].width > layout->metrics.layoutWidth) &&
                      (layout->format.wrapping != DWRITE_WORD_WRAPPING_NO_WRAP));
              if (overflow)
                  break;
  
              if (layout_can_wrap_after(layout, i))
                  last_breaking_point = i;
              width += layout->clustermetrics[i].width;
              i++;
          }
          i = min(i, layout->cluster_count - 1);
  
          /* Ignore if overflown on whitespace */
          if (overflow && !(layout->clustermetrics[i].isWhitespace && layout_can_wrap_after(layout, i))) {
              /* Use most recently found breaking point */
              if (last_breaking_point != ~0u) {
                  i = last_breaking_point;
                  last_breaking_point = ~0u;
              }
              else {
                  /* Otherwise proceed forward to next newline or breaking point */
                  for (; i < layout->cluster_count; i++)
                      if (layout_can_wrap_after(layout, i) || layout->clustermetrics[i].isNewline)
                          break;
              }
          }
          i = min(i, layout->cluster_count - 1);
  
          layout_add_line(layout, start, i, &textpos);
          start = i + 1;
          width = 0.0f;
      }
  
      /* Add dummy line if:
         - there's no text, metrics come from first range in this case;
         - last ended with a mandatory break, metrics come from last text position.
      */
      if (layout->len == 0)
          hr = layout_set_dummy_line_metrics(layout, 0);
      else if (layout->cluster_count && layout->clustermetrics[layout->cluster_count - 1].isNewline)
          hr = layout_set_dummy_line_metrics(layout, layout->len - 1);
      if (FAILED(hr))
          return hr;
  
      layout->metrics.left = is_rtl ? layout->metrics.layoutWidth - layout->metrics.width : 0.0f;
      layout->metrics.top = 0.0f;
      layout->metrics.maxBidiReorderingDepth = 1; /* FIXME */
  
      /* Add explicit underlined runs */
      erun = layout_get_next_erun(layout, NULL);
      first_underlined = erun && erun->underlined ? erun : NULL;
      for (line = 0; line < layout->metrics.lineCount; line++) {
          while (erun && erun->line == line) {
              erun = layout_get_next_erun(layout, erun);
  
              if (first_underlined && (!erun || !erun->underlined)) {
                  layout_add_underline(layout, first_underlined, erun);
                  first_underlined = NULL;
              }
              else if (!first_underlined && erun && erun->underlined)
                  first_underlined = erun;
          }
      }
  
      /* Position runs in flow direction */
      layout_set_line_positions(layout);
  
      /* Initial alignment is always leading */
      if (layout->format.textalignment != DWRITE_TEXT_ALIGNMENT_LEADING)
          layout_apply_text_alignment(layout);
  
      layout->recompute &= ~RECOMPUTE_LINES;
      return hr;
  }
  
  static BOOL is_same_layout_attrvalue(struct layout_range_header const *h, enum layout_range_attr_kind attr,
          struct layout_range_attr_value *value)
  {
      struct layout_range_spacing const *range_spacing = (struct layout_range_spacing*)h;
      struct layout_range_iface const *range_iface = (struct layout_range_iface*)h;
      struct layout_range_bool const *range_bool = (struct layout_range_bool*)h;
      struct layout_range const *range = (struct layout_range*)h;
  
      switch (attr) {
      case LAYOUT_RANGE_ATTR_WEIGHT:
          return range->weight == value->u.weight;
      case LAYOUT_RANGE_ATTR_STYLE:
          return range->style == value->u.style;
      case LAYOUT_RANGE_ATTR_STRETCH:
          return range->stretch == value->u.stretch;
      case LAYOUT_RANGE_ATTR_FONTSIZE:
          return range->fontsize == value->u.fontsize;
      case LAYOUT_RANGE_ATTR_INLINE:
          return range->object == value->u.object;
      case LAYOUT_RANGE_ATTR_EFFECT:
          return range_iface->iface == value->u.effect;
      case LAYOUT_RANGE_ATTR_UNDERLINE:
          return range_bool->value == value->u.underline;
      case LAYOUT_RANGE_ATTR_STRIKETHROUGH:
          return range_bool->value == value->u.strikethrough;
      case LAYOUT_RANGE_ATTR_PAIR_KERNING:
          return range->pair_kerning == value->u.pair_kerning;
      case LAYOUT_RANGE_ATTR_FONTCOLL:
          return range->collection == value->u.collection;
      case LAYOUT_RANGE_ATTR_LOCALE:
          return !wcsicmp(range->locale, value->u.locale);
      case LAYOUT_RANGE_ATTR_FONTFAMILY:
          return !wcscmp(range->fontfamily, value->u.fontfamily);
      case LAYOUT_RANGE_ATTR_SPACING:
          return range_spacing->leading == value->u.spacing.leading &&
                 range_spacing->trailing == value->u.spacing.trailing &&
                 range_spacing->min_advance == value->u.spacing.min_advance;
      case LAYOUT_RANGE_ATTR_TYPOGRAPHY:
          return range_iface->iface == (IUnknown*)value->u.typography;
      default:
          ;
      }
  
      return FALSE;
  }
  
  static inline BOOL is_same_layout_attributes(struct layout_range_header const *hleft, struct layout_range_header const *hright)
  {
      switch (hleft->kind)
      {
      case LAYOUT_RANGE_REGULAR:
      {
          struct layout_range const *left = (struct layout_range const*)hleft;
          struct layout_range const *right = (struct layout_range const*)hright;
          return left->weight == right->weight &&
                 left->style  == right->style &&
                 left->stretch == right->stretch &&
                 left->fontsize == right->fontsize &&
                 left->object == right->object &&
                 left->pair_kerning == right->pair_kerning &&
                 left->collection == right->collection &&
                !wcsicmp(left->locale, right->locale) &&
                !wcscmp(left->fontfamily, right->fontfamily);
      }
      case LAYOUT_RANGE_UNDERLINE:
      case LAYOUT_RANGE_STRIKETHROUGH:
      {
          struct layout_range_bool const *left = (struct layout_range_bool const*)hleft;
          struct layout_range_bool const *right = (struct layout_range_bool const*)hright;
          return left->value == right->value;
      }
      case LAYOUT_RANGE_EFFECT:
      case LAYOUT_RANGE_TYPOGRAPHY:
      {
          struct layout_range_iface const *left = (struct layout_range_iface const*)hleft;
          struct layout_range_iface const *right = (struct layout_range_iface const*)hright;
          return left->iface == right->iface;
      }
      case LAYOUT_RANGE_SPACING:
      {
          struct layout_range_spacing const *left = (struct layout_range_spacing const*)hleft;
          struct layout_range_spacing const *right = (struct layout_range_spacing const*)hright;
          return left->leading == right->leading &&
                 left->trailing == right->trailing &&
                 left->min_advance == right->min_advance;
      }
      default:
          FIXME("unknown range kind %d\n", hleft->kind);
          return FALSE;
      }
  }
  
  static inline BOOL is_same_text_range(const DWRITE_TEXT_RANGE *left, const DWRITE_TEXT_RANGE *right)
  {
      return left->startPosition == right->startPosition && left->length == right->length;
  }
  
  /* Allocates range and inits it with default values from text format. */
  static struct layout_range_header *alloc_layout_range(struct dwrite_textlayout *layout, const DWRITE_TEXT_RANGE *r,
      enum layout_range_kind kind)
  {
      struct layout_range_header *h;
  
      switch (kind)
      {
      case LAYOUT_RANGE_REGULAR:
      {
          struct layout_range *range;
  
          if (!(range = calloc(1, sizeof(*range))))
              return NULL;
  
          range->weight = layout->format.weight;
          range->style  = layout->format.style;
          range->stretch = layout->format.stretch;
          range->fontsize = layout->format.fontsize;
  
          range->fontfamily = wcsdup(layout->format.family_name);
          if (!range->fontfamily)
          {
              free(range);
              return NULL;
          }
  
          range->collection = layout->format.collection;
          if (range->collection)
              IDWriteFontCollection_AddRef(range->collection);
          wcscpy(range->locale, layout->format.locale);
  
          h = &range->h;
          break;
      }
      case LAYOUT_RANGE_UNDERLINE:
      case LAYOUT_RANGE_STRIKETHROUGH:
      {
          struct layout_range_bool *range;
  
          if (!(range = calloc(1, sizeof(*range))))
              return NULL;
  
          h = &range->h;
          break;
      }
      case LAYOUT_RANGE_EFFECT:
      case LAYOUT_RANGE_TYPOGRAPHY:
      {
          struct layout_range_iface *range;
  
          if (!(range = calloc(1, sizeof(*range))))
              return NULL;
  
          h = &range->h;
          break;
      }
      case LAYOUT_RANGE_SPACING:
      {
          struct layout_range_spacing *range;
  
          if (!(range = calloc(1, sizeof(*range))))
              return NULL;
  
          h = &range->h;
          break;
      }
      default:
          FIXME("unknown range kind %d\n", kind);
          return NULL;
      }
  
      h->kind = kind;
      h->range = *r;
      return h;
  }
  
  static struct layout_range_header *alloc_layout_range_from(struct layout_range_header *h, const DWRITE_TEXT_RANGE *r)
  {
      struct layout_range_header *ret;
  
      switch (h->kind)
      {
      case LAYOUT_RANGE_REGULAR:
      {
          struct layout_range *from = (struct layout_range *)h, *range;
  
          if (!(range = malloc(sizeof(*range))))
              return NULL;
  
          *range = *from;
          range->fontfamily = wcsdup(from->fontfamily);
          if (!range->fontfamily)
          {
              free(range);
              return NULL;
          }
  
          /* update refcounts */
          if (range->object)
              IDWriteInlineObject_AddRef(range->object);
          if (range->collection)
              IDWriteFontCollection_AddRef(range->collection);
          ret = &range->h;
          break;
      }
      case LAYOUT_RANGE_UNDERLINE:
      case LAYOUT_RANGE_STRIKETHROUGH:
      {
          struct layout_range_bool *strike = malloc(sizeof(*strike));
          if (!strike) return NULL;
  
          *strike = *(struct layout_range_bool*)h;
          ret = &strike->h;
          break;
      }
      case LAYOUT_RANGE_EFFECT:
      case LAYOUT_RANGE_TYPOGRAPHY:
      {
          struct layout_range_iface *effect = malloc(sizeof(*effect));
          if (!effect) return NULL;
  
          *effect = *(struct layout_range_iface*)h;
          if (effect->iface)
              IUnknown_AddRef(effect->iface);
          ret = &effect->h;
          break;
      }
      case LAYOUT_RANGE_SPACING:
      {
          struct layout_range_spacing *spacing = malloc(sizeof(*spacing));
          if (!spacing) return NULL;
  
          *spacing = *(struct layout_range_spacing*)h;
          ret = &spacing->h;
          break;
      }
      default:
          FIXME("unknown range kind %d\n", h->kind);
          return NULL;
      }
  
      ret->range = *r;
      return ret;
  }
  
  static void free_layout_range(struct layout_range_header *h)
  {
      if (!h)
          return;
  
      switch (h->kind)
      {
      case LAYOUT_RANGE_REGULAR:
      {
          struct layout_range *range = (struct layout_range*)h;
  
          if (range->object)
              IDWriteInlineObject_Release(range->object);
          if (range->collection)
              IDWriteFontCollection_Release(range->collection);
          free(range->fontfamily);
          break;
      }
      case LAYOUT_RANGE_EFFECT:
      case LAYOUT_RANGE_TYPOGRAPHY:
      {
          struct layout_range_iface *range = (struct layout_range_iface*)h;
          if (range->iface)
              IUnknown_Release(range->iface);
          break;
      }
      default:
          ;
      }
  
      free(h);
  }
  
  static void free_layout_ranges_list(struct dwrite_textlayout *layout)
  {
      struct layout_range_header *cur, *cur2;
  
      LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->ranges, struct layout_range_header, entry) {
          list_remove(&cur->entry);
          free_layout_range(cur);
      }
  
      LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->underline_ranges, struct layout_range_header, entry) {
          list_remove(&cur->entry);
          free_layout_range(cur);
      }
  
      LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->strike_ranges, struct layout_range_header, entry) {
          list_remove(&cur->entry);
          free_layout_range(cur);
      }
  
      LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->effects, struct layout_range_header, entry) {
          list_remove(&cur->entry);
          free_layout_range(cur);
      }
  
      LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->spacing, struct layout_range_header, entry) {
          list_remove(&cur->entry);
          free_layout_range(cur);
      }
  
      LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->typographies, struct layout_range_header, entry) {
          list_remove(&cur->entry);
          free_layout_range(cur);
      }
  }
  
  static struct layout_range_header *find_outer_range(struct list *ranges, const DWRITE_TEXT_RANGE *range)
  {
      struct layout_range_header *cur;
  
      LIST_FOR_EACH_ENTRY(cur, ranges, struct layout_range_header, entry) {
  
          if (cur->range.startPosition > range->startPosition)
              return NULL;
  
          if ((cur->range.startPosition + cur->range.length < range->startPosition + range->length) &&
              (range->startPosition < cur->range.startPosition + cur->range.length))
              return NULL;
          if (cur->range.startPosition + cur->range.length >= range->startPosition + range->length)
              return cur;
      }
  
      return NULL;
  }
  
  static inline BOOL set_layout_range_iface_attr(IUnknown **dest, IUnknown *value)
  {
      if (*dest == value) return FALSE;
  
      if (*dest)
          IUnknown_Release(*dest);
      *dest = value;
      if (*dest)
          IUnknown_AddRef(*dest);
  
      return TRUE;
  }
  
  static BOOL set_layout_range_attrval(struct layout_range_header *h, enum layout_range_attr_kind attr, struct layout_range_attr_value *value)
  {
      struct layout_range_spacing *dest_spacing = (struct layout_range_spacing*)h;
      struct layout_range_iface *dest_iface = (struct layout_range_iface*)h;
      struct layout_range_bool *dest_bool = (struct layout_range_bool*)h;
      struct layout_range *dest = (struct layout_range*)h;
  
      BOOL changed = FALSE;
  
      switch (attr) {
      case LAYOUT_RANGE_ATTR_WEIGHT:
          changed = dest->weight != value->u.weight;
          dest->weight = value->u.weight;
          break;
      case LAYOUT_RANGE_ATTR_STYLE:
          changed = dest->style != value->u.style;
          dest->style = value->u.style;
          break;
      case LAYOUT_RANGE_ATTR_STRETCH:
          changed = dest->stretch != value->u.stretch;
          dest->stretch = value->u.stretch;
          break;
      case LAYOUT_RANGE_ATTR_FONTSIZE:
          changed = dest->fontsize != value->u.fontsize;
          dest->fontsize = value->u.fontsize;
          break;
      case LAYOUT_RANGE_ATTR_INLINE:
          changed = set_layout_range_iface_attr((IUnknown**)&dest->object, (IUnknown*)value->u.object);
          break;
      case LAYOUT_RANGE_ATTR_EFFECT:
          changed = set_layout_range_iface_attr(&dest_iface->iface, value->u.effect);
          break;
      case LAYOUT_RANGE_ATTR_UNDERLINE:
          changed = dest_bool->value != value->u.underline;
          dest_bool->value = value->u.underline;
          break;
      case LAYOUT_RANGE_ATTR_STRIKETHROUGH:
          changed = dest_bool->value != value->u.strikethrough;
          dest_bool->value = value->u.strikethrough;
          break;
      case LAYOUT_RANGE_ATTR_PAIR_KERNING:
          changed = dest->pair_kerning != value->u.pair_kerning;
          dest->pair_kerning = value->u.pair_kerning;
          break;
      case LAYOUT_RANGE_ATTR_FONTCOLL:
          changed = set_layout_range_iface_attr((IUnknown**)&dest->collection, (IUnknown*)value->u.collection);
          break;
      case LAYOUT_RANGE_ATTR_LOCALE:
          changed = !!wcsicmp(dest->locale, value->u.locale);
          if (changed)
          {
              wcscpy(dest->locale, value->u.locale);
              wcslwr(dest->locale);
          }
          break;
      case LAYOUT_RANGE_ATTR_FONTFAMILY:
          changed = !!wcscmp(dest->fontfamily, value->u.fontfamily);
          if (changed)
          {
              free(dest->fontfamily);
              dest->fontfamily = wcsdup(value->u.fontfamily);
          }
          break;
      case LAYOUT_RANGE_ATTR_SPACING:
          changed = dest_spacing->leading != value->u.spacing.leading ||
              dest_spacing->trailing != value->u.spacing.trailing ||
              dest_spacing->min_advance != value->u.spacing.min_advance;
          dest_spacing->leading = value->u.spacing.leading;
          dest_spacing->trailing = value->u.spacing.trailing;
          dest_spacing->min_advance = value->u.spacing.min_advance;
          break;
      case LAYOUT_RANGE_ATTR_TYPOGRAPHY:
          changed = set_layout_range_iface_attr(&dest_iface->iface, (IUnknown*)value->u.typography);
          break;
      default:
          ;
      }
  
      return changed;
  }
  
  static inline BOOL is_in_layout_range(const DWRITE_TEXT_RANGE *outer, const DWRITE_TEXT_RANGE *inner)
  {
      return (inner->startPosition >= outer->startPosition) &&
             (inner->startPosition + inner->length <= outer->startPosition + outer->length);
  }
  
  static inline HRESULT return_range(const struct layout_range_header *h, DWRITE_TEXT_RANGE *r)
  {
      if (r) *r = h->range;
      return S_OK;
  }
  
  /* Sets attribute value for given range, does all needed splitting/merging of existing ranges. */
  static HRESULT set_layout_range_attr(struct dwrite_textlayout *layout, enum layout_range_attr_kind attr, struct layout_range_attr_value *value)
  {
      struct layout_range_header *cur, *right, *left, *outer;
      BOOL changed = FALSE;
      struct list *ranges;
      DWRITE_TEXT_RANGE r;
  
      /* ignore zero length ranges */
      if (value->range.length == 0)
          return S_OK;
  
      if (~0u - value->range.startPosition < value->range.length)
          return E_INVALIDARG;
  
      /* select from ranges lists */
      switch (attr)
      {
      case LAYOUT_RANGE_ATTR_WEIGHT:
      case LAYOUT_RANGE_ATTR_STYLE:
      case LAYOUT_RANGE_ATTR_STRETCH:
      case LAYOUT_RANGE_ATTR_FONTSIZE:
      case LAYOUT_RANGE_ATTR_INLINE:
      case LAYOUT_RANGE_ATTR_PAIR_KERNING:
      case LAYOUT_RANGE_ATTR_FONTCOLL:
      case LAYOUT_RANGE_ATTR_LOCALE:
      case LAYOUT_RANGE_ATTR_FONTFAMILY:
          ranges = &layout->ranges;
          break;
      case LAYOUT_RANGE_ATTR_UNDERLINE:
          ranges = &layout->underline_ranges;
          break;
      case LAYOUT_RANGE_ATTR_STRIKETHROUGH:
          ranges = &layout->strike_ranges;
          break;
      case LAYOUT_RANGE_ATTR_EFFECT:
          ranges = &layout->effects;
          break;
      case LAYOUT_RANGE_ATTR_SPACING:
          ranges = &layout->spacing;
          break;
      case LAYOUT_RANGE_ATTR_TYPOGRAPHY:
          ranges = &layout->typographies;
          break;
      default:
          FIXME("unknown attr kind %d\n", attr);
          return E_FAIL;
      }
  
      /* If new range is completely within existing range, split existing range in two */
      if ((outer = find_outer_range(ranges, &value->range))) {
  
          /* no need to add same range */
          if (is_same_layout_attrvalue(outer, attr, value))
              return S_OK;
  
          /* for matching range bounds just replace data */
          if (is_same_text_range(&outer->range, &value->range)) {
              changed = set_layout_range_attrval(outer, attr, value);
              goto done;
          }
  
          /* add new range to the left */
          if (value->range.startPosition == outer->range.startPosition) {
              left = alloc_layout_range_from(outer, &value->range);
              if (!left) return E_OUTOFMEMORY;
  
              changed = set_layout_range_attrval(left, attr, value);
              list_add_before(&outer->entry, &left->entry);
              outer->range.startPosition += value->range.length;
              outer->range.length -= value->range.length;
              goto done;
          }
  
          /* add new range to the right */
          if (value->range.startPosition + value->range.length == outer->range.startPosition + outer->range.length) {
              right = alloc_layout_range_from(outer, &value->range);
              if (!right) return E_OUTOFMEMORY;
  
              changed = set_layout_range_attrval(right, attr, value);
              list_add_after(&outer->entry, &right->entry);
              outer->range.length -= value->range.length;
              goto done;
          }
  
          r.startPosition = value->range.startPosition + value->range.length;
          r.length = outer->range.length + outer->range.startPosition - r.startPosition;
  
          /* right part */
          right = alloc_layout_range_from(outer, &r);
          /* new range in the middle */
          cur = alloc_layout_range_from(outer, &value->range);
          if (!right || !cur) {
              free_layout_range(right);
              free_layout_range(cur);
              return E_OUTOFMEMORY;
          }
  
          /* reuse container range as a left part */
          outer->range.length = value->range.startPosition - outer->range.startPosition;
  
          /* new part */
          set_layout_range_attrval(cur, attr, value);
  
          list_add_after(&outer->entry, &cur->entry);
          list_add_after(&cur->entry, &right->entry);
  
          layout->recompute = RECOMPUTE_EVERYTHING;
          return S_OK;
      }
  
      /* Now it's only possible that given range contains some existing ranges, fully or partially.
         Update all of them. */
      left = get_layout_range_header_by_pos(ranges, value->range.startPosition);
      if (left->range.startPosition == value->range.startPosition)
          changed = set_layout_range_attrval(left, attr, value);
      else /* need to split */ {
          r.startPosition = value->range.startPosition;
          r.length = left->range.length - value->range.startPosition + left->range.startPosition;
          left->range.length -= r.length;
          cur = alloc_layout_range_from(left, &r);
          changed = set_layout_range_attrval(cur, attr, value);
          list_add_after(&left->entry, &cur->entry);
      }
      cur = LIST_ENTRY(list_next(ranges, &left->entry), struct layout_range_header, entry);
  
      /* for all existing ranges covered by new one update value */
      while (cur && is_in_layout_range(&value->range, &cur->range)) {
          changed |= set_layout_range_attrval(cur, attr, value);
          cur = LIST_ENTRY(list_next(ranges, &cur->entry), struct layout_range_header, entry);
      }
  
      /* it's possible rightmost range intersects */
      if (cur && (cur->range.startPosition < value->range.startPosition + value->range.length)) {
          r.startPosition = cur->range.startPosition;
          r.length = value->range.startPosition + value->range.length - cur->range.startPosition;
          left = alloc_layout_range_from(cur, &r);
          changed |= set_layout_range_attrval(left, attr, value);
          cur->range.startPosition += left->range.length;
          cur->range.length -= left->range.length;
          list_add_before(&cur->entry, &left->entry);
      }
  
  done:
      if (changed) {
          struct list *next, *i;
  
          layout->recompute = RECOMPUTE_EVERYTHING;
          i = list_head(ranges);
          while ((next = list_next(ranges, i))) {
              struct layout_range_header *next_range = LIST_ENTRY(next, struct layout_range_header, entry);
  
              cur = LIST_ENTRY(i, struct layout_range_header, entry);
              if (is_same_layout_attributes(cur, next_range)) {
                  /* remove similar range */
                  cur->range.length += next_range->range.length;
                  list_remove(next);
                  free_layout_range(next_range);
              }
              else
                  i = list_next(ranges, i);
          }
      }
  
      return S_OK;
  }
  
  static inline const WCHAR *get_string_attribute_ptr(struct layout_range *range, enum layout_range_attr_kind kind)
  {
      const WCHAR *str;
  
      switch (kind) {
          case LAYOUT_RANGE_ATTR_LOCALE:
              str = range->locale;
              break;
          case LAYOUT_RANGE_ATTR_FONTFAMILY:
              str = range->fontfamily;
              break;
          default:
              str = NULL;
      }
  
      return str;
  }
  
  static HRESULT get_string_attribute_length(struct dwrite_textlayout *layout, enum layout_range_attr_kind kind, UINT32 position,
      UINT32 *length, DWRITE_TEXT_RANGE *r)
  {
      struct layout_range *range;
      const WCHAR *str;
  
      range = get_layout_range_by_pos(layout, position);
      if (!range) {
          *length = 0;
          return S_OK;
      }
  
      str = get_string_attribute_ptr(range, kind);
      *length = wcslen(str);
      return return_range(&range->h, r);
  }
  
  static HRESULT get_string_attribute_value(struct dwrite_textlayout *layout, enum layout_range_attr_kind kind, UINT32 position,
      WCHAR *ret, UINT32 length, DWRITE_TEXT_RANGE *r)
  {
      struct layout_range *range;
      const WCHAR *str;
  
      if (length == 0)
          return E_INVALIDARG;
  
      ret[0] = 0;
      range = get_layout_range_by_pos(layout, position);
      if (!range)
          return E_INVALIDARG;
  
      str = get_string_attribute_ptr(range, kind);
      if (length < wcslen(str) + 1)
          return E_NOT_SUFFICIENT_BUFFER;
  
      wcscpy(ret, str);
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_QueryInterface(IDWriteTextLayout4 *iface, REFIID riid, void **obj)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj);
  
      *obj = NULL;
  
      if (IsEqualIID(riid, &IID_IDWriteTextLayout4) ||
          IsEqualIID(riid, &IID_IDWriteTextLayout3) ||
          IsEqualIID(riid, &IID_IDWriteTextLayout2) ||
          IsEqualIID(riid, &IID_IDWriteTextLayout1) ||
          IsEqualIID(riid, &IID_IDWriteTextLayout) ||
          IsEqualIID(riid, &IID_IUnknown))
      {
          *obj = iface;
      }
      else if (IsEqualIID(riid, &IID_IDWriteTextFormat3) ||
               IsEqualIID(riid, &IID_IDWriteTextFormat2) ||
               IsEqualIID(riid, &IID_IDWriteTextFormat1) ||
               IsEqualIID(riid, &IID_IDWriteTextFormat))
      {
          *obj = &layout->IDWriteTextFormat3_iface;
      }
  
      if (*obj) {
          IDWriteTextLayout4_AddRef(iface);
          return S_OK;
      }
  
      WARN("%s not implemented.\n", debugstr_guid(riid));
  
      return E_NOINTERFACE;
  }
  
  static ULONG WINAPI dwritetextlayout_AddRef(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      ULONG refcount = InterlockedIncrement(&layout->refcount);
  
      TRACE("%p, refcount %u.\n", iface, refcount);
  
      return refcount;
  }
  
  static ULONG WINAPI dwritetextlayout_Release(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      ULONG refcount = InterlockedDecrement(&layout->refcount);
  
      TRACE("%p, refcount %u.\n", iface, refcount);
  
      if (!refcount)
      {
          IDWriteFactory7_Release(layout->factory);
          free_layout_ranges_list(layout);
          free_layout_eruns(layout);
          free_layout_runs(layout);
          release_format_data(&layout->format);
          free(layout->nominal_breakpoints);
          free(layout->actual_breakpoints);
          free(layout->clustermetrics);
          free(layout->clusters);
          free(layout->lines);
          free(layout->str);
          free(layout);
      }
  
      return refcount;
  }
  
  static HRESULT WINAPI dwritetextlayout_SetTextAlignment(IDWriteTextLayout4 *iface, DWRITE_TEXT_ALIGNMENT alignment)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_SetTextAlignment(&layout->IDWriteTextFormat3_iface, alignment);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetParagraphAlignment(IDWriteTextLayout4 *iface,
          DWRITE_PARAGRAPH_ALIGNMENT alignment)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_SetParagraphAlignment(&layout->IDWriteTextFormat3_iface, alignment);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetWordWrapping(IDWriteTextLayout4 *iface, DWRITE_WORD_WRAPPING wrapping)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_SetWordWrapping(&layout->IDWriteTextFormat3_iface, wrapping);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetReadingDirection(IDWriteTextLayout4 *iface,
          DWRITE_READING_DIRECTION direction)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_SetReadingDirection(&layout->IDWriteTextFormat3_iface, direction);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetFlowDirection(IDWriteTextLayout4 *iface, DWRITE_FLOW_DIRECTION direction)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_SetFlowDirection(&layout->IDWriteTextFormat3_iface, direction);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetIncrementalTabStop(IDWriteTextLayout4 *iface, FLOAT tabstop)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_SetIncrementalTabStop(&layout->IDWriteTextFormat3_iface, tabstop);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetTrimming(IDWriteTextLayout4 *iface, DWRITE_TRIMMING const *trimming,
      IDWriteInlineObject *trimming_sign)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_SetTrimming(&layout->IDWriteTextFormat3_iface, trimming, trimming_sign);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetLineSpacing(IDWriteTextLayout4 *iface, DWRITE_LINE_SPACING_METHOD spacing,
      FLOAT line_spacing, FLOAT baseline)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat1_SetLineSpacing((IDWriteTextFormat1 *)&layout->IDWriteTextFormat3_iface, spacing,
              line_spacing, baseline);
  }
  
  static DWRITE_TEXT_ALIGNMENT WINAPI dwritetextlayout_GetTextAlignment(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetTextAlignment(&layout->IDWriteTextFormat3_iface);
  }
  
  static DWRITE_PARAGRAPH_ALIGNMENT WINAPI dwritetextlayout_GetParagraphAlignment(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetParagraphAlignment(&layout->IDWriteTextFormat3_iface);
  }
  
  static DWRITE_WORD_WRAPPING WINAPI dwritetextlayout_GetWordWrapping(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetWordWrapping(&layout->IDWriteTextFormat3_iface);
  }
  
  static DWRITE_READING_DIRECTION WINAPI dwritetextlayout_GetReadingDirection(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetReadingDirection(&layout->IDWriteTextFormat3_iface);
  }
  
  static DWRITE_FLOW_DIRECTION WINAPI dwritetextlayout_GetFlowDirection(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetFlowDirection(&layout->IDWriteTextFormat3_iface);
  }
  
  static FLOAT WINAPI dwritetextlayout_GetIncrementalTabStop(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetIncrementalTabStop(&layout->IDWriteTextFormat3_iface);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetTrimming(IDWriteTextLayout4 *iface, DWRITE_TRIMMING *options,
      IDWriteInlineObject **trimming_sign)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetTrimming(&layout->IDWriteTextFormat3_iface, options, trimming_sign);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetLineSpacing(IDWriteTextLayout4 *iface, DWRITE_LINE_SPACING_METHOD *method,
      FLOAT *spacing, FLOAT *baseline)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat_GetLineSpacing((IDWriteTextFormat *)&layout->IDWriteTextFormat3_iface, method,
              spacing, baseline);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetFontCollection(IDWriteTextLayout4 *iface, IDWriteFontCollection **collection)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetFontCollection(&layout->IDWriteTextFormat3_iface, collection);
  }
  
  static UINT32 WINAPI dwritetextlayout_GetFontFamilyNameLength(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetFontFamilyNameLength(&layout->IDWriteTextFormat3_iface);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetFontFamilyName(IDWriteTextLayout4 *iface, WCHAR *name, UINT32 size)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetFontFamilyName(&layout->IDWriteTextFormat3_iface, name, size);
  }
  
  static DWRITE_FONT_WEIGHT WINAPI dwritetextlayout_GetFontWeight(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetFontWeight(&layout->IDWriteTextFormat3_iface);
  }
  
  static DWRITE_FONT_STYLE WINAPI dwritetextlayout_GetFontStyle(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetFontStyle(&layout->IDWriteTextFormat3_iface);
  }
  
  static DWRITE_FONT_STRETCH WINAPI dwritetextlayout_GetFontStretch(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetFontStretch(&layout->IDWriteTextFormat3_iface);
  }
  
  static FLOAT WINAPI dwritetextlayout_GetFontSize(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetFontSize(&layout->IDWriteTextFormat3_iface);
  }
  
  static UINT32 WINAPI dwritetextlayout_GetLocaleNameLength(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetLocaleNameLength(&layout->IDWriteTextFormat3_iface);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetLocaleName(IDWriteTextLayout4 *iface, WCHAR *name, UINT32 size)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      return IDWriteTextFormat3_GetLocaleName(&layout->IDWriteTextFormat3_iface, name, size);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetMaxWidth(IDWriteTextLayout4 *iface, FLOAT maxWidth)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      BOOL changed;
  
      TRACE("%p, %.8e.\n", iface, maxWidth);
  
      if (maxWidth < 0.0f)
          return E_INVALIDARG;
  
      changed = layout->metrics.layoutWidth != maxWidth;
      layout->metrics.layoutWidth = maxWidth;
  
      if (changed)
          layout->recompute |= RECOMPUTE_LINES_AND_OVERHANGS;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_SetMaxHeight(IDWriteTextLayout4 *iface, FLOAT maxHeight)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      BOOL changed;
  
      TRACE("%p, %.8e.\n", iface, maxHeight);
  
      if (maxHeight < 0.0f)
          return E_INVALIDARG;
  
      changed = layout->metrics.layoutHeight != maxHeight;
      layout->metrics.layoutHeight = maxHeight;
  
      if (changed)
          layout->recompute |= RECOMPUTE_LINES_AND_OVERHANGS;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_SetFontCollection(IDWriteTextLayout4 *iface, IDWriteFontCollection *collection,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %p, %s.\n", iface, collection, debugstr_range(&range));
  
      value.range = range;
      value.u.collection = collection;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_FONTCOLL, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetFontFamilyName(IDWriteTextLayout4 *iface, WCHAR const *name,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %s, %s.\n", iface, debugstr_w(name), debugstr_range(&range));
  
      if (!name)
          return E_INVALIDARG;
  
      value.range = range;
      value.u.fontfamily = name;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_FONTFAMILY, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetFontWeight(IDWriteTextLayout4 *iface, DWRITE_FONT_WEIGHT weight,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %d, %s.\n", iface, weight, debugstr_range(&range));
  
      if ((UINT32)weight > DWRITE_FONT_WEIGHT_ULTRA_BLACK)
          return E_INVALIDARG;
  
      value.range = range;
      value.u.weight = weight;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_WEIGHT, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetFontStyle(IDWriteTextLayout4 *iface, DWRITE_FONT_STYLE style,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %d, %s.\n", iface, style, debugstr_range(&range));
  
      if ((UINT32)style > DWRITE_FONT_STYLE_ITALIC)
          return E_INVALIDARG;
  
      value.range = range;
      value.u.style = style;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_STYLE, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetFontStretch(IDWriteTextLayout4 *iface, DWRITE_FONT_STRETCH stretch,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %d, %s.\n", iface, stretch, debugstr_range(&range));
  
      if (stretch == DWRITE_FONT_STRETCH_UNDEFINED || (UINT32)stretch > DWRITE_FONT_STRETCH_ULTRA_EXPANDED)
          return E_INVALIDARG;
  
      value.range = range;
      value.u.stretch = stretch;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_STRETCH, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetFontSize(IDWriteTextLayout4 *iface, FLOAT size, DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %.8e, %s.\n", iface, size, debugstr_range(&range));
  
      if (size <= 0.0f)
          return E_INVALIDARG;
  
      value.range = range;
      value.u.fontsize = size;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_FONTSIZE, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetUnderline(IDWriteTextLayout4 *iface, BOOL underline, DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %d, %s.\n", iface, underline, debugstr_range(&range));
  
      value.range = range;
      value.u.underline = underline;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_UNDERLINE, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetStrikethrough(IDWriteTextLayout4 *iface, BOOL strikethrough,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %d, %s.\n", iface, strikethrough, debugstr_range(&range));
  
      value.range = range;
      value.u.strikethrough = strikethrough;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_STRIKETHROUGH, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetDrawingEffect(IDWriteTextLayout4 *iface, IUnknown* effect,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %p, %s.\n", iface, effect, debugstr_range(&range));
  
      value.range = range;
      value.u.effect = effect;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_EFFECT, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetInlineObject(IDWriteTextLayout4 *iface, IDWriteInlineObject *object,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %p, %s.\n", iface, object, debugstr_range(&range));
  
      value.range = range;
      value.u.object = object;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_INLINE, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetTypography(IDWriteTextLayout4 *iface, IDWriteTypography *typography,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %p, %s.\n", iface, typography, debugstr_range(&range));
  
      value.range = range;
      value.u.typography = typography;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_TYPOGRAPHY, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout_SetLocaleName(IDWriteTextLayout4 *iface, WCHAR const* locale,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %s, %s.\n", iface, debugstr_w(locale), debugstr_range(&range));
  
      if (!locale || wcslen(locale) > LOCALE_NAME_MAX_LENGTH-1)
          return E_INVALIDARG;
  
      value.range = range;
      value.u.locale = locale;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_LOCALE, &value);
  }
  
  static FLOAT WINAPI dwritetextlayout_GetMaxWidth(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->metrics.layoutWidth;
  }
  
  static FLOAT WINAPI dwritetextlayout_GetMaxHeight(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->metrics.layoutHeight;
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetFontCollection(IDWriteTextLayout4 *iface, UINT32 position,
          IDWriteFontCollection **collection, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, collection, r);
  
      range = get_layout_range_by_pos(layout, position);
      *collection = range->collection;
      if (*collection)
          IDWriteFontCollection_AddRef(*collection);
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetFontFamilyNameLength(IDWriteTextLayout4 *iface,
      UINT32 position, UINT32 *length, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %d, %p, %p.\n", iface, position, length, r);
  
      return get_string_attribute_length(layout, LAYOUT_RANGE_ATTR_FONTFAMILY, position, length, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetFontFamilyName(IDWriteTextLayout4 *iface,
      UINT32 position, WCHAR *name, UINT32 length, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %u, %p, %u, %p.\n", iface, position, name, length, r);
  
      return get_string_attribute_value(layout, LAYOUT_RANGE_ATTR_FONTFAMILY, position, name, length, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetFontWeight(IDWriteTextLayout4 *iface,
      UINT32 position, DWRITE_FONT_WEIGHT *weight, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, weight, r);
  
      range = get_layout_range_by_pos(layout, position);
      *weight = range->weight;
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetFontStyle(IDWriteTextLayout4 *iface,
      UINT32 position, DWRITE_FONT_STYLE *style, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, style, r);
  
      range = get_layout_range_by_pos(layout, position);
      *style = range->style;
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetFontStretch(IDWriteTextLayout4 *iface,
      UINT32 position, DWRITE_FONT_STRETCH *stretch, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, stretch, r);
  
      range = get_layout_range_by_pos(layout, position);
      *stretch = range->stretch;
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetFontSize(IDWriteTextLayout4 *iface,
      UINT32 position, FLOAT *size, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, size, r);
  
      range = get_layout_range_by_pos(layout, position);
      *size = range->fontsize;
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetUnderline(IDWriteTextLayout4 *iface,
      UINT32 position, BOOL *underline, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_bool *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, underline, r);
  
      range = (struct layout_range_bool *)get_layout_range_header_by_pos(&layout->underline_ranges, position);
      *underline = range->value;
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetStrikethrough(IDWriteTextLayout4 *iface,
      UINT32 position, BOOL *strikethrough, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_bool *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, strikethrough, r);
  
      range = (struct layout_range_bool *)get_layout_range_header_by_pos(&layout->strike_ranges, position);
      *strikethrough = range->value;
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetDrawingEffect(IDWriteTextLayout4 *iface,
      UINT32 position, IUnknown **effect, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_iface *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, effect, r);
  
      range = (struct layout_range_iface *)get_layout_range_header_by_pos(&layout->effects, position);
      *effect = range->iface;
      if (*effect)
          IUnknown_AddRef(*effect);
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetInlineObject(IDWriteTextLayout4 *iface,
      UINT32 position, IDWriteInlineObject **object, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, object, r);
  
      range = get_layout_range_by_pos(layout, position);
      *object = range->object;
      if (*object)
          IDWriteInlineObject_AddRef(*object);
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetTypography(IDWriteTextLayout4 *iface,
      UINT32 position, IDWriteTypography** typography, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_iface *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, typography, r);
  
      range = (struct layout_range_iface *)get_layout_range_header_by_pos(&layout->typographies, position);
      *typography = (IDWriteTypography *)range->iface;
      if (*typography)
          IDWriteTypography_AddRef(*typography);
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetLocaleNameLength(IDWriteTextLayout4 *iface,
          UINT32 position, UINT32 *length, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %u, %p, %p.\n", iface, position, length, r);
  
      return get_string_attribute_length(layout, LAYOUT_RANGE_ATTR_LOCALE, position, length, r);
  }
  
  static HRESULT WINAPI dwritetextlayout_layout_GetLocaleName(IDWriteTextLayout4 *iface,
          UINT32 position, WCHAR *locale, UINT32 length, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %u, %p, %u, %p.\n", iface, position, locale, length, r);
  
      return get_string_attribute_value(layout, LAYOUT_RANGE_ATTR_LOCALE, position, locale, length, r);
  }
  
  static inline FLOAT renderer_apply_snapping(FLOAT coord, BOOL skiptransform, FLOAT ppdip, FLOAT det,
      const DWRITE_MATRIX *m)
  {
      D2D1_POINT_2F vec, vec2;
  
      if (!skiptransform) {
          /* apply transform */
          vec.x = 0.0f;
          vec.y = coord * ppdip;
  
          vec2.x = m->m11 * vec.x + m->m21 * vec.y + m->dx;
          vec2.y = m->m12 * vec.x + m->m22 * vec.y + m->dy;
  
          /* snap */
          vec2.x = floorf(vec2.x + 0.5f);
          vec2.y = floorf(vec2.y + 0.5f);
  
          /* apply inverted transform, we don't care about X component at this point */
          vec.y = (-m->m12 * vec2.x + m->m11 * vec2.y - (m->m11 * m->dy - m->m12 * m->dx)) / det;
          vec.y /= ppdip;
      }
      else
          vec.y = floorf(coord * ppdip + 0.5f) / ppdip;
  
      return vec.y;
  }
  
  static HRESULT WINAPI dwritetextlayout_Draw(IDWriteTextLayout4 *iface,
      void *context, IDWriteTextRenderer* renderer, FLOAT origin_x, FLOAT origin_y)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      BOOL disabled = FALSE, skiptransform = FALSE;
      struct layout_effective_inline *inlineobject;
      struct layout_effective_run *run;
      struct layout_strikethrough *s;
      struct layout_underline *u;
      FLOAT det = 0.0f, ppdip = 0.0f;
      DWRITE_MATRIX m = { 0 };
      HRESULT hr;
  
      TRACE("%p, %p, %p, %.8e, %.8e.\n", iface, context, renderer, origin_x, origin_y);
  
      hr = layout_compute_effective_runs(layout);
      if (FAILED(hr))
          return hr;
  
      hr = IDWriteTextRenderer_IsPixelSnappingDisabled(renderer, context, &disabled);
      if (FAILED(hr))
          return hr;
  
      if (!disabled) {
          hr = IDWriteTextRenderer_GetPixelsPerDip(renderer, context, &ppdip);
          if (FAILED(hr))
              return hr;
  
          hr = IDWriteTextRenderer_GetCurrentTransform(renderer, context, &m);
          if (FAILED(hr))
              return hr;
  
          /* it's only allowed to have a diagonal/antidiagonal transform matrix */
          if (ppdip <= 0.0f ||
              (m.m11 * m.m22 != 0.0f && (m.m12 != 0.0f || m.m21 != 0.0f)) ||
              (m.m12 * m.m21 != 0.0f && (m.m11 != 0.0f || m.m22 != 0.0f)))
              disabled = TRUE;
          else
              skiptransform = should_skip_transform(&m, &det);
      }
  
  #define SNAP_COORD(x) (disabled ? (x) : renderer_apply_snapping((x), skiptransform, ppdip, det, &m))
      /* 1. Regular runs */
      LIST_FOR_EACH_ENTRY(run, &layout->eruns, struct layout_effective_run, entry)
      {
          const struct regular_layout_run *regular = &run->run->u.regular;
          UINT32 start_glyph = regular->clustermap[run->start];
          DWRITE_GLYPH_RUN_DESCRIPTION descr;
          DWRITE_GLYPH_RUN glyph_run;
  
          /* Everything but cluster map will be reused from nominal run, as we only need
             to adjust some pointers. Cluster map however is rebuilt when effective run is added,
             it can't be reused because it has to start with 0 index for each reported run. */
          glyph_run = regular->run;
          glyph_run.glyphCount = run->glyphcount;
  
          /* fixup glyph data arrays */
          glyph_run.glyphIndices += start_glyph;
          glyph_run.glyphAdvances += start_glyph;
          glyph_run.glyphOffsets += start_glyph;
  
          /* description */
          descr = regular->descr;
          descr.stringLength = run->length;
          descr.string += run->start;
          descr.clusterMap = run->clustermap;
          descr.textPosition += run->start;
  
          /* return value is ignored */
          IDWriteTextRenderer_DrawGlyphRun(renderer,
              context,
              run->origin.x + run->align_dx + origin_x,
              SNAP_COORD(run->origin.y + origin_y),
              layout->measuringmode,
              &glyph_run,
              &descr,
              run->effect);
      }
  
      /* 2. Inline objects */
      LIST_FOR_EACH_ENTRY(inlineobject, &layout->inlineobjects, struct layout_effective_inline, entry)
      {
          IDWriteTextRenderer_DrawInlineObject(renderer,
              context,
              inlineobject->origin.x + inlineobject->align_dx + origin_x,
              SNAP_COORD(inlineobject->origin.y + origin_y),
              inlineobject->object,
              inlineobject->is_sideways,
              inlineobject->is_rtl,
              inlineobject->effect);
      }
  
      /* 3. Underlines */
      LIST_FOR_EACH_ENTRY(u, &layout->underlines, struct layout_underline, entry)
      {
          IDWriteTextRenderer_DrawUnderline(renderer,
              context,
              /* horizontal underline always grows from left to right, width is always added to origin regardless of run direction */
              (is_run_rtl(u->run) ? u->run->origin.x - u->run->width : u->run->origin.x) + u->run->align_dx + origin_x,
              SNAP_COORD(u->run->origin.y + origin_y),
              &u->u,
              u->run->effect);
      }
  
      /* 4. Strikethrough */
      LIST_FOR_EACH_ENTRY(s, &layout->strikethrough, struct layout_strikethrough, entry)
      {
          IDWriteTextRenderer_DrawStrikethrough(renderer,
              context,
              s->run->origin.x + s->run->align_dx + origin_x,
              SNAP_COORD(s->run->origin.y + origin_y),
              &s->s,
              s->run->effect);
      }
  #undef SNAP_COORD
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_GetLineMetrics(IDWriteTextLayout4 *iface,
      DWRITE_LINE_METRICS *metrics, UINT32 max_count, UINT32 *count)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      unsigned int line_count;
      HRESULT hr;
      size_t i;
  
      TRACE("%p, %p, %u, %p.\n", iface, metrics, max_count, count);
  
      if (FAILED(hr = layout_compute_effective_runs(layout)))
          return hr;
  
      if (metrics)
      {
          line_count = min(max_count, layout->metrics.lineCount);
          for (i = 0; i < line_count; ++i)
              memcpy(&metrics[i], &layout->lines[i].metrics, sizeof(*metrics));
      }
  
      *count = layout->metrics.lineCount;
      return max_count >= layout->metrics.lineCount ? S_OK : E_NOT_SUFFICIENT_BUFFER;
  }
  
  static HRESULT layout_update_metrics(struct dwrite_textlayout *layout)
  {
      return layout_compute_effective_runs(layout);
  }
  
  static HRESULT WINAPI dwritetextlayout_GetMetrics(IDWriteTextLayout4 *iface, DWRITE_TEXT_METRICS *metrics)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      HRESULT hr;
  
      TRACE("%p, %p.\n", iface, metrics);
  
      hr = layout_update_metrics(layout);
      if (hr == S_OK)
          memcpy(metrics, &layout->metrics, sizeof(*metrics));
  
      return hr;
  }
  
  static void d2d_rect_offset(D2D1_RECT_F *rect, FLOAT x, FLOAT y)
  {
      rect->left += x;
      rect->right += x;
      rect->top += y;
      rect->bottom += y;
  }
  
  static BOOL d2d_rect_is_empty(const D2D1_RECT_F *rect)
  {
      return ((rect->left >= rect->right) || (rect->top >= rect->bottom));
  }
  
  static void d2d_rect_union(D2D1_RECT_F *dst, const D2D1_RECT_F *src)
  {
      if (d2d_rect_is_empty(dst)) {
          if (d2d_rect_is_empty(src)) {
              dst->left = dst->right = dst->top = dst->bottom = 0.0f;
              return;
          }
          else
              *dst = *src;
      }
      else {
          if (!d2d_rect_is_empty(src)) {
              dst->left   = min(dst->left, src->left);
              dst->right  = max(dst->right, src->right);
              dst->top    = min(dst->top, src->top);
              dst->bottom = max(dst->bottom, src->bottom);
          }
      }
  }
  
  static void layout_get_erun_bbox(struct dwrite_textlayout *layout, struct layout_effective_run *run, D2D1_RECT_F *bbox)
  {
      const struct regular_layout_run *regular = &run->run->u.regular;
      UINT32 start_glyph = regular->clustermap[run->start];
      D2D1_POINT_2F baseline_origin = { 0 }, *origins;
      DWRITE_GLYPH_RUN glyph_run;
      unsigned int i;
      HRESULT hr;
  
      if (run->bbox.top == run->bbox.bottom)
      {
          struct dwrite_glyphbitmap glyph_bitmap;
          RECT *bbox;
  
          glyph_run = regular->run;
          glyph_run.glyphCount = run->glyphcount;
          glyph_run.glyphIndices = &regular->run.glyphIndices[start_glyph];
          glyph_run.glyphAdvances = &regular->run.glyphAdvances[start_glyph];
          glyph_run.glyphOffsets = &regular->run.glyphOffsets[start_glyph];
  
          memset(&glyph_bitmap, 0, sizeof(glyph_bitmap));
          glyph_bitmap.simulations = IDWriteFontFace_GetSimulations(glyph_run.fontFace);
          glyph_bitmap.emsize = glyph_run.fontEmSize;
          glyph_bitmap.nohint = layout->measuringmode == DWRITE_MEASURING_MODE_NATURAL;
  
          bbox = &glyph_bitmap.bbox;
  
          if (!(origins = calloc(glyph_run.glyphCount, sizeof(*origins))))
              return;
  
          if (FAILED(hr = compute_glyph_origins(&glyph_run, layout->measuringmode, baseline_origin, &layout->transform, origins)))
          {
              WARN("Failed to compute glyph origins, hr %#x.\n", hr);
              free(origins);
              return;
          }
  
          for (i = 0; i < glyph_run.glyphCount; ++i)
          {
              D2D1_RECT_F glyph_bbox;
  
              glyph_bitmap.glyph = glyph_run.glyphIndices[i];
              dwrite_fontface_get_glyph_bbox(glyph_run.fontFace, &glyph_bitmap);
  
              glyph_bbox.left = bbox->left;
              glyph_bbox.top = bbox->top;
              glyph_bbox.right = bbox->right;
              glyph_bbox.bottom = bbox->bottom;
  
              d2d_rect_offset(&glyph_bbox, origins[i].x, origins[i].y);
              d2d_rect_union(&run->bbox, &glyph_bbox);
          }
  
          free(origins);
      }
  
      *bbox = run->bbox;
      d2d_rect_offset(bbox, run->origin.x + run->align_dx, run->origin.y);
  }
  
  static void layout_get_inlineobj_bbox(struct dwrite_textlayout *layout, struct layout_effective_inline *run,
          D2D1_RECT_F *bbox)
  {
      DWRITE_OVERHANG_METRICS overhang_metrics = { 0 };
      DWRITE_INLINE_OBJECT_METRICS metrics = { 0 };
      HRESULT hr;
  
      if (FAILED(hr = IDWriteInlineObject_GetMetrics(run->object, &metrics))) {
          WARN("Failed to get inline object metrics, hr %#x.\n", hr);
          memset(bbox, 0, sizeof(*bbox));
          return;
      }
  
      bbox->left = run->origin.x + run->align_dx;
      bbox->right = bbox->left + metrics.width;
      bbox->top = run->origin.y;
      bbox->bottom = bbox->top + metrics.height;
  
      IDWriteInlineObject_GetOverhangMetrics(run->object, &overhang_metrics);
  
      bbox->left -= overhang_metrics.left;
      bbox->right += overhang_metrics.right;
      bbox->top -= overhang_metrics.top;
      bbox->bottom += overhang_metrics.bottom;
  }
  
  static HRESULT WINAPI dwritetextlayout_GetOverhangMetrics(IDWriteTextLayout4 *iface,
          DWRITE_OVERHANG_METRICS *overhangs)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_effective_inline *inline_run;
      struct layout_effective_run *run;
      D2D1_RECT_F bbox = { 0 };
      HRESULT hr;
  
      TRACE("%p, %p.\n", iface, overhangs);
  
      memset(overhangs, 0, sizeof(*overhangs));
  
      if (!(layout->recompute & RECOMPUTE_OVERHANGS))
      {
          *overhangs = layout->overhangs;
          return S_OK;
      }
  
      hr = layout_compute_effective_runs(layout);
      if (FAILED(hr))
          return hr;
  
      LIST_FOR_EACH_ENTRY(run, &layout->eruns, struct layout_effective_run, entry)
      {
          D2D1_RECT_F run_bbox;
  
          layout_get_erun_bbox(layout, run, &run_bbox);
          d2d_rect_union(&bbox, &run_bbox);
      }
  
      LIST_FOR_EACH_ENTRY(inline_run, &layout->inlineobjects, struct layout_effective_inline, entry)
      {
          D2D1_RECT_F object_bbox;
  
          layout_get_inlineobj_bbox(layout, inline_run, &object_bbox);
          d2d_rect_union(&bbox, &object_bbox);
      }
  
      /* Deltas from layout box. */
      layout->overhangs.left = -bbox.left;
      layout->overhangs.top = -bbox.top;
      layout->overhangs.right = bbox.right - layout->metrics.layoutWidth;
      layout->overhangs.bottom = bbox.bottom - layout->metrics.layoutHeight;
      layout->recompute &= ~RECOMPUTE_OVERHANGS;
  
      *overhangs = layout->overhangs;
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_GetClusterMetrics(IDWriteTextLayout4 *iface,
      DWRITE_CLUSTER_METRICS *metrics, UINT32 max_count, UINT32 *count)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      HRESULT hr;
  
      TRACE("%p, %p, %u, %p.\n", iface, metrics, max_count, count);
  
      hr = layout_compute(layout);
      if (FAILED(hr))
          return hr;
  
      if (metrics)
          memcpy(metrics, layout->clustermetrics, sizeof(DWRITE_CLUSTER_METRICS) * min(max_count, layout->cluster_count));
  
      *count = layout->cluster_count;
      return max_count >= layout->cluster_count ? S_OK : E_NOT_SUFFICIENT_BUFFER;
  }
  
  static HRESULT WINAPI dwritetextlayout_DetermineMinWidth(IDWriteTextLayout4 *iface, FLOAT* min_width)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      UINT32 start;
      FLOAT width;
      HRESULT hr;
  
      TRACE("%p, %p.\n", iface, min_width);
  
      if (!min_width)
          return E_INVALIDARG;
  
      if (!(layout->recompute & RECOMPUTE_MINIMAL_WIDTH))
          goto width_done;
  
      *min_width = 0.0f;
      hr = layout_compute(layout);
      if (FAILED(hr))
          return hr;
  
      /* Find widest word without emergency breaking between clusters, trailing whitespaces
         preceding breaking point do not contribute to word width. */
      for (start = 0; start < layout->cluster_count;)
      {
          UINT32 end = start, j, next;
  
          /* Last cluster always could be wrapped after. */
          while (!layout->clustermetrics[end].canWrapLineAfter)
              end++;
          /* make is so current cluster range that we can wrap after is [start,end) */
          end++;
  
          next = end;
  
          /* Ignore trailing whitespace clusters, in case of single space range will
             be reduced to empty range, or [start,start+1). */
          while (end > start && layout->clustermetrics[end-1].isWhitespace)
              end--;
  
          /* check if cluster range exceeds last minimal width */
          width = 0.0f;
          for (j = start; j < end; j++)
              width += layout->clustermetrics[j].width;
  
          start = next;
  
          if (width > layout->minwidth)
              layout->minwidth = width;
      }
      layout->recompute &= ~RECOMPUTE_MINIMAL_WIDTH;
  
  width_done:
      *min_width = layout->minwidth;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_HitTestPoint(IDWriteTextLayout4 *iface,
      FLOAT pointX, FLOAT pointY, BOOL* is_trailinghit, BOOL* is_inside, DWRITE_HIT_TEST_METRICS *metrics)
  {
      FIXME("%p, %.8e, %.8e, %p, %p, %p): stub\n", iface, pointX, pointY, is_trailinghit, is_inside, metrics);
  
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI dwritetextlayout_HitTestTextPosition(IDWriteTextLayout4 *iface,
          UINT32 textPosition, BOOL is_trailinghit, FLOAT *pointX, FLOAT *pointY, DWRITE_HIT_TEST_METRICS *metrics)
  {
      FIXME("%p, %u, %d, %p, %p, %p): stub\n", iface, textPosition, is_trailinghit, pointX, pointY, metrics);
  
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI dwritetextlayout_HitTestTextRange(IDWriteTextLayout4 *iface,
      UINT32 textPosition, UINT32 textLength, FLOAT originX, FLOAT originY,
      DWRITE_HIT_TEST_METRICS *metrics, UINT32 max_metricscount, UINT32* actual_metricscount)
  {
      FIXME("%p, %u, %u, %f, %f, %p, %u, %p): stub\n", iface, textPosition, textLength, originX, originY, metrics,
          max_metricscount, actual_metricscount);
  
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI dwritetextlayout1_SetPairKerning(IDWriteTextLayout4 *iface, BOOL is_pairkerning_enabled,
          DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %d, %s.\n", iface, is_pairkerning_enabled, debugstr_range(&range));
  
      value.range = range;
      value.u.pair_kerning = !!is_pairkerning_enabled;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_PAIR_KERNING, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout1_GetPairKerning(IDWriteTextLayout4 *iface, UINT32 position,
          BOOL *is_pairkerning_enabled, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range *range;
  
      TRACE("%p, %u, %p, %p.\n", iface, position, is_pairkerning_enabled, r);
  
      range = get_layout_range_by_pos(layout, position);
      *is_pairkerning_enabled = range->pair_kerning;
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout1_SetCharacterSpacing(IDWriteTextLayout4 *iface, FLOAT leading, FLOAT trailing,
      FLOAT min_advance, DWRITE_TEXT_RANGE range)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_attr_value value;
  
      TRACE("%p, %.8e, %.8e, %.8e, %s.\n", iface, leading, trailing, min_advance, debugstr_range(&range));
  
      if (min_advance < 0.0f)
          return E_INVALIDARG;
  
      value.range = range;
      value.u.spacing.leading = leading;
      value.u.spacing.trailing = trailing;
      value.u.spacing.min_advance = min_advance;
      return set_layout_range_attr(layout, LAYOUT_RANGE_ATTR_SPACING, &value);
  }
  
  static HRESULT WINAPI dwritetextlayout1_GetCharacterSpacing(IDWriteTextLayout4 *iface, UINT32 position, FLOAT *leading,
      FLOAT *trailing, FLOAT *min_advance, DWRITE_TEXT_RANGE *r)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      struct layout_range_spacing *range;
  
      TRACE("%p, %u, %p, %p, %p, %p.\n", iface, position, leading, trailing, min_advance, r);
  
      range = (struct layout_range_spacing *)get_layout_range_header_by_pos(&layout->spacing, position);
      *leading = range->leading;
      *trailing = range->trailing;
      *min_advance = range->min_advance;
  
      return return_range(&range->h, r);
  }
  
  static HRESULT WINAPI dwritetextlayout2_GetMetrics(IDWriteTextLayout4 *iface, DWRITE_TEXT_METRICS1 *metrics)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      HRESULT hr;
  
      TRACE("%p, %p.\n", iface, metrics);
  
      if (SUCCEEDED(hr = layout_update_metrics(layout)))
          *metrics = layout->metrics;
  
      return hr;
  }
  
  static HRESULT layout_set_vertical_orientation(struct dwrite_textlayout *layout,
          DWRITE_VERTICAL_GLYPH_ORIENTATION orientation)
  {
      BOOL changed;
      HRESULT hr;
  
      if (FAILED(hr = format_set_vertical_orientation(&layout->format, orientation, &changed)))
          return hr;
  
      if (changed)
          layout->recompute = RECOMPUTE_EVERYTHING;
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout2_SetVerticalGlyphOrientation(IDWriteTextLayout4 *iface,
          DWRITE_VERTICAL_GLYPH_ORIENTATION orientation)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %d.\n", iface, orientation);
  
      return layout_set_vertical_orientation(layout, orientation);
  }
  
  static DWRITE_VERTICAL_GLYPH_ORIENTATION WINAPI dwritetextlayout2_GetVerticalGlyphOrientation(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.vertical_orientation;
  }
  
  static HRESULT WINAPI dwritetextlayout2_SetLastLineWrapping(IDWriteTextLayout4 *iface, BOOL lastline_wrapping_enabled)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %d.\n", iface, lastline_wrapping_enabled);
  
      return IDWriteTextFormat3_SetLastLineWrapping(&layout->IDWriteTextFormat3_iface, lastline_wrapping_enabled);
  }
  
  static BOOL WINAPI dwritetextlayout2_GetLastLineWrapping(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p.\n", iface);
  
      return IDWriteTextFormat3_GetLastLineWrapping(&layout->IDWriteTextFormat3_iface);
  }
  
  static HRESULT WINAPI dwritetextlayout2_SetOpticalAlignment(IDWriteTextLayout4 *iface,
          DWRITE_OPTICAL_ALIGNMENT alignment)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %d.\n", iface, alignment);
  
      return IDWriteTextFormat3_SetOpticalAlignment(&layout->IDWriteTextFormat3_iface, alignment);
  }
  
  static DWRITE_OPTICAL_ALIGNMENT WINAPI dwritetextlayout2_GetOpticalAlignment(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p.\n", iface);
  
      return IDWriteTextFormat3_GetOpticalAlignment(&layout->IDWriteTextFormat3_iface);
  }
  
  static HRESULT WINAPI dwritetextlayout2_SetFontFallback(IDWriteTextLayout4 *iface, IDWriteFontFallback *fallback)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %p.\n", iface, fallback);
  
      return set_fontfallback_for_format(&layout->format, fallback);
  }
  
  static HRESULT WINAPI dwritetextlayout2_GetFontFallback(IDWriteTextLayout4 *iface, IDWriteFontFallback **fallback)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %p.\n", iface, fallback);
  
      return get_fontfallback_from_format(&layout->format, fallback);
  }
  
  static HRESULT WINAPI dwritetextlayout3_InvalidateLayout(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p.\n", iface);
  
      layout->recompute = RECOMPUTE_EVERYTHING;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout3_SetLineSpacing(IDWriteTextLayout4 *iface, DWRITE_LINE_SPACING const *spacing)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      BOOL changed;
      HRESULT hr;
  
      TRACE("%p, %p.\n", iface, spacing);
  
      hr = format_set_linespacing(&layout->format, spacing, &changed);
      if (FAILED(hr))
          return hr;
  
      if (changed)
      {
          if (!(layout->recompute & RECOMPUTE_LINES))
          {
              UINT32 line;
  
              for (line = 0; line < layout->metrics.lineCount; line++)
                  layout_apply_line_spacing(layout, line);
  
              layout_set_line_positions(layout);
          }
  
          layout->recompute |= RECOMPUTE_OVERHANGS;
      }
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout3_GetLineSpacing(IDWriteTextLayout4 *iface, DWRITE_LINE_SPACING *spacing)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %p.\n", iface, spacing);
  
      *spacing = layout->format.spacing;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout3_GetLineMetrics(IDWriteTextLayout4 *iface, DWRITE_LINE_METRICS1 *metrics,
      UINT32 max_count, UINT32 *count)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
      unsigned int line_count;
      HRESULT hr;
      size_t i;
  
      TRACE("%p, %p, %u, %p.\n", iface, metrics, max_count, count);
  
      if (FAILED(hr = layout_compute_effective_runs(layout)))
          return hr;
  
      if (metrics)
      {
          line_count = min(max_count, layout->metrics.lineCount);
          for (i = 0; i < line_count; ++i)
              metrics[i] = layout->lines[i].metrics;
      }
  
      *count = layout->metrics.lineCount;
      return max_count >= layout->metrics.lineCount ? S_OK : E_NOT_SUFFICIENT_BUFFER;
  }
  
  static HRESULT WINAPI dwritetextlayout4_SetFontAxisValues(IDWriteTextLayout4 *iface,
          DWRITE_FONT_AXIS_VALUE const *axis_values, UINT32 num_values, DWRITE_TEXT_RANGE range)
  {
      FIXME("%p, %p, %u, %s.\n", iface, axis_values, num_values, debugstr_range(&range));
  
      return E_NOTIMPL;
  }
  
  static UINT32 WINAPI dwritetextlayout4_GetFontAxisValueCount(IDWriteTextLayout4 *iface, UINT32 pos)
  {
      FIXME("%p, %u.\n", iface, pos);
  
      return 0;
  }
  
  static HRESULT WINAPI dwritetextlayout4_GetFontAxisValues(IDWriteTextLayout4 *iface, UINT32 pos,
          DWRITE_FONT_AXIS_VALUE *values, UINT32 num_values, DWRITE_TEXT_RANGE *range)
  {
      FIXME("%p, %u, %p, %u, %p.\n", iface, pos, values, num_values, range);
  
      return E_NOTIMPL;
  }
  
  static DWRITE_AUTOMATIC_FONT_AXES WINAPI dwritetextlayout4_GetAutomaticFontAxes(IDWriteTextLayout4 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.automatic_axes;
  }
  
  static HRESULT WINAPI dwritetextlayout4_SetAutomaticFontAxes(IDWriteTextLayout4 *iface,
          DWRITE_AUTOMATIC_FONT_AXES axes)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextLayout4(iface);
  
      TRACE("%p, %d.\n", iface, axes);
  
      if ((unsigned int)axes > DWRITE_AUTOMATIC_FONT_AXES_OPTICAL_SIZE)
          return E_INVALIDARG;
  
      layout->format.automatic_axes = axes;
      return S_OK;
  }
  
  static const IDWriteTextLayout4Vtbl dwritetextlayoutvtbl =
  {
      dwritetextlayout_QueryInterface,
      dwritetextlayout_AddRef,
      dwritetextlayout_Release,
      dwritetextlayout_SetTextAlignment,
      dwritetextlayout_SetParagraphAlignment,
      dwritetextlayout_SetWordWrapping,
      dwritetextlayout_SetReadingDirection,
      dwritetextlayout_SetFlowDirection,
      dwritetextlayout_SetIncrementalTabStop,
      dwritetextlayout_SetTrimming,
      dwritetextlayout_SetLineSpacing,
      dwritetextlayout_GetTextAlignment,
      dwritetextlayout_GetParagraphAlignment,
      dwritetextlayout_GetWordWrapping,
      dwritetextlayout_GetReadingDirection,
      dwritetextlayout_GetFlowDirection,
      dwritetextlayout_GetIncrementalTabStop,
      dwritetextlayout_GetTrimming,
      dwritetextlayout_GetLineSpacing,
      dwritetextlayout_GetFontCollection,
      dwritetextlayout_GetFontFamilyNameLength,
      dwritetextlayout_GetFontFamilyName,
      dwritetextlayout_GetFontWeight,
      dwritetextlayout_GetFontStyle,
      dwritetextlayout_GetFontStretch,
      dwritetextlayout_GetFontSize,
      dwritetextlayout_GetLocaleNameLength,
      dwritetextlayout_GetLocaleName,
      dwritetextlayout_SetMaxWidth,
      dwritetextlayout_SetMaxHeight,
      dwritetextlayout_SetFontCollection,
      dwritetextlayout_SetFontFamilyName,
      dwritetextlayout_SetFontWeight,
      dwritetextlayout_SetFontStyle,
      dwritetextlayout_SetFontStretch,
      dwritetextlayout_SetFontSize,
      dwritetextlayout_SetUnderline,
      dwritetextlayout_SetStrikethrough,
      dwritetextlayout_SetDrawingEffect,
      dwritetextlayout_SetInlineObject,
      dwritetextlayout_SetTypography,
      dwritetextlayout_SetLocaleName,
      dwritetextlayout_GetMaxWidth,
      dwritetextlayout_GetMaxHeight,
      dwritetextlayout_layout_GetFontCollection,
      dwritetextlayout_layout_GetFontFamilyNameLength,
      dwritetextlayout_layout_GetFontFamilyName,
      dwritetextlayout_layout_GetFontWeight,
      dwritetextlayout_layout_GetFontStyle,
      dwritetextlayout_layout_GetFontStretch,
      dwritetextlayout_layout_GetFontSize,
      dwritetextlayout_GetUnderline,
      dwritetextlayout_GetStrikethrough,
      dwritetextlayout_GetDrawingEffect,
      dwritetextlayout_GetInlineObject,
      dwritetextlayout_GetTypography,
      dwritetextlayout_layout_GetLocaleNameLength,
      dwritetextlayout_layout_GetLocaleName,
      dwritetextlayout_Draw,
      dwritetextlayout_GetLineMetrics,
      dwritetextlayout_GetMetrics,
      dwritetextlayout_GetOverhangMetrics,
      dwritetextlayout_GetClusterMetrics,
      dwritetextlayout_DetermineMinWidth,
      dwritetextlayout_HitTestPoint,
      dwritetextlayout_HitTestTextPosition,
      dwritetextlayout_HitTestTextRange,
      dwritetextlayout1_SetPairKerning,
      dwritetextlayout1_GetPairKerning,
      dwritetextlayout1_SetCharacterSpacing,
      dwritetextlayout1_GetCharacterSpacing,
      dwritetextlayout2_GetMetrics,
      dwritetextlayout2_SetVerticalGlyphOrientation,
      dwritetextlayout2_GetVerticalGlyphOrientation,
      dwritetextlayout2_SetLastLineWrapping,
      dwritetextlayout2_GetLastLineWrapping,
      dwritetextlayout2_SetOpticalAlignment,
      dwritetextlayout2_GetOpticalAlignment,
      dwritetextlayout2_SetFontFallback,
      dwritetextlayout2_GetFontFallback,
      dwritetextlayout3_InvalidateLayout,
      dwritetextlayout3_SetLineSpacing,
      dwritetextlayout3_GetLineSpacing,
      dwritetextlayout3_GetLineMetrics,
      dwritetextlayout4_SetFontAxisValues,
      dwritetextlayout4_GetFontAxisValueCount,
      dwritetextlayout4_GetFontAxisValues,
      dwritetextlayout4_GetAutomaticFontAxes,
      dwritetextlayout4_SetAutomaticFontAxes,
  };
  
  static HRESULT WINAPI dwritetextformat_layout_QueryInterface(IDWriteTextFormat3 *iface, REFIID riid, void **obj)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj);
  
      return IDWriteTextLayout4_QueryInterface(&layout->IDWriteTextLayout4_iface, riid, obj);
  }
  
  static ULONG WINAPI dwritetextformat_layout_AddRef(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      return IDWriteTextLayout4_AddRef(&layout->IDWriteTextLayout4_iface);
  }
  
  static ULONG WINAPI dwritetextformat_layout_Release(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      return IDWriteTextLayout4_Release(&layout->IDWriteTextLayout4_iface);
  }
  
  static HRESULT WINAPI dwritetextformat_layout_SetTextAlignment(IDWriteTextFormat3 *iface,
          DWRITE_TEXT_ALIGNMENT alignment)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      BOOL changed;
      HRESULT hr;
  
      TRACE("%p, %d.\n", iface, alignment);
  
      hr = format_set_textalignment(&layout->format, alignment, &changed);
      if (FAILED(hr))
          return hr;
  
      if (changed)
      {
          /* if layout is not ready there's nothing to align */
          if (!(layout->recompute & RECOMPUTE_LINES))
              layout_apply_text_alignment(layout);
          layout->recompute |= RECOMPUTE_OVERHANGS;
      }
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_SetParagraphAlignment(IDWriteTextFormat3 *iface,
          DWRITE_PARAGRAPH_ALIGNMENT alignment)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      BOOL changed;
      HRESULT hr;
  
      TRACE("%p, %d.\n", iface, alignment);
  
      hr = format_set_paralignment(&layout->format, alignment, &changed);
      if (FAILED(hr))
          return hr;
  
      if (changed)
      {
          /* if layout is not ready there's nothing to align */
          if (!(layout->recompute & RECOMPUTE_LINES))
              layout_apply_par_alignment(layout);
          layout->recompute |= RECOMPUTE_OVERHANGS;
      }
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_SetWordWrapping(IDWriteTextFormat3 *iface, DWRITE_WORD_WRAPPING wrapping)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      BOOL changed;
      HRESULT hr;
  
      TRACE("%p, %d.\n", iface, wrapping);
  
      hr = format_set_wordwrapping(&layout->format, wrapping, &changed);
      if (FAILED(hr))
          return hr;
  
      if (changed)
          layout->recompute |= RECOMPUTE_LINES_AND_OVERHANGS;
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_SetReadingDirection(IDWriteTextFormat3 *iface,
          DWRITE_READING_DIRECTION direction)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      BOOL changed;
      HRESULT hr;
  
      TRACE("%p, %d.\n", iface, direction);
  
      hr = format_set_readingdirection(&layout->format, direction, &changed);
      if (FAILED(hr))
          return hr;
  
      if (changed)
          layout->recompute = RECOMPUTE_EVERYTHING;
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_SetFlowDirection(IDWriteTextFormat3 *iface,
          DWRITE_FLOW_DIRECTION direction)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      BOOL changed;
      HRESULT hr;
  
      TRACE("%p, %d.\n", iface, direction);
  
      hr = format_set_flowdirection(&layout->format, direction, &changed);
      if (FAILED(hr))
          return hr;
  
      if (changed)
          layout->recompute = RECOMPUTE_EVERYTHING;
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_SetIncrementalTabStop(IDWriteTextFormat3 *iface, FLOAT tabstop)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %.8e.\n", iface, tabstop);
  
      if (tabstop <= 0.0f)
          return E_INVALIDARG;
  
      layout->format.tabstop = tabstop;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_SetTrimming(IDWriteTextFormat3 *iface, DWRITE_TRIMMING const *trimming,
      IDWriteInlineObject *trimming_sign)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      BOOL changed;
      HRESULT hr;
  
      TRACE("%p, %p, %p.\n", iface, trimming, trimming_sign);
  
      hr = format_set_trimming(&layout->format, trimming, trimming_sign, &changed);
  
      if (changed)
          layout->recompute |= RECOMPUTE_LINES_AND_OVERHANGS;
  
      return hr;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_SetLineSpacing(IDWriteTextFormat3 *iface,
          DWRITE_LINE_SPACING_METHOD method, FLOAT height, FLOAT baseline)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      DWRITE_LINE_SPACING spacing;
  
      TRACE("%p, %d, %.8e, %.8e.\n", iface, method, height, baseline);
  
      spacing = layout->format.spacing;
      spacing.method = method;
      spacing.height = height;
      spacing.baseline = baseline;
      return IDWriteTextLayout4_SetLineSpacing(&layout->IDWriteTextLayout4_iface, &spacing);
  }
  
  static DWRITE_TEXT_ALIGNMENT WINAPI dwritetextformat_layout_GetTextAlignment(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.textalignment;
  }
  
  static DWRITE_PARAGRAPH_ALIGNMENT WINAPI dwritetextformat_layout_GetParagraphAlignment(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.paralign;
  }
  
  static DWRITE_WORD_WRAPPING WINAPI dwritetextformat_layout_GetWordWrapping(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.wrapping;
  }
  
  static DWRITE_READING_DIRECTION WINAPI dwritetextformat_layout_GetReadingDirection(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.readingdir;
  }
  
  static DWRITE_FLOW_DIRECTION WINAPI dwritetextformat_layout_GetFlowDirection(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.flow;
  }
  
  static FLOAT WINAPI dwritetextformat_layout_GetIncrementalTabStop(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.tabstop;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_GetTrimming(IDWriteTextFormat3 *iface, DWRITE_TRIMMING *options,
      IDWriteInlineObject **trimming_sign)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %p.\n", iface, options, trimming_sign);
  
      *options = layout->format.trimming;
      *trimming_sign = layout->format.trimmingsign;
      if (*trimming_sign)
          IDWriteInlineObject_AddRef(*trimming_sign);
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_GetLineSpacing(IDWriteTextFormat3 *iface,
          DWRITE_LINE_SPACING_METHOD *method, FLOAT *spacing, FLOAT *baseline)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %p, %p.\n", iface, method, spacing, baseline);
  
      *method = layout->format.spacing.method;
      *spacing = layout->format.spacing.height;
      *baseline = layout->format.spacing.baseline;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_GetFontCollection(IDWriteTextFormat3 *iface,
          IDWriteFontCollection **collection)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p.\n", iface, collection);
  
      *collection = layout->format.collection;
      if (*collection)
          IDWriteFontCollection_AddRef(*collection);
      return S_OK;
  }
  
  static UINT32 WINAPI dwritetextformat_layout_GetFontFamilyNameLength(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.family_len;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_GetFontFamilyName(IDWriteTextFormat3 *iface, WCHAR *name, UINT32 size)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %u.\n", iface, name, size);
  
      if (size <= layout->format.family_len) return E_NOT_SUFFICIENT_BUFFER;
      wcscpy(name, layout->format.family_name);
      return S_OK;
  }
  
  static DWRITE_FONT_WEIGHT WINAPI dwritetextformat_layout_GetFontWeight(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.weight;
  }
  
  static DWRITE_FONT_STYLE WINAPI dwritetextformat_layout_GetFontStyle(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.style;
  }
  
  static DWRITE_FONT_STRETCH WINAPI dwritetextformat_layout_GetFontStretch(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.stretch;
  }
  
  static FLOAT WINAPI dwritetextformat_layout_GetFontSize(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.fontsize;
  }
  
  static UINT32 WINAPI dwritetextformat_layout_GetLocaleNameLength(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.locale_len;
  }
  
  static HRESULT WINAPI dwritetextformat_layout_GetLocaleName(IDWriteTextFormat3 *iface, WCHAR *name, UINT32 size)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %u.\n", iface, name, size);
  
      if (size <= layout->format.locale_len) return E_NOT_SUFFICIENT_BUFFER;
      wcscpy(name, layout->format.locale);
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat1_layout_SetVerticalGlyphOrientation(IDWriteTextFormat3 *iface,
          DWRITE_VERTICAL_GLYPH_ORIENTATION orientation)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, orientation);
  
      return layout_set_vertical_orientation(layout, orientation);
  }
  
  static DWRITE_VERTICAL_GLYPH_ORIENTATION WINAPI dwritetextformat1_layout_GetVerticalGlyphOrientation(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.vertical_orientation;
  }
  
  static HRESULT WINAPI dwritetextformat1_layout_SetLastLineWrapping(IDWriteTextFormat3 *iface,
          BOOL lastline_wrapping_enabled)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, lastline_wrapping_enabled);
  
      layout->format.last_line_wrapping = !!lastline_wrapping_enabled;
      return S_OK;
  }
  
  static BOOL WINAPI dwritetextformat1_layout_GetLastLineWrapping(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.last_line_wrapping;
  }
  
  static HRESULT WINAPI dwritetextformat1_layout_SetOpticalAlignment(IDWriteTextFormat3 *iface,
          DWRITE_OPTICAL_ALIGNMENT alignment)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, alignment);
  
      return format_set_optical_alignment(&layout->format, alignment);
  }
  
  static DWRITE_OPTICAL_ALIGNMENT WINAPI dwritetextformat1_layout_GetOpticalAlignment(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.optical_alignment;
  }
  
  static HRESULT WINAPI dwritetextformat1_layout_SetFontFallback(IDWriteTextFormat3 *iface,
          IDWriteFontFallback *fallback)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p.\n", iface, fallback);
  
      return IDWriteTextLayout4_SetFontFallback(&layout->IDWriteTextLayout4_iface, fallback);
  }
  
  static HRESULT WINAPI dwritetextformat1_layout_GetFontFallback(IDWriteTextFormat3 *iface,
          IDWriteFontFallback **fallback)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p.\n", iface, fallback);
  
      return IDWriteTextLayout4_GetFontFallback(&layout->IDWriteTextLayout4_iface, fallback);
  }
  
  static HRESULT WINAPI dwritetextformat2_layout_SetLineSpacing(IDWriteTextFormat3 *iface,
          DWRITE_LINE_SPACING const *spacing)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      return IDWriteTextLayout4_SetLineSpacing(&layout->IDWriteTextLayout4_iface, spacing);
  }
  
  static HRESULT WINAPI dwritetextformat2_layout_GetLineSpacing(IDWriteTextFormat3 *iface, DWRITE_LINE_SPACING *spacing)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      return IDWriteTextLayout4_GetLineSpacing(&layout->IDWriteTextLayout4_iface, spacing);
  }
  
  static HRESULT WINAPI dwritetextformat3_layout_SetFontAxisValues(IDWriteTextFormat3 *iface,
          DWRITE_FONT_AXIS_VALUE const *axis_values, UINT32 num_values)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %u.\n", iface, axis_values, num_values);
  
      return format_set_font_axisvalues(&layout->format, axis_values, num_values);
  }
  
  static UINT32 WINAPI dwritetextformat3_layout_GetFontAxisValueCount(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return layout->format.axis_values_count;
  }
  
  static HRESULT WINAPI dwritetextformat3_layout_GetFontAxisValues(IDWriteTextFormat3 *iface,
          DWRITE_FONT_AXIS_VALUE *axis_values, UINT32 num_values)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %u.\n", iface, axis_values, num_values);
  
      return format_get_font_axisvalues(&layout->format, axis_values, num_values);
  }
  
  static DWRITE_AUTOMATIC_FONT_AXES WINAPI dwritetextformat3_layout_GetAutomaticFontAxes(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      return IDWriteTextLayout4_GetAutomaticFontAxes(&layout->IDWriteTextLayout4_iface);
  }
  
  static HRESULT WINAPI dwritetextformat3_layout_SetAutomaticFontAxes(IDWriteTextFormat3 *iface,
          DWRITE_AUTOMATIC_FONT_AXES axes)
  {
      struct dwrite_textlayout *layout = impl_layout_from_IDWriteTextFormat3(iface);
      return IDWriteTextLayout4_SetAutomaticFontAxes(&layout->IDWriteTextLayout4_iface, axes);
  }
  
  static const IDWriteTextFormat3Vtbl dwritetextformat3_layout_vtbl =
  {
      dwritetextformat_layout_QueryInterface,
      dwritetextformat_layout_AddRef,
      dwritetextformat_layout_Release,
      dwritetextformat_layout_SetTextAlignment,
      dwritetextformat_layout_SetParagraphAlignment,
      dwritetextformat_layout_SetWordWrapping,
      dwritetextformat_layout_SetReadingDirection,
      dwritetextformat_layout_SetFlowDirection,
      dwritetextformat_layout_SetIncrementalTabStop,
      dwritetextformat_layout_SetTrimming,
      dwritetextformat_layout_SetLineSpacing,
      dwritetextformat_layout_GetTextAlignment,
      dwritetextformat_layout_GetParagraphAlignment,
      dwritetextformat_layout_GetWordWrapping,
      dwritetextformat_layout_GetReadingDirection,
      dwritetextformat_layout_GetFlowDirection,
      dwritetextformat_layout_GetIncrementalTabStop,
      dwritetextformat_layout_GetTrimming,
      dwritetextformat_layout_GetLineSpacing,
      dwritetextformat_layout_GetFontCollection,
      dwritetextformat_layout_GetFontFamilyNameLength,
      dwritetextformat_layout_GetFontFamilyName,
      dwritetextformat_layout_GetFontWeight,
      dwritetextformat_layout_GetFontStyle,
      dwritetextformat_layout_GetFontStretch,
      dwritetextformat_layout_GetFontSize,
      dwritetextformat_layout_GetLocaleNameLength,
      dwritetextformat_layout_GetLocaleName,
      dwritetextformat1_layout_SetVerticalGlyphOrientation,
      dwritetextformat1_layout_GetVerticalGlyphOrientation,
      dwritetextformat1_layout_SetLastLineWrapping,
      dwritetextformat1_layout_GetLastLineWrapping,
      dwritetextformat1_layout_SetOpticalAlignment,
      dwritetextformat1_layout_GetOpticalAlignment,
      dwritetextformat1_layout_SetFontFallback,
      dwritetextformat1_layout_GetFontFallback,
      dwritetextformat2_layout_SetLineSpacing,
      dwritetextformat2_layout_GetLineSpacing,
      dwritetextformat3_layout_SetFontAxisValues,
      dwritetextformat3_layout_GetFontAxisValueCount,
      dwritetextformat3_layout_GetFontAxisValues,
      dwritetextformat3_layout_GetAutomaticFontAxes,
      dwritetextformat3_layout_SetAutomaticFontAxes,
  };
  
  static HRESULT WINAPI dwritetextlayout_sink_QueryInterface(IDWriteTextAnalysisSink1 *iface,
      REFIID riid, void **obj)
  {
      if (IsEqualIID(riid, &IID_IDWriteTextAnalysisSink1) ||
          IsEqualIID(riid, &IID_IDWriteTextAnalysisSink) ||
          IsEqualIID(riid, &IID_IUnknown))
      {
          *obj = iface;
          IDWriteTextAnalysisSink1_AddRef(iface);
          return S_OK;
      }
  
      WARN("%s not implemented.\n", debugstr_guid(riid));
  
      *obj = NULL;
      return E_NOINTERFACE;
  }
  
  static ULONG WINAPI dwritetextlayout_sink_AddRef(IDWriteTextAnalysisSink1 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSink1(iface);
      return IDWriteTextLayout4_AddRef(&layout->IDWriteTextLayout4_iface);
  }
  
  static ULONG WINAPI dwritetextlayout_sink_Release(IDWriteTextAnalysisSink1 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSink1(iface);
      return IDWriteTextLayout4_Release(&layout->IDWriteTextLayout4_iface);
  }
  
  static HRESULT WINAPI dwritetextlayout_sink_SetScriptAnalysis(IDWriteTextAnalysisSink1 *iface,
      UINT32 position, UINT32 length, DWRITE_SCRIPT_ANALYSIS const* sa)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSink1(iface);
      struct layout_run *run;
      HRESULT hr;
  
      TRACE("[%u,%u) script=%u:%s\n", position, position + length, sa->script, debugstr_sa_script(sa->script));
  
      if (FAILED(hr = alloc_layout_run(LAYOUT_RUN_REGULAR, position, &run)))
          return hr;
  
      run->u.regular.descr.string = &layout->str[position];
      run->u.regular.descr.stringLength = length;
      run->u.regular.descr.textPosition = position;
      run->u.regular.sa = *sa;
      list_add_tail(&layout->runs, &run->entry);
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_sink_SetLineBreakpoints(IDWriteTextAnalysisSink1 *iface,
      UINT32 position, UINT32 length, DWRITE_LINE_BREAKPOINT const* breakpoints)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSink1(iface);
  
      if (position + length > layout->len)
          return E_FAIL;
  
      memcpy(&layout->nominal_breakpoints[position], breakpoints, length*sizeof(DWRITE_LINE_BREAKPOINT));
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_sink_SetBidiLevel(IDWriteTextAnalysisSink1 *iface, UINT32 position,
      UINT32 length, UINT8 explicitLevel, UINT8 resolvedLevel)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSink1(iface);
      struct layout_run *cur_run;
      HRESULT hr;
  
      TRACE("[%u,%u) %u %u\n", position, position + length, explicitLevel, resolvedLevel);
  
      LIST_FOR_EACH_ENTRY(cur_run, &layout->runs, struct layout_run, entry) {
          struct regular_layout_run *cur = &cur_run->u.regular;
          struct layout_run *run;
  
          if (cur_run->kind == LAYOUT_RUN_INLINE)
              continue;
  
          /* FIXME: levels are reported in a natural forward direction, so start loop from a run we ended on */
          if (position < cur->descr.textPosition || position >= cur->descr.textPosition + cur->descr.stringLength)
              continue;
  
          /* full hit - just set run level */
          if (cur->descr.textPosition == position && cur->descr.stringLength == length) {
              cur->run.bidiLevel = resolvedLevel;
              break;
          }
  
          /* current run is fully covered, move to next one */
          if (cur->descr.textPosition == position && cur->descr.stringLength < length) {
              cur->run.bidiLevel = resolvedLevel;
              position += cur->descr.stringLength;
              length -= cur->descr.stringLength;
              continue;
          }
  
          /* all fully covered runs are processed at this point, reuse existing run for remaining
             reported bidi range and add another run for the rest of original one */
  
          if (FAILED(hr = alloc_layout_run(LAYOUT_RUN_REGULAR, position + length, &run)))
              return hr;
  
          *run = *cur_run;
          run->u.regular.descr.textPosition = position + length;
          run->u.regular.descr.stringLength = cur->descr.stringLength - length;
          run->u.regular.descr.string = &layout->str[position + length];
  
          /* reduce existing run */
          cur->run.bidiLevel = resolvedLevel;
          cur->descr.stringLength = length;
  
          list_add_after(&cur_run->entry, &run->entry);
          break;
      }
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_sink_SetNumberSubstitution(IDWriteTextAnalysisSink1 *iface,
      UINT32 position, UINT32 length, IDWriteNumberSubstitution* substitution)
  {
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI dwritetextlayout_sink_SetGlyphOrientation(IDWriteTextAnalysisSink1 *iface,
      UINT32 position, UINT32 length, DWRITE_GLYPH_ORIENTATION_ANGLE angle, UINT8 adjusted_bidi_level,
      BOOL is_sideways, BOOL is_rtl)
  {
      return E_NOTIMPL;
  }
  
  static const IDWriteTextAnalysisSink1Vtbl dwritetextlayoutsinkvtbl = {
      dwritetextlayout_sink_QueryInterface,
      dwritetextlayout_sink_AddRef,
      dwritetextlayout_sink_Release,
      dwritetextlayout_sink_SetScriptAnalysis,
      dwritetextlayout_sink_SetLineBreakpoints,
      dwritetextlayout_sink_SetBidiLevel,
      dwritetextlayout_sink_SetNumberSubstitution,
      dwritetextlayout_sink_SetGlyphOrientation
  };
  
  static HRESULT WINAPI dwritetextlayout_source_QueryInterface(IDWriteTextAnalysisSource1 *iface,
      REFIID riid, void **obj)
  {
      if (IsEqualIID(riid, &IID_IDWriteTextAnalysisSource1) ||
          IsEqualIID(riid, &IID_IDWriteTextAnalysisSource) ||
          IsEqualIID(riid, &IID_IUnknown))
      {
          *obj = iface;
          IDWriteTextAnalysisSource1_AddRef(iface);
          return S_OK;
      }
  
      WARN("%s not implemented.\n", debugstr_guid(riid));
  
      *obj = NULL;
      return E_NOINTERFACE;
  }
  
  static ULONG WINAPI dwritetextlayout_source_AddRef(IDWriteTextAnalysisSource1 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSource1(iface);
      return IDWriteTextLayout4_AddRef(&layout->IDWriteTextLayout4_iface);
  }
  
  static ULONG WINAPI dwritetextlayout_source_Release(IDWriteTextAnalysisSource1 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSource1(iface);
      return IDWriteTextLayout4_Release(&layout->IDWriteTextLayout4_iface);
  }
  
  static HRESULT WINAPI dwritetextlayout_source_GetTextAtPosition(IDWriteTextAnalysisSource1 *iface,
      UINT32 position, WCHAR const** text, UINT32* text_len)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSource1(iface);
  
      TRACE("%p, %u, %p, %p.\n", iface, position, text, text_len);
  
      if (position < layout->len) {
          *text = &layout->str[position];
          *text_len = layout->len - position;
      }
      else {
          *text = NULL;
          *text_len = 0;
      }
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_source_GetTextBeforePosition(IDWriteTextAnalysisSource1 *iface,
      UINT32 position, WCHAR const** text, UINT32* text_len)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSource1(iface);
  
      TRACE("%p, %u, %p, %p.\n", iface, position, text, text_len);
  
      if (position > 0 && position < layout->len) {
          *text = layout->str;
          *text_len = position;
      }
      else {
          *text = NULL;
          *text_len = 0;
      }
  
      return S_OK;
  }
  
  static DWRITE_READING_DIRECTION WINAPI dwritetextlayout_source_GetParagraphReadingDirection(IDWriteTextAnalysisSource1 *iface)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSource1(iface);
      return IDWriteTextLayout4_GetReadingDirection(&layout->IDWriteTextLayout4_iface);
  }
  
  static HRESULT WINAPI dwritetextlayout_source_GetLocaleName(IDWriteTextAnalysisSource1 *iface,
      UINT32 position, UINT32* text_len, WCHAR const** locale)
  {
      struct dwrite_textlayout *layout = impl_from_IDWriteTextAnalysisSource1(iface);
      struct layout_range *range = get_layout_range_by_pos(layout, position);
  
      if (position < layout->len) {
          struct layout_range *next;
  
          *locale = range->locale;
          *text_len = range->h.range.length - position;
  
          next = LIST_ENTRY(list_next(&layout->ranges, &range->h.entry), struct layout_range, h.entry);
          while (next && next->h.range.startPosition < layout->len && !wcscmp(range->locale, next->locale))
          {
              *text_len += next->h.range.length;
              next = LIST_ENTRY(list_next(&layout->ranges, &next->h.entry), struct layout_range, h.entry);
          }
  
          *text_len = min(*text_len, layout->len - position);
      }
      else {
          *locale = NULL;
          *text_len = 0;
      }
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextlayout_source_GetNumberSubstitution(IDWriteTextAnalysisSource1 *iface,
      UINT32 position, UINT32* text_len, IDWriteNumberSubstitution **substitution)
  {
      FIXME("%u %p %p: stub\n", position, text_len, substitution);
      return E_NOTIMPL;
  }
  
  static HRESULT WINAPI dwritetextlayout_source_GetVerticalGlyphOrientation(IDWriteTextAnalysisSource1 *iface,
      UINT32 position, UINT32 *length, DWRITE_VERTICAL_GLYPH_ORIENTATION *orientation, UINT8 *bidi_level)
  {
      FIXME("%u %p %p %p: stub\n", position, length, orientation, bidi_level);
      return E_NOTIMPL;
  }
  
  static const IDWriteTextAnalysisSource1Vtbl dwritetextlayoutsourcevtbl = {
      dwritetextlayout_source_QueryInterface,
      dwritetextlayout_source_AddRef,
      dwritetextlayout_source_Release,
      dwritetextlayout_source_GetTextAtPosition,
      dwritetextlayout_source_GetTextBeforePosition,
      dwritetextlayout_source_GetParagraphReadingDirection,
      dwritetextlayout_source_GetLocaleName,
      dwritetextlayout_source_GetNumberSubstitution,
      dwritetextlayout_source_GetVerticalGlyphOrientation
  };
  
  static HRESULT layout_format_from_textformat(struct dwrite_textlayout *layout, IDWriteTextFormat *format)
  {
      struct dwrite_textformat *textformat;
      IDWriteTextFormat1 *format1;
      IDWriteTextFormat3 *format3;
      UINT32 len;
      HRESULT hr;
  
      if ((textformat = unsafe_impl_from_IDWriteTextFormat(format))) {
          layout->format = textformat->format;
  
          layout->format.locale = wcsdup(textformat->format.locale);
          layout->format.family_name = wcsdup(textformat->format.family_name);
          if (!layout->format.locale || !layout->format.family_name)
          {
              free(layout->format.locale);
              free(layout->format.family_name);
              return E_OUTOFMEMORY;
          }
  
          if (layout->format.trimmingsign)
              IDWriteInlineObject_AddRef(layout->format.trimmingsign);
          if (layout->format.collection)
              IDWriteFontCollection_AddRef(layout->format.collection);
          if (layout->format.fallback)
              IDWriteFontFallback_AddRef(layout->format.fallback);
  
          return S_OK;
      }
  
      layout->format.weight  = IDWriteTextFormat_GetFontWeight(format);
      layout->format.style   = IDWriteTextFormat_GetFontStyle(format);
      layout->format.stretch = IDWriteTextFormat_GetFontStretch(format);
      layout->format.fontsize= IDWriteTextFormat_GetFontSize(format);
      layout->format.tabstop = IDWriteTextFormat_GetIncrementalTabStop(format);
      layout->format.textalignment = IDWriteTextFormat_GetTextAlignment(format);
      layout->format.paralign = IDWriteTextFormat_GetParagraphAlignment(format);
      layout->format.wrapping = IDWriteTextFormat_GetWordWrapping(format);
      layout->format.readingdir = IDWriteTextFormat_GetReadingDirection(format);
      layout->format.flow = IDWriteTextFormat_GetFlowDirection(format);
      hr = IDWriteTextFormat_GetLineSpacing(format, &layout->format.spacing.method,
          &layout->format.spacing.height, &layout->format.spacing.baseline);
      if (FAILED(hr))
          return hr;
  
      hr = IDWriteTextFormat_GetTrimming(format, &layout->format.trimming, &layout->format.trimmingsign);
      if (FAILED(hr))
          return hr;
  
      /* locale name and length */
      len = IDWriteTextFormat_GetLocaleNameLength(format);
      if (!(layout->format.locale = malloc((len + 1) * sizeof(WCHAR))))
          return E_OUTOFMEMORY;
  
      hr = IDWriteTextFormat_GetLocaleName(format, layout->format.locale, len+1);
      if (FAILED(hr))
          return hr;
      layout->format.locale_len = len;
  
      /* font family name and length */
      len = IDWriteTextFormat_GetFontFamilyNameLength(format);
      if (!(layout->format.family_name = malloc((len + 1) * sizeof(WCHAR))))
          return E_OUTOFMEMORY;
  
      hr = IDWriteTextFormat_GetFontFamilyName(format, layout->format.family_name, len+1);
      if (FAILED(hr))
          return hr;
      layout->format.family_len = len;
  
      hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat1, (void**)&format1);
      if (hr == S_OK)
      {
          IDWriteTextFormat2 *format2;
  
          layout->format.vertical_orientation = IDWriteTextFormat1_GetVerticalGlyphOrientation(format1);
          layout->format.optical_alignment = IDWriteTextFormat1_GetOpticalAlignment(format1);
          IDWriteTextFormat1_GetFontFallback(format1, &layout->format.fallback);
  
          if (IDWriteTextFormat1_QueryInterface(format1, &IID_IDWriteTextFormat2, (void**)&format2) == S_OK) {
              IDWriteTextFormat2_GetLineSpacing(format2, &layout->format.spacing);
              IDWriteTextFormat2_Release(format2);
          }
  
          IDWriteTextFormat1_Release(format1);
      }
  
      hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat3, (void **)&format3);
      if (hr == S_OK)
      {
          layout->format.automatic_axes = IDWriteTextFormat3_GetAutomaticFontAxes(format3);
          IDWriteTextFormat3_Release(format3);
      }
  
      return IDWriteTextFormat_GetFontCollection(format, &layout->format.collection);
  }
  
  static HRESULT init_textlayout(const struct textlayout_desc *desc, struct dwrite_textlayout *layout)
  {
      struct layout_range_header *range, *strike, *underline, *effect, *spacing, *typography;
      static const DWRITE_TEXT_RANGE r = { 0, ~0u };
      HRESULT hr;
  
      layout->IDWriteTextLayout4_iface.lpVtbl = &dwritetextlayoutvtbl;
      layout->IDWriteTextFormat3_iface.lpVtbl = &dwritetextformat3_layout_vtbl;
      layout->IDWriteTextAnalysisSink1_iface.lpVtbl = &dwritetextlayoutsinkvtbl;
      layout->IDWriteTextAnalysisSource1_iface.lpVtbl = &dwritetextlayoutsourcevtbl;
      layout->refcount = 1;
      layout->len = desc->length;
      layout->recompute = RECOMPUTE_EVERYTHING;
      list_init(&layout->eruns);
      list_init(&layout->inlineobjects);
      list_init(&layout->underlines);
      list_init(&layout->strikethrough);
      list_init(&layout->runs);
      list_init(&layout->ranges);
      list_init(&layout->strike_ranges);
      list_init(&layout->underline_ranges);
      list_init(&layout->effects);
      list_init(&layout->spacing);
      list_init(&layout->typographies);
      layout->metrics.layoutWidth = desc->max_width;
      layout->metrics.layoutHeight = desc->max_height;
  
      layout->str = heap_strdupnW(desc->string, desc->length);
      if (desc->length && !layout->str) {
          hr = E_OUTOFMEMORY;
          goto fail;
      }
  
      hr = layout_format_from_textformat(layout, desc->format);
      if (FAILED(hr))
          goto fail;
  
      range = alloc_layout_range(layout, &r, LAYOUT_RANGE_REGULAR);
      strike = alloc_layout_range(layout, &r, LAYOUT_RANGE_STRIKETHROUGH);
      underline = alloc_layout_range(layout, &r, LAYOUT_RANGE_UNDERLINE);
      effect = alloc_layout_range(layout, &r, LAYOUT_RANGE_EFFECT);
      spacing = alloc_layout_range(layout, &r, LAYOUT_RANGE_SPACING);
      typography = alloc_layout_range(layout, &r, LAYOUT_RANGE_TYPOGRAPHY);
      if (!range || !strike || !effect || !spacing || !typography || !underline) {
          free_layout_range(range);
          free_layout_range(strike);
          free_layout_range(underline);
          free_layout_range(effect);
          free_layout_range(spacing);
          free_layout_range(typography);
          hr = E_OUTOFMEMORY;
          goto fail;
      }
  
      layout->measuringmode = desc->is_gdi_compatible ? (desc->use_gdi_natural ? DWRITE_MEASURING_MODE_GDI_NATURAL :
              DWRITE_MEASURING_MODE_GDI_CLASSIC) : DWRITE_MEASURING_MODE_NATURAL;
      layout->ppdip = desc->ppdip;
      layout->transform = desc->transform ? *desc->transform : identity;
  
      layout->factory = desc->factory;
      IDWriteFactory7_AddRef(layout->factory);
      list_add_head(&layout->ranges, &range->entry);
      list_add_head(&layout->strike_ranges, &strike->entry);
      list_add_head(&layout->underline_ranges, &underline->entry);
      list_add_head(&layout->effects, &effect->entry);
      list_add_head(&layout->spacing, &spacing->entry);
      list_add_head(&layout->typographies, &typography->entry);
      return S_OK;
  
  fail:
      IDWriteTextLayout4_Release(&layout->IDWriteTextLayout4_iface);
      return hr;
  }
  
  HRESULT create_textlayout(const struct textlayout_desc *desc, IDWriteTextLayout **layout)
  {
      struct dwrite_textlayout *object;
      HRESULT hr;
  
      *layout = NULL;
  
      if (desc->max_width < 0.0f || desc->max_height < 0.0f)
          return E_INVALIDARG;
  
      if (!desc->format || !desc->string)
          return E_INVALIDARG;
  
      if (!(object = calloc(1, sizeof(*object))))
          return E_OUTOFMEMORY;
  
      hr = init_textlayout(desc, object);
      if (hr == S_OK)
          *layout = (IDWriteTextLayout *)&object->IDWriteTextLayout4_iface;
  
      return hr;
  }
  
  static HRESULT WINAPI dwritetrimmingsign_QueryInterface(IDWriteInlineObject *iface, REFIID riid, void **obj)
  {
      TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj);
  
      if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDWriteInlineObject)) {
          *obj = iface;
          IDWriteInlineObject_AddRef(iface);
          return S_OK;
      }
  
      WARN("%s not implemented.\n", debugstr_guid(riid));
  
      *obj = NULL;
      return E_NOINTERFACE;
  }
  
  static ULONG WINAPI dwritetrimmingsign_AddRef(IDWriteInlineObject *iface)
  {
      struct dwrite_trimmingsign *sign = impl_from_IDWriteInlineObject(iface);
      ULONG refcount = InterlockedIncrement(&sign->refcount);
  
      TRACE("%p, refcount %d.\n", iface, refcount);
  
      return refcount;
  }
  
  static ULONG WINAPI dwritetrimmingsign_Release(IDWriteInlineObject *iface)
  {
      struct dwrite_trimmingsign *sign = impl_from_IDWriteInlineObject(iface);
      ULONG refcount = InterlockedDecrement(&sign->refcount);
  
      TRACE("%p, refcount %d.\n", iface, refcount);
  
      if (!refcount)
      {
          IDWriteTextLayout_Release(sign->layout);
          free(sign);
      }
  
      return refcount;
  }
  
  static HRESULT WINAPI dwritetrimmingsign_Draw(IDWriteInlineObject *iface, void *context, IDWriteTextRenderer *renderer,
      FLOAT originX, FLOAT originY, BOOL is_sideways, BOOL is_rtl, IUnknown *effect)
  {
      struct dwrite_trimmingsign *sign = impl_from_IDWriteInlineObject(iface);
      DWRITE_LINE_METRICS line;
      UINT32 line_count;
  
      TRACE("%p, %p, %p, %.2f, %.2f, %d, %d, %p.\n", iface, context, renderer, originX, originY,
              is_sideways, is_rtl, effect);
  
      IDWriteTextLayout_GetLineMetrics(sign->layout, &line, 1, &line_count);
      return IDWriteTextLayout_Draw(sign->layout, context, renderer, originX, originY - line.baseline);
  }
  
  static HRESULT WINAPI dwritetrimmingsign_GetMetrics(IDWriteInlineObject *iface, DWRITE_INLINE_OBJECT_METRICS *ret)
  {
      struct dwrite_trimmingsign *sign = impl_from_IDWriteInlineObject(iface);
      DWRITE_TEXT_METRICS metrics;
      HRESULT hr;
  
      TRACE("%p, %p.\n", iface, ret);
  
      hr = IDWriteTextLayout_GetMetrics(sign->layout, &metrics);
      if (FAILED(hr))
      {
          memset(ret, 0, sizeof(*ret));
          return hr;
      }
  
      ret->width = metrics.width;
      ret->height = 0.0f;
      ret->baseline = 0.0f;
      ret->supportsSideways = FALSE;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetrimmingsign_GetOverhangMetrics(IDWriteInlineObject *iface, DWRITE_OVERHANG_METRICS *overhangs)
  {
      struct dwrite_trimmingsign *sign = impl_from_IDWriteInlineObject(iface);
  
      TRACE("%p, %p.\n", iface, overhangs);
  
      return IDWriteTextLayout_GetOverhangMetrics(sign->layout, overhangs);
  }
  
  static HRESULT WINAPI dwritetrimmingsign_GetBreakConditions(IDWriteInlineObject *iface, DWRITE_BREAK_CONDITION *before,
          DWRITE_BREAK_CONDITION *after)
  {
      TRACE("%p, %p, %p.\n", iface, before, after);
  
      *before = *after = DWRITE_BREAK_CONDITION_NEUTRAL;
      return S_OK;
  }
  
  static const IDWriteInlineObjectVtbl dwritetrimmingsignvtbl =
  {
      dwritetrimmingsign_QueryInterface,
      dwritetrimmingsign_AddRef,
      dwritetrimmingsign_Release,
      dwritetrimmingsign_Draw,
      dwritetrimmingsign_GetMetrics,
      dwritetrimmingsign_GetOverhangMetrics,
      dwritetrimmingsign_GetBreakConditions
  };
  
  static inline BOOL is_reading_direction_horz(DWRITE_READING_DIRECTION direction)
  {
      return (direction == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT) ||
             (direction == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
  }
  
  static inline BOOL is_reading_direction_vert(DWRITE_READING_DIRECTION direction)
  {
      return (direction == DWRITE_READING_DIRECTION_TOP_TO_BOTTOM) ||
             (direction == DWRITE_READING_DIRECTION_BOTTOM_TO_TOP);
  }
  
  static inline BOOL is_flow_direction_horz(DWRITE_FLOW_DIRECTION direction)
  {
      return (direction == DWRITE_FLOW_DIRECTION_LEFT_TO_RIGHT) ||
             (direction == DWRITE_FLOW_DIRECTION_RIGHT_TO_LEFT);
  }
  
  static inline BOOL is_flow_direction_vert(DWRITE_FLOW_DIRECTION direction)
  {
      return (direction == DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM) ||
             (direction == DWRITE_FLOW_DIRECTION_BOTTOM_TO_TOP);
  }
  
  HRESULT create_trimmingsign(IDWriteFactory7 *factory, IDWriteTextFormat *format, IDWriteInlineObject **sign)
  {
      static const WCHAR ellipsisW = 0x2026;
      struct dwrite_trimmingsign *object;
      DWRITE_READING_DIRECTION reading;
      DWRITE_FLOW_DIRECTION flow;
      HRESULT hr;
  
      *sign = NULL;
  
      if (!format)
          return E_INVALIDARG;
  
      /* Validate reading/flow direction here, layout creation won't complain about
         invalid combinations. */
      reading = IDWriteTextFormat_GetReadingDirection(format);
      flow = IDWriteTextFormat_GetFlowDirection(format);
  
      if ((is_reading_direction_horz(reading) && is_flow_direction_horz(flow)) ||
          (is_reading_direction_vert(reading) && is_flow_direction_vert(flow)))
          return DWRITE_E_FLOWDIRECTIONCONFLICTS;
  
      if (!(object = calloc(1, sizeof(*object))))
          return E_OUTOFMEMORY;
  
      object->IDWriteInlineObject_iface.lpVtbl = &dwritetrimmingsignvtbl;
      object->refcount = 1;
  
      hr = IDWriteFactory7_CreateTextLayout(factory, &ellipsisW, 1, format, 0.0f, 0.0f, &object->layout);
      if (FAILED(hr))
      {
          free(object);
          return hr;
      }
  
      IDWriteTextLayout_SetWordWrapping(object->layout, DWRITE_WORD_WRAPPING_NO_WRAP);
      IDWriteTextLayout_SetParagraphAlignment(object->layout, DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
      IDWriteTextLayout_SetTextAlignment(object->layout, DWRITE_TEXT_ALIGNMENT_LEADING);
  
      *sign = &object->IDWriteInlineObject_iface;
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_QueryInterface(IDWriteTextFormat3 *iface, REFIID riid, void **obj)
  {
      TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj);
  
      if (IsEqualIID(riid, &IID_IDWriteTextFormat3) ||
          IsEqualIID(riid, &IID_IDWriteTextFormat2) ||
          IsEqualIID(riid, &IID_IDWriteTextFormat1) ||
          IsEqualIID(riid, &IID_IDWriteTextFormat)  ||
          IsEqualIID(riid, &IID_IUnknown))
      {
          *obj = iface;
          IDWriteTextFormat3_AddRef(iface);
          return S_OK;
      }
  
      WARN("%s not implemented.\n", debugstr_guid(riid));
  
      *obj = NULL;
  
      return E_NOINTERFACE;
  }
  
  static ULONG WINAPI dwritetextformat_AddRef(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
      ULONG refcount = InterlockedIncrement(&format->refcount);
  
      TRACE("%p, refcount %d.\n", iface, refcount);
  
      return refcount;
  }
  
  static ULONG WINAPI dwritetextformat_Release(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
      ULONG refcount = InterlockedDecrement(&format->refcount);
  
      TRACE("%p, refcount %d.\n", iface, refcount);
  
      if (!refcount)
      {
          release_format_data(&format->format);
          free(format);
      }
  
      return refcount;
  }
  
  static HRESULT WINAPI dwritetextformat_SetTextAlignment(IDWriteTextFormat3 *iface, DWRITE_TEXT_ALIGNMENT alignment)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, alignment);
  
      return format_set_textalignment(&format->format, alignment, NULL);
  }
  
  static HRESULT WINAPI dwritetextformat_SetParagraphAlignment(IDWriteTextFormat3 *iface,
          DWRITE_PARAGRAPH_ALIGNMENT alignment)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, alignment);
  
      return format_set_paralignment(&format->format, alignment, NULL);
  }
  
  static HRESULT WINAPI dwritetextformat_SetWordWrapping(IDWriteTextFormat3 *iface, DWRITE_WORD_WRAPPING wrapping)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, wrapping);
  
      return format_set_wordwrapping(&format->format, wrapping, NULL);
  }
  
  static HRESULT WINAPI dwritetextformat_SetReadingDirection(IDWriteTextFormat3 *iface, DWRITE_READING_DIRECTION direction)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, direction);
  
      return format_set_readingdirection(&format->format, direction, NULL);
  }
  
  static HRESULT WINAPI dwritetextformat_SetFlowDirection(IDWriteTextFormat3 *iface, DWRITE_FLOW_DIRECTION direction)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, direction);
  
      return format_set_flowdirection(&format->format, direction, NULL);
  }
  
  static HRESULT WINAPI dwritetextformat_SetIncrementalTabStop(IDWriteTextFormat3 *iface, FLOAT tabstop)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %f.\n", iface, tabstop);
  
      if (tabstop <= 0.0f)
          return E_INVALIDARG;
  
      format->format.tabstop = tabstop;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_SetTrimming(IDWriteTextFormat3 *iface, DWRITE_TRIMMING const *trimming,
      IDWriteInlineObject *trimming_sign)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %p.\n", iface, trimming, trimming_sign);
  
      return format_set_trimming(&format->format, trimming, trimming_sign, NULL);
  }
  
  static HRESULT WINAPI dwritetextformat_SetLineSpacing(IDWriteTextFormat3 *iface, DWRITE_LINE_SPACING_METHOD method,
      FLOAT height, FLOAT baseline)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
      DWRITE_LINE_SPACING spacing;
  
      TRACE("%p, %d, %f, %f.\n", iface, method, height, baseline);
  
      spacing = format->format.spacing;
      spacing.method = method;
      spacing.height = height;
      spacing.baseline = baseline;
  
      return format_set_linespacing(&format->format, &spacing, NULL);
  }
  
  static DWRITE_TEXT_ALIGNMENT WINAPI dwritetextformat_GetTextAlignment(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.textalignment;
  }
  
  static DWRITE_PARAGRAPH_ALIGNMENT WINAPI dwritetextformat_GetParagraphAlignment(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.paralign;
  }
  
  static DWRITE_WORD_WRAPPING WINAPI dwritetextformat_GetWordWrapping(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.wrapping;
  }
  
  static DWRITE_READING_DIRECTION WINAPI dwritetextformat_GetReadingDirection(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.readingdir;
  }
  
  static DWRITE_FLOW_DIRECTION WINAPI dwritetextformat_GetFlowDirection(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.flow;
  }
  
  static FLOAT WINAPI dwritetextformat_GetIncrementalTabStop(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.tabstop;
  }
  
  static HRESULT WINAPI dwritetextformat_GetTrimming(IDWriteTextFormat3 *iface, DWRITE_TRIMMING *options,
      IDWriteInlineObject **trimming_sign)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %p.\n", iface, options, trimming_sign);
  
      *options = format->format.trimming;
      if ((*trimming_sign = format->format.trimmingsign))
          IDWriteInlineObject_AddRef(*trimming_sign);
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_GetLineSpacing(IDWriteTextFormat3 *iface, DWRITE_LINE_SPACING_METHOD *method,
      FLOAT *spacing, FLOAT *baseline)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %p, %p.\n", iface, method, spacing, baseline);
  
      *method = format->format.spacing.method;
      *spacing = format->format.spacing.height;
      *baseline = format->format.spacing.baseline;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat_GetFontCollection(IDWriteTextFormat3 *iface, IDWriteFontCollection **collection)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p.\n", iface, collection);
  
      *collection = format->format.collection;
      IDWriteFontCollection_AddRef(*collection);
  
      return S_OK;
  }
  
  static UINT32 WINAPI dwritetextformat_GetFontFamilyNameLength(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.family_len;
  }
  
  static HRESULT WINAPI dwritetextformat_GetFontFamilyName(IDWriteTextFormat3 *iface, WCHAR *name, UINT32 size)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %u.\n", iface, name, size);
  
      if (size <= format->format.family_len)
          return E_NOT_SUFFICIENT_BUFFER;
      wcscpy(name, format->format.family_name);
      return S_OK;
  }
  
  static DWRITE_FONT_WEIGHT WINAPI dwritetextformat_GetFontWeight(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.weight;
  }
  
  static DWRITE_FONT_STYLE WINAPI dwritetextformat_GetFontStyle(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.style;
  }
  
  static DWRITE_FONT_STRETCH WINAPI dwritetextformat_GetFontStretch(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.stretch;
  }
  
  static FLOAT WINAPI dwritetextformat_GetFontSize(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.fontsize;
  }
  
  static UINT32 WINAPI dwritetextformat_GetLocaleNameLength(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.locale_len;
  }
  
  static HRESULT WINAPI dwritetextformat_GetLocaleName(IDWriteTextFormat3 *iface, WCHAR *name, UINT32 size)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p %u.\n", iface, name, size);
  
      if (size <= format->format.locale_len)
          return E_NOT_SUFFICIENT_BUFFER;
      wcscpy(name, format->format.locale);
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat1_SetVerticalGlyphOrientation(IDWriteTextFormat3 *iface,
          DWRITE_VERTICAL_GLYPH_ORIENTATION orientation)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, orientation);
  
      return format_set_vertical_orientation(&format->format, orientation, NULL);
  }
  
  static DWRITE_VERTICAL_GLYPH_ORIENTATION WINAPI dwritetextformat1_GetVerticalGlyphOrientation(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.vertical_orientation;
  }
  
  static HRESULT WINAPI dwritetextformat1_SetLastLineWrapping(IDWriteTextFormat3 *iface, BOOL lastline_wrapping_enabled)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, lastline_wrapping_enabled);
  
      format->format.last_line_wrapping = !!lastline_wrapping_enabled;
      return S_OK;
  }
  
  static BOOL WINAPI dwritetextformat1_GetLastLineWrapping(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.last_line_wrapping;
  }
  
  static HRESULT WINAPI dwritetextformat1_SetOpticalAlignment(IDWriteTextFormat3 *iface, DWRITE_OPTICAL_ALIGNMENT alignment)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, alignment);
  
      return format_set_optical_alignment(&format->format, alignment);
  }
  
  static DWRITE_OPTICAL_ALIGNMENT WINAPI dwritetextformat1_GetOpticalAlignment(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.optical_alignment;
  }
  
  static HRESULT WINAPI dwritetextformat1_SetFontFallback(IDWriteTextFormat3 *iface, IDWriteFontFallback *fallback)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p.\n", iface, fallback);
  
      return set_fontfallback_for_format(&format->format, fallback);
  }
  
  static HRESULT WINAPI dwritetextformat1_GetFontFallback(IDWriteTextFormat3 *iface, IDWriteFontFallback **fallback)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p.\n", iface, fallback);
  
      return get_fontfallback_from_format(&format->format, fallback);
  }
  
  static HRESULT WINAPI dwritetextformat2_SetLineSpacing(IDWriteTextFormat3 *iface, DWRITE_LINE_SPACING const *spacing)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p.\n", iface, spacing);
  
      return format_set_linespacing(&format->format, spacing, NULL);
  }
  
  static HRESULT WINAPI dwritetextformat2_GetLineSpacing(IDWriteTextFormat3 *iface, DWRITE_LINE_SPACING *spacing)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p.\n", iface, spacing);
  
      *spacing = format->format.spacing;
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetextformat3_SetFontAxisValues(IDWriteTextFormat3 *iface,
          DWRITE_FONT_AXIS_VALUE const *axis_values, UINT32 num_values)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %u.\n", iface, axis_values, num_values);
  
      return format_set_font_axisvalues(&format->format, axis_values, num_values);
  }
  
  static UINT32 WINAPI dwritetextformat3_GetFontAxisValueCount(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.axis_values_count;
  }
  
  static HRESULT WINAPI dwritetextformat3_GetFontAxisValues(IDWriteTextFormat3 *iface,
          DWRITE_FONT_AXIS_VALUE *axis_values, UINT32 num_values)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %p, %u.\n", iface, axis_values, num_values);
  
      return format_get_font_axisvalues(&format->format, axis_values, num_values);
  }
  
  static DWRITE_AUTOMATIC_FONT_AXES WINAPI dwritetextformat3_GetAutomaticFontAxes(IDWriteTextFormat3 *iface)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p.\n", iface);
  
      return format->format.automatic_axes;
  }
  
  static HRESULT WINAPI dwritetextformat3_SetAutomaticFontAxes(IDWriteTextFormat3 *iface, DWRITE_AUTOMATIC_FONT_AXES axes)
  {
      struct dwrite_textformat *format = impl_from_IDWriteTextFormat3(iface);
  
      TRACE("%p, %d.\n", iface, axes);
  
      format->format.automatic_axes = axes;
  
      return S_OK;
  }
  
  static const IDWriteTextFormat3Vtbl dwritetextformatvtbl =
  {
      dwritetextformat_QueryInterface,
      dwritetextformat_AddRef,
      dwritetextformat_Release,
      dwritetextformat_SetTextAlignment,
      dwritetextformat_SetParagraphAlignment,
      dwritetextformat_SetWordWrapping,
      dwritetextformat_SetReadingDirection,
      dwritetextformat_SetFlowDirection,
      dwritetextformat_SetIncrementalTabStop,
      dwritetextformat_SetTrimming,
      dwritetextformat_SetLineSpacing,
      dwritetextformat_GetTextAlignment,
      dwritetextformat_GetParagraphAlignment,
      dwritetextformat_GetWordWrapping,
      dwritetextformat_GetReadingDirection,
      dwritetextformat_GetFlowDirection,
      dwritetextformat_GetIncrementalTabStop,
      dwritetextformat_GetTrimming,
      dwritetextformat_GetLineSpacing,
      dwritetextformat_GetFontCollection,
      dwritetextformat_GetFontFamilyNameLength,
      dwritetextformat_GetFontFamilyName,
      dwritetextformat_GetFontWeight,
      dwritetextformat_GetFontStyle,
      dwritetextformat_GetFontStretch,
      dwritetextformat_GetFontSize,
      dwritetextformat_GetLocaleNameLength,
      dwritetextformat_GetLocaleName,
      dwritetextformat1_SetVerticalGlyphOrientation,
      dwritetextformat1_GetVerticalGlyphOrientation,
      dwritetextformat1_SetLastLineWrapping,
      dwritetextformat1_GetLastLineWrapping,
      dwritetextformat1_SetOpticalAlignment,
      dwritetextformat1_GetOpticalAlignment,
      dwritetextformat1_SetFontFallback,
      dwritetextformat1_GetFontFallback,
      dwritetextformat2_SetLineSpacing,
      dwritetextformat2_GetLineSpacing,
      dwritetextformat3_SetFontAxisValues,
      dwritetextformat3_GetFontAxisValueCount,
      dwritetextformat3_GetFontAxisValues,
      dwritetextformat3_GetAutomaticFontAxes,
      dwritetextformat3_SetAutomaticFontAxes,
  };
  
  static struct dwrite_textformat *unsafe_impl_from_IDWriteTextFormat(IDWriteTextFormat *iface)
  {
      return (iface->lpVtbl == (IDWriteTextFormatVtbl*)&dwritetextformatvtbl) ?
          CONTAINING_RECORD(iface, struct dwrite_textformat, IDWriteTextFormat3_iface) : NULL;
  }
  
  HRESULT create_textformat(const WCHAR *family_name, IDWriteFontCollection *collection, DWRITE_FONT_WEIGHT weight,
          DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch, FLOAT size, const WCHAR *locale, IDWriteTextFormat **format)
  {
      struct dwrite_textformat *object;
  
      *format = NULL;
  
      if (size <= 0.0f)
          return E_INVALIDARG;
  
      if (((UINT32)weight > DWRITE_FONT_WEIGHT_ULTRA_BLACK) ||
          ((UINT32)stretch > DWRITE_FONT_STRETCH_ULTRA_EXPANDED) ||
          ((UINT32)style > DWRITE_FONT_STYLE_ITALIC))
          return E_INVALIDARG;
  
      if (!(object = calloc(1, sizeof(*object))))
          return E_OUTOFMEMORY;
  
      object->IDWriteTextFormat3_iface.lpVtbl = &dwritetextformatvtbl;
      object->refcount = 1;
      object->format.family_name = wcsdup(family_name);
      object->format.family_len = wcslen(family_name);
      object->format.locale = wcsdup(locale);
      object->format.locale_len = wcslen(locale);
      /* Force locale name to lower case, layout will inherit this modified value. */
      wcslwr(object->format.locale);
      object->format.weight = weight;
      object->format.style = style;
      object->format.fontsize = size;
      object->format.tabstop = 4.0f * size;
      object->format.stretch = stretch;
      object->format.last_line_wrapping = TRUE;
      object->format.collection = collection;
      IDWriteFontCollection_AddRef(object->format.collection);
  
      *format = (IDWriteTextFormat *)&object->IDWriteTextFormat3_iface;
  
      return S_OK;
  }
  
  static HRESULT WINAPI dwritetypography_QueryInterface(IDWriteTypography *iface, REFIID riid, void **obj)
  {
      TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj);
  
      if (IsEqualIID(riid, &IID_IDWriteTypography) || IsEqualIID(riid, &IID_IUnknown)) {
          *obj = iface;
          IDWriteTypography_AddRef(iface);
          return S_OK;
      }
  
      WARN("%s not implemented.\n", debugstr_guid(riid));
  
      *obj = NULL;
  
      return E_NOINTERFACE;
  }
  
  static ULONG WINAPI dwritetypography_AddRef(IDWriteTypography *iface)
  {
      struct dwrite_typography *typography = impl_from_IDWriteTypography(iface);
      ULONG refcount = InterlockedIncrement(&typography->refcount);
  
      TRACE("%p, refcount %d.\n", iface, refcount);
  
      return refcount;
  }
  
  static ULONG WINAPI dwritetypography_Release(IDWriteTypography *iface)
  {
      struct dwrite_typography *typography = impl_from_IDWriteTypography(iface);
      ULONG refcount = InterlockedDecrement(&typography->refcount);
  
      TRACE("%p, refcount %d.\n", iface, refcount);
  
      if (!refcount)
      {
          free(typography->features);
          free(typography);
      }
  
      return refcount;
  }
  
  static HRESULT WINAPI dwritetypography_AddFontFeature(IDWriteTypography *iface, DWRITE_FONT_FEATURE feature)
  {
      struct dwrite_typography *typography = impl_from_IDWriteTypography(iface);
  
      TRACE("%p, %s, %u.\n", iface, debugstr_tag(feature.nameTag), feature.parameter);
  
      if (!dwrite_array_reserve((void **)&typography->features, &typography->capacity, typography->count + 1,
              sizeof(*typography->features)))
      {
          return E_OUTOFMEMORY;
      }
  
      typography->features[typography->count++] = feature;
  
      return S_OK;
  }
  
  static UINT32 WINAPI dwritetypography_GetFontFeatureCount(IDWriteTypography *iface)
  {
      struct dwrite_typography *typography = impl_from_IDWriteTypography(iface);
  
      TRACE("%p.\n", iface);
  
      return typography->count;
  }
  
  static HRESULT WINAPI dwritetypography_GetFontFeature(IDWriteTypography *iface, UINT32 index,
          DWRITE_FONT_FEATURE *feature)
  {
      struct dwrite_typography *typography = impl_from_IDWriteTypography(iface);
  
      TRACE("%p, %u, %p.\n", iface, index, feature);
  
      if (index >= typography->count)
          return E_INVALIDARG;
  
      *feature = typography->features[index];
      return S_OK;
  }
  
  static const IDWriteTypographyVtbl dwritetypographyvtbl = {
      dwritetypography_QueryInterface,
      dwritetypography_AddRef,
      dwritetypography_Release,
      dwritetypography_AddFontFeature,
      dwritetypography_GetFontFeatureCount,
      dwritetypography_GetFontFeature
  };
  
  HRESULT create_typography(IDWriteTypography **ret)
  {
      struct dwrite_typography *typography;
  
      *ret = NULL;
  
      if (!(typography = calloc(1, sizeof(*typography))))
          return E_OUTOFMEMORY;
  
      typography->IDWriteTypography_iface.lpVtbl = &dwritetypographyvtbl;
      typography->refcount = 1;
  
      *ret = &typography->IDWriteTypography_iface;
  
      return S_OK;
  }
  