[uniscribe/directwrite] Move feature setup to shared file

Fixes https://github.com/harfbuzz/harfbuzz/issues/3070
diff --git a/src/hb-directwrite.cc b/src/hb-directwrite.cc
index a1e57b0..40cb09b 100644
--- a/src/hb-directwrite.cc
+++ b/src/hb-directwrite.cc
@@ -32,6 +32,7 @@
 
 #include "hb-directwrite.h"
 
+#include "hb-ms-feature-ranges.hh"
 
 /**
  * SECTION:hb-directwrite
@@ -510,44 +511,6 @@
  * shaper
  */
 
-struct active_feature_t {
-  DWRITE_FONT_FEATURE fea;
-  unsigned int order;
-
-  HB_INTERNAL static int cmp (const void *pa, const void *pb) {
-    const active_feature_t *a = (const active_feature_t *) pa;
-    const active_feature_t *b = (const active_feature_t *) pb;
-    return a->fea.nameTag < b->fea.nameTag ? -1 : a->fea.nameTag > b->fea.nameTag ? 1 :
-	   a->order < b->order ? -1 : a->order > b->order ? 1 :
-	   a->fea.parameter < b->fea.parameter ? -1 : a->fea.parameter > b->fea.parameter ? 1 :
-	   0;
-  }
-  bool operator== (const active_feature_t *f)
-  { return cmp (this, f) == 0; }
-};
-
-struct feature_event_t {
-  unsigned int index;
-  bool start;
-  active_feature_t feature;
-
-  HB_INTERNAL static int cmp (const void *pa, const void *pb)
-  {
-    const feature_event_t *a = (const feature_event_t *) pa;
-    const feature_event_t *b = (const feature_event_t *) pb;
-    return a->index < b->index ? -1 : a->index > b->index ? 1 :
-	   a->start < b->start ? -1 : a->start > b->start ? 1 :
-	   active_feature_t::cmp (&a->feature, &b->feature);
-  }
-};
-
-struct range_record_t {
-  DWRITE_TYPOGRAPHIC_FEATURES features;
-  unsigned int index_first; /* == start */
-  unsigned int index_last;  /* == end - 1 */
-};
-
-
 hb_bool_t
 _hb_directwrite_shape (hb_shape_plan_t    *shape_plan,
 		       hb_font_t          *font,
@@ -644,149 +607,24 @@
   /*
    * Set up features.
    */
-  hb_vector_t<DWRITE_FONT_FEATURE> feature_records;
-  hb_vector_t<range_record_t> range_records;
-  if (num_features)
-  {
-    /* Sort features by start/end events. */
-    hb_vector_t<feature_event_t> feature_events;
-    for (unsigned int i = 0; i < num_features; i++)
-    {
-      active_feature_t feature;
-      feature.fea.nameTag = (DWRITE_FONT_FEATURE_TAG) hb_uint32_swap (features[i].tag);
-      feature.fea.parameter = features[i].value;
-      feature.order = i;
-
-      feature_event_t *event;
-
-      event = feature_events.push ();
-      event->index = features[i].start;
-      event->start = true;
-      event->feature = feature;
-
-      event = feature_events.push ();
-      event->index = features[i].end;
-      event->start = false;
-      event->feature = feature;
-    }
-    feature_events.qsort ();
-    /* Add a strategic final event. */
-    {
-      active_feature_t feature;
-      feature.fea.nameTag = (DWRITE_FONT_FEATURE_TAG) 0;
-      feature.fea.parameter = 0;
-      feature.order = num_features + 1;
-
-      feature_event_t *event = feature_events.push ();
-      event->index = 0; /* This value does magic. */
-      event->start = false;
-      event->feature = feature;
-    }
-
-    /* Scan events and save features for each range. */
-    hb_vector_t<active_feature_t> active_features;
-    unsigned int last_index = 0;
-    for (unsigned int i = 0; i < feature_events.length; i++)
-    {
-      feature_event_t *event = &feature_events[i];
-
-      if (event->index != last_index)
-      {
-	/* Save a snapshot of active features and the range. */
-	range_record_t *range = range_records.push ();
-
-	unsigned int offset = feature_records.length;
-
-	active_features.qsort ();
-	for (unsigned int j = 0; j < active_features.length; j++)
-	{
-	  if (!j || active_features[j].fea.nameTag != feature_records[feature_records.length - 1].nameTag)
-	  {
-	    feature_records.push (active_features[j].fea);
-	  }
-	  else
-	  {
-	    /* Overrides value for existing feature. */
-	    feature_records[feature_records.length - 1].parameter = active_features[j].fea.parameter;
-	  }
-	}
-
-	/* Will convert to pointer after all is ready, since feature_records.array
-	 * may move as we grow it. */
-	range->features.features = reinterpret_cast<DWRITE_FONT_FEATURE *> (offset);
-	range->features.featureCount = feature_records.length - offset;
-	range->index_first = last_index;
-	range->index_last  = event->index - 1;
-
-	last_index = event->index;
-      }
-
-      if (event->start)
-      {
-	active_features.push (event->feature);
-      }
-      else
-      {
-	active_feature_t *feature = active_features.find (&event->feature);
-	if (feature)
-	  active_features.remove (feature - active_features.arrayZ);
-      }
-    }
-
-    if (!range_records.length) /* No active feature found. */
-      num_features = 0;
-
-    /* Fixup the pointers. */
-    for (unsigned int i = 0; i < range_records.length; i++)
-    {
-      range_record_t *range = &range_records[i];
-      range->features.features = (DWRITE_FONT_FEATURE *) feature_records + reinterpret_cast<uintptr_t> (range->features.features);
-    }
-  }
-
-  hb_vector_t<DWRITE_TYPOGRAPHIC_FEATURES *> range_features;
+  static_assert ((sizeof (DWRITE_TYPOGRAPHIC_FEATURES) == sizeof (hb_ms_features_t)), "");
+  static_assert ((sizeof (DWRITE_FONT_FEATURE) == sizeof (hb_ms_feature_t)), "");
+  hb_vector_t<hb_ms_features_t *> range_features;
   hb_vector_t<uint32_t> range_char_counts;
   if (num_features)
   {
-      range_features.shrink (0);
-      range_char_counts.shrink (0);
-
-      range_record_t *last_range = &range_records[0];
-      for (unsigned int i = 0; i < textLength; i++)
-      {
-	range_record_t *range = last_range;
-	while (log_clusters[i] < range->index_first)
-	  range--;
-	while (log_clusters[i] > range->index_last)
-	  range++;
-	if (!range_features.length ||
-	    &range->features != range_features[range_features.length - 1])
-	{
-	  auto **typoFeatures = range_features.push ();
-	  auto *c = range_char_counts.push ();
-	  if (unlikely (!typoFeatures || !c))
-	  {
-	    range_features.shrink (0);
-	    range_char_counts.shrink (0);
-	    break;
-	  }
-	  *typoFeatures = &range->features;
-	  *c = 1;
-	}
-	else
-	{
-	  range_char_counts[range_char_counts.length - 1]++;
-	}
-
-	last_range = range;
-      }
+    hb_vector_t<hb_ms_feature_t> feature_records;
+    hb_vector_t<range_record_t> range_records;
+    if (hb_ms_setup_features (features, num_features, feature_records, range_records))
+      hb_ms_make_feature_ranges (feature_records,
+				 range_records,
+				 0,
+				 chars_len,
+				 log_clusters,
+				 range_features,
+				 range_char_counts);
   }
 
-  const auto **dwFeatures = (const DWRITE_TYPOGRAPHIC_FEATURES**) range_features.arrayZ;
-  auto featureRanges = range_features.length;
-  const auto *featureRangeLengths = range_char_counts.arrayZ;
-  //
-
   uint16_t* clusterMap;
   clusterMap = new uint16_t[textLength];
   DWRITE_SHAPING_TEXT_PROPERTIES* textProperties;
@@ -797,11 +635,23 @@
   DWRITE_SHAPING_GLYPH_PROPERTIES* glyphProperties;
   glyphProperties = new DWRITE_SHAPING_GLYPH_PROPERTIES[maxGlyphCount];
 
-  hr = analyzer->GetGlyphs (textString, textLength, fontFace, false,
-			    isRightToLeft, &runHead->mScript, localeName,
-			    nullptr, dwFeatures, featureRangeLengths, featureRanges,
-			    maxGlyphCount, clusterMap, textProperties,
-			    glyphIndices, glyphProperties, &glyphCount);
+  hr = analyzer->GetGlyphs (textString,
+			    chars_len,
+			    fontFace,
+			    false,
+			    isRightToLeft,
+			    &runHead->mScript,
+			    localeName,
+			    nullptr,
+			    (const DWRITE_TYPOGRAPHIC_FEATURES**) range_features.arrayZ,
+			    range_char_counts.arrayZ,
+			    range_features.length,
+			    maxGlyphCount,
+			    clusterMap,
+			    textProperties,
+			    glyphIndices,
+			    glyphProperties,
+			    &glyphCount);
 
   if (unlikely (hr == HRESULT_FROM_WIN32 (ERROR_INSUFFICIENT_BUFFER)))
   {
@@ -837,12 +687,24 @@
   double x_mult = (double) font->x_scale / fontEmSize;
   double y_mult = (double) font->y_scale / fontEmSize;
 
-  hr = analyzer->GetGlyphPlacements (textString, clusterMap, textProperties,
-				     textLength, glyphIndices, glyphProperties,
-				     glyphCount, fontFace, fontEmSize,
-				     false, isRightToLeft, &runHead->mScript, localeName,
-				     dwFeatures, featureRangeLengths, featureRanges,
-				     glyphAdvances, glyphOffsets);
+  hr = analyzer->GetGlyphPlacements (textString,
+				     clusterMap,
+				     textProperties,
+				     chars_len,
+				     glyphIndices,
+				     glyphProperties,
+				     glyphCount,
+				     fontFace,
+				     fontEmSize,
+				     false,
+				     isRightToLeft,
+				     &runHead->mScript,
+				     localeName,
+				     (const DWRITE_TYPOGRAPHIC_FEATURES**) range_features.arrayZ,
+				     range_char_counts.arrayZ,
+				     range_features.length,
+				     glyphAdvances,
+				     glyphOffsets);
 
   if (FAILED (hr))
     FAIL ("Analyzer failed to get glyph placements.");
diff --git a/src/hb-ms-feature-ranges.hh b/src/hb-ms-feature-ranges.hh
new file mode 100644
index 0000000..2056fd2
--- /dev/null
+++ b/src/hb-ms-feature-ranges.hh
@@ -0,0 +1,229 @@
+/*
+ * Copyright © 2011,2012,2013  Google, Inc.
+ * Copyright © 2021  Khaled Hosny
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#ifndef HB_MS_FEATURE_RANGES_HH
+#define HB_MS_FEATURE_RANGES_HH
+
+#include "hb.hh"
+
+typedef struct hb_ms_feature_t {
+  hb_tag_t tag;
+  uint32_t value;
+} hb_ms_feature_t;
+
+typedef struct hb_ms_features_t {
+  hb_ms_feature_t *features;
+  uint32_t         num_features;
+} hb_ms_features_t;
+
+struct active_feature_t {
+  hb_ms_feature_t fea;
+  unsigned int order;
+
+  HB_INTERNAL static int cmp (const void *pa, const void *pb) {
+    const active_feature_t *a = (const active_feature_t *) pa;
+    const active_feature_t *b = (const active_feature_t *) pb;
+    return a->fea.tag < b->fea.tag ? -1 : a->fea.tag > b->fea.tag ? 1 :
+	   a->order < b->order ? -1 : a->order > b->order ? 1 :
+	   a->fea.value < b->fea.value ? -1 : a->fea.value > b->fea.value ? 1 :
+	   0;
+  }
+  bool operator== (const active_feature_t *f)
+  { return cmp (this, f) == 0; }
+};
+
+struct feature_event_t {
+  unsigned int index;
+  bool start;
+  active_feature_t feature;
+
+  HB_INTERNAL static int cmp (const void *pa, const void *pb)
+  {
+    const feature_event_t *a = (const feature_event_t *) pa;
+    const feature_event_t *b = (const feature_event_t *) pb;
+    return a->index < b->index ? -1 : a->index > b->index ? 1 :
+	   a->start < b->start ? -1 : a->start > b->start ? 1 :
+	   active_feature_t::cmp (&a->feature, &b->feature);
+  }
+};
+
+struct range_record_t {
+  hb_ms_features_t features;
+  unsigned int index_first; /* == start */
+  unsigned int index_last;  /* == end - 1 */
+};
+
+HB_INTERNAL static bool
+hb_ms_setup_features (const hb_feature_t          *features,
+		      unsigned int                 num_features,
+		      hb_vector_t<hb_ms_feature_t> &feature_records, /* OUT */
+		      hb_vector_t<range_record_t>  &range_records /* OUT */)
+{
+
+  /* Sort features by start/end events. */
+  hb_vector_t<feature_event_t> feature_events;
+  for (unsigned int i = 0; i < num_features; i++)
+  {
+    active_feature_t feature;
+    feature.fea.tag = hb_uint32_swap (features[i].tag);
+    feature.fea.value = features[i].value;
+    feature.order = i;
+
+    feature_event_t *event;
+
+    event = feature_events.push ();
+    event->index = features[i].start;
+    event->start = true;
+    event->feature = feature;
+
+    event = feature_events.push ();
+    event->index = features[i].end;
+    event->start = false;
+    event->feature = feature;
+  }
+  feature_events.qsort ();
+  /* Add a strategic final event. */
+  {
+    active_feature_t feature;
+    feature.fea.tag = 0;
+    feature.fea.value = 0;
+    feature.order = num_features + 1;
+
+    feature_event_t *event = feature_events.push ();
+    event->index = 0; /* This value does magic. */
+    event->start = false;
+    event->feature = feature;
+  }
+
+  /* Scan events and save features for each range. */
+  hb_vector_t<active_feature_t> active_features;
+  unsigned int last_index = 0;
+  for (unsigned int i = 0; i < feature_events.length; i++)
+  {
+    feature_event_t *event = &feature_events[i];
+
+    if (event->index != last_index)
+    {
+      /* Save a snapshot of active features and the range. */
+      range_record_t *range = range_records.push ();
+
+      unsigned int offset = feature_records.length;
+
+      active_features.qsort ();
+      for (unsigned int j = 0; j < active_features.length; j++)
+      {
+        if (!j || active_features[j].fea.tag != feature_records[feature_records.length - 1].tag)
+        {
+          feature_records.push (active_features[j].fea);
+        }
+        else
+        {
+          /* Overrides value for existing feature. */
+          feature_records[feature_records.length - 1].value = active_features[j].fea.value;
+        }
+      }
+
+      /* Will convert to pointer after all is ready, since feature_records.array
+       * may move as we grow it. */
+      range->features.features = reinterpret_cast<hb_ms_feature_t *> (offset);
+      range->features.num_features = feature_records.length - offset;
+      range->index_first = last_index;
+      range->index_last  = event->index - 1;
+
+      last_index = event->index;
+    }
+
+    if (event->start)
+    {
+      active_features.push (event->feature);
+    }
+    else
+    {
+      active_feature_t *feature = active_features.find (&event->feature);
+      if (feature)
+        active_features.remove (feature - active_features.arrayZ);
+    }
+  }
+
+  if (!range_records.length) /* No active feature found. */
+    num_features = 0;
+
+  /* Fixup the pointers. */
+  for (unsigned int i = 0; i < range_records.length; i++)
+  {
+    range_record_t *range = &range_records[i];
+    range->features.features = (hb_ms_feature_t *) feature_records + reinterpret_cast<uintptr_t> (range->features.features);
+  }
+
+  return !!num_features;
+
+}
+
+HB_INTERNAL static void
+hb_ms_make_feature_ranges (hb_vector_t<hb_ms_feature_t>  &feature_records,
+			   hb_vector_t<range_record_t>   &range_records,
+			   unsigned int                   chars_offset,
+			   unsigned int                   chars_len,
+			   uint16_t                      *log_clusters,
+			   hb_vector_t<hb_ms_features_t*> &range_features, /* OUT */
+			   hb_vector_t<uint32_t>          &range_counts /* OUT */)
+{
+  range_features.shrink (0);
+  range_counts.shrink (0);
+
+  range_record_t *last_range = &range_records[0];
+  for (unsigned int i = chars_offset; i < chars_len; i++)
+  {
+    range_record_t *range = last_range;
+    while (log_clusters[i] < range->index_first)
+      range--;
+    while (log_clusters[i] > range->index_last)
+      range++;
+    if (!range_features.length ||
+        &range->features != range_features[range_features.length - 1])
+    {
+      auto **features = range_features.push ();
+      auto *c = range_counts.push ();
+      if (unlikely (!features || !c))
+      {
+        range_features.shrink (0);
+        range_counts.shrink (0);
+        break;
+      }
+      *features = &range->features;
+      *c = 1;
+    }
+    else
+    {
+      range_counts[range_counts.length - 1]++;
+    }
+
+    last_range = range;
+  }
+}
+
+#endif /* HB_MS_FEATURE_RANGES_HH */
diff --git a/src/hb-uniscribe.cc b/src/hb-uniscribe.cc
index a9bed1b..492d04d 100644
--- a/src/hb-uniscribe.cc
+++ b/src/hb-uniscribe.cc
@@ -44,6 +44,7 @@
 
 #include "hb-uniscribe.h"
 
+#include "hb-ms-feature-ranges.hh"
 #include "hb-open-file.hh"
 #include "hb-ot-name-table.hh"
 #include "hb-ot-layout.h"
@@ -284,44 +285,6 @@
 }
 
 
-struct active_feature_t {
-  OPENTYPE_FEATURE_RECORD rec;
-  unsigned int order;
-
-  HB_INTERNAL static int cmp (const void *pa, const void *pb) {
-    const active_feature_t *a = (const active_feature_t *) pa;
-    const active_feature_t *b = (const active_feature_t *) pb;
-    return a->rec.tagFeature < b->rec.tagFeature ? -1 : a->rec.tagFeature > b->rec.tagFeature ? 1 :
-	   a->order < b->order ? -1 : a->order > b->order ? 1 :
-	   a->rec.lParameter < b->rec.lParameter ? -1 : a->rec.lParameter > b->rec.lParameter ? 1 :
-	   0;
-  }
-  bool operator== (const active_feature_t *f)
-  { return cmp (this, f) == 0; }
-};
-
-struct feature_event_t {
-  unsigned int index;
-  bool start;
-  active_feature_t feature;
-
-  HB_INTERNAL static int cmp (const void *pa, const void *pb)
-  {
-    const feature_event_t *a = (const feature_event_t *) pa;
-    const feature_event_t *b = (const feature_event_t *) pb;
-    return a->index < b->index ? -1 : a->index > b->index ? 1 :
-	   a->start < b->start ? -1 : a->start > b->start ? 1 :
-	   active_feature_t::cmp (&a->feature, &b->feature);
-  }
-};
-
-struct range_record_t {
-  TEXTRANGE_PROPERTIES props;
-  unsigned int index_first; /* == start */
-  unsigned int index_last;  /* == end - 1 */
-};
-
-
 /*
  * shaper face data
  */
@@ -635,109 +598,6 @@
   const hb_uniscribe_font_data_t *font_data = font->data.uniscribe;
   hb_uniscribe_shaper_funcs_t *funcs = face_data->funcs;
 
-  /*
-   * Set up features.
-   */
-  hb_vector_t<OPENTYPE_FEATURE_RECORD> feature_records;
-  hb_vector_t<range_record_t> range_records;
-  if (num_features)
-  {
-    /* Sort features by start/end events. */
-    hb_vector_t<feature_event_t> feature_events;
-    for (unsigned int i = 0; i < num_features; i++)
-    {
-      active_feature_t feature;
-      feature.rec.tagFeature = hb_uint32_swap (features[i].tag);
-      feature.rec.lParameter = features[i].value;
-      feature.order = i;
-
-      feature_event_t *event;
-
-      event = feature_events.push ();
-      event->index = features[i].start;
-      event->start = true;
-      event->feature = feature;
-
-      event = feature_events.push ();
-      event->index = features[i].end;
-      event->start = false;
-      event->feature = feature;
-    }
-    feature_events.qsort ();
-    /* Add a strategic final event. */
-    {
-      active_feature_t feature;
-      feature.rec.tagFeature = 0;
-      feature.rec.lParameter = 0;
-      feature.order = num_features + 1;
-
-      feature_event_t *event = feature_events.push ();
-      event->index = 0; /* This value does magic. */
-      event->start = false;
-      event->feature = feature;
-    }
-
-    /* Scan events and save features for each range. */
-    hb_vector_t<active_feature_t> active_features;
-    unsigned int last_index = 0;
-    for (unsigned int i = 0; i < feature_events.length; i++)
-    {
-      feature_event_t *event = &feature_events[i];
-
-      if (event->index != last_index)
-      {
-	/* Save a snapshot of active features and the range. */
-	range_record_t *range = range_records.push ();
-
-	unsigned int offset = feature_records.length;
-
-	active_features.qsort ();
-	for (unsigned int j = 0; j < active_features.length; j++)
-	{
-	  if (!j || active_features[j].rec.tagFeature != feature_records[feature_records.length - 1].tagFeature)
-	  {
-	    feature_records.push (active_features[j].rec);
-	  }
-	  else
-	  {
-	    /* Overrides value for existing feature. */
-	    feature_records[feature_records.length - 1].lParameter = active_features[j].rec.lParameter;
-	  }
-	}
-
-	/* Will convert to pointer after all is ready, since feature_records.array
-	 * may move as we grow it. */
-	range->props.potfRecords = reinterpret_cast<OPENTYPE_FEATURE_RECORD *> (offset);
-	range->props.cotfRecords = feature_records.length - offset;
-	range->index_first = last_index;
-	range->index_last  = event->index - 1;
-
-	last_index = event->index;
-      }
-
-      if (event->start)
-      {
-	active_features.push (event->feature);
-      }
-      else
-      {
-	active_feature_t *feature = active_features.find (&event->feature);
-	if (feature)
-	  active_features.remove (feature - active_features.arrayZ);
-      }
-    }
-
-    if (!range_records.length) /* No active feature found. */
-      num_features = 0;
-
-    /* Fixup the pointers. */
-    for (unsigned int i = 0; i < range_records.length; i++)
-    {
-      range_record_t *range = &range_records[i];
-      range->props.potfRecords = (OPENTYPE_FEATURE_RECORD *) feature_records + reinterpret_cast<uintptr_t> (range->props.potfRecords);
-    }
-  }
-
 #define FAIL(...) \
   HB_STMT_START { \
     DEBUG_MSG (UNISCRIBE, nullptr, __VA_ARGS__); \
@@ -856,8 +716,23 @@
 				       nullptr, nullptr,
 				       &lang_count, &lang_tag);
   OPENTYPE_TAG language_tag = hb_uint32_swap (lang_count ? lang_tag : HB_TAG_NONE);
-  hb_vector_t<TEXTRANGE_PROPERTIES*> range_properties;
-  hb_vector_t<int> range_char_counts;
+
+  /*
+   * Set up features.
+   */
+  static_assert ((sizeof (TEXTRANGE_PROPERTIES) == sizeof (hb_ms_features_t)), "");
+  static_assert ((sizeof (OPENTYPE_FEATURE_RECORD) == sizeof (hb_ms_feature_t)), "");
+  hb_vector_t<hb_ms_feature_t> feature_records;
+  hb_vector_t<range_record_t> range_records;
+  bool has_features = false;
+  if (num_features)
+    has_features = hb_ms_setup_features (features,
+					 num_features,
+					 feature_records,
+					 range_records);
+
+  hb_vector_t<hb_ms_features_t*> range_properties;
+  hb_vector_t<uint32_t> range_char_counts;
 
   unsigned int glyphs_offset = 0;
   unsigned int glyphs_len;
@@ -867,42 +742,14 @@
     unsigned int chars_offset = items[i].iCharPos;
     unsigned int item_chars_len = items[i + 1].iCharPos - chars_offset;
 
-    if (num_features)
-    {
-      range_properties.shrink (0);
-      range_char_counts.shrink (0);
-
-      range_record_t *last_range = &range_records[0];
-
-      for (unsigned int k = chars_offset; k < chars_offset + item_chars_len; k++)
-      {
-	range_record_t *range = last_range;
-	while (log_clusters[k] < range->index_first)
-	  range--;
-	while (log_clusters[k] > range->index_last)
-	  range++;
-	if (!range_properties.length ||
-	    &range->props != range_properties[range_properties.length - 1])
-	{
-	  TEXTRANGE_PROPERTIES **props = range_properties.push ();
-	  int *c = range_char_counts.push ();
-	  if (unlikely (!props || !c))
-	  {
-	    range_properties.shrink (0);
-	    range_char_counts.shrink (0);
-	    break;
-	  }
-	  *props = &range->props;
-	  *c = 1;
-	}
-	else
-	{
-	  range_char_counts[range_char_counts.length - 1]++;
-	}
-
-	last_range = range;
-      }
-    }
+    if (has_features)
+      hb_ms_make_feature_ranges (feature_records,
+				 range_records,
+				 item_chars_len,
+				 chars_offset,
+				 log_clusters,
+				 range_properties,
+				 range_char_counts);
 
     /* Asking for glyphs in logical order circumvents at least
      * one bug in Uniscribe. */
@@ -914,8 +761,8 @@
 				     &items[i].a,
 				     script_tags[i],
 				     language_tag,
-				     range_char_counts.arrayZ,
-				     range_properties.arrayZ,
+				     (int *) range_char_counts.arrayZ,
+				     (TEXTRANGE_PROPERTIES**) range_properties.arrayZ,
 				     range_properties.length,
 				     pchars + chars_offset,
 				     item_chars_len,
@@ -955,8 +802,8 @@
 				     &items[i].a,
 				     script_tags[i],
 				     language_tag,
-				     range_char_counts.arrayZ,
-				     range_properties.arrayZ,
+				     (int *) range_char_counts.arrayZ,
+				     (TEXTRANGE_PROPERTIES**) range_properties.arrayZ,
 				     range_properties.length,
 				     pchars + chars_offset,
 				     log_clusters + chars_offset,