Merge pull request #745 from googlefonts/master

Interface for hb_subset, skeleton for the hb-subset cli, and basic testing rigging.
diff --git a/src/hb-aat-layout-common-private.hh b/src/hb-aat-layout-common-private.hh
index 1ea8318..74f9fdb 100644
--- a/src/hb-aat-layout-common-private.hh
+++ b/src/hb-aat-layout-common-private.hh
@@ -614,8 +614,7 @@
 			   hb_face_t *face_) :
 	      machine (machine_),
 	      buffer (buffer_),
-	      num_glyphs (face_->get_num_glyphs ()),
-	      last_zero (0) {}
+	      num_glyphs (face_->get_num_glyphs ()) {}
 
   template <typename context_t>
   inline void drive (context_t *c)
@@ -629,9 +628,6 @@
     bool last_was_dont_advance = false;
     for (buffer->idx = 0;;)
     {
-      if (!state)
-	last_zero = buffer->idx;
-
       unsigned int klass = buffer->idx < buffer->len ?
 			   machine.get_class (info[buffer->idx].codepoint, num_glyphs) :
 			   0 /* End of text */;
@@ -639,6 +635,30 @@
       if (unlikely (!entry))
 	break;
 
+      /* Unsafe-to-break before this if not in state 0, as things might
+       * go differently if we start from state 0 here. */
+      if (state && buffer->idx)
+      {
+	/* Special-case easy cases: if starting here at state 0 is not
+	 * actionable, and leads to the same next state, then it's safe.
+	 * Let's hope...  Maybe disable the conditional later, if proves
+	 * insufficient. */
+	const Entry<EntryData> *start_entry = machine.get_entryZ (0, klass);
+	if (start_entry->newState != entry->newState ||
+	    (start_entry->flags & context_t::DontAdvance) != (entry->flags & context_t::DontAdvance) ||
+	    c->is_actionable (this, entry) ||
+	    c->is_actionable (this, start_entry))
+	  buffer->unsafe_to_break (buffer->idx - 1, buffer->idx + 1);
+      }
+
+      /* Unsafe-to-break if end-of-text would kick in here. */
+      if (buffer->idx + 2 <= buffer->len)
+      {
+	const Entry<EntryData> *end_entry = machine.get_entryZ (state, 0);
+	if (c->is_actionable (this, end_entry))
+	  buffer->unsafe_to_break (buffer->idx, buffer->idx + 2);
+      }
+
       if (unlikely (!c->transition (this, entry)))
         break;
 
@@ -665,7 +685,6 @@
   const StateTable<EntryData> &machine;
   hb_buffer_t *buffer;
   unsigned int num_glyphs;
-  unsigned int last_zero;
 };
 
 
diff --git a/src/hb-aat-layout-morx-table.hh b/src/hb-aat-layout-morx-table.hh
index ffe4d03..4e75dd3 100644
--- a/src/hb-aat-layout-morx-table.hh
+++ b/src/hb-aat-layout-morx-table.hh
@@ -58,9 +58,13 @@
 
     inline driver_context_t (const RearrangementSubtable *table) :
 	ret (false),
-	start (0), end (0),
-	last_zero_before_start (0) {}
+	start (0), end (0) {}
 
+    inline bool is_actionable (StateTableDriver<void> *driver,
+			       const Entry<void> *entry)
+    {
+      return (entry->flags & Verb) && start < end;
+    }
     inline bool transition (StateTableDriver<void> *driver,
 			    const Entry<void> *entry)
     {
@@ -68,10 +72,7 @@
       unsigned int flags = entry->flags;
 
       if (flags & MarkFirst)
-      {
 	start = buffer->idx;
-	last_zero_before_start = driver->last_zero;
-      }
 
       if (flags & MarkLast)
 	end = MIN (buffer->idx + 1, buffer->len);
@@ -110,7 +111,7 @@
 
 	if (end - start >= l + r)
 	{
-	  buffer->unsafe_to_break (last_zero_before_start, MIN (buffer->idx + 1, buffer->len));
+	  buffer->merge_clusters (start, MIN (buffer->idx + 1, buffer->len));
 	  buffer->merge_clusters (start, end);
 
 	  hb_glyph_info_t *info = buffer->info;
@@ -147,7 +148,6 @@
     private:
     unsigned int start;
     unsigned int end;
-    unsigned int last_zero_before_start;
   };
 
   inline bool apply (hb_aat_apply_context_t *c) const
@@ -198,44 +198,59 @@
 
     inline driver_context_t (const ContextualSubtable *table) :
 	ret (false),
+	mark_set (false),
 	mark (0),
-	last_zero_before_mark (0),
 	subs (table+table->substitutionTables) {}
 
+    inline bool is_actionable (StateTableDriver<EntryData> *driver,
+			       const Entry<EntryData> *entry)
+    {
+      hb_buffer_t *buffer = driver->buffer;
+
+      if (buffer->idx == buffer->len && !mark_set)
+        return false;
+
+      return entry->data.markIndex != 0xFFFF || entry->data.currentIndex != 0xFFFF;
+    }
     inline bool transition (StateTableDriver<EntryData> *driver,
 			    const Entry<EntryData> *entry)
     {
       hb_buffer_t *buffer = driver->buffer;
 
-      if (entry->data.markIndex != 0xFFFF && mark < buffer->len)
+      /* Looks like CoreText applies neither mark nor current substitution for
+       * end-of-text if mark was not explicitly set. */
+      if (buffer->idx == buffer->len && !mark_set)
+        return true;
+
+      if (entry->data.markIndex != 0xFFFF)
       {
 	const Lookup<GlyphID> &lookup = subs[entry->data.markIndex];
 	hb_glyph_info_t *info = buffer->info;
 	const GlyphID *replacement = lookup.get_value (info[mark].codepoint, driver->num_glyphs);
 	if (replacement)
 	{
-	  buffer->unsafe_to_break (last_zero_before_mark, MIN (buffer->idx + 1, buffer->len));
+	  buffer->unsafe_to_break (mark, MIN (buffer->idx + 1, buffer->len));
 	  info[mark].codepoint = *replacement;
 	  ret = true;
 	}
       }
-      if (entry->data.currentIndex != 0xFFFF && buffer->idx < buffer->len)
+      if (entry->data.currentIndex != 0xFFFF)
       {
+        unsigned int idx = MIN (buffer->idx, buffer->len - 1);
 	const Lookup<GlyphID> &lookup = subs[entry->data.currentIndex];
 	hb_glyph_info_t *info = buffer->info;
-	const GlyphID *replacement = lookup.get_value (info[buffer->idx].codepoint, driver->num_glyphs);
+	const GlyphID *replacement = lookup.get_value (info[idx].codepoint, driver->num_glyphs);
 	if (replacement)
 	{
-	  buffer->unsafe_to_break (driver->last_zero, MIN (buffer->idx + 1, buffer->len));
-	  info[buffer->idx].codepoint = *replacement;
+	  info[idx].codepoint = *replacement;
 	  ret = true;
 	}
       }
 
       if (entry->flags & SetMark)
       {
+	mark_set = true;
 	mark = buffer->idx;
-	last_zero_before_mark = driver->last_zero;
       }
 
       return true;
@@ -244,8 +259,8 @@
     public:
     bool ret;
     private:
+    bool mark_set;
     unsigned int mark;
-    unsigned int last_zero_before_mark;
     const UnsizedOffsetListOf<Lookup<GlyphID>, HBUINT32> &subs;
   };
 
@@ -335,6 +350,11 @@
 	ligature (table+table->ligature),
 	match_length (0) {}
 
+    inline bool is_actionable (StateTableDriver<EntryData> *driver,
+			       const Entry<EntryData> *entry)
+    {
+      return !!(entry->flags & PerformAction);
+    }
     inline bool transition (StateTableDriver<EntryData> *driver,
 			    const Entry<EntryData> *entry)
     {
diff --git a/src/hb-buffer.cc b/src/hb-buffer.cc
index 7ead43b..7fb000d 100644
--- a/src/hb-buffer.cc
+++ b/src/hb-buffer.cc
@@ -1933,7 +1933,7 @@
       result |= HB_BUFFER_DIFF_FLAG_CODEPOINT_MISMATCH;
     if (buf_info->cluster != ref_info->cluster)
       result |= HB_BUFFER_DIFF_FLAG_CLUSTER_MISMATCH;
-    if ((buf_info->mask & HB_GLYPH_FLAG_DEFINED) != (ref_info->mask & HB_GLYPH_FLAG_DEFINED))
+    if ((buf_info->mask & HB_GLYPH_FLAG_DEFINED) & (ref_info->mask & HB_GLYPH_FLAG_DEFINED) != (ref_info->mask & HB_GLYPH_FLAG_DEFINED))
       result |= HB_BUFFER_DIFF_FLAG_GLYPH_FLAGS_MISMATCH;
     if (contains && ref_info->codepoint == dottedcircle_glyph)
       result |= HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT;
diff --git a/test/shaping/data/text-rendering-tests/DISABLED b/test/shaping/data/text-rendering-tests/DISABLED
index 1439d43..5117455 100644
--- a/test/shaping/data/text-rendering-tests/DISABLED
+++ b/test/shaping/data/text-rendering-tests/DISABLED
@@ -23,6 +23,7 @@
 tests/MORX-20.tests
 tests/MORX-21.tests
 tests/MORX-22.tests
+tests/MORX-23.tests
 
 # Rounding differences
 tests/SHARAN-1.tests
diff --git a/test/shaping/data/text-rendering-tests/Makefile.sources b/test/shaping/data/text-rendering-tests/Makefile.sources
index c28487f..b12f173 100644
--- a/test/shaping/data/text-rendering-tests/Makefile.sources
+++ b/test/shaping/data/text-rendering-tests/Makefile.sources
@@ -43,10 +43,11 @@
 	tests/MORX-17.tests \
 	tests/MORX-18.tests \
 	tests/MORX-19.tests \
+	tests/MORX-1.tests \
 	tests/MORX-20.tests \
 	tests/MORX-21.tests \
 	tests/MORX-22.tests \
-	tests/MORX-1.tests \
+	tests/MORX-23.tests \
 	tests/MORX-2.tests \
 	tests/MORX-3.tests \
 	tests/MORX-4.tests \
diff --git a/test/shaping/data/text-rendering-tests/fonts/TestMORXTwentythree.ttf b/test/shaping/data/text-rendering-tests/fonts/TestMORXTwentythree.ttf
new file mode 100644
index 0000000..df34912
--- /dev/null
+++ b/test/shaping/data/text-rendering-tests/fonts/TestMORXTwentythree.ttf
Binary files differ
diff --git a/test/shaping/data/text-rendering-tests/tests/MORX-23.tests b/test/shaping/data/text-rendering-tests/tests/MORX-23.tests
new file mode 100644
index 0000000..9575a80
--- /dev/null
+++ b/test/shaping/data/text-rendering-tests/tests/MORX-23.tests
@@ -0,0 +1 @@
+../fonts/TestMORXTwentythree.ttf:--font-size=1000 --ned --remove-default-ignorables --font-funcs=ft:U+0041,U+0042,U+0043,U+0044,U+0045:[E|E@556,0|E@1112,0|E@1668,0|E@2224,0]