[Indic] Decompose Sinhala split matras the way old HarfBuzz / Pango did

Had to do some refactoring to make this happen...

Under uniscribe bug compatibility mode, we still plit them
Uniscrie-style, but Jonathan and I convinced ourselves that there is no
harm doing this the Unicode way.  This change makes that happen, and
unbreaks free Sinhala fonts.
diff --git a/src/hb-ot-shape-complex-arabic.cc b/src/hb-ot-shape-complex-arabic.cc
index 697e54e..cba7933 100644
--- a/src/hb-ot-shape-complex-arabic.cc
+++ b/src/hb-ot-shape-complex-arabic.cc
@@ -348,6 +348,8 @@
   data_destroy_arabic,
   NULL, /* preprocess_text_arabic */
   NULL, /* normalization_preference */
+  NULL, /* decompose */
+  NULL, /* compose */
   setup_masks_arabic,
   true, /* zero_width_attached_marks */
 };
diff --git a/src/hb-ot-shape-complex-indic.cc b/src/hb-ot-shape-complex-indic.cc
index 60fe3fb..a948d52 100644
--- a/src/hb-ot-shape-complex-indic.cc
+++ b/src/hb-ot-shape-complex-indic.cc
@@ -1268,11 +1268,81 @@
 
 
 static hb_ot_shape_normalization_mode_t
-normalization_preference_indic (const hb_ot_shape_plan_t *plan)
+normalization_preference_indic (const hb_segment_properties_t *props)
 {
   return HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT;
 }
 
+static hb_bool_t
+decompose_indic (hb_unicode_funcs_t *unicode,
+		 hb_codepoint_t  ab,
+		 hb_codepoint_t *a,
+		 hb_codepoint_t *b)
+{
+  switch (ab)
+  {
+    /* Don't decompose these. */
+    case 0x0931  : return false;
+    case 0x0B94  : return false;
+
+
+    /*
+     * Decompose split matras that don't have Unicode decompositions.
+     */
+
+    case 0x0F77  : *a = 0x0FB2; *b= 0x0F81; return true;
+    case 0x0F79  : *a = 0x0FB3; *b= 0x0F81; return true;
+    case 0x17BE  : *a = 0x17C1; *b= 0x17BE; return true;
+    case 0x17BF  : *a = 0x17C1; *b= 0x17BF; return true;
+    case 0x17C0  : *a = 0x17C1; *b= 0x17C0; return true;
+    case 0x17C4  : *a = 0x17C1; *b= 0x17C4; return true;
+    case 0x17C5  : *a = 0x17C1; *b= 0x17C5; return true;
+    case 0x1925  : *a = 0x1920; *b= 0x1923; return true;
+    case 0x1926  : *a = 0x1920; *b= 0x1924; return true;
+    case 0x1B3C  : *a = 0x1B42; *b= 0x1B3C; return true;
+    case 0x1112E  : *a = 0x11127; *b= 0x11131; return true;
+    case 0x1112F  : *a = 0x11127; *b= 0x11132; return true;
+#if 0
+    /* This one has no decomposition in Unicode, but needs no decomposition either. */
+    /* case 0x0AC9  : return false; */
+    case 0x0B57  : *a = no decomp, -> RIGHT; return true;
+    case 0x1C29  : *a = no decomp, -> LEFT; return true;
+    case 0xA9C0  : *a = no decomp, -> RIGHT; return true;
+    case 0x111BF  : *a = no decomp, -> ABOVE; return true;
+#endif
+  }
+
+  if (indic_options ().uniscribe_bug_compatible)
+  switch (ab)
+  {
+    /* These Sinhala ones have Unicode decompositions, but Uniscribe
+     * decomposes them "Khmer-style". */
+    case 0x0DDA  : *a = 0x0DD9; *b= 0x0DDA; return true;
+    case 0x0DDC  : *a = 0x0DD9; *b= 0x0DDC; return true;
+    case 0x0DDD  : *a = 0x0DD9; *b= 0x0DDD; return true;
+    case 0x0DDE  : *a = 0x0DD9; *b= 0x0DDE; return true;
+  }
+
+  return unicode->decompose (ab, a, b);
+}
+
+static hb_bool_t
+compose_indic (hb_unicode_funcs_t *unicode,
+	       hb_codepoint_t  a,
+	       hb_codepoint_t  b,
+	       hb_codepoint_t *ab)
+{
+  /* Avoid recomposing split matras. */
+  if (HB_UNICODE_GENERAL_CATEGORY_IS_MARK (unicode->general_category (a)))
+    return false;
+
+  /* Composition-exclusion exceptions that we want to recompose. */
+  if (a == 0x09AF && b == 0x09BC) { *ab = 0x09DF; return true; }
+
+  return unicode->compose (a, b, ab);
+}
+
+
 const hb_ot_complex_shaper_t _hb_ot_complex_shaper_indic =
 {
   "indic",
@@ -1282,6 +1352,8 @@
   data_destroy_indic,
   NULL, /* preprocess_text */
   normalization_preference_indic,
+  decompose_indic,
+  compose_indic,
   setup_masks_indic,
   false, /* zero_width_attached_marks */
 };
diff --git a/src/hb-ot-shape-complex-misc.cc b/src/hb-ot-shape-complex-misc.cc
index 13bc22b..a65de2f 100644
--- a/src/hb-ot-shape-complex-misc.cc
+++ b/src/hb-ot-shape-complex-misc.cc
@@ -72,9 +72,9 @@
 }
 
 static hb_ot_shape_normalization_mode_t
-normalization_preference_default (const hb_ot_shape_plan_t *plan)
+normalization_preference_default (const hb_segment_properties_t *props)
 {
-  switch ((hb_tag_t) plan->props.script)
+  switch ((hb_tag_t) props->script)
   {
     /* Unicode-1.1 additions */
     case HB_SCRIPT_HANGUL:
@@ -83,6 +83,131 @@
   return HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS;
 }
 
+static hb_bool_t
+compose_default (hb_unicode_funcs_t *unicode,
+		 hb_codepoint_t  a,
+		 hb_codepoint_t  b,
+		 hb_codepoint_t *ab)
+{
+  /* Hebrew presentation-form shaping.
+   * https://bugzilla.mozilla.org/show_bug.cgi?id=728866 */
+  // Hebrew presentation forms with dagesh, for characters 0x05D0..0x05EA;
+  // note that some letters do not have a dagesh presForm encoded
+  static const hb_codepoint_t sDageshForms[0x05EA - 0x05D0 + 1] = {
+    0xFB30, // ALEF
+    0xFB31, // BET
+    0xFB32, // GIMEL
+    0xFB33, // DALET
+    0xFB34, // HE
+    0xFB35, // VAV
+    0xFB36, // ZAYIN
+    0, // HET
+    0xFB38, // TET
+    0xFB39, // YOD
+    0xFB3A, // FINAL KAF
+    0xFB3B, // KAF
+    0xFB3C, // LAMED
+    0, // FINAL MEM
+    0xFB3E, // MEM
+    0, // FINAL NUN
+    0xFB40, // NUN
+    0xFB41, // SAMEKH
+    0, // AYIN
+    0xFB43, // FINAL PE
+    0xFB44, // PE
+    0, // FINAL TSADI
+    0xFB46, // TSADI
+    0xFB47, // QOF
+    0xFB48, // RESH
+    0xFB49, // SHIN
+    0xFB4A // TAV
+  };
+
+  hb_bool_t found = unicode->compose (a, b, ab);
+
+  if (!found && (b & ~0x7F) == 0x0580) {
+      // special-case Hebrew presentation forms that are excluded from
+      // standard normalization, but wanted for old fonts
+      switch (b) {
+      case 0x05B4: // HIRIQ
+	  if (a == 0x05D9) { // YOD
+	      *ab = 0xFB1D;
+	      found = true;
+	  }
+	  break;
+      case 0x05B7: // patah
+	  if (a == 0x05F2) { // YIDDISH YOD YOD
+	      *ab = 0xFB1F;
+	      found = true;
+	  } else if (a == 0x05D0) { // ALEF
+	      *ab = 0xFB2E;
+	      found = true;
+	  }
+	  break;
+      case 0x05B8: // QAMATS
+	  if (a == 0x05D0) { // ALEF
+	      *ab = 0xFB2F;
+	      found = true;
+	  }
+	  break;
+      case 0x05B9: // HOLAM
+	  if (a == 0x05D5) { // VAV
+	      *ab = 0xFB4B;
+	      found = true;
+	  }
+	  break;
+      case 0x05BC: // DAGESH
+	  if (a >= 0x05D0 && a <= 0x05EA) {
+	      *ab = sDageshForms[a - 0x05D0];
+	      found = (*ab != 0);
+	  } else if (a == 0xFB2A) { // SHIN WITH SHIN DOT
+	      *ab = 0xFB2C;
+	      found = true;
+	  } else if (a == 0xFB2B) { // SHIN WITH SIN DOT
+	      *ab = 0xFB2D;
+	      found = true;
+	  }
+	  break;
+      case 0x05BF: // RAFE
+	  switch (a) {
+	  case 0x05D1: // BET
+	      *ab = 0xFB4C;
+	      found = true;
+	      break;
+	  case 0x05DB: // KAF
+	      *ab = 0xFB4D;
+	      found = true;
+	      break;
+	  case 0x05E4: // PE
+	      *ab = 0xFB4E;
+	      found = true;
+	      break;
+	  }
+	  break;
+      case 0x05C1: // SHIN DOT
+	  if (a == 0x05E9) { // SHIN
+	      *ab = 0xFB2A;
+	      found = true;
+	  } else if (a == 0xFB49) { // SHIN WITH DAGESH
+	      *ab = 0xFB2C;
+	      found = true;
+	  }
+	  break;
+      case 0x05C2: // SIN DOT
+	  if (a == 0x05E9) { // SHIN
+	      *ab = 0xFB2B;
+	      found = true;
+	  } else if (a == 0xFB49) { // SHIN WITH DAGESH
+	      *ab = 0xFB2D;
+	      found = true;
+	  }
+	  break;
+      }
+  }
+
+  return found;
+}
+
 const hb_ot_complex_shaper_t _hb_ot_complex_shaper_default =
 {
   "default",
@@ -92,6 +217,8 @@
   NULL, /* data_destroy */
   NULL, /* preprocess_text */
   normalization_preference_default,
+  NULL, /* decompose */
+  compose_default,
   NULL, /* setup_masks */
   true, /* zero_width_attached_marks */
 };
@@ -203,6 +330,8 @@
   NULL, /* data_destroy */
   preprocess_text_thai,
   NULL, /* normalization_preference */
+  NULL, /* decompose */
+  NULL, /* compose */
   NULL, /* setup_masks */
   true, /* zero_width_attached_marks */
 };
diff --git a/src/hb-ot-shape-complex-private.hh b/src/hb-ot-shape-complex-private.hh
index a45b0f4..477a250 100644
--- a/src/hb-ot-shape-complex-private.hh
+++ b/src/hb-ot-shape-complex-private.hh
@@ -56,6 +56,7 @@
   /* collect_features()
    * Called during shape_plan().
    * Shapers should use plan->map to add their features and callbacks.
+   * May be NULL.
    */
   void (*collect_features) (hb_ot_shape_planner_t *plan);
 
@@ -63,6 +64,7 @@
    * Called during shape_plan().
    * Shapers should use plan->map to override features and add callbacks after
    * common features are added.
+   * May be NULL.
    */
   void (*override_features) (hb_ot_shape_planner_t *plan);
 
@@ -78,13 +80,15 @@
    * Called when the shape_plan is being destroyed.
    * plan->data is passed here for destruction.
    * If NULL is returned, means a plan failure.
-   * May be NULL. */
+   * May be NULL.
+   */
   void (*data_destroy) (void *data);
 
 
   /* preprocess_text()
    * Called during shape().
    * Shapers can use to modify text before shaping starts.
+   * May be NULL.
    */
   void (*preprocess_text) (const hb_ot_shape_plan_t *plan,
 			   hb_buffer_t              *buffer,
@@ -93,14 +97,34 @@
 
   /* normalization_preference()
    * Called during shape().
+   * May be NULL.
    */
   hb_ot_shape_normalization_mode_t
-  (*normalization_preference) (const hb_ot_shape_plan_t *plan);
+  (*normalization_preference) (const hb_segment_properties_t *props);
+
+  /* decompose()
+   * Called during shape()'s normalization.
+   * May be NULL.
+   */
+  hb_bool_t (*decompose) (hb_unicode_funcs_t *unicode,
+			  hb_codepoint_t  ab,
+			  hb_codepoint_t *a,
+			  hb_codepoint_t *b);
+
+  /* compose()
+   * Called during shape()'s normalization.
+   * May be NULL.
+   */
+  hb_bool_t (*compose) (hb_unicode_funcs_t *unicode,
+			hb_codepoint_t  a,
+			hb_codepoint_t  b,
+			hb_codepoint_t *ab);
 
   /* setup_masks()
    * Called during shape().
    * Shapers should use map to get feature masks and set on buffer.
    * Shapers may NOT modify characters.
+   * May be NULL.
    */
   void (*setup_masks) (const hb_ot_shape_plan_t *plan,
 		       hb_buffer_t              *buffer,
diff --git a/src/hb-ot-shape-normalize-private.hh b/src/hb-ot-shape-normalize-private.hh
index c5fcbea..4b77699 100644
--- a/src/hb-ot-shape-normalize-private.hh
+++ b/src/hb-ot-shape-normalize-private.hh
@@ -35,6 +35,8 @@
 /* buffer var allocations, used during the normalization process */
 #define glyph_index()	var1.u32
 
+struct hb_ot_complex_shaper_t;
+
 enum hb_ot_shape_normalization_mode_t {
   HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED,
   HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS, /* never composes base-to-base */
@@ -44,8 +46,8 @@
   HB_OT_SHAPE_NORMALIZATION_MODE_DEFAULT = HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS
 };
 
-HB_INTERNAL void _hb_ot_shape_normalize (hb_font_t *font,
+HB_INTERNAL void _hb_ot_shape_normalize (const hb_ot_complex_shaper_t *shaper,
 					 hb_buffer_t *buffer,
-					 hb_ot_shape_normalization_mode_t mode);
+					 hb_font_t *font);
 
 #endif /* HB_OT_SHAPE_NORMALIZE_PRIVATE_HH */
diff --git a/src/hb-ot-shape-normalize.cc b/src/hb-ot-shape-normalize.cc
index 18a3f3f..df15f7d 100644
--- a/src/hb-ot-shape-normalize.cc
+++ b/src/hb-ot-shape-normalize.cc
@@ -25,6 +25,7 @@
  */
 
 #include "hb-ot-shape-normalize-private.hh"
+#include "hb-ot-shape-complex-private.hh"
 #include "hb-ot-shape-private.hh"
 
 
@@ -82,180 +83,23 @@
  */
 
 static hb_bool_t
-decompose_func (hb_unicode_funcs_t *unicode,
-		hb_codepoint_t  ab,
-		hb_codepoint_t *a,
-		hb_codepoint_t *b)
+decompose_unicode (hb_unicode_funcs_t *unicode,
+		   hb_codepoint_t  ab,
+		   hb_codepoint_t *a,
+		   hb_codepoint_t *b)
 {
-  /* XXX FIXME, move these to complex shapers and propagage to normalizer.*/
-  switch (ab) {
-    case 0x0AC9  : return false;
-
-    case 0x0931  : return false;
-    case 0x0B94  : return false;
-
-    /* These ones have Unicode decompositions, but we do it
-     * this way to be close to what Uniscribe does. */
-    case 0x0DDA  : *a = 0x0DD9; *b= 0x0DDA; return true;
-    case 0x0DDC  : *a = 0x0DD9; *b= 0x0DDC; return true;
-    case 0x0DDD  : *a = 0x0DD9; *b= 0x0DDD; return true;
-    case 0x0DDE  : *a = 0x0DD9; *b= 0x0DDE; return true;
-
-    case 0x0F77  : *a = 0x0FB2; *b= 0x0F81; return true;
-    case 0x0F79  : *a = 0x0FB3; *b= 0x0F81; return true;
-    case 0x17BE  : *a = 0x17C1; *b= 0x17BE; return true;
-    case 0x17BF  : *a = 0x17C1; *b= 0x17BF; return true;
-    case 0x17C0  : *a = 0x17C1; *b= 0x17C0; return true;
-    case 0x17C4  : *a = 0x17C1; *b= 0x17C4; return true;
-    case 0x17C5  : *a = 0x17C1; *b= 0x17C5; return true;
-    case 0x1925  : *a = 0x1920; *b= 0x1923; return true;
-    case 0x1926  : *a = 0x1920; *b= 0x1924; return true;
-    case 0x1B3C  : *a = 0x1B42; *b= 0x1B3C; return true;
-    case 0x1112E  : *a = 0x11127; *b= 0x11131; return true;
-    case 0x1112F  : *a = 0x11127; *b= 0x11132; return true;
-#if 0
-    case 0x0B57  : *a = 0xno decomp, -> RIGHT; return true;
-    case 0x1C29  : *a = 0xno decomp, -> LEFT; return true;
-    case 0xA9C0  : *a = 0xno decomp, -> RIGHT; return true;
-    case 0x111BF  : *a = 0xno decomp, -> ABOVE; return true;
-#endif
-  }
   return unicode->decompose (ab, a, b);
 }
 
 static hb_bool_t
-compose_func (hb_unicode_funcs_t *unicode,
-	      hb_codepoint_t  a,
-	      hb_codepoint_t  b,
-	      hb_codepoint_t *ab)
+compose_unicode (hb_unicode_funcs_t *unicode,
+		 hb_codepoint_t  a,
+		 hb_codepoint_t  b,
+		 hb_codepoint_t *ab)
 {
-  /* XXX, this belongs to indic normalizer. */
-  if (HB_UNICODE_GENERAL_CATEGORY_IS_MARK (unicode->general_category (a)))
-    return false;
-  /* XXX, add composition-exclusion exceptions to Indic shaper. */
-  if (a == 0x09AF && b == 0x09BC) { *ab = 0x09DF; return true; }
-
-  /* XXX, these belong to the hebew / default shaper. */
-  /* Hebrew presentation-form shaping.
-   * https://bugzilla.mozilla.org/show_bug.cgi?id=728866 */
-  // Hebrew presentation forms with dagesh, for characters 0x05D0..0x05EA;
-  // note that some letters do not have a dagesh presForm encoded
-  static const hb_codepoint_t sDageshForms[0x05EA - 0x05D0 + 1] = {
-    0xFB30, // ALEF
-    0xFB31, // BET
-    0xFB32, // GIMEL
-    0xFB33, // DALET
-    0xFB34, // HE
-    0xFB35, // VAV
-    0xFB36, // ZAYIN
-    0, // HET
-    0xFB38, // TET
-    0xFB39, // YOD
-    0xFB3A, // FINAL KAF
-    0xFB3B, // KAF
-    0xFB3C, // LAMED
-    0, // FINAL MEM
-    0xFB3E, // MEM
-    0, // FINAL NUN
-    0xFB40, // NUN
-    0xFB41, // SAMEKH
-    0, // AYIN
-    0xFB43, // FINAL PE
-    0xFB44, // PE
-    0, // FINAL TSADI
-    0xFB46, // TSADI
-    0xFB47, // QOF
-    0xFB48, // RESH
-    0xFB49, // SHIN
-    0xFB4A // TAV
-  };
-
-  hb_bool_t found = unicode->compose (a, b, ab);
-
-  if (!found && (b & ~0x7F) == 0x0580) {
-      // special-case Hebrew presentation forms that are excluded from
-      // standard normalization, but wanted for old fonts
-      switch (b) {
-      case 0x05B4: // HIRIQ
-	  if (a == 0x05D9) { // YOD
-	      *ab = 0xFB1D;
-	      found = true;
-	  }
-	  break;
-      case 0x05B7: // patah
-	  if (a == 0x05F2) { // YIDDISH YOD YOD
-	      *ab = 0xFB1F;
-	      found = true;
-	  } else if (a == 0x05D0) { // ALEF
-	      *ab = 0xFB2E;
-	      found = true;
-	  }
-	  break;
-      case 0x05B8: // QAMATS
-	  if (a == 0x05D0) { // ALEF
-	      *ab = 0xFB2F;
-	      found = true;
-	  }
-	  break;
-      case 0x05B9: // HOLAM
-	  if (a == 0x05D5) { // VAV
-	      *ab = 0xFB4B;
-	      found = true;
-	  }
-	  break;
-      case 0x05BC: // DAGESH
-	  if (a >= 0x05D0 && a <= 0x05EA) {
-	      *ab = sDageshForms[a - 0x05D0];
-	      found = (*ab != 0);
-	  } else if (a == 0xFB2A) { // SHIN WITH SHIN DOT
-	      *ab = 0xFB2C;
-	      found = true;
-	  } else if (a == 0xFB2B) { // SHIN WITH SIN DOT
-	      *ab = 0xFB2D;
-	      found = true;
-	  }
-	  break;
-      case 0x05BF: // RAFE
-	  switch (a) {
-	  case 0x05D1: // BET
-	      *ab = 0xFB4C;
-	      found = true;
-	      break;
-	  case 0x05DB: // KAF
-	      *ab = 0xFB4D;
-	      found = true;
-	      break;
-	  case 0x05E4: // PE
-	      *ab = 0xFB4E;
-	      found = true;
-	      break;
-	  }
-	  break;
-      case 0x05C1: // SHIN DOT
-	  if (a == 0x05E9) { // SHIN
-	      *ab = 0xFB2A;
-	      found = true;
-	  } else if (a == 0xFB49) { // SHIN WITH DAGESH
-	      *ab = 0xFB2C;
-	      found = true;
-	  }
-	  break;
-      case 0x05C2: // SIN DOT
-	  if (a == 0x05E9) { // SHIN
-	      *ab = 0xFB2B;
-	      found = true;
-	  } else if (a == 0xFB49) { // SHIN WITH DAGESH
-	      *ab = 0xFB2D;
-	      found = true;
-	  }
-	  break;
-      }
-  }
-
-  return found;
+  return unicode->compose (a, b, ab);
 }
 
-
 static inline void
 set_glyph (hb_glyph_info_t &info, hb_font_t *font)
 {
@@ -283,40 +127,54 @@
   buffer->skip_glyph ();
 }
 
+struct normalize_context_t
+{
+  hb_buffer_t *buffer;
+  hb_font_t *font;
+  hb_bool_t (*decompose) (hb_unicode_funcs_t *unicode,
+			  hb_codepoint_t  ab,
+			  hb_codepoint_t *a,
+			  hb_codepoint_t *b);
+  hb_bool_t (*compose) (hb_unicode_funcs_t *unicode,
+			hb_codepoint_t  a,
+			hb_codepoint_t  b,
+			hb_codepoint_t *ab);
+};
+
 /* Returns 0 if didn't decompose, number of resulting characters otherwise. */
 static inline unsigned int
-decompose (hb_font_t *font, hb_buffer_t *buffer, bool shortest, hb_codepoint_t ab)
+decompose (const normalize_context_t *c, bool shortest, hb_codepoint_t ab)
 {
   hb_codepoint_t a, b, a_glyph, b_glyph;
 
-  if (!decompose_func (buffer->unicode, ab, &a, &b) ||
-      (b && !font->get_glyph (b, 0, &b_glyph)))
+  if (!c->decompose (c->buffer->unicode, ab, &a, &b) ||
+      (b && !c->font->get_glyph (b, 0, &b_glyph)))
     return 0;
 
-  bool has_a = font->get_glyph (a, 0, &a_glyph);
+  bool has_a = c->font->get_glyph (a, 0, &a_glyph);
   if (shortest && has_a) {
     /* Output a and b */
-    output_char (buffer, a, a_glyph);
+    output_char (c->buffer, a, a_glyph);
     if (likely (b)) {
-      output_char (buffer, b, b_glyph);
+      output_char (c->buffer, b, b_glyph);
       return 2;
     }
     return 1;
   }
 
   unsigned int ret;
-  if ((ret = decompose (font, buffer, shortest, a))) {
+  if ((ret = decompose (c, shortest, a))) {
     if (b) {
-      output_char (buffer, b, b_glyph);
+      output_char (c->buffer, b, b_glyph);
       return ret + 1;
     }
     return ret;
   }
 
   if (has_a) {
-    output_char (buffer, a, a_glyph);
+    output_char (c->buffer, a, a_glyph);
     if (likely (b)) {
-      output_char (buffer, b, b_glyph);
+      output_char (c->buffer, b, b_glyph);
       return 2;
     }
     return 1;
@@ -327,41 +185,42 @@
 
 /* Returns 0 if didn't decompose, number of resulting characters otherwise. */
 static inline bool
-decompose_compatibility (hb_font_t *font, hb_buffer_t *buffer, hb_codepoint_t u)
+decompose_compatibility (const normalize_context_t *c, hb_codepoint_t u)
 {
   unsigned int len, i;
   hb_codepoint_t decomposed[HB_UNICODE_MAX_DECOMPOSITION_LEN];
   hb_codepoint_t glyphs[HB_UNICODE_MAX_DECOMPOSITION_LEN];
 
-  len = buffer->unicode->decompose_compatibility (u, decomposed);
+  len = c->buffer->unicode->decompose_compatibility (u, decomposed);
   if (!len)
     return 0;
 
   for (i = 0; i < len; i++)
-    if (!font->get_glyph (decomposed[i], 0, &glyphs[i]))
+    if (!c->font->get_glyph (decomposed[i], 0, &glyphs[i]))
       return 0;
 
   for (i = 0; i < len; i++)
-    output_char (buffer, decomposed[i], glyphs[i]);
+    output_char (c->buffer, decomposed[i], glyphs[i]);
 
   return len;
 }
 
 /* Returns true if recomposition may be benefitial. */
 static inline bool
-decompose_current_character (hb_font_t *font, hb_buffer_t *buffer, bool shortest)
+decompose_current_character (const normalize_context_t *c, bool shortest)
 {
+  hb_buffer_t * const buffer = c->buffer;
   hb_codepoint_t glyph;
   unsigned int len = 1;
 
   /* Kind of a cute waterfall here... */
-  if (shortest && font->get_glyph (buffer->cur().codepoint, 0, &glyph))
+  if (shortest && c->font->get_glyph (buffer->cur().codepoint, 0, &glyph))
     next_char (buffer, glyph);
-  else if ((len = decompose (font, buffer, shortest, buffer->cur().codepoint)))
+  else if ((len = decompose (c, shortest, buffer->cur().codepoint)))
     skip_char (buffer);
-  else if (!shortest && font->get_glyph (buffer->cur().codepoint, 0, &glyph))
+  else if (!shortest && c->font->get_glyph (buffer->cur().codepoint, 0, &glyph))
     next_char (buffer, glyph);
-  else if ((len = decompose_compatibility (font, buffer, buffer->cur().codepoint)))
+  else if ((len = decompose_compatibility (c, buffer->cur().codepoint)))
     skip_char (buffer);
   else
     next_char (buffer, glyph); /* glyph is initialized in earlier branches. */
@@ -374,49 +233,51 @@
 }
 
 static inline void
-handle_variation_selector_cluster (hb_font_t *font, hb_buffer_t *buffer, unsigned int end)
+handle_variation_selector_cluster (const normalize_context_t *c, unsigned int end)
 {
+  hb_buffer_t * const buffer = c->buffer;
   for (; buffer->idx < end - 1;) {
     if (unlikely (buffer->unicode->is_variation_selector (buffer->cur(+1).codepoint))) {
       /* The next two lines are some ugly lines... But work. */
-      font->get_glyph (buffer->cur().codepoint, buffer->cur(+1).codepoint, &buffer->cur().glyph_index());
+      c->font->get_glyph (buffer->cur().codepoint, buffer->cur(+1).codepoint, &buffer->cur().glyph_index());
       buffer->replace_glyphs (2, 1, &buffer->cur().codepoint);
     } else {
-      set_glyph (buffer->cur(), font);
+      set_glyph (buffer->cur(), c->font);
       buffer->next_glyph ();
     }
   }
   if (likely (buffer->idx < end)) {
-    set_glyph (buffer->cur(), font);
+    set_glyph (buffer->cur(), c->font);
     buffer->next_glyph ();
   }
 }
 
 /* Returns true if recomposition may be benefitial. */
 static inline bool
-decompose_multi_char_cluster (hb_font_t *font, hb_buffer_t *buffer, unsigned int end)
+decompose_multi_char_cluster (const normalize_context_t *c, unsigned int end)
 {
+  hb_buffer_t * const buffer = c->buffer;
   /* TODO Currently if there's a variation-selector we give-up, it's just too hard. */
   for (unsigned int i = buffer->idx; i < end; i++)
     if (unlikely (buffer->unicode->is_variation_selector (buffer->info[i].codepoint))) {
-      handle_variation_selector_cluster (font, buffer, end);
+      handle_variation_selector_cluster (c, end);
       return false;
     }
 
   while (buffer->idx < end)
-    decompose_current_character (font, buffer, false);
+    decompose_current_character (c, false);
   /* We can be smarter here and only return true if there are at least two ccc!=0 marks.
    * But does not matter. */
   return true;
 }
 
 static inline bool
-decompose_cluster (hb_font_t *font, hb_buffer_t *buffer, bool short_circuit, unsigned int end)
+decompose_cluster (const normalize_context_t *c, bool short_circuit, unsigned int end)
 {
-  if (likely (buffer->idx + 1 == end))
-    return decompose_current_character (font, buffer, short_circuit);
+  if (likely (c->buffer->idx + 1 == end))
+    return decompose_current_character (c, short_circuit);
   else
-    return decompose_multi_char_cluster (font, buffer, end);
+    return decompose_multi_char_cluster (c, end);
 }
 
 
@@ -431,9 +292,20 @@
 
 
 void
-_hb_ot_shape_normalize (hb_font_t *font, hb_buffer_t *buffer,
-			hb_ot_shape_normalization_mode_t mode)
+_hb_ot_shape_normalize (const hb_ot_complex_shaper_t *shaper,
+			hb_buffer_t *buffer,
+			hb_font_t *font)
 {
+  hb_ot_shape_normalization_mode_t mode = shaper->normalization_preference ?
+					  shaper->normalization_preference (&buffer->props) :
+					  HB_OT_SHAPE_NORMALIZATION_MODE_DEFAULT;
+  const normalize_context_t c = {
+    buffer,
+    font,
+    shaper->decompose ? shaper->decompose : decompose_unicode,
+    shaper->compose ? shaper->compose : compose_unicode
+  };
+
   bool short_circuit = mode != HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED &&
 		       mode != HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT;
   bool can_use_recompose = false;
@@ -457,7 +329,7 @@
       if (buffer->cur().cluster != buffer->info[end].cluster)
         break;
 
-    can_use_recompose = decompose_cluster (font, buffer, short_circuit, end) || can_use_recompose;
+    can_use_recompose = decompose_cluster (&c, short_circuit, end) || can_use_recompose;
   }
   buffer->swap_buffers ();
 
@@ -517,10 +389,10 @@
 	(starter == buffer->out_len - 1 ||
 	 _hb_glyph_info_get_modified_combining_class (&buffer->prev()) < _hb_glyph_info_get_modified_combining_class (&buffer->cur())) &&
 	/* And compose. */
-	compose_func (buffer->unicode,
-		      buffer->out_info[starter].codepoint,
-		      buffer->cur().codepoint,
-		      &composed) &&
+	c.compose (buffer->unicode,
+		   buffer->out_info[starter].codepoint,
+		   buffer->cur().codepoint,
+		   &composed) &&
 	/* And the font has glyph for the composite. */
 	font->get_glyph (composed, 0, &glyph))
     {
diff --git a/src/hb-ot-shape.cc b/src/hb-ot-shape.cc
index d168ae7..26ec4db 100644
--- a/src/hb-ot-shape.cc
+++ b/src/hb-ot-shape.cc
@@ -362,10 +362,7 @@
 
   HB_BUFFER_ALLOCATE_VAR (c->buffer, glyph_index);
 
-  _hb_ot_shape_normalize (c->font, c->buffer,
-			  c->plan->shaper->normalization_preference ?
-			  c->plan->shaper->normalization_preference (c->plan) :
-			  HB_OT_SHAPE_NORMALIZATION_MODE_DEFAULT);
+  _hb_ot_shape_normalize (c->plan->shaper, c->buffer, c->font);
 
   hb_ot_shape_setup_masks (c);