+ // if neither case was true, just add the character
+ last = ch;
+ }
+
+ return length;
+ }
+
+ static int Fetch (StringBuilder sb, string s, int i)
+ {
+ return (int) (sb != null ? sb [i] : s [i]);
+ }
+
+ // Cf. figure 7, section 1.3 of http://unicode.org/reports/tr15/.
+ static int TryComposeWithPreviousStarter (StringBuilder sb, string s, int current)
+ {
+ // Backtrack to previous starter.
+ int i = current - 1;
+ if (GetCombiningClass (Fetch (sb, s, current)) == 0) {
+ if (i < 0 || GetCombiningClass (Fetch (sb, s, i)) != 0)
+ return current + 1;
+ } else {
+ while (i >= 0 && GetCombiningClass (Fetch (sb, s, i)) != 0)
+ i--;
+ if (i < 0)
+ return current + 1;
+ }
+
+ int starter = Fetch (sb, s, i);
+
+ // The various decompositions involving starter follow this index.
+ int comp_idx = GetPrimaryCompositeHelperIndex (starter);
+ if (comp_idx == 0)
+ return current + 1;
+
+ int length = (sb != null ? sb.Length : s.Length);
+ int prevCombiningClass = -1;
+ for (int j = i + 1; j < length; j++) {
+ int candidate = Fetch (sb, s, j);
+
+ int combiningClass = GetCombiningClass (candidate);
+ if (combiningClass == prevCombiningClass)
+ // We skipped over a guy with the same class, without
+ // combining. Skip this one, too.
+ continue;
+
+ int composed = TryCompose (comp_idx, starter, candidate);
+ if (composed != 0) {
+ if (sb == null)
+ // Not normalized, and we are only checking.
+ return -1;
+
+ // Full Unicode warning: This will break when the underlying
+ // tables are extended.
+ sb [i] = (char) composed;
+ sb.Remove (j, 1);
+
+ return current;
+ }
+
+ // Gray box. We're done.
+ if (combiningClass == 0)
+ return j + 1;
+
+ prevCombiningClass = combiningClass;