bug 863248 - update harfbuzz to version 0.9.16 from upstream. r=jdaggett
authorJonathan Kew <jkew@mozilla.com>
Tue, 30 Apr 2013 13:02:25 +0100
changeset 130329 b4fd9ec8b0d01a64a0aae9abb1b2fb7f9baac41f
parent 130328 d4b0ad30ba0d2ccb3e84cd42e96c8b496365b985
child 130330 a2892f59d8bd7e877feb7977b5e48abf25dce186
push id24611
push userryanvm@gmail.com
push dateTue, 30 Apr 2013 17:34:15 +0000
treeherdermozilla-central@45c053246b2a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdaggett
bugs863248
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 863248 - update harfbuzz to version 0.9.16 from upstream. r=jdaggett
gfx/harfbuzz/src/Makefile.am
gfx/harfbuzz/src/check-symbols.sh
gfx/harfbuzz/src/hb-atomic-private.hh
gfx/harfbuzz/src/hb-blob.cc
gfx/harfbuzz/src/hb-blob.h
gfx/harfbuzz/src/hb-buffer-deserialize-json.hh
gfx/harfbuzz/src/hb-buffer-deserialize-json.rl
gfx/harfbuzz/src/hb-buffer-deserialize-text.hh
gfx/harfbuzz/src/hb-buffer-deserialize-text.rl
gfx/harfbuzz/src/hb-buffer-private.hh
gfx/harfbuzz/src/hb-buffer-serialize.cc
gfx/harfbuzz/src/hb-buffer.cc
gfx/harfbuzz/src/hb-buffer.h
gfx/harfbuzz/src/hb-fallback-shape.cc
gfx/harfbuzz/src/hb-ft.cc
gfx/harfbuzz/src/hb-graphite2.cc
gfx/harfbuzz/src/hb-open-type-private.hh
gfx/harfbuzz/src/hb-ot-layout-common-private.hh
gfx/harfbuzz/src/hb-ot-layout-gpos-table.hh
gfx/harfbuzz/src/hb-ot-layout-gsub-table.hh
gfx/harfbuzz/src/hb-ot-layout-gsubgpos-private.hh
gfx/harfbuzz/src/hb-ot-layout-private.hh
gfx/harfbuzz/src/hb-ot-layout.cc
gfx/harfbuzz/src/hb-ot-map-private.hh
gfx/harfbuzz/src/hb-ot-map.cc
gfx/harfbuzz/src/hb-ot-shape-complex-arabic-fallback.hh
gfx/harfbuzz/src/hb-ot-shape-complex-arabic.cc
gfx/harfbuzz/src/hb-ot-shape-complex-default.cc
gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.hh
gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.rl
gfx/harfbuzz/src/hb-ot-shape-complex-indic.cc
gfx/harfbuzz/src/hb-ot-shape-complex-myanmar-machine.hh
gfx/harfbuzz/src/hb-ot-shape-complex-myanmar-machine.rl
gfx/harfbuzz/src/hb-ot-shape-complex-myanmar.cc
gfx/harfbuzz/src/hb-ot-shape-complex-private.hh
gfx/harfbuzz/src/hb-ot-shape-complex-sea-machine.hh
gfx/harfbuzz/src/hb-ot-shape-complex-sea-machine.rl
gfx/harfbuzz/src/hb-ot-shape-complex-sea.cc
gfx/harfbuzz/src/hb-ot-shape-fallback-private.hh
gfx/harfbuzz/src/hb-ot-shape-fallback.cc
gfx/harfbuzz/src/hb-ot-shape-normalize-private.hh
gfx/harfbuzz/src/hb-ot-shape-normalize.cc
gfx/harfbuzz/src/hb-ot-shape-private.hh
gfx/harfbuzz/src/hb-ot-shape.cc
gfx/harfbuzz/src/hb-private.hh
gfx/harfbuzz/src/hb-set-private.hh
gfx/harfbuzz/src/hb-set.cc
gfx/harfbuzz/src/hb-shape-plan.cc
gfx/harfbuzz/src/hb-shape.cc
gfx/harfbuzz/src/hb-unicode-private.hh
gfx/harfbuzz/src/hb-version.h
gfx/harfbuzz/src/test-buffer-serialize.cc
gfx/harfbuzz/src/test.cc
layout/reftests/font-features/reftest.list
--- a/gfx/harfbuzz/src/Makefile.am
+++ b/gfx/harfbuzz/src/Makefile.am
@@ -15,17 +15,20 @@ DISTCHECK_CONFIGURE_FLAGS = --enable-int
 
 lib_LTLIBRARIES = libharfbuzz.la
 
 HBCFLAGS =
 HBLIBS =
 HBSOURCES =  \
 	hb-atomic-private.hh \
 	hb-blob.cc \
+	hb-buffer-deserialize-json.hh \
+	hb-buffer-deserialize-text.hh \
 	hb-buffer-private.hh \
+	hb-buffer-serialize.cc \
 	hb-buffer.cc \
 	hb-cache-private.hh \
 	hb-common.cc \
 	hb-fallback-shape.cc \
 	hb-font-private.hh \
 	hb-font.cc \
 	hb-mutex-private.hh \
 	hb-object-private.hh \
@@ -252,30 +255,40 @@ arabic-table: gen-arabic-table.py Arabic
 	$(AM_V_GEN) $(builddir)/$^ > hb-ot-shape-complex-arabic-table.hh.tmp && \
 	mv hb-ot-shape-complex-arabic-table.hh.tmp $(srcdir)/hb-ot-shape-complex-arabic-table.hh || \
 	($(RM) hb-ot-shape-complex-arabic-table.hh.tmp; false)
 
 
 .PHONY: unicode-tables arabic-table indic-table
 
 BUILT_SOURCES += \
+	hb-buffer-deserialize-json.hh \
+	hb-buffer-deserialize-text.hh \
 	hb-ot-shape-complex-indic-machine.hh \
 	hb-ot-shape-complex-myanmar-machine.hh \
 	hb-ot-shape-complex-sea-machine.hh \
 	$(NULL)
 EXTRA_DIST += \
+	hb-buffer-deserialize-json.rl \
+	hb-buffer-deserialize-text.rl \
 	hb-ot-shape-complex-indic-machine.rl \
 	hb-ot-shape-complex-myanmar-machine.rl \
 	hb-ot-shape-complex-sea-machine.rl \
 	$(NULL)
 %.hh: %.rl
 	$(AM_V_GEN)$(top_srcdir)/missing --run ragel -e -F1 -o "$@.tmp" "$<" && \
 	mv "$@.tmp" "$@" || ( $(RM) "$@.tmp" && false )
 
-noinst_PROGRAMS = main test test-would-substitute test-size-params
+noinst_PROGRAMS = \
+	main \
+	test \
+	test-buffer-serialize \
+	test-size-params \
+	test-would-substitute \
+	$(NULL)
 bin_PROGRAMS =
 
 main_SOURCES = main.cc
 main_CPPFLAGS = $(HBCFLAGS)
 main_LDADD = libharfbuzz.la $(HBLIBS)
 
 test_SOURCES = test.cc
 test_CPPFLAGS = $(HBCFLAGS) $(FREETYPE_CFLAGS)
@@ -284,16 +297,20 @@ test_LDADD = libharfbuzz.la $(HBLIBS) $(
 test_would_substitute_SOURCES = test-would-substitute.cc
 test_would_substitute_CPPFLAGS = $(HBCFLAGS) $(FREETYPE_CFLAGS)
 test_would_substitute_LDADD = libharfbuzz.la $(HBLIBS) $(FREETYPE_LIBS)
 
 test_size_params_SOURCES = test-size-params.cc
 test_size_params_CPPFLAGS = $(HBCFLAGS)
 test_size_params_LDADD = libharfbuzz.la $(HBLIBS)
 
+test_buffer_serialize_SOURCES = test-buffer-serialize.cc
+test_buffer_serialize_CPPFLAGS = $(HBCFLAGS)
+test_buffer_serialize_LDADD = libharfbuzz.la $(HBLIBS)
+
 dist_check_SCRIPTS = \
 	check-c-linkage-decls.sh \
 	check-header-guards.sh \
 	check-includes.sh \
 	check-symbols.sh \
 	$(NULL)
 
 if HAVE_ICU
new file mode 100755
--- /dev/null
+++ b/gfx/harfbuzz/src/check-symbols.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+LC_ALL=C
+export LC_ALL
+
+test -z "$srcdir" && srcdir=.
+test -z "$MAKE" && MAKE=make
+stat=0
+
+if which nm 2>/dev/null >/dev/null; then
+	:
+else
+	echo "check-symbols.sh: 'nm' not found; skipping test"
+	exit 77
+fi
+
+defs="harfbuzz.def"
+$MAKE $defs > /dev/null
+tested=false
+for def in $defs; do
+	lib=`echo "$def" | sed 's/[.]def$//;s@.*/@@'`
+	so=.libs/lib${lib}.so
+
+	EXPORTED_SYMBOLS="`nm "$so" | grep ' [BCDGINRSTVW] ' | grep -v ' _fini\>\| _init\>\| _fdata\>\| _ftext\>\| __bss_start\>\| __bss_start__\>\| __bss_end__\>\| _edata\>\| _end\>\| _bss_end__\>\| __end__\>' | cut -d' ' -f3`"
+
+	if test -f "$so"; then
+
+		echo "Checking that $so has the same symbol list as $def"
+		{
+			echo EXPORTS
+			echo "$EXPORTED_SYMBOLS"
+			# cheat: copy the last line from the def file!
+			tail -n1 "$def"
+		} | diff "$def" - >&2 || stat=1
+
+		echo "Checking that we are not exposing internal symbols"
+		if echo "$EXPORTED_SYMBOLS" | grep -v 'hb_'; then
+			echo "Ouch, internal symbols exposed"
+			stat=1
+		fi
+
+		tested=true
+	fi
+done
+if ! $tested; then
+	echo "check-exported-symbols.sh: libharfbuzz shared library not found; skipping test"
+	exit 77
+fi
+
+exit $stat
--- a/gfx/harfbuzz/src/hb-atomic-private.hh
+++ b/gfx/harfbuzz/src/hb-atomic-private.hh
@@ -42,32 +42,28 @@
 #if 0
 
 
 #elif !defined(HB_NO_MT) && (defined(_WIN32) || defined(__CYGWIN__))
 
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
 
-/* mingw32 does not have MemoryBarrier.
- * MemoryBarrier may be defined as a macro or a function.
- * Just make a failsafe version for ourselves. */
-#ifdef MemoryBarrier
-#define HBMemoryBarrier MemoryBarrier
-#else
-static inline void HBMemoryBarrier (void) {
+#if defined(__MINGW32__) && !defined(MemoryBarrier)
+static inline void _HBMemoryBarrier (void) {
   long dummy = 0;
   InterlockedExchange (&dummy, 1);
 }
+# define MemoryBarrier _HBMemoryBarrier
 #endif
 
 typedef LONG hb_atomic_int_t;
 #define hb_atomic_int_add(AI, V)	InterlockedExchangeAdd (&(AI), (V))
 
-#define hb_atomic_ptr_get(P)		(HBMemoryBarrier (), (void *) *(P))
+#define hb_atomic_ptr_get(P)		(MemoryBarrier (), (void *) *(P))
 #define hb_atomic_ptr_cmpexch(P,O,N)	(InterlockedCompareExchangePointer ((void **) (P), (void *) (N), (void *) (O)) == (void *) (O))
 
 
 #elif !defined(HB_NO_MT) && defined(__APPLE__)
 
 #include <libkern/OSAtomic.h>
 #ifdef __MAC_OS_X_MIN_REQUIRED
 #include <AvailabilityMacros.h>
--- a/gfx/harfbuzz/src/hb-blob.cc
+++ b/gfx/harfbuzz/src/hb-blob.cc
@@ -118,17 +118,17 @@ hb_blob_create_sub_blob (hb_blob_t    *p
 
   if (!length || offset >= parent->length)
     return hb_blob_get_empty ();
 
   hb_blob_make_immutable (parent);
 
   blob = hb_blob_create (parent->data + offset,
 			 MIN (length, parent->length - offset),
-			 parent->mode,
+			 HB_MEMORY_MODE_READONLY,
 			 hb_blob_reference (parent),
 			 (hb_destroy_func_t) hb_blob_destroy);
 
   return blob;
 }
 
 hb_blob_t *
 hb_blob_get_empty (void)
--- a/gfx/harfbuzz/src/hb-blob.h
+++ b/gfx/harfbuzz/src/hb-blob.h
@@ -31,32 +31,58 @@
 #ifndef HB_BLOB_H
 #define HB_BLOB_H
 
 #include "hb-common.h"
 
 HB_BEGIN_DECLS
 
 
+/*
+ * Note re various memory-modes:
+ *
+ * - In no case shall the HarfBuzz client modify memory
+ *   that is passed to HarfBuzz in a blob.  If there is
+ *   any such possibility, MODE_DUPLICATE should be used
+ *   such that HarfBuzz makes a copy immediately,
+ *
+ * - Use MODE_READONLY otherse, unless you really really
+ *   really know what you are doing,
+ *
+ * - MODE_WRITABLE is appropriate if you relaly made a
+ *   copy of data solely for the purpose of passing to
+ *   HarfBuzz and doing that just once (no reuse!),
+ *
+ * - If the font is mmap()ed, it's ok to use
+ *   READONLY_MAY_MAKE_WRITABLE, however, there were
+ *   design problems with that mode, so HarfBuzz do not
+ *   really use it anymore.  If not sure, use MODE_READONLY.
+ */
 typedef enum {
   HB_MEMORY_MODE_DUPLICATE,
   HB_MEMORY_MODE_READONLY,
   HB_MEMORY_MODE_WRITABLE,
   HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE
 } hb_memory_mode_t;
 
 typedef struct hb_blob_t hb_blob_t;
 
 hb_blob_t *
 hb_blob_create (const char        *data,
 		unsigned int       length,
 		hb_memory_mode_t   mode,
 		void              *user_data,
 		hb_destroy_func_t  destroy);
 
+/* Always creates with MEMORY_MODE_READONLY.
+ * Even if the parent blob is writable, we don't
+ * want the user of the sub-blob to be able to
+ * modify the parent data as that data may be
+ * shared among multiple sub-blobs.
+ */
 hb_blob_t *
 hb_blob_create_sub_blob (hb_blob_t    *parent,
 			 unsigned int  offset,
 			 unsigned int  length);
 
 hb_blob_t *
 hb_blob_get_empty (void);
 
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-buffer-deserialize-json.hh
@@ -0,0 +1,643 @@
+
+#line 1 "../../src/hb-buffer-deserialize-json.rl"
+/*
+ * Copyright © 2013  Google, Inc.
+ *
+ *  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_BUFFER_DESERIALIZE_JSON_HH
+#define HB_BUFFER_DESERIALIZE_JSON_HH
+
+#include "hb-private.hh"
+
+
+#line 36 "hb-buffer-deserialize-json.hh.tmp"
+static const unsigned char _deserialize_json_trans_keys[] = {
+	0u, 0u, 9u, 123u, 9u, 34u, 97u, 103u, 120u, 121u, 34u, 34u, 9u, 58u, 9u, 57u, 
+	48u, 57u, 9u, 125u, 9u, 125u, 9u, 125u, 34u, 34u, 9u, 58u, 9u, 57u, 48u, 57u, 
+	9u, 125u, 9u, 125u, 108u, 108u, 34u, 34u, 9u, 58u, 9u, 57u, 9u, 125u, 9u, 125u, 
+	120u, 121u, 34u, 34u, 9u, 58u, 9u, 57u, 48u, 57u, 9u, 125u, 9u, 125u, 34u, 34u, 
+	9u, 58u, 9u, 57u, 48u, 57u, 9u, 125u, 9u, 125u, 34u, 34u, 9u, 58u, 9u, 57u, 
+	65u, 122u, 34u, 122u, 9u, 125u, 9u, 125u, 9u, 93u, 9u, 123u, 0u, 0u, 0
+};
+
+static const char _deserialize_json_key_spans[] = {
+	0, 115, 26, 7, 2, 1, 50, 49, 
+	10, 117, 117, 117, 1, 50, 49, 10, 
+	117, 117, 1, 1, 50, 49, 117, 117, 
+	2, 1, 50, 49, 10, 117, 117, 1, 
+	50, 49, 10, 117, 117, 1, 50, 49, 
+	58, 89, 117, 117, 85, 115, 0
+};
+
+static const short _deserialize_json_index_offsets[] = {
+	0, 0, 116, 143, 151, 154, 156, 207, 
+	257, 268, 386, 504, 622, 624, 675, 725, 
+	736, 854, 972, 974, 976, 1027, 1077, 1195, 
+	1313, 1316, 1318, 1369, 1419, 1430, 1548, 1666, 
+	1668, 1719, 1769, 1780, 1898, 2016, 2018, 2069, 
+	2119, 2178, 2268, 2386, 2504, 2590, 2706
+};
+
+static const char _deserialize_json_indicies[] = {
+	0, 0, 0, 0, 0, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	0, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 2, 1, 3, 3, 3, 
+	3, 3, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 3, 1, 4, 1, 
+	5, 1, 6, 7, 1, 1, 8, 1, 
+	9, 10, 1, 11, 1, 11, 11, 11, 
+	11, 11, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 11, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 12, 1, 
+	12, 12, 12, 12, 12, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 12, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 13, 1, 1, 14, 
+	15, 15, 15, 15, 15, 15, 15, 15, 
+	15, 1, 16, 17, 17, 17, 17, 17, 
+	17, 17, 17, 17, 1, 18, 18, 18, 
+	18, 18, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 18, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	19, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 20, 1, 21, 21, 21, 21, 21, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 21, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 3, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 22, 
+	1, 18, 18, 18, 18, 18, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	18, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 19, 1, 1, 1, 
+	17, 17, 17, 17, 17, 17, 17, 17, 
+	17, 17, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 20, 1, 23, 
+	1, 23, 23, 23, 23, 23, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	23, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 24, 1, 24, 24, 24, 24, 
+	24, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 24, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	25, 1, 1, 26, 27, 27, 27, 27, 
+	27, 27, 27, 27, 27, 1, 28, 29, 
+	29, 29, 29, 29, 29, 29, 29, 29, 
+	1, 30, 30, 30, 30, 30, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	30, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 31, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 32, 1, 30, 
+	30, 30, 30, 30, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 30, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 31, 1, 1, 1, 29, 29, 
+	29, 29, 29, 29, 29, 29, 29, 29, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 32, 1, 33, 1, 34, 
+	1, 34, 34, 34, 34, 34, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	34, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 35, 1, 35, 35, 35, 35, 
+	35, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 35, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 36, 37, 37, 37, 37, 
+	37, 37, 37, 37, 37, 1, 38, 38, 
+	38, 38, 38, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 38, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 39, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 40, 1, 38, 38, 38, 38, 
+	38, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 38, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 39, 
+	1, 1, 1, 41, 41, 41, 41, 41, 
+	41, 41, 41, 41, 41, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	40, 1, 42, 43, 1, 44, 1, 44, 
+	44, 44, 44, 44, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 44, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	45, 1, 45, 45, 45, 45, 45, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 45, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 46, 1, 
+	1, 47, 48, 48, 48, 48, 48, 48, 
+	48, 48, 48, 1, 49, 50, 50, 50, 
+	50, 50, 50, 50, 50, 50, 1, 51, 
+	51, 51, 51, 51, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 51, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 52, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 53, 1, 51, 51, 51, 
+	51, 51, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 51, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	52, 1, 1, 1, 50, 50, 50, 50, 
+	50, 50, 50, 50, 50, 50, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 53, 1, 54, 1, 54, 54, 54, 
+	54, 54, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 54, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 55, 1, 
+	55, 55, 55, 55, 55, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 55, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 56, 1, 1, 57, 
+	58, 58, 58, 58, 58, 58, 58, 58, 
+	58, 1, 59, 60, 60, 60, 60, 60, 
+	60, 60, 60, 60, 1, 61, 61, 61, 
+	61, 61, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 61, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	62, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 63, 1, 61, 61, 61, 61, 61, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 61, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 62, 1, 
+	1, 1, 60, 60, 60, 60, 60, 60, 
+	60, 60, 60, 60, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 63, 
+	1, 64, 1, 64, 64, 64, 64, 64, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 64, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 65, 1, 65, 65, 
+	65, 65, 65, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 65, 1, 66, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 67, 68, 68, 
+	68, 68, 68, 68, 68, 68, 68, 1, 
+	69, 69, 69, 69, 69, 69, 69, 69, 
+	69, 69, 69, 69, 69, 69, 69, 69, 
+	69, 69, 69, 69, 69, 69, 69, 69, 
+	69, 69, 1, 1, 1, 1, 1, 1, 
+	69, 69, 69, 69, 69, 69, 69, 69, 
+	69, 69, 69, 69, 69, 69, 69, 69, 
+	69, 69, 69, 69, 69, 69, 69, 69, 
+	69, 69, 1, 70, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 71, 71, 
+	1, 71, 71, 71, 71, 71, 71, 71, 
+	71, 71, 71, 1, 1, 1, 1, 1, 
+	1, 1, 71, 71, 71, 71, 71, 71, 
+	71, 71, 71, 71, 71, 71, 71, 71, 
+	71, 71, 71, 71, 71, 71, 71, 71, 
+	71, 71, 71, 71, 1, 1, 1, 1, 
+	71, 1, 71, 71, 71, 71, 71, 71, 
+	71, 71, 71, 71, 71, 71, 71, 71, 
+	71, 71, 71, 71, 71, 71, 71, 71, 
+	71, 71, 71, 71, 1, 72, 72, 72, 
+	72, 72, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 72, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	73, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 74, 1, 72, 72, 72, 72, 72, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 72, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 73, 1, 
+	1, 1, 75, 75, 75, 75, 75, 75, 
+	75, 75, 75, 75, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 74, 
+	1, 76, 76, 76, 76, 76, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	76, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 77, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 78, 1, 0, 
+	0, 0, 0, 0, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 0, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 2, 1, 1, 0
+};
+
+static const char _deserialize_json_trans_targs[] = {
+	1, 0, 2, 2, 3, 4, 18, 24, 
+	37, 5, 12, 6, 7, 8, 9, 11, 
+	9, 11, 10, 2, 44, 10, 44, 13, 
+	14, 15, 16, 17, 16, 17, 10, 2, 
+	44, 19, 20, 21, 22, 23, 10, 2, 
+	44, 23, 25, 31, 26, 27, 28, 29, 
+	30, 29, 30, 10, 2, 44, 32, 33, 
+	34, 35, 36, 35, 36, 10, 2, 44, 
+	38, 39, 40, 42, 43, 41, 10, 41, 
+	10, 2, 44, 43, 44, 45, 46
+};
+
+static const char _deserialize_json_trans_actions[] = {
+	0, 0, 1, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 2, 2, 2, 
+	0, 0, 3, 3, 4, 0, 5, 0, 
+	0, 2, 2, 2, 0, 0, 6, 6, 
+	7, 0, 0, 0, 2, 2, 8, 8, 
+	9, 0, 0, 0, 0, 0, 2, 2, 
+	2, 0, 0, 10, 10, 11, 0, 0, 
+	2, 2, 2, 0, 0, 12, 12, 13, 
+	0, 0, 0, 2, 2, 2, 14, 0, 
+	15, 15, 16, 0, 0, 0, 0
+};
+
+static const int deserialize_json_start = 1;
+static const int deserialize_json_first_final = 44;
+static const int deserialize_json_error = 0;
+
+static const int deserialize_json_en_main = 1;
+
+
+#line 97 "../../src/hb-buffer-deserialize-json.rl"
+
+
+static hb_bool_t
+_hb_buffer_deserialize_glyphs_json (hb_buffer_t *buffer,
+				    const char *buf,
+				    unsigned int buf_len,
+				    const char **end_ptr,
+				    hb_font_t *font)
+{
+  const char *p = buf, *pe = buf + buf_len;
+
+  /* Ensure we have positions. */
+  (void) hb_buffer_get_glyph_positions (buffer, NULL);
+
+  while (p < pe && ISSPACE (*p))
+    p++;
+  if (p < pe && *p == (buffer->len ? ',' : '['))
+  {
+    *end_ptr = ++p;
+  }
+
+  const char *tok = NULL;
+  int cs;
+  hb_glyph_info_t info;
+  hb_glyph_position_t pos;
+  
+#line 466 "hb-buffer-deserialize-json.hh.tmp"
+	{
+	cs = deserialize_json_start;
+	}
+
+#line 471 "hb-buffer-deserialize-json.hh.tmp"
+	{
+	int _slen;
+	int _trans;
+	const unsigned char *_keys;
+	const char *_inds;
+	if ( p == pe )
+		goto _test_eof;
+	if ( cs == 0 )
+		goto _out;
+_resume:
+	_keys = _deserialize_json_trans_keys + (cs<<1);
+	_inds = _deserialize_json_indicies + _deserialize_json_index_offsets[cs];
+
+	_slen = _deserialize_json_key_spans[cs];
+	_trans = _inds[ _slen > 0 && _keys[0] <=(*p) &&
+		(*p) <= _keys[1] ?
+		(*p) - _keys[0] : _slen ];
+
+	cs = _deserialize_json_trans_targs[_trans];
+
+	if ( _deserialize_json_trans_actions[_trans] == 0 )
+		goto _again;
+
+	switch ( _deserialize_json_trans_actions[_trans] ) {
+	case 1:
+#line 38 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	memset (&info, 0, sizeof (info));
+	memset (&pos , 0, sizeof (pos ));
+}
+	break;
+	case 5:
+#line 43 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 2:
+#line 51 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	tok = p;
+}
+	break;
+	case 14:
+#line 55 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	if (!hb_font_glyph_from_string (font,
+					tok, p - tok,
+					&info.codepoint))
+	  return false;
+}
+	break;
+	case 15:
+#line 62 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_uint (tok, p, &info.codepoint)) return false; }
+	break;
+	case 8:
+#line 63 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_uint (tok, p, &info.cluster )) return false; }
+	break;
+	case 10:
+#line 64 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_int  (tok, p, &pos.x_offset )) return false; }
+	break;
+	case 12:
+#line 65 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_int  (tok, p, &pos.y_offset )) return false; }
+	break;
+	case 3:
+#line 66 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_int  (tok, p, &pos.x_advance)) return false; }
+	break;
+	case 6:
+#line 67 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_int  (tok, p, &pos.y_advance)) return false; }
+	break;
+	case 16:
+#line 62 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_uint (tok, p, &info.codepoint)) return false; }
+#line 43 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 9:
+#line 63 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_uint (tok, p, &info.cluster )) return false; }
+#line 43 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 11:
+#line 64 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_int  (tok, p, &pos.x_offset )) return false; }
+#line 43 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 13:
+#line 65 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_int  (tok, p, &pos.y_offset )) return false; }
+#line 43 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 4:
+#line 66 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_int  (tok, p, &pos.x_advance)) return false; }
+#line 43 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 7:
+#line 67 "../../src/hb-buffer-deserialize-json.rl"
+	{ if (!parse_int  (tok, p, &pos.y_advance)) return false; }
+#line 43 "../../src/hb-buffer-deserialize-json.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+#line 624 "hb-buffer-deserialize-json.hh.tmp"
+	}
+
+_again:
+	if ( cs == 0 )
+		goto _out;
+	if ( ++p != pe )
+		goto _resume;
+	_test_eof: {}
+	_out: {}
+	}
+
+#line 125 "../../src/hb-buffer-deserialize-json.rl"
+
+
+  *end_ptr = p;
+
+  return p == pe && *(p-1) != ']';
+}
+
+#endif /* HB_BUFFER_DESERIALIZE_JSON_HH */
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-buffer-deserialize-json.rl
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2013  Google, Inc.
+ *
+ *  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_BUFFER_DESERIALIZE_JSON_HH
+#define HB_BUFFER_DESERIALIZE_JSON_HH
+
+#include "hb-private.hh"
+
+%%{
+
+machine deserialize_json;
+alphtype unsigned char;
+write data;
+
+action clear_item {
+	memset (&info, 0, sizeof (info));
+	memset (&pos , 0, sizeof (pos ));
+}
+
+action add_item {
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+
+action tok {
+	tok = p;
+}
+
+action parse_glyph {
+	if (!hb_font_glyph_from_string (font,
+					tok, p - tok,
+					&info.codepoint))
+	  return false;
+}
+
+action parse_gid       { if (!parse_uint (tok, p, &info.codepoint)) return false; }
+action parse_cluster   { if (!parse_uint (tok, p, &info.cluster )) return false; }
+action parse_x_offset  { if (!parse_int  (tok, p, &pos.x_offset )) return false; }
+action parse_y_offset  { if (!parse_int  (tok, p, &pos.y_offset )) return false; }
+action parse_x_advance { if (!parse_int  (tok, p, &pos.x_advance)) return false; }
+action parse_y_advance { if (!parse_int  (tok, p, &pos.y_advance)) return false; }
+
+unum	= '0' | [1-9] digit*;
+num	= '-'? unum;
+
+comma = space* ',' space*;
+colon = space* ':' space*;
+
+glyph_id = unum;
+glyph_name = alpha (alnum|'_'|'.'|'-')*;
+
+glyph_string   = '"' (glyph_name >tok %parse_glyph) '"';
+glyph_number = (glyph_id >tok %parse_gid);
+
+glyph	= "\"g\""  colon (glyph_string | glyph_number);
+cluster	= "\"cl\"" colon (unum >tok %parse_cluster);
+xoffset	= "\"dx\"" colon (num >tok %parse_x_offset);
+yoffset	= "\"dy\"" colon (num >tok %parse_y_offset);
+xadvance= "\"ax\"" colon (num >tok %parse_x_advance);
+yadvance= "\"ay\"" colon (num >tok %parse_y_advance);
+
+element = glyph | cluster | xoffset | yoffset | xadvance | yadvance;
+item	=
+	( '{' space* element (comma element)* space* '}')
+	>clear_item
+	@add_item
+	;
+
+main := space* item (comma item)* space* (','|']')?;
+
+}%%
+
+static hb_bool_t
+_hb_buffer_deserialize_glyphs_json (hb_buffer_t *buffer,
+				    const char *buf,
+				    unsigned int buf_len,
+				    const char **end_ptr,
+				    hb_font_t *font)
+{
+  const char *p = buf, *pe = buf + buf_len;
+
+  /* Ensure we have positions. */
+  (void) hb_buffer_get_glyph_positions (buffer, NULL);
+
+  while (p < pe && ISSPACE (*p))
+    p++;
+  if (p < pe && *p == (buffer->len ? ',' : '['))
+  {
+    *end_ptr = ++p;
+  }
+
+  const char *tok = NULL;
+  int cs;
+  hb_glyph_info_t info;
+  hb_glyph_position_t pos;
+  %%{
+    write init;
+    write exec;
+  }%%
+
+  *end_ptr = p;
+
+  return p == pe && *(p-1) != ']';
+}
+
+#endif /* HB_BUFFER_DESERIALIZE_JSON_HH */
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-buffer-deserialize-text.hh
@@ -0,0 +1,571 @@
+
+#line 1 "../../src/hb-buffer-deserialize-text.rl"
+/*
+ * Copyright © 2013  Google, Inc.
+ *
+ *  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_BUFFER_DESERIALIZE_TEXT_HH
+#define HB_BUFFER_DESERIALIZE_TEXT_HH
+
+#include "hb-private.hh"
+
+
+#line 36 "hb-buffer-deserialize-text.hh.tmp"
+static const unsigned char _deserialize_text_trans_keys[] = {
+	0u, 0u, 9u, 122u, 45u, 57u, 48u, 57u, 45u, 57u, 48u, 57u, 48u, 57u, 45u, 57u, 
+	48u, 57u, 44u, 44u, 45u, 57u, 48u, 57u, 44u, 57u, 9u, 124u, 9u, 124u, 0u, 0u, 
+	9u, 122u, 9u, 124u, 9u, 124u, 9u, 124u, 9u, 124u, 9u, 124u, 9u, 124u, 9u, 124u, 
+	9u, 124u, 9u, 124u, 9u, 124u, 0
+};
+
+static const char _deserialize_text_key_spans[] = {
+	0, 114, 13, 10, 13, 10, 10, 13, 
+	10, 1, 13, 10, 14, 116, 116, 0, 
+	114, 116, 116, 116, 116, 116, 116, 116, 
+	116, 116, 116
+};
+
+static const short _deserialize_text_index_offsets[] = {
+	0, 0, 115, 129, 140, 154, 165, 176, 
+	190, 201, 203, 217, 228, 243, 360, 477, 
+	478, 593, 710, 827, 944, 1061, 1178, 1295, 
+	1412, 1529, 1646
+};
+
+static const char _deserialize_text_indicies[] = {
+	0, 0, 0, 0, 0, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	0, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	2, 3, 3, 3, 3, 3, 3, 3, 
+	3, 3, 1, 1, 1, 1, 1, 1, 
+	1, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 1, 1, 1, 1, 1, 
+	1, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 1, 5, 1, 1, 6, 
+	7, 7, 7, 7, 7, 7, 7, 7, 
+	7, 1, 8, 9, 9, 9, 9, 9, 
+	9, 9, 9, 9, 1, 10, 1, 1, 
+	11, 12, 12, 12, 12, 12, 12, 12, 
+	12, 12, 1, 13, 14, 14, 14, 14, 
+	14, 14, 14, 14, 14, 1, 15, 16, 
+	16, 16, 16, 16, 16, 16, 16, 16, 
+	1, 17, 1, 1, 18, 19, 19, 19, 
+	19, 19, 19, 19, 19, 19, 1, 20, 
+	21, 21, 21, 21, 21, 21, 21, 21, 
+	21, 1, 22, 1, 23, 1, 1, 24, 
+	25, 25, 25, 25, 25, 25, 25, 25, 
+	25, 1, 26, 27, 27, 27, 27, 27, 
+	27, 27, 27, 27, 1, 22, 1, 1, 
+	1, 21, 21, 21, 21, 21, 21, 21, 
+	21, 21, 21, 1, 28, 28, 28, 28, 
+	28, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 28, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 29, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	30, 1, 1, 31, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	32, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 33, 
+	1, 34, 34, 34, 34, 34, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	34, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 35, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 36, 1, 1, 0, 
+	0, 0, 0, 0, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 0, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 2, 3, 
+	3, 3, 3, 3, 3, 3, 3, 3, 
+	1, 1, 1, 1, 1, 1, 1, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 1, 1, 1, 1, 1, 1, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 4, 4, 4, 4, 4, 4, 4, 
+	4, 1, 28, 28, 28, 28, 28, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 28, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 29, 1, 1, 1, 
+	1, 37, 37, 37, 37, 37, 37, 37, 
+	37, 37, 37, 1, 1, 1, 30, 1, 
+	1, 31, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 32, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 33, 1, 38, 
+	38, 38, 38, 38, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 38, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 39, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 40, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 41, 1, 42, 42, 42, 42, 
+	42, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 42, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	43, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 44, 
+	1, 42, 42, 42, 42, 42, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	42, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	14, 14, 14, 14, 14, 14, 14, 14, 
+	14, 14, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 43, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 44, 1, 38, 38, 
+	38, 38, 38, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 38, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 39, 1, 1, 1, 9, 9, 9, 
+	9, 9, 9, 9, 9, 9, 9, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 40, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 41, 1, 45, 45, 45, 45, 45, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 45, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 46, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 47, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 48, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 49, 1, 
+	50, 50, 50, 50, 50, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 50, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 51, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 52, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 53, 1, 50, 50, 50, 
+	50, 50, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 50, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 51, 
+	1, 1, 1, 1, 27, 27, 27, 27, 
+	27, 27, 27, 27, 27, 27, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 52, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	53, 1, 45, 45, 45, 45, 45, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 45, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 46, 1, 1, 1, 
+	1, 54, 54, 54, 54, 54, 54, 54, 
+	54, 54, 54, 1, 1, 1, 1, 1, 
+	1, 47, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 48, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 49, 1, 28, 
+	28, 28, 28, 28, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 28, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 29, 1, 55, 55, 1, 55, 55, 
+	55, 55, 55, 55, 55, 55, 55, 55, 
+	1, 1, 1, 30, 1, 1, 31, 55, 
+	55, 55, 55, 55, 55, 55, 55, 55, 
+	55, 55, 55, 55, 55, 55, 55, 55, 
+	55, 55, 55, 55, 55, 55, 55, 55, 
+	55, 1, 1, 32, 1, 55, 1, 55, 
+	55, 55, 55, 55, 55, 55, 55, 55, 
+	55, 55, 55, 55, 55, 55, 55, 55, 
+	55, 55, 55, 55, 55, 55, 55, 55, 
+	55, 1, 33, 1, 0
+};
+
+static const char _deserialize_text_trans_targs[] = {
+	1, 0, 13, 17, 26, 3, 18, 21, 
+	18, 21, 5, 19, 20, 19, 20, 22, 
+	25, 8, 9, 12, 9, 12, 10, 11, 
+	23, 24, 23, 24, 14, 2, 6, 7, 
+	15, 16, 14, 15, 16, 17, 14, 4, 
+	15, 16, 14, 15, 16, 14, 2, 7, 
+	15, 16, 14, 2, 15, 16, 25, 26
+};
+
+static const char _deserialize_text_trans_actions[] = {
+	0, 0, 1, 1, 1, 2, 2, 2, 
+	0, 0, 2, 2, 2, 0, 0, 2, 
+	2, 2, 2, 2, 0, 0, 3, 2, 
+	2, 2, 0, 0, 4, 5, 5, 5, 
+	4, 4, 0, 0, 0, 0, 6, 7, 
+	6, 6, 8, 8, 8, 9, 10, 10, 
+	9, 9, 11, 12, 11, 11, 0, 0
+};
+
+static const char _deserialize_text_eof_actions[] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 4, 0, 0, 
+	0, 4, 6, 8, 8, 6, 9, 11, 
+	11, 9, 4
+};
+
+static const int deserialize_text_start = 1;
+static const int deserialize_text_first_final = 13;
+static const int deserialize_text_error = 0;
+
+static const int deserialize_text_en_main = 1;
+
+
+#line 91 "../../src/hb-buffer-deserialize-text.rl"
+
+
+static hb_bool_t
+_hb_buffer_deserialize_glyphs_text (hb_buffer_t *buffer,
+				    const char *buf,
+				    unsigned int buf_len,
+				    const char **end_ptr,
+				    hb_font_t *font)
+{
+  const char *p = buf, *pe = buf + buf_len;
+
+  /* Ensure we have positions. */
+  (void) hb_buffer_get_glyph_positions (buffer, NULL);
+
+  while (p < pe && ISSPACE (*p))
+    p++;
+  if (p < pe && *p == (buffer->len ? '|' : '['))
+  {
+    *end_ptr = ++p;
+  }
+
+  const char *eof = pe, *tok = NULL;
+  int cs;
+  hb_glyph_info_t info;
+  hb_glyph_position_t pos;
+  
+#line 343 "hb-buffer-deserialize-text.hh.tmp"
+	{
+	cs = deserialize_text_start;
+	}
+
+#line 348 "hb-buffer-deserialize-text.hh.tmp"
+	{
+	int _slen;
+	int _trans;
+	const unsigned char *_keys;
+	const char *_inds;
+	if ( p == pe )
+		goto _test_eof;
+	if ( cs == 0 )
+		goto _out;
+_resume:
+	_keys = _deserialize_text_trans_keys + (cs<<1);
+	_inds = _deserialize_text_indicies + _deserialize_text_index_offsets[cs];
+
+	_slen = _deserialize_text_key_spans[cs];
+	_trans = _inds[ _slen > 0 && _keys[0] <=(*p) &&
+		(*p) <= _keys[1] ?
+		(*p) - _keys[0] : _slen ];
+
+	cs = _deserialize_text_trans_targs[_trans];
+
+	if ( _deserialize_text_trans_actions[_trans] == 0 )
+		goto _again;
+
+	switch ( _deserialize_text_trans_actions[_trans] ) {
+	case 2:
+#line 51 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	tok = p;
+}
+	break;
+	case 5:
+#line 55 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	if (!hb_font_glyph_from_string (font,
+					tok, p - tok,
+					&info.codepoint))
+	  return false;
+}
+	break;
+	case 10:
+#line 62 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_uint (tok, p, &info.cluster )) return false; }
+	break;
+	case 3:
+#line 63 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.x_offset )) return false; }
+	break;
+	case 12:
+#line 64 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.y_offset )) return false; }
+	break;
+	case 7:
+#line 65 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.x_advance)) return false; }
+	break;
+	case 1:
+#line 38 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	memset (&info, 0, sizeof (info));
+	memset (&pos , 0, sizeof (pos ));
+}
+#line 51 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	tok = p;
+}
+	break;
+	case 4:
+#line 55 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	if (!hb_font_glyph_from_string (font,
+					tok, p - tok,
+					&info.codepoint))
+	  return false;
+}
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 9:
+#line 62 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_uint (tok, p, &info.cluster )) return false; }
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 11:
+#line 64 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.y_offset )) return false; }
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 6:
+#line 65 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.x_advance)) return false; }
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 8:
+#line 66 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.y_advance)) return false; }
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+#line 480 "hb-buffer-deserialize-text.hh.tmp"
+	}
+
+_again:
+	if ( cs == 0 )
+		goto _out;
+	if ( ++p != pe )
+		goto _resume;
+	_test_eof: {}
+	if ( p == eof )
+	{
+	switch ( _deserialize_text_eof_actions[cs] ) {
+	case 4:
+#line 55 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	if (!hb_font_glyph_from_string (font,
+					tok, p - tok,
+					&info.codepoint))
+	  return false;
+}
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 9:
+#line 62 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_uint (tok, p, &info.cluster )) return false; }
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 11:
+#line 64 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.y_offset )) return false; }
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 6:
+#line 65 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.x_advance)) return false; }
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+	case 8:
+#line 66 "../../src/hb-buffer-deserialize-text.rl"
+	{ if (!parse_int  (tok, p, &pos.y_advance)) return false; }
+#line 43 "../../src/hb-buffer-deserialize-text.rl"
+	{
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+	break;
+#line 557 "hb-buffer-deserialize-text.hh.tmp"
+	}
+	}
+
+	_out: {}
+	}
+
+#line 119 "../../src/hb-buffer-deserialize-text.rl"
+
+
+  *end_ptr = p;
+
+  return p == pe && *(p-1) != ']';
+}
+
+#endif /* HB_BUFFER_DESERIALIZE_TEXT_HH */
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-buffer-deserialize-text.rl
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2013  Google, Inc.
+ *
+ *  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_BUFFER_DESERIALIZE_TEXT_HH
+#define HB_BUFFER_DESERIALIZE_TEXT_HH
+
+#include "hb-private.hh"
+
+%%{
+
+machine deserialize_text;
+alphtype unsigned char;
+write data;
+
+action clear_item {
+	memset (&info, 0, sizeof (info));
+	memset (&pos , 0, sizeof (pos ));
+}
+
+action add_item {
+	buffer->add_info (info);
+	if (buffer->in_error)
+	  return false;
+	buffer->pos[buffer->len - 1] = pos;
+	*end_ptr = p;
+}
+
+action tok {
+	tok = p;
+}
+
+action parse_glyph {
+	if (!hb_font_glyph_from_string (font,
+					tok, p - tok,
+					&info.codepoint))
+	  return false;
+}
+
+action parse_cluster   { if (!parse_uint (tok, p, &info.cluster )) return false; }
+action parse_x_offset  { if (!parse_int  (tok, p, &pos.x_offset )) return false; }
+action parse_y_offset  { if (!parse_int  (tok, p, &pos.y_offset )) return false; }
+action parse_x_advance { if (!parse_int  (tok, p, &pos.x_advance)) return false; }
+action parse_y_advance { if (!parse_int  (tok, p, &pos.y_advance)) return false; }
+
+unum	= '0' | [1-9] digit*;
+num	= '-'? unum;
+
+glyph_id = unum;
+glyph_name = alpha (alnum|'_'|'.'|'-')*;
+
+glyph	= (glyph_id | glyph_name) >tok %parse_glyph;
+cluster	= '=' (unum >tok %parse_cluster);
+offsets	= '@' (num >tok %parse_x_offset)   ',' (num >tok %parse_y_offset );
+advances= '+' (num >tok %parse_x_advance) (',' (num >tok %parse_y_advance))?;
+item	=
+	(
+		glyph
+		cluster?
+		offsets?
+		advances?
+	)
+	>clear_item
+	%add_item
+	;
+
+main := space* item (space* '|' space* item)* space* ('|'|']')?;
+
+}%%
+
+static hb_bool_t
+_hb_buffer_deserialize_glyphs_text (hb_buffer_t *buffer,
+				    const char *buf,
+				    unsigned int buf_len,
+				    const char **end_ptr,
+				    hb_font_t *font)
+{
+  const char *p = buf, *pe = buf + buf_len;
+
+  /* Ensure we have positions. */
+  (void) hb_buffer_get_glyph_positions (buffer, NULL);
+
+  while (p < pe && ISSPACE (*p))
+    p++;
+  if (p < pe && *p == (buffer->len ? '|' : '['))
+  {
+    *end_ptr = ++p;
+  }
+
+  const char *eof = pe, *tok = NULL;
+  int cs;
+  hb_glyph_info_t info;
+  hb_glyph_position_t pos;
+  %%{
+    write init;
+    write exec;
+  }%%
+
+  *end_ptr = p;
+
+  return p == pe && *(p-1) != ']';
+}
+
+#endif /* HB_BUFFER_DESERIALIZE_TEXT_HH */
--- a/gfx/harfbuzz/src/hb-buffer-private.hh
+++ b/gfx/harfbuzz/src/hb-buffer-private.hh
@@ -105,16 +105,17 @@ struct hb_buffer_t {
 
   HB_INTERNAL void allocate_var (unsigned int byte_i, unsigned int count, const char *owner);
   HB_INTERNAL void deallocate_var (unsigned int byte_i, unsigned int count, const char *owner);
   HB_INTERNAL void assert_var (unsigned int byte_i, unsigned int count, const char *owner);
   HB_INTERNAL void deallocate_var_all (void);
 
   HB_INTERNAL void add (hb_codepoint_t  codepoint,
 			unsigned int    cluster);
+  HB_INTERNAL void add_info (const hb_glyph_info_t &glyph_info);
 
   HB_INTERNAL void reverse_range (unsigned int start, unsigned int end);
   HB_INTERNAL void reverse (void);
   HB_INTERNAL void reverse_clusters (void);
   HB_INTERNAL void guess_segment_properties (void);
 
   HB_INTERNAL void swap_buffers (void);
   HB_INTERNAL void remove_output (void);
@@ -123,17 +124,17 @@ struct hb_buffer_t {
 
   HB_INTERNAL void replace_glyphs (unsigned int num_in,
 				   unsigned int num_out,
 				   const hb_codepoint_t *glyph_data);
 
   HB_INTERNAL void replace_glyph (hb_codepoint_t glyph_index);
   /* Makes a copy of the glyph at idx to output and replace glyph_index */
   HB_INTERNAL void output_glyph (hb_codepoint_t glyph_index);
-  HB_INTERNAL void output_info (hb_glyph_info_t &glyph_info);
+  HB_INTERNAL void output_info (const hb_glyph_info_t &glyph_info);
   /* Copies glyph at idx to output but doesn't advance idx */
   HB_INTERNAL void copy_glyph (void);
   /* Copies glyph at idx to output and advance idx.
    * If there's no output, just advance idx. */
   inline void
   next_glyph (void)
   {
     if (have_output)
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-buffer-serialize.cc
@@ -0,0 +1,336 @@
+/*
+ * Copyright © 2012,2013  Google, Inc.
+ *
+ *  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
+ */
+
+#include "hb-buffer-private.hh"
+
+
+static const char *serialize_formats[] = {
+  "text",
+  "json",
+  NULL
+};
+
+const char **
+hb_buffer_serialize_list_formats (void)
+{
+  return serialize_formats;
+}
+
+hb_buffer_serialize_format_t
+hb_buffer_serialize_format_from_string (const char *str, int len)
+{
+  /* Upper-case it. */
+  return (hb_buffer_serialize_format_t) (hb_tag_from_string (str, len) & ~0x20202020);
+}
+
+const char *
+hb_buffer_serialize_format_to_string (hb_buffer_serialize_format_t format)
+{
+  switch (format)
+  {
+    case HB_BUFFER_SERIALIZE_FORMAT_TEXT:	return serialize_formats[0];
+    case HB_BUFFER_SERIALIZE_FORMAT_JSON:	return serialize_formats[1];
+    default:
+    case HB_BUFFER_SERIALIZE_FORMAT_INVALID:	return NULL;
+  }
+}
+
+static unsigned int
+_hb_buffer_serialize_glyphs_json (hb_buffer_t *buffer,
+				  unsigned int start,
+				  unsigned int end,
+				  char *buf,
+				  unsigned int buf_size,
+				  unsigned int *buf_consumed,
+				  hb_font_t *font,
+				  hb_buffer_serialize_flags_t flags)
+{
+  hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
+  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
+
+  *buf_consumed = 0;
+  for (unsigned int i = start; i < end; i++)
+  {
+    char b[1024];
+    char *p = b;
+
+    /* In the following code, we know b is large enough that no overflow can happen. */
+
+#define APPEND(s) HB_STMT_START { strcpy (p, s); p += strlen (s); } HB_STMT_END
+
+    if (i)
+      *p++ = ',';
+
+    *p++ = '{';
+
+    APPEND ("\"g\":");
+    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES))
+    {
+      char g[128];
+      hb_font_glyph_to_string (font, info[i].codepoint, g, sizeof (g));
+      *p++ = '"';
+      for (char *q = g; *q; q++) {
+        if (*q == '"')
+	  *p++ = '\\';
+	*p++ = *q;
+      }
+      *p++ = '"';
+    }
+    else
+      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "%u", info[i].codepoint);
+
+    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS)) {
+      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"cl\":%u", info[i].cluster);
+    }
+
+    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS))
+    {
+      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"dx\":%d,\"dy\":%d",
+		     pos[i].x_offset, pos[i].y_offset);
+      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"ax\":%d,\"ay\":%d",
+		     pos[i].x_advance, pos[i].y_advance);
+    }
+
+    *p++ = '}';
+
+    if (buf_size > (p - b))
+    {
+      unsigned int l = p - b;
+      memcpy (buf, b, l);
+      buf += l;
+      buf_size -= l;
+      *buf_consumed += l;
+      *buf = '\0';
+    } else
+      return i - start;
+  }
+
+  return end - start;
+}
+
+static unsigned int
+_hb_buffer_serialize_glyphs_text (hb_buffer_t *buffer,
+				  unsigned int start,
+				  unsigned int end,
+				  char *buf,
+				  unsigned int buf_size,
+				  unsigned int *buf_consumed,
+				  hb_font_t *font,
+				  hb_buffer_serialize_flags_t flags)
+{
+  hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
+  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
+
+  *buf_consumed = 0;
+  for (unsigned int i = start; i < end; i++)
+  {
+    char b[1024];
+    char *p = b;
+
+    /* In the following code, we know b is large enough that no overflow can happen. */
+
+    if (i)
+      *p++ = '|';
+
+    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES))
+    {
+      hb_font_glyph_to_string (font, info[i].codepoint, p, 128);
+      p += strlen (p);
+    }
+    else
+      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "%u", info[i].codepoint);
+
+    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS)) {
+      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "=%u", info[i].cluster);
+    }
+
+    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS))
+    {
+      if (pos[i].x_offset || pos[i].y_offset)
+	p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "@%d,%d", pos[i].x_offset, pos[i].y_offset);
+
+      *p++ = '+';
+      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "%d", pos[i].x_advance);
+      if (pos->y_advance)
+	p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",%d", pos[i].y_advance);
+    }
+
+    if (buf_size > (p - b))
+    {
+      unsigned int l = p - b;
+      memcpy (buf, b, l);
+      buf += l;
+      buf_size -= l;
+      *buf_consumed += l;
+      *buf = '\0';
+    } else
+      return i - start;
+  }
+
+  return end - start;
+}
+
+/* Returns number of items, starting at start, that were serialized. */
+unsigned int
+hb_buffer_serialize_glyphs (hb_buffer_t *buffer,
+			    unsigned int start,
+			    unsigned int end,
+			    char *buf,
+			    unsigned int buf_size,
+			    unsigned int *buf_consumed, /* May be NULL */
+			    hb_font_t *font, /* May be NULL */
+			    hb_buffer_serialize_format_t format,
+			    hb_buffer_serialize_flags_t flags)
+{
+  assert (start <= end && end <= buffer->len);
+
+  unsigned int sconsumed;
+  if (!buf_consumed)
+    buf_consumed = &sconsumed;
+  *buf_consumed = 0;
+
+  assert ((!buffer->len && buffer->content_type == HB_BUFFER_CONTENT_TYPE_INVALID) ||
+	  buffer->content_type == HB_BUFFER_CONTENT_TYPE_GLYPHS);
+
+  if (unlikely (start == end))
+    return 0;
+
+  if (!font)
+    font = hb_font_get_empty ();
+
+  switch (format)
+  {
+    case HB_BUFFER_SERIALIZE_FORMAT_TEXT:
+      return _hb_buffer_serialize_glyphs_text (buffer, start, end,
+					       buf, buf_size, buf_consumed,
+					       font, flags);
+
+    case HB_BUFFER_SERIALIZE_FORMAT_JSON:
+      return _hb_buffer_serialize_glyphs_json (buffer, start, end,
+					       buf, buf_size, buf_consumed,
+					       font, flags);
+
+    default:
+    case HB_BUFFER_SERIALIZE_FORMAT_INVALID:
+      return 0;
+
+  }
+}
+
+
+static hb_bool_t
+parse_uint (const char *pp, const char *end, uint32_t *pv)
+{
+  char buf[32];
+  unsigned int len = MIN (ARRAY_LENGTH (buf) - 1, (unsigned int) (end - pp));
+  strncpy (buf, pp, len);
+  buf[len] = '\0';
+
+  char *p = buf;
+  char *pend = p;
+  uint32_t v;
+
+  errno = 0;
+  v = strtol (p, &pend, 10);
+  if (errno || p == pend || pend - p != end - pp)
+    return false;
+
+  *pv = v;
+  return true;
+}
+
+static hb_bool_t
+parse_int (const char *pp, const char *end, int32_t *pv)
+{
+  char buf[32];
+  unsigned int len = MIN (ARRAY_LENGTH (buf) - 1, (unsigned int) (end - pp));
+  strncpy (buf, pp, len);
+  buf[len] = '\0';
+
+  char *p = buf;
+  char *pend = p;
+  int32_t v;
+
+  errno = 0;
+  v = strtol (p, &pend, 10);
+  if (errno || p == pend || pend - p != end - pp)
+    return false;
+
+  *pv = v;
+  return true;
+}
+
+#include "hb-buffer-deserialize-json.hh"
+#include "hb-buffer-deserialize-text.hh"
+
+hb_bool_t
+hb_buffer_deserialize_glyphs (hb_buffer_t *buffer,
+			      const char *buf,
+			      int buf_len, /* -1 means nul-terminated */
+			      const char **end_ptr, /* May be NULL */
+			      hb_font_t *font, /* May be NULL */
+			      hb_buffer_serialize_format_t format)
+{
+  const char *end;
+  if (!end_ptr)
+    end_ptr = &end;
+  *end_ptr = buf;
+
+  assert ((!buffer->len && buffer->content_type == HB_BUFFER_CONTENT_TYPE_INVALID) ||
+	  buffer->content_type == HB_BUFFER_CONTENT_TYPE_GLYPHS);
+
+  if (buf_len == -1)
+    buf_len = strlen (buf);
+
+  if (!buf_len)
+  {
+    *end_ptr = buf;
+    return false;
+  }
+
+  hb_buffer_set_content_type (buffer, HB_BUFFER_CONTENT_TYPE_GLYPHS);
+
+  if (!font)
+    font = hb_font_get_empty ();
+
+  switch (format)
+  {
+    case HB_BUFFER_SERIALIZE_FORMAT_TEXT:
+      return _hb_buffer_deserialize_glyphs_text (buffer,
+						 buf, buf_len, end_ptr,
+						 font);
+
+    case HB_BUFFER_SERIALIZE_FORMAT_JSON:
+      return _hb_buffer_deserialize_glyphs_json (buffer,
+						 buf, buf_len, end_ptr,
+						 font);
+
+    default:
+    case HB_BUFFER_SERIALIZE_FORMAT_INVALID:
+      return false;
+
+  }
+}
--- a/gfx/harfbuzz/src/hb-buffer.cc
+++ b/gfx/harfbuzz/src/hb-buffer.cc
@@ -210,16 +210,27 @@ hb_buffer_t::add (hb_codepoint_t  codepo
   glyph->codepoint = codepoint;
   glyph->mask = 1;
   glyph->cluster = cluster;
 
   len++;
 }
 
 void
+hb_buffer_t::add_info (const hb_glyph_info_t &glyph_info)
+{
+  if (unlikely (!ensure (len + 1))) return;
+
+  info[len] = glyph_info;
+
+  len++;
+}
+
+
+void
 hb_buffer_t::remove_output (void)
 {
   if (unlikely (hb_object_is_inert (this)))
     return;
 
   have_output = false;
   have_positions = false;
 
@@ -310,17 +321,17 @@ hb_buffer_t::output_glyph (hb_codepoint_
 
   out_info[out_len] = info[idx];
   out_info[out_len].codepoint = glyph_index;
 
   out_len++;
 }
 
 void
-hb_buffer_t::output_info (hb_glyph_info_t &glyph_info)
+hb_buffer_t::output_info (const hb_glyph_info_t &glyph_info)
 {
   if (unlikely (!make_room_for (0, 1))) return;
 
   out_info[out_len] = glyph_info;
 
   out_len++;
 }
 
@@ -1059,236 +1070,8 @@ hb_buffer_normalize_glyphs (hb_buffer_t 
   unsigned int end;
   for (end = start + 1; end < count; end++)
     if (info[start].cluster != info[end].cluster) {
       normalize_glyphs_cluster (buffer, start, end, backward);
       start = end;
     }
   normalize_glyphs_cluster (buffer, start, end, backward);
 }
-
-
-/*
- * Serialize
- */
-
-static const char *serialize_formats[] = {
-  "text",
-  "json",
-  NULL
-};
-
-const char **
-hb_buffer_serialize_list_formats (void)
-{
-  return serialize_formats;
-}
-
-hb_buffer_serialize_format_t
-hb_buffer_serialize_format_from_string (const char *str, int len)
-{
-  /* Upper-case it. */
-  return (hb_buffer_serialize_format_t) (hb_tag_from_string (str, len) & ~0x20202020);
-}
-
-const char *
-hb_buffer_serialize_format_to_string (hb_buffer_serialize_format_t format)
-{
-  switch (format)
-  {
-    case HB_BUFFER_SERIALIZE_FORMAT_TEXT:	return serialize_formats[0];
-    case HB_BUFFER_SERIALIZE_FORMAT_JSON:	return serialize_formats[1];
-    default:
-    case HB_BUFFER_SERIALIZE_FORMAT_INVALID:	return NULL;
-  }
-}
-
-static unsigned int
-_hb_buffer_serialize_glyphs_json (hb_buffer_t *buffer,
-				  unsigned int start,
-				  unsigned int end,
-				  char *buf,
-				  unsigned int buf_size,
-				  unsigned int *buf_consumed,
-				  hb_font_t *font,
-				  hb_buffer_serialize_flags_t flags)
-{
-  hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
-  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
-
-  *buf_consumed = 0;
-  for (unsigned int i = start; i < end; i++)
-  {
-    char b[1024];
-    char *p = b;
-
-    /* In the following code, we know b is large enough that no overflow can happen. */
-
-#define APPEND(s) HB_STMT_START { strcpy (p, s); p += strlen (s); } HB_STMT_END
-
-    if (i)
-      *p++ = ',';
-
-    *p++ = '{';
-
-    APPEND ("\"g\":");
-    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES))
-    {
-      char g[128];
-      hb_font_glyph_to_string (font, info[i].codepoint, g, sizeof (g));
-      *p++ = '"';
-      for (char *q = g; *q; q++) {
-        if (*q == '"')
-	  *p++ = '\\';
-	*p++ = *q;
-      }
-      *p++ = '"';
-    }
-    else
-      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "%u", info[i].codepoint);
-
-    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS)) {
-      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"cl\":%u", info[i].cluster);
-    }
-
-    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS))
-    {
-      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"dx\":%d,\"dy\":%d",
-		     pos[i].x_offset, pos[i].y_offset);
-      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"ax\":%d,\"ay\":%d",
-		     pos[i].x_advance, pos[i].y_advance);
-    }
-
-    *p++ = '}';
-
-    if (buf_size > (p - b))
-    {
-      unsigned int l = p - b;
-      memcpy (buf, b, l);
-      buf += l;
-      buf_size -= l;
-      *buf_consumed += l;
-      *buf = '\0';
-    } else
-      return i - start;
-  }
-
-  return end - start;
-}
-
-static unsigned int
-_hb_buffer_serialize_glyphs_text (hb_buffer_t *buffer,
-				  unsigned int start,
-				  unsigned int end,
-				  char *buf,
-				  unsigned int buf_size,
-				  unsigned int *buf_consumed,
-				  hb_font_t *font,
-				  hb_buffer_serialize_flags_t flags)
-{
-  hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
-  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
-  hb_direction_t direction = hb_buffer_get_direction (buffer);
-
-  *buf_consumed = 0;
-  for (unsigned int i = start; i < end; i++)
-  {
-    char b[1024];
-    char *p = b;
-
-    /* In the following code, we know b is large enough that no overflow can happen. */
-
-    if (i)
-      *p++ = '|';
-
-    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES))
-    {
-      hb_font_glyph_to_string (font, info[i].codepoint, p, 128);
-      p += strlen (p);
-    }
-    else
-      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "%u", info[i].codepoint);
-
-    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS)) {
-      p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "=%u", info[i].cluster);
-    }
-
-    if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS))
-    {
-      if (pos[i].x_offset || pos[i].y_offset)
-	p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "@%d,%d", pos[i].x_offset, pos[i].y_offset);
-
-      *p++ = '+';
-      if (HB_DIRECTION_IS_HORIZONTAL (direction) || pos[i].x_advance)
-	p += snprintf (p, ARRAY_LENGTH (b) - (p - b), "%d", pos[i].x_advance);
-      if (HB_DIRECTION_IS_VERTICAL (direction) || pos->y_advance)
-	p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",%d", pos[i].y_advance);
-    }
-
-    if (buf_size > (p - b))
-    {
-      unsigned int l = p - b;
-      memcpy (buf, b, l);
-      buf += l;
-      buf_size -= l;
-      *buf_consumed += l;
-      *buf = '\0';
-    } else
-      return i - start;
-  }
-
-  return end - start;
-}
-
-/* Returns number of items, starting at start, that were serialized. */
-unsigned int
-hb_buffer_serialize_glyphs (hb_buffer_t *buffer,
-			    unsigned int start,
-			    unsigned int end,
-			    char *buf,
-			    unsigned int buf_size,
-			    unsigned int *buf_consumed,
-			    hb_font_t *font, /* May be NULL */
-			    hb_buffer_serialize_format_t format,
-			    hb_buffer_serialize_flags_t flags)
-{
-  assert (start <= end && end <= buffer->len);
-
-  *buf_consumed = 0;
-
-  assert ((!buffer->len && buffer->content_type == HB_BUFFER_CONTENT_TYPE_INVALID) ||
-	  buffer->content_type == HB_BUFFER_CONTENT_TYPE_GLYPHS);
-
-  if (unlikely (start == end))
-    return 0;
-
-  if (!font)
-    font = hb_font_get_empty ();
-
-  switch (format)
-  {
-    case HB_BUFFER_SERIALIZE_FORMAT_TEXT:
-      return _hb_buffer_serialize_glyphs_text (buffer, start, end,
-					       buf, buf_size, buf_consumed,
-					       font, flags);
-
-    case HB_BUFFER_SERIALIZE_FORMAT_JSON:
-      return _hb_buffer_serialize_glyphs_json (buffer, start, end,
-					       buf, buf_size, buf_consumed,
-					       font, flags);
-
-    default:
-    case HB_BUFFER_SERIALIZE_FORMAT_INVALID:
-      return 0;
-
-  }
-}
-
-hb_bool_t
-hb_buffer_deserialize_glyphs (hb_buffer_t *buffer,
-			      const char *buf,
-			      unsigned int buf_len,
-			      unsigned int *buf_consumed,
-			      hb_font_t *font, /* May be NULL */
-			      hb_buffer_serialize_format_t format)
-{
-  return false;
-}
--- a/gfx/harfbuzz/src/hb-buffer.h
+++ b/gfx/harfbuzz/src/hb-buffer.h
@@ -299,25 +299,25 @@ hb_buffer_serialize_list_formats (void);
 
 /* Returns number of items, starting at start, that were serialized. */
 unsigned int
 hb_buffer_serialize_glyphs (hb_buffer_t *buffer,
 			    unsigned int start,
 			    unsigned int end,
 			    char *buf,
 			    unsigned int buf_size,
-			    unsigned int *buf_consumed,
+			    unsigned int *buf_consumed, /* May be NULL */
 			    hb_font_t *font, /* May be NULL */
 			    hb_buffer_serialize_format_t format,
 			    hb_buffer_serialize_flags_t flags);
 
 hb_bool_t
 hb_buffer_deserialize_glyphs (hb_buffer_t *buffer,
 			      const char *buf,
-			      unsigned int buf_len,
-			      unsigned int *buf_consumed,
+			      int buf_len, /* -1 means nul-terminated */
+			      const char **end_ptr, /* May be NULL */
 			      hb_font_t *font, /* May be NULL */
 			      hb_buffer_serialize_format_t format);
 
 
 HB_END_DECLS
 
 #endif /* HB_BUFFER_H */
--- a/gfx/harfbuzz/src/hb-fallback-shape.cc
+++ b/gfx/harfbuzz/src/hb-fallback-shape.cc
@@ -93,17 +93,16 @@ hb_bool_t
 		    hb_font_t          *font,
 		    hb_buffer_t        *buffer,
 		    const hb_feature_t *features HB_UNUSED,
 		    unsigned int        num_features HB_UNUSED)
 {
   hb_codepoint_t space;
   font->get_glyph (' ', 0, &space);
 
-  buffer->guess_segment_properties ();
   buffer->clear_positions ();
 
   unsigned int count = buffer->len;
 
   for (unsigned int i = 0; i < count; i++)
   {
     if (buffer->unicode->is_default_ignorable (buffer->info[i].codepoint)) {
       buffer->info[i].codepoint = space;
--- a/gfx/harfbuzz/src/hb-ft.cc
+++ b/gfx/harfbuzz/src/hb-ft.cc
@@ -48,24 +48,17 @@
  *
  *   - We don't handle any load_flags.  That definitely has API implications. :(
  *     I believe hb_ft_font_create() should take load_flags input.
  *     In particular, FT_Get_Advance() without the NO_HINTING flag seems to be
  *     buggy.
  *
  *   - We don't handle / allow for emboldening / obliqueing.
  *
- *   - Rounding, etc?
- *
- *   - In the future, we should add constructors to create fonts in font space.
- *
- *   - I believe transforms are not correctly implemented.  FreeType does not
- *     provide any API to get to the transform/delta set on the face. :(
- *
- *   - Always use FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH?
+ *   - In the future, we should add constructors to create fonts in font space?
  *
  *   - FT_Load_Glyph() is exteremely costly.  Do something about it?
  */
 
 
 static hb_bool_t
 hb_ft_get_glyph (hb_font_t *font HB_UNUSED,
 		 void *font_data,
@@ -237,18 +230,18 @@ hb_ft_get_glyph_name (hb_font_t *font HB
 		      void *font_data,
 		      hb_codepoint_t glyph,
 		      char *name, unsigned int size,
 		      void *user_data HB_UNUSED)
 {
   FT_Face ft_face = (FT_Face) font_data;
 
   hb_bool_t ret = !FT_Get_Glyph_Name (ft_face, glyph, name, size);
-  if (!ret || (size && !*name))
-    snprintf (name, size, "gid%u", glyph);
+  if (ret && (size && !*name))
+    ret = false;
 
   return ret;
 }
 
 static hb_bool_t
 hb_ft_get_glyph_from_name (hb_font_t *font HB_UNUSED,
 			   void *font_data,
 			   const char *name, int len, /* -1 means nul-terminated */
--- a/gfx/harfbuzz/src/hb-graphite2.cc
+++ b/gfx/harfbuzz/src/hb-graphite2.cc
@@ -221,17 +221,17 @@ hb_bool_t
 		     const hb_feature_t *features,
 		     unsigned int        num_features)
 {
   hb_face_t *face = font->face;
   gr_face *grface = HB_SHAPER_DATA_GET (face)->grface;
   gr_font *grfont = HB_SHAPER_DATA_GET (font);
 
   const char *lang = hb_language_to_string (hb_buffer_get_language (buffer));
-  const char *lang_end = strchr (lang, '-');
+  const char *lang_end = lang ? strchr (lang, '-') : NULL;
   int lang_len = lang_end ? lang_end - lang : -1;
   gr_feature_val *feats = gr_face_featureval_for_lang (grface, lang ? hb_tag_from_string (lang, lang_len) : 0);
 
   while (num_features--)
   {
     const gr_feature_ref *fref = gr_face_find_fref (grface, features->tag);
     if (fref)
       gr_fref_set_feature_value (fref, features->value, feats);
--- a/gfx/harfbuzz/src/hb-open-type-private.hh
+++ b/gfx/harfbuzz/src/hb-open-type-private.hh
@@ -166,24 +166,28 @@ ASSERT_STATIC (Type::min_size + 1 <= siz
 #endif
 
 
 #define TRACE_SANITIZE(this) \
 	hb_auto_trace_t<HB_DEBUG_SANITIZE, bool> trace \
 	(&c->debug_depth, c->get_name (), this, HB_FUNC, \
 	 "");
 
+/* This limits sanitizing time on really broken fonts. */
+#ifndef HB_SANITIZE_MAX_EDITS
+#define HB_SANITIZE_MAX_EDITS 100
+#endif
 
 struct hb_sanitize_context_t
 {
   inline const char *get_name (void) { return "SANITIZE"; }
   static const unsigned int max_debug_depth = HB_DEBUG_SANITIZE;
   typedef bool return_t;
   template <typename T>
-  inline return_t process (const T &obj) { return obj.sanitize (this); }
+  inline return_t dispatch (const T &obj) { return obj.sanitize (this); }
   static return_t default_return_value (void) { return true; }
   bool stop_sublookup_iteration (const return_t r HB_UNUSED) const { return false; }
 
   inline void init (hb_blob_t *b)
   {
     this->blob = hb_blob_reference (b);
     this->writable = false;
   }
@@ -242,16 +246,19 @@ struct hb_sanitize_context_t
   template <typename Type>
   inline bool check_struct (const Type *obj) const
   {
     return likely (this->check_range (obj, obj->min_size));
   }
 
   inline bool may_edit (const void *base HB_UNUSED, unsigned int len HB_UNUSED)
   {
+    if (this->edit_count >= HB_SANITIZE_MAX_EDITS)
+      return false;
+
     const char *p = (const char *) base;
     this->edit_count++;
 
     hb_auto_trace_t<HB_DEBUG_SANITIZE, bool> trace
       (&this->debug_depth, "SANITIZE", this->blob, NULL,
        "may_edit(%u) [%p..%p] (%d bytes) in [%p..%p] -> %s",
        this->edit_count,
        p, p + len, len,
@@ -399,17 +406,17 @@ struct hb_serialize_context_t
     if (p)
       memcpy (p, this->start, len);
     return reinterpret_cast<Type *> (p);
   }
 
   template <typename Type>
   inline Type *allocate_size (unsigned int size)
   {
-    if (unlikely (this->ran_out_of_room || this->end - this->head < size)) {
+    if (unlikely (this->ran_out_of_room || this->end - this->head < ptrdiff_t (size))) {
       this->ran_out_of_room = true;
       return NULL;
     }
     memset (this->head, 0, size);
     char *ret = this->head;
     this->head += size;
     return reinterpret_cast<Type *> (ret);
   }
@@ -679,21 +686,16 @@ template <typename OffsetType, typename 
 struct GenericOffsetTo : OffsetType
 {
   inline const Type& operator () (const void *base) const
   {
     unsigned int offset = *this;
     if (unlikely (!offset)) return Null(Type);
     return StructAtOffset<Type> (base, offset);
   }
-  inline Type& operator () (void *base)
-  {
-    unsigned int offset = *this;
-    return StructAtOffset<Type> (base, offset);
-  }
 
   inline Type& serialize (hb_serialize_context_t *c, void *base)
   {
     Type *t = c->start_embed<Type> ();
     this->set ((char *) t - (char *) base); /* TODO(serialize) Overflow? */
     return *t;
   }
 
@@ -942,31 +944,32 @@ struct HeadlessArrayOf
 };
 
 
 /* An array with sorted elements.  Supports binary searching. */
 template <typename Type>
 struct SortedArrayOf : ArrayOf<Type> {
 
   template <typename SearchType>
-  inline int search (const SearchType &x) const {
-    unsigned int count = this->len;
-    /* Linear search is *much* faster for small counts. */
-    if (likely (count < 32)) {
-      for (unsigned int i = 0; i < count; i++)
-	if (this->array[i].cmp (x) == 0)
-	  return i;
-      return -1;
-    } else {
-      struct Cmp {
-	static int cmp (const SearchType *a, const Type *b) { return b->cmp (*a); }
-      };
-      const Type *p = (const Type *) bsearch (&x, this->array, this->len, sizeof (this->array[0]), (hb_compare_func_t) Cmp::cmp);
-      return p ? p - this->array : -1;
+  inline int search (const SearchType &x) const
+  {
+    /* Hand-coded bsearch here since this is in the hot inner loop. */
+    int min = 0, max = (int) this->len - 1;
+    while (min <= max)
+    {
+      int mid = (min + max) / 2;
+      int c = this->array[mid].cmp (x);
+      if (c < 0)
+        max = mid - 1;
+      else if (c > 0)
+        min = mid + 1;
+      else
+        return mid;
     }
+    return -1;
   }
 };
 
 
 } /* namespace OT */
 
 
 #endif /* HB_OPEN_TYPE_PRIVATE_HH */
--- a/gfx/harfbuzz/src/hb-ot-layout-common-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-layout-common-private.hh
@@ -124,18 +124,17 @@ struct RecordListOf : RecordArrayOf<Type
     return TRACE_RETURN (RecordArrayOf<Type>::sanitize (c, this));
   }
 };
 
 
 struct RangeRecord
 {
   inline int cmp (hb_codepoint_t g) const {
-    hb_codepoint_t a = start, b = end;
-    return g < a ? -1 : g <= b ? 0 : +1 ;
+    return g < start ? -1 : g <= end ? 0 : +1 ;
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     return TRACE_RETURN (c->check_struct (this));
   }
 
   inline bool intersects (const hb_set_t *glyphs) const {
--- a/gfx/harfbuzz/src/hb-ot-layout-gpos-table.hh
+++ b/gfx/harfbuzz/src/hb-ot-layout-gpos-table.hh
@@ -514,22 +514,22 @@ struct SinglePosFormat2
 					 * values applied to glyphs */
   public:
   DEFINE_SIZE_ARRAY (8, values);
 };
 
 struct SinglePos
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
-    case 2: return TRACE_RETURN (c->process (u.format2));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
+    case 2: return TRACE_RETURN (c->dispatch (u.format2));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -591,16 +591,17 @@ struct PairSet
     unsigned int len1 = valueFormats[0].get_len ();
     unsigned int len2 = valueFormats[1].get_len ();
     unsigned int record_size = USHORT::static_size * (1 + len1 + len2);
 
     const PairValueRecord *record = CastP<PairValueRecord> (array);
     unsigned int count = len;
     for (unsigned int i = 0; i < count; i++)
     {
+      /* TODO bsearch */
       if (c->buffer->info[pos].codepoint == record->secondGlyph)
       {
 	valueFormats[0].apply_value (c->font, c->direction, this,
 				     &record->values[0], c->buffer->cur_pos());
 	valueFormats[1].apply_value (c->font, c->direction, this,
 				     &record->values[len1], c->buffer->pos[pos]);
 	if (len2)
 	  pos++;
@@ -804,22 +805,22 @@ struct PairPosFormat2
 					 * Each entry has value1 and value2 */
   public:
   DEFINE_SIZE_ARRAY (16, values);
 };
 
 struct PairPos
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
-    case 2: return TRACE_RETURN (c->process (u.format2));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
+    case 2: return TRACE_RETURN (c->dispatch (u.format2));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -873,17 +874,17 @@ struct CursivePosFormat1
     return this+coverage;
   }
 
   inline bool apply (hb_apply_context_t *c) const
   {
     TRACE_APPLY (this);
 
     /* We don't handle mark glyphs here. */
-    if (c->property & HB_OT_LAYOUT_GLYPH_PROPS_MARK) return TRACE_RETURN (false);
+    if (c->buffer->cur().glyph_props() & HB_OT_LAYOUT_GLYPH_PROPS_MARK) return TRACE_RETURN (false);
 
     hb_apply_context_t::skipping_forward_iterator_t skippy_iter (c, c->buffer->idx, 1);
     if (skippy_iter.has_no_chance ()) return TRACE_RETURN (false);
 
     const EntryExitRecord &this_record = entryExitRecord[(this+coverage).get_coverage  (c->buffer->cur().codepoint)];
     if (!this_record.exitAnchor) return TRACE_RETURN (false);
 
     if (!skippy_iter.next ()) return TRACE_RETURN (false);
@@ -970,21 +971,21 @@ struct CursivePosFormat1
 					 * Coverage Index order */
   public:
   DEFINE_SIZE_ARRAY (6, entryExitRecord);
 };
 
 struct CursivePos
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -1023,27 +1024,27 @@ struct MarkBasePosFormat1
 
   inline bool apply (hb_apply_context_t *c) const
   {
     TRACE_APPLY (this);
     unsigned int mark_index = (this+markCoverage).get_coverage  (c->buffer->cur().codepoint);
     if (likely (mark_index == NOT_COVERED)) return TRACE_RETURN (false);
 
     /* now we search backwards for a non-mark glyph */
-    unsigned int property;
     hb_apply_context_t::skipping_backward_iterator_t skippy_iter (c, c->buffer->idx, 1);
+    skippy_iter.set_lookup_props (LookupFlag::IgnoreMarks);
     do {
-      if (!skippy_iter.prev (&property, LookupFlag::IgnoreMarks)) return TRACE_RETURN (false);
+      if (!skippy_iter.prev ()) return TRACE_RETURN (false);
       /* We only want to attach to the first of a MultipleSubst sequence.  Reject others. */
       if (0 == get_lig_comp (c->buffer->info[skippy_iter.idx])) break;
       skippy_iter.reject ();
     } while (1);
 
     /* The following assertion is too strong, so we've disabled it. */
-    if (!(property & HB_OT_LAYOUT_GLYPH_PROPS_BASE_GLYPH)) {/*return TRACE_RETURN (false);*/}
+    if (!(c->buffer->info[skippy_iter.idx].glyph_props() & HB_OT_LAYOUT_GLYPH_PROPS_BASE_GLYPH)) {/*return TRACE_RETURN (false);*/}
 
     unsigned int base_index = (this+baseCoverage).get_coverage  (c->buffer->info[skippy_iter.idx].codepoint);
     if (base_index == NOT_COVERED) return TRACE_RETURN (false);
 
     return TRACE_RETURN ((this+markArray).apply (c, mark_index, base_index, this+baseArray, classCount, skippy_iter.idx));
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
@@ -1069,21 +1070,21 @@ struct MarkBasePosFormat1
 					 * beginning of MarkBasePos subtable */
   public:
   DEFINE_SIZE_STATIC (12);
 };
 
 struct MarkBasePos
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -1127,22 +1128,22 @@ struct MarkLigPosFormat1
 
   inline bool apply (hb_apply_context_t *c) const
   {
     TRACE_APPLY (this);
     unsigned int mark_index = (this+markCoverage).get_coverage  (c->buffer->cur().codepoint);
     if (likely (mark_index == NOT_COVERED)) return TRACE_RETURN (false);
 
     /* now we search backwards for a non-mark glyph */
-    unsigned int property;
     hb_apply_context_t::skipping_backward_iterator_t skippy_iter (c, c->buffer->idx, 1);
-    if (!skippy_iter.prev (&property, LookupFlag::IgnoreMarks)) return TRACE_RETURN (false);
+    skippy_iter.set_lookup_props (LookupFlag::IgnoreMarks);
+    if (!skippy_iter.prev ()) return TRACE_RETURN (false);
 
     /* The following assertion is too strong, so we've disabled it. */
-    if (!(property & HB_OT_LAYOUT_GLYPH_PROPS_LIGATURE)) {/*return TRACE_RETURN (false);*/}
+    if (!(c->buffer->info[skippy_iter.idx].glyph_props() & HB_OT_LAYOUT_GLYPH_PROPS_LIGATURE)) {/*return TRACE_RETURN (false);*/}
 
     unsigned int j = skippy_iter.idx;
     unsigned int lig_index = (this+ligatureCoverage).get_coverage  (c->buffer->info[j].codepoint);
     if (lig_index == NOT_COVERED) return TRACE_RETURN (false);
 
     const LigatureArray& lig_array = this+ligatureArray;
     const LigatureAttach& lig_attach = lig_array[lig_index];
 
@@ -1190,21 +1191,21 @@ struct MarkLigPosFormat1
 					 * beginning of MarkLigPos subtable */
   public:
   DEFINE_SIZE_STATIC (12);
 };
 
 struct MarkLigPos
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -1243,21 +1244,21 @@ struct MarkMarkPosFormat1
 
   inline bool apply (hb_apply_context_t *c) const
   {
     TRACE_APPLY (this);
     unsigned int mark1_index = (this+mark1Coverage).get_coverage  (c->buffer->cur().codepoint);
     if (likely (mark1_index == NOT_COVERED)) return TRACE_RETURN (false);
 
     /* now we search backwards for a suitable mark glyph until a non-mark glyph */
-    unsigned int property;
     hb_apply_context_t::skipping_backward_iterator_t skippy_iter (c, c->buffer->idx, 1);
-    if (!skippy_iter.prev (&property, c->lookup_props & ~LookupFlag::IgnoreFlags)) return TRACE_RETURN (false);
+    skippy_iter.set_lookup_props (c->lookup_props & ~LookupFlag::IgnoreFlags);
+    if (!skippy_iter.prev ()) return TRACE_RETURN (false);
 
-    if (!(property & HB_OT_LAYOUT_GLYPH_PROPS_MARK)) return TRACE_RETURN (false);
+    if (!(c->buffer->info[skippy_iter.idx].glyph_props() & HB_OT_LAYOUT_GLYPH_PROPS_MARK)) { return TRACE_RETURN (false); }
 
     unsigned int j = skippy_iter.idx;
 
     unsigned int id1 = get_lig_id (c->buffer->cur());
     unsigned int id2 = get_lig_id (c->buffer->info[j]);
     unsigned int comp1 = get_lig_comp (c->buffer->cur());
     unsigned int comp2 = get_lig_comp (c->buffer->info[j]);
 
@@ -1309,21 +1310,21 @@ struct MarkMarkPosFormat1
 					 * beginning of MarkMarkPos subtable */
   public:
   DEFINE_SIZE_STATIC (12);
 };
 
 struct MarkMarkPos
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -1368,29 +1369,29 @@ struct PosLookupSubTable
     MarkLig		= 5,
     MarkMark		= 6,
     Context		= 7,
     ChainContext	= 8,
     Extension		= 9
   };
 
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c, unsigned int lookup_type) const
+  inline typename context_t::return_t dispatch (context_t *c, unsigned int lookup_type) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (lookup_type) {
-    case Single:		return TRACE_RETURN (u.single.process (c));
-    case Pair:			return TRACE_RETURN (u.pair.process (c));
-    case Cursive:		return TRACE_RETURN (u.cursive.process (c));
-    case MarkBase:		return TRACE_RETURN (u.markBase.process (c));
-    case MarkLig:		return TRACE_RETURN (u.markLig.process (c));
-    case MarkMark:		return TRACE_RETURN (u.markMark.process (c));
-    case Context:		return TRACE_RETURN (u.context.process (c));
-    case ChainContext:		return TRACE_RETURN (u.chainContext.process (c));
-    case Extension:		return TRACE_RETURN (u.extension.process (c));
+    case Single:		return TRACE_RETURN (u.single.dispatch (c));
+    case Pair:			return TRACE_RETURN (u.pair.dispatch (c));
+    case Cursive:		return TRACE_RETURN (u.cursive.dispatch (c));
+    case MarkBase:		return TRACE_RETURN (u.markBase.dispatch (c));
+    case MarkLig:		return TRACE_RETURN (u.markLig.dispatch (c));
+    case MarkMark:		return TRACE_RETURN (u.markMark.dispatch (c));
+    case Context:		return TRACE_RETURN (u.context.dispatch (c));
+    case ChainContext:		return TRACE_RETURN (u.chainContext.dispatch (c));
+    case Extension:		return TRACE_RETURN (u.extension.dispatch (c));
     default:			return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c, unsigned int lookup_type) {
     TRACE_SANITIZE (this);
     if (!u.header.sub_format.sanitize (c))
       return TRACE_RETURN (false);
@@ -1428,60 +1429,44 @@ struct PosLookupSubTable
 };
 
 
 struct PosLookup : Lookup
 {
   inline const PosLookupSubTable& get_subtable (unsigned int i) const
   { return this+CastR<OffsetArrayOf<PosLookupSubTable> > (subTable)[i]; }
 
-  template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
-  {
-    TRACE_PROCESS (this);
-    unsigned int lookup_type = get_type ();
-    unsigned int count = get_subtable_count ();
-    for (unsigned int i = 0; i < count; i++) {
-      typename context_t::return_t r = get_subtable (i).process (c, lookup_type);
-      if (c->stop_sublookup_iteration (r))
-        return TRACE_RETURN (r);
-    }
-    return TRACE_RETURN (c->default_return_value ());
-  }
-  template <typename context_t>
-  static inline typename context_t::return_t process_recurse_func (context_t *c, unsigned int lookup_index);
-
   inline hb_collect_glyphs_context_t::return_t collect_glyphs_lookup (hb_collect_glyphs_context_t *c) const
   {
     TRACE_COLLECT_GLYPHS (this);
     c->set_recurse_func (NULL);
-    return TRACE_RETURN (process (c));
+    return TRACE_RETURN (dispatch (c));
   }
 
   template <typename set_t>
   inline void add_coverage (set_t *glyphs) const
   {
     hb_get_coverage_context_t c;
     const Coverage *last = NULL;
     unsigned int count = get_subtable_count ();
     for (unsigned int i = 0; i < count; i++) {
-      const Coverage *coverage = &get_subtable (i).process (&c, get_type ());
+      const Coverage *coverage = &get_subtable (i).dispatch (&c, get_type ());
       if (coverage != last) {
         coverage->add_coverage (glyphs);
         last = coverage;
       }
     }
   }
 
   inline bool apply_once (hb_apply_context_t *c) const
   {
     TRACE_APPLY (this);
-    if (!c->check_glyph_property (&c->buffer->cur(), c->lookup_props, &c->property))
+    if (!c->check_glyph_property (&c->buffer->cur(), c->lookup_props))
       return TRACE_RETURN (false);
-    return TRACE_RETURN (process (c));
+    return TRACE_RETURN (dispatch (c));
   }
 
   static bool apply_recurse_func (hb_apply_context_t *c, unsigned int lookup_index);
 
   inline bool apply_string (hb_apply_context_t *c, const hb_set_digest_t *digest) const
   {
     bool ret = false;
 
@@ -1490,27 +1475,44 @@ struct PosLookup : Lookup
 
     c->set_recurse_func (apply_recurse_func);
     c->set_lookup (*this);
 
     c->buffer->idx = 0;
 
     while (c->buffer->idx < c->buffer->len)
     {
-      if ((c->buffer->cur().mask & c->lookup_mask) &&
-	  digest->may_have (c->buffer->cur().codepoint) &&
+      if (digest->may_have (c->buffer->cur().codepoint) &&
+	  (c->buffer->cur().mask & c->lookup_mask) &&
 	  apply_once (c))
 	ret = true;
       else
 	c->buffer->idx++;
     }
 
     return ret;
   }
 
+  template <typename context_t>
+  static inline typename context_t::return_t dispatch_recurse_func (context_t *c, unsigned int lookup_index);
+
+  template <typename context_t>
+  inline typename context_t::return_t dispatch (context_t *c) const
+  {
+    TRACE_DISPATCH (this);
+    unsigned int lookup_type = get_type ();
+    unsigned int count = get_subtable_count ();
+    for (unsigned int i = 0; i < count; i++) {
+      typename context_t::return_t r = get_subtable (i).dispatch (c, lookup_type);
+      if (c->stop_sublookup_iteration (r))
+        return TRACE_RETURN (r);
+    }
+    return TRACE_RETURN (c->default_return_value ());
+  }
+
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (unlikely (!Lookup::sanitize (c))) return TRACE_RETURN (false);
     OffsetArrayOf<PosLookupSubTable> &list = CastR<OffsetArrayOf<PosLookupSubTable> > (subTable);
     return TRACE_RETURN (list.sanitize (c, this, get_type ()));
   }
 };
 
@@ -1612,33 +1614,31 @@ GPOS::position_finish (hb_font_t *font H
   HB_BUFFER_DEALLOCATE_VAR (buffer, lig_props);
   HB_BUFFER_DEALLOCATE_VAR (buffer, glyph_props);
 }
 
 
 /* Out-of-class implementation for methods recursing */
 
 template <typename context_t>
-inline typename context_t::return_t PosLookup::process_recurse_func (context_t *c, unsigned int lookup_index)
+inline typename context_t::return_t PosLookup::dispatch_recurse_func (context_t *c, unsigned int lookup_index)
 {
   const GPOS &gpos = *(hb_ot_layout_from_face (c->face)->gpos);
   const PosLookup &l = gpos.get_lookup (lookup_index);
-  return l.process (c);
+  return l.dispatch (c);
 }
 
 inline bool PosLookup::apply_recurse_func (hb_apply_context_t *c, unsigned int lookup_index)
 {
   const GPOS &gpos = *(hb_ot_layout_from_face (c->face)->gpos);
   const PosLookup &l = gpos.get_lookup (lookup_index);
   unsigned int saved_lookup_props = c->lookup_props;
-  unsigned int saved_property = c->property;
   c->set_lookup (l);
   bool ret = l.apply_once (c);
   c->lookup_props = saved_lookup_props;
-  c->property = saved_property;
   return ret;
 }
 
 
 #undef attach_lookback
 #undef cursive_chain
 
 
--- a/gfx/harfbuzz/src/hb-ot-layout-gsub-table.hh
+++ b/gfx/harfbuzz/src/hb-ot-layout-gsub-table.hh
@@ -187,27 +187,16 @@ struct SingleSubstFormat2
 		substitute;		/* Array of substitute
 					 * GlyphIDs--ordered by Coverage Index */
   public:
   DEFINE_SIZE_ARRAY (6, substitute);
 };
 
 struct SingleSubst
 {
-  template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
-  {
-    TRACE_PROCESS (this);
-    switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
-    case 2: return TRACE_RETURN (c->process (u.format2));
-    default:return TRACE_RETURN (c->default_return_value ());
-    }
-  }
-
   inline bool serialize (hb_serialize_context_t *c,
 			 Supplier<GlyphID> &glyphs,
 			 Supplier<GlyphID> &substitutes,
 			 unsigned int num_glyphs)
   {
     TRACE_SERIALIZE (this);
     if (unlikely (!c->extend_min (u.format))) return TRACE_RETURN (false);
     unsigned int format = 2;
@@ -225,16 +214,27 @@ struct SingleSubst
     u.format.set (format);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.serialize (c, glyphs, num_glyphs, delta));
     case 2: return TRACE_RETURN (u.format2.serialize (c, glyphs, substitutes, num_glyphs));
     default:return TRACE_RETURN (false);
     }
   }
 
+  template <typename context_t>
+  inline typename context_t::return_t dispatch (context_t *c) const
+  {
+    TRACE_DISPATCH (this);
+    switch (u.format) {
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
+    case 2: return TRACE_RETURN (c->dispatch (u.format2));
+    default:return TRACE_RETURN (c->default_return_value ());
+    }
+  }
+
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.sanitize (c));
     case 2: return TRACE_RETURN (u.format2.sanitize (c));
     default:return TRACE_RETURN (true);
     }
@@ -267,17 +267,18 @@ struct Sequence
       c->output->add (substitute[i]);
   }
 
   inline bool apply (hb_apply_context_t *c) const
   {
     TRACE_APPLY (this);
     if (unlikely (!substitute.len)) return TRACE_RETURN (false);
 
-    unsigned int klass = c->property & HB_OT_LAYOUT_GLYPH_PROPS_LIGATURE ? HB_OT_LAYOUT_GLYPH_PROPS_BASE_GLYPH : 0;
+    unsigned int klass = c->buffer->cur().glyph_props() &
+			 HB_OT_LAYOUT_GLYPH_PROPS_LIGATURE ? HB_OT_LAYOUT_GLYPH_PROPS_BASE_GLYPH : 0;
     unsigned int count = substitute.len;
     for (unsigned int i = 0; i < count; i++) {
       set_lig_props_for_component (c->buffer->cur(), i);
       c->output_glyph (substitute.array[i], klass);
     }
     c->buffer->skip_glyph ();
 
     return TRACE_RETURN (true);
@@ -379,42 +380,42 @@ struct MultipleSubstFormat1
 		sequence;		/* Array of Sequence tables
 					 * ordered by Coverage Index */
   public:
   DEFINE_SIZE_ARRAY (6, sequence);
 };
 
 struct MultipleSubst
 {
-  template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
-  {
-    TRACE_PROCESS (this);
-    switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
-    default:return TRACE_RETURN (c->default_return_value ());
-    }
-  }
-
   inline bool serialize (hb_serialize_context_t *c,
 			 Supplier<GlyphID> &glyphs,
 			 Supplier<unsigned int> &substitute_len_list,
 			 unsigned int num_glyphs,
 			 Supplier<GlyphID> &substitute_glyphs_list)
   {
     TRACE_SERIALIZE (this);
     if (unlikely (!c->extend_min (u.format))) return TRACE_RETURN (false);
     unsigned int format = 1;
     u.format.set (format);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.serialize (c, glyphs, substitute_len_list, num_glyphs, substitute_glyphs_list));
     default:return TRACE_RETURN (false);
     }
   }
 
+  template <typename context_t>
+  inline typename context_t::return_t dispatch (context_t *c) const
+  {
+    TRACE_DISPATCH (this);
+    switch (u.format) {
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
+    default:return TRACE_RETURN (c->default_return_value ());
+    }
+  }
+
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.sanitize (c));
     default:return TRACE_RETURN (true);
     }
   }
@@ -530,42 +531,42 @@ struct AlternateSubstFormat1
 		alternateSet;		/* Array of AlternateSet tables
 					 * ordered by Coverage Index */
   public:
   DEFINE_SIZE_ARRAY (6, alternateSet);
 };
 
 struct AlternateSubst
 {
-  template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
-  {
-    TRACE_PROCESS (this);
-    switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
-    default:return TRACE_RETURN (c->default_return_value ());
-    }
-  }
-
   inline bool serialize (hb_serialize_context_t *c,
 			 Supplier<GlyphID> &glyphs,
 			 Supplier<unsigned int> &alternate_len_list,
 			 unsigned int num_glyphs,
 			 Supplier<GlyphID> &alternate_glyphs_list)
   {
     TRACE_SERIALIZE (this);
     if (unlikely (!c->extend_min (u.format))) return TRACE_RETURN (false);
     unsigned int format = 1;
     u.format.set (format);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.serialize (c, glyphs, alternate_len_list, num_glyphs, alternate_glyphs_list));
     default:return TRACE_RETURN (false);
     }
   }
 
+  template <typename context_t>
+  inline typename context_t::return_t dispatch (context_t *c) const
+  {
+    TRACE_DISPATCH (this);
+    switch (u.format) {
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
+    default:return TRACE_RETURN (c->default_return_value ());
+    }
+  }
+
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.sanitize (c));
     default:return TRACE_RETURN (true);
     }
   }
@@ -632,19 +633,19 @@ struct Ligature
       return TRACE_RETURN (false);
 
     /* Deal, we are forming the ligature. */
     c->buffer->merge_clusters (c->buffer->idx, c->buffer->idx + end_offset);
 
     ligate_input (c,
 		  count,
 		  &component[1],
-		  ligGlyph,
 		  match_glyph,
 		  NULL,
+		  ligGlyph,
 		  is_mark_ligature,
 		  total_component_count);
 
     return TRACE_RETURN (true);
   }
 
   inline bool serialize (hb_serialize_context_t *c,
 			 GlyphID ligature,
@@ -835,26 +836,16 @@ struct LigatureSubstFormat1
 		ligatureSet;		/* Array LigatureSet tables
 					 * ordered by Coverage Index */
   public:
   DEFINE_SIZE_ARRAY (6, ligatureSet);
 };
 
 struct LigatureSubst
 {
-  template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
-  {
-    TRACE_PROCESS (this);
-    switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
-    default:return TRACE_RETURN (c->default_return_value ());
-    }
-  }
-
   inline bool serialize (hb_serialize_context_t *c,
 			 Supplier<GlyphID> &first_glyphs,
 			 Supplier<unsigned int> &ligature_per_first_glyph_count_list,
 			 unsigned int num_first_glyphs,
 			 Supplier<GlyphID> &ligatures_list,
 			 Supplier<unsigned int> &component_count_list,
 			 Supplier<GlyphID> &component_list /* Starting from second for each ligature */)
   {
@@ -864,16 +855,26 @@ struct LigatureSubst
     u.format.set (format);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.serialize (c, first_glyphs, ligature_per_first_glyph_count_list, num_first_glyphs,
 						      ligatures_list, component_count_list, component_list));
     default:return TRACE_RETURN (false);
     }
   }
 
+  template <typename context_t>
+  inline typename context_t::return_t dispatch (context_t *c) const
+  {
+    TRACE_DISPATCH (this);
+    switch (u.format) {
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
+    default:return TRACE_RETURN (c->default_return_value ());
+    }
+  }
+
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.sanitize (c));
     default:return TRACE_RETURN (true);
     }
   }
@@ -1017,21 +1018,21 @@ struct ReverseChainSingleSubstFormat1
 					 * GlyphIDs--ordered by Coverage Index */
   public:
   DEFINE_SIZE_MIN (10);
 };
 
 struct ReverseChainSingleSubst
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -1064,28 +1065,28 @@ struct SubstLookupSubTable
     Ligature		= 4,
     Context		= 5,
     ChainContext	= 6,
     Extension		= 7,
     ReverseChainSingle	= 8
   };
 
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c, unsigned int lookup_type) const
+  inline typename context_t::return_t dispatch (context_t *c, unsigned int lookup_type) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (lookup_type) {
-    case Single:		return TRACE_RETURN (u.single.process (c));
-    case Multiple:		return TRACE_RETURN (u.multiple.process (c));
-    case Alternate:		return TRACE_RETURN (u.alternate.process (c));
-    case Ligature:		return TRACE_RETURN (u.ligature.process (c));
-    case Context:		return TRACE_RETURN (u.context.process (c));
-    case ChainContext:		return TRACE_RETURN (u.chainContext.process (c));
-    case Extension:		return TRACE_RETURN (u.extension.process (c));
-    case ReverseChainSingle:	return TRACE_RETURN (u.reverseChainContextSingle.process (c));
+    case Single:		return TRACE_RETURN (u.single.dispatch (c));
+    case Multiple:		return TRACE_RETURN (u.multiple.dispatch (c));
+    case Alternate:		return TRACE_RETURN (u.alternate.dispatch (c));
+    case Ligature:		return TRACE_RETURN (u.ligature.dispatch (c));
+    case Context:		return TRACE_RETURN (u.context.dispatch (c));
+    case ChainContext:		return TRACE_RETURN (u.chainContext.dispatch (c));
+    case Extension:		return TRACE_RETURN (u.extension.dispatch (c));
+    case ReverseChainSingle:	return TRACE_RETURN (u.reverseChainContextSingle.dispatch (c));
     default:			return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c, unsigned int lookup_type) {
     TRACE_SANITIZE (this);
     if (!u.header.sub_format.sanitize (c))
       return TRACE_RETURN (false);
@@ -1132,75 +1133,59 @@ struct SubstLookup : Lookup
   inline bool is_reverse (void) const
   {
     unsigned int type = get_type ();
     if (unlikely (type == SubstLookupSubTable::Extension))
       return CastR<ExtensionSubst> (get_subtable(0)).is_reverse ();
     return lookup_type_is_reverse (type);
   }
 
-  template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
-  {
-    TRACE_PROCESS (this);
-    unsigned int lookup_type = get_type ();
-    unsigned int count = get_subtable_count ();
-    for (unsigned int i = 0; i < count; i++) {
-      typename context_t::return_t r = get_subtable (i).process (c, lookup_type);
-      if (c->stop_sublookup_iteration (r))
-        return TRACE_RETURN (r);
-    }
-    return TRACE_RETURN (c->default_return_value ());
-  }
-  template <typename context_t>
-  static inline typename context_t::return_t process_recurse_func (context_t *c, unsigned int lookup_index);
-
   inline hb_closure_context_t::return_t closure (hb_closure_context_t *c) const
   {
     TRACE_CLOSURE (this);
-    c->set_recurse_func (process_recurse_func<hb_closure_context_t>);
-    return TRACE_RETURN (process (c));
+    c->set_recurse_func (dispatch_recurse_func<hb_closure_context_t>);
+    return TRACE_RETURN (dispatch (c));
   }
 
   inline hb_collect_glyphs_context_t::return_t collect_glyphs_lookup (hb_collect_glyphs_context_t *c) const
   {
     TRACE_COLLECT_GLYPHS (this);
-    c->set_recurse_func (process_recurse_func<hb_collect_glyphs_context_t>);
-    return TRACE_RETURN (process (c));
+    c->set_recurse_func (dispatch_recurse_func<hb_collect_glyphs_context_t>);
+    return TRACE_RETURN (dispatch (c));
   }
 
   template <typename set_t>
   inline void add_coverage (set_t *glyphs) const
   {
     hb_get_coverage_context_t c;
     const Coverage *last = NULL;
     unsigned int count = get_subtable_count ();
     for (unsigned int i = 0; i < count; i++) {
-      const Coverage *coverage = &get_subtable (i).process (&c, get_type ());
+      const Coverage *coverage = &get_subtable (i).dispatch (&c, get_type ());
       if (coverage != last) {
         coverage->add_coverage (glyphs);
         last = coverage;
       }
     }
   }
 
   inline bool would_apply (hb_would_apply_context_t *c, const hb_set_digest_t *digest) const
   {
     TRACE_WOULD_APPLY (this);
     if (unlikely (!c->len))  return TRACE_RETURN (false);
     if (!digest->may_have (c->glyphs[0]))  return TRACE_RETURN (false);
-      return TRACE_RETURN (process (c));
+      return TRACE_RETURN (dispatch (c));
   }
 
   inline bool apply_once (hb_apply_context_t *c) const
   {
     TRACE_APPLY (this);
-    if (!c->check_glyph_property (&c->buffer->cur(), c->lookup_props, &c->property))
+    if (!c->check_glyph_property (&c->buffer->cur(), c->lookup_props))
       return TRACE_RETURN (false);
-    return TRACE_RETURN (process (c));
+    return TRACE_RETURN (dispatch (c));
   }
 
   static bool apply_recurse_func (hb_apply_context_t *c, unsigned int lookup_index);
   inline bool apply_string (hb_apply_context_t *c, const hb_set_digest_t *digest) const
   {
     bool ret = false;
 
     if (unlikely (!c->buffer->len || !c->lookup_mask))
@@ -1212,35 +1197,35 @@ struct SubstLookup : Lookup
     if (likely (!is_reverse ()))
     {
 	/* in/out forward substitution */
 	c->buffer->clear_output ();
 	c->buffer->idx = 0;
 
 	while (c->buffer->idx < c->buffer->len)
 	{
-	  if ((c->buffer->cur().mask & c->lookup_mask) &&
-	      digest->may_have (c->buffer->cur().codepoint) &&
+	  if (digest->may_have (c->buffer->cur().codepoint) &&
+	      (c->buffer->cur().mask & c->lookup_mask) &&
 	      apply_once (c))
 	    ret = true;
 	  else
 	    c->buffer->next_glyph ();
 	}
 	if (ret)
 	  c->buffer->swap_buffers ();
     }
     else
     {
 	/* in-place backward substitution */
 	c->buffer->remove_output ();
 	c->buffer->idx = c->buffer->len - 1;
 	do
 	{
-	  if ((c->buffer->cur().mask & c->lookup_mask) &&
-	      digest->may_have (c->buffer->cur().codepoint) &&
+	  if (digest->may_have (c->buffer->cur().codepoint) &&
+	      (c->buffer->cur().mask & c->lookup_mask) &&
 	      apply_once (c))
 	    ret = true;
 	  else
 	    c->buffer->idx--;
 
 	}
 	while ((int) c->buffer->idx >= 0);
     }
@@ -1299,30 +1284,45 @@ struct SubstLookup : Lookup
 				  Supplier<GlyphID> &component_list /* Starting from second for each ligature */)
   {
     TRACE_SERIALIZE (this);
     if (unlikely (!Lookup::serialize (c, SubstLookupSubTable::Ligature, lookup_props, 1))) return TRACE_RETURN (false);
     return TRACE_RETURN (serialize_subtable (c, 0).u.ligature.serialize (c, first_glyphs, ligature_per_first_glyph_count_list, num_first_glyphs,
 									 ligatures_list, component_count_list, component_list));
   }
 
+  template <typename context_t>
+  static inline typename context_t::return_t dispatch_recurse_func (context_t *c, unsigned int lookup_index);
+
+  template <typename context_t>
+  inline typename context_t::return_t dispatch (context_t *c) const
+  {
+    TRACE_DISPATCH (this);
+    unsigned int lookup_type = get_type ();
+    unsigned int count = get_subtable_count ();
+    for (unsigned int i = 0; i < count; i++) {
+      typename context_t::return_t r = get_subtable (i).dispatch (c, lookup_type);
+      if (c->stop_sublookup_iteration (r))
+        return TRACE_RETURN (r);
+    }
+    return TRACE_RETURN (c->default_return_value ());
+  }
+
   inline bool sanitize (hb_sanitize_context_t *c)
   {
     TRACE_SANITIZE (this);
     if (unlikely (!Lookup::sanitize (c))) return TRACE_RETURN (false);
     OffsetArrayOf<SubstLookupSubTable> &list = CastR<OffsetArrayOf<SubstLookupSubTable> > (subTable);
     if (unlikely (!list.sanitize (c, this, get_type ()))) return TRACE_RETURN (false);
 
     if (unlikely (get_type () == SubstLookupSubTable::Extension))
     {
       /* The spec says all subtables of an Extension lookup should
        * have the same type.  This is specially important if one has
-       * a reverse type!
-       *
-       * We just check that they are all either forward, or reverse. */
+       * a reverse type! */
       unsigned int type = get_subtable (0).u.extension.get_type ();
       unsigned int count = get_subtable_count ();
       for (unsigned int i = 1; i < count; i++)
         if (get_subtable (i).u.extension.get_type () != type)
 	  return TRACE_RETURN (false);
     }
     return TRACE_RETURN (true);
   }
@@ -1382,33 +1382,31 @@ inline bool ExtensionSubst::is_reverse (
 {
   unsigned int type = get_type ();
   if (unlikely (type == SubstLookupSubTable::Extension))
     return CastR<ExtensionSubst> (get_subtable<SubstLookupSubTable>()).is_reverse ();
   return SubstLookup::lookup_type_is_reverse (type);
 }
 
 template <typename context_t>
-inline typename context_t::return_t SubstLookup::process_recurse_func (context_t *c, unsigned int lookup_index)
+inline typename context_t::return_t SubstLookup::dispatch_recurse_func (context_t *c, unsigned int lookup_index)
 {
   const GSUB &gsub = *(hb_ot_layout_from_face (c->face)->gsub);
   const SubstLookup &l = gsub.get_lookup (lookup_index);
-  return l.process (c);
+  return l.dispatch (c);
 }
 
 inline bool SubstLookup::apply_recurse_func (hb_apply_context_t *c, unsigned int lookup_index)
 {
   const GSUB &gsub = *(hb_ot_layout_from_face (c->face)->gsub);
   const SubstLookup &l = gsub.get_lookup (lookup_index);
   unsigned int saved_lookup_props = c->lookup_props;
-  unsigned int saved_property = c->property;
   c->set_lookup (l);
   bool ret = l.apply_once (c);
   c->lookup_props = saved_lookup_props;
-  c->property = saved_property;
   return ret;
 }
 
 
 } /* namespace OT */
 
 
 #endif /* HB_OT_LAYOUT_GSUB_TABLE_HH */
--- a/gfx/harfbuzz/src/hb-ot-layout-gsubgpos-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-layout-gsubgpos-private.hh
@@ -33,17 +33,17 @@
 #include "hb-ot-layout-gdef-table.hh"
 #include "hb-set-private.hh"
 
 
 namespace OT {
 
 
 
-#define TRACE_PROCESS(this) \
+#define TRACE_DISPATCH(this) \
 	hb_auto_trace_t<context_t::max_debug_depth, typename context_t::return_t> trace \
 	(&c->debug_depth, c->get_name (), this, HB_FUNC, \
 	 "");
 
 
 #ifndef HB_DEBUG_CLOSURE
 #define HB_DEBUG_CLOSURE (HB_DEBUG+0)
 #endif
@@ -55,17 +55,17 @@ namespace OT {
 
 struct hb_closure_context_t
 {
   inline const char *get_name (void) { return "CLOSURE"; }
   static const unsigned int max_debug_depth = HB_DEBUG_CLOSURE;
   typedef hb_void_t return_t;
   typedef return_t (*recurse_func_t) (hb_closure_context_t *c, unsigned int lookup_index);
   template <typename T>
-  inline return_t process (const T &obj) { obj.closure (this); return HB_VOID; }
+  inline return_t dispatch (const T &obj) { obj.closure (this); return HB_VOID; }
   static return_t default_return_value (void) { return HB_VOID; }
   bool stop_sublookup_iteration (return_t r HB_UNUSED) const { return false; }
   return_t recurse (unsigned int lookup_index)
   {
     if (unlikely (nesting_level_left == 0 || !recurse_func))
       return default_return_value ();
 
     nesting_level_left--;
@@ -104,17 +104,17 @@ struct hb_closure_context_t
 	 "%d glyphs", c->len);
 
 struct hb_would_apply_context_t
 {
   inline const char *get_name (void) { return "WOULD_APPLY"; }
   static const unsigned int max_debug_depth = HB_DEBUG_WOULD_APPLY;
   typedef bool return_t;
   template <typename T>
-  inline return_t process (const T &obj) { return obj.would_apply (this); }
+  inline return_t dispatch (const T &obj) { return obj.would_apply (this); }
   static return_t default_return_value (void) { return false; }
   bool stop_sublookup_iteration (return_t r) const { return r; }
 
   hb_face_t *face;
   const hb_codepoint_t *glyphs;
   unsigned int len;
   bool zero_context;
   unsigned int debug_depth;
@@ -143,17 +143,17 @@ struct hb_would_apply_context_t
 
 struct hb_collect_glyphs_context_t
 {
   inline const char *get_name (void) { return "COLLECT_GLYPHS"; }
   static const unsigned int max_debug_depth = HB_DEBUG_COLLECT_GLYPHS;
   typedef hb_void_t return_t;
   typedef return_t (*recurse_func_t) (hb_collect_glyphs_context_t *c, unsigned int lookup_index);
   template <typename T>
-  inline return_t process (const T &obj) { obj.collect_glyphs (this); return HB_VOID; }
+  inline return_t dispatch (const T &obj) { obj.collect_glyphs (this); return HB_VOID; }
   static return_t default_return_value (void) { return HB_VOID; }
   bool stop_sublookup_iteration (return_t r HB_UNUSED) const { return false; }
   return_t recurse (unsigned int lookup_index)
   {
     if (unlikely (nesting_level_left == 0 || !recurse_func))
       return default_return_value ();
 
     /* Note that GPOS sets recurse_func to NULL already, so it doesn't get
@@ -209,17 +209,17 @@ struct hb_collect_glyphs_context_t
 
 
 struct hb_get_coverage_context_t
 {
   inline const char *get_name (void) { return "GET_COVERAGE"; }
   static const unsigned int max_debug_depth = 0;
   typedef const Coverage &return_t;
   template <typename T>
-  inline return_t process (const T &obj) { return obj.get_coverage (); }
+  inline return_t dispatch (const T &obj) { return obj.get_coverage (); }
   static return_t default_return_value (void) { return Null(Coverage); }
 
   hb_get_coverage_context_t (void) :
 			    debug_depth (0) {}
 
   unsigned int debug_depth;
 };
 
@@ -236,156 +236,284 @@ struct hb_get_coverage_context_t
 
 struct hb_apply_context_t
 {
   inline const char *get_name (void) { return "APPLY"; }
   static const unsigned int max_debug_depth = HB_DEBUG_APPLY;
   typedef bool return_t;
   typedef return_t (*recurse_func_t) (hb_apply_context_t *c, unsigned int lookup_index);
   template <typename T>
-  inline return_t process (const T &obj) { return obj.apply (this); }
+  inline return_t dispatch (const T &obj) { return obj.apply (this); }
   static return_t default_return_value (void) { return false; }
   bool stop_sublookup_iteration (return_t r) const { return r; }
   return_t recurse (unsigned int lookup_index)
   {
     if (unlikely (nesting_level_left == 0 || !recurse_func))
       return default_return_value ();
 
     nesting_level_left--;
     bool ret = recurse_func (this, lookup_index);
     nesting_level_left++;
     return ret;
   }
 
+  unsigned int table_index; /* GSUB/GPOS */
   hb_font_t *font;
   hb_face_t *face;
   hb_buffer_t *buffer;
   hb_direction_t direction;
   hb_mask_t lookup_mask;
+  bool auto_zwj;
   recurse_func_t recurse_func;
   unsigned int nesting_level_left;
   unsigned int lookup_props;
-  unsigned int property; /* propety of first glyph */
   const GDEF &gdef;
   bool has_glyph_classes;
   unsigned int debug_depth;
 
 
-  hb_apply_context_t (hb_font_t *font_,
+  hb_apply_context_t (unsigned int table_index_,
+		      hb_font_t *font_,
 		      hb_buffer_t *buffer_,
-		      hb_mask_t lookup_mask_) :
+		      hb_mask_t lookup_mask_,
+		      bool auto_zwj_) :
+			table_index (table_index_),
 			font (font_), face (font->face), buffer (buffer_),
 			direction (buffer_->props.direction),
 			lookup_mask (lookup_mask_),
+			auto_zwj (auto_zwj_),
 			recurse_func (NULL),
 			nesting_level_left (MAX_NESTING_LEVEL),
-			lookup_props (0), property (0),
+			lookup_props (0),
 			gdef (*hb_ot_layout_from_face (face)->gdef),
 			has_glyph_classes (gdef.has_glyph_classes ()),
 			debug_depth (0) {}
 
-  void set_recurse_func (recurse_func_t func) { recurse_func = func; }
-  void set_lookup_props (unsigned int lookup_props_) { lookup_props = lookup_props_; }
-  void set_lookup (const Lookup &l) { lookup_props = l.get_props (); }
+  inline void set_recurse_func (recurse_func_t func) { recurse_func = func; }
+  inline void set_lookup_props (unsigned int lookup_props_) { lookup_props = lookup_props_; }
+  inline void set_lookup (const Lookup &l) { lookup_props = l.get_props (); }
+
+  struct matcher_t
+  {
+    inline matcher_t (void) :
+	     lookup_props (0),
+	     ignore_zwnj (false),
+	     ignore_zwj (false),
+	     mask (-1),
+#define arg1(arg) (arg) /* Remove the macro to see why it's needed! */
+	     syllable arg1(0),
+#undef arg1
+	     match_func (NULL),
+	     match_data (NULL) {};
+
+    typedef bool (*match_func_t) (hb_codepoint_t glyph_id, const USHORT &value, const void *data);
+
+    inline void set_ignore_zwnj (bool ignore_zwnj_) { ignore_zwnj = ignore_zwnj_; }
+    inline void set_ignore_zwj (bool ignore_zwj_) { ignore_zwj = ignore_zwj_; }
+    inline void set_lookup_props (unsigned int lookup_props_) { lookup_props = lookup_props_; }
+    inline void set_mask (hb_mask_t mask_) { mask = mask_; }
+    inline void set_syllable (uint8_t syllable_)  { syllable = syllable_; }
+    inline void set_match_func (match_func_t match_func_,
+				const void *match_data_)
+    { match_func = match_func_; match_data = match_data_; }
+
+    enum may_match_t {
+      MATCH_NO,
+      MATCH_YES,
+      MATCH_MAYBE
+    };
+
+    inline may_match_t may_match (const hb_glyph_info_t &info,
+				  const USHORT          *glyph_data) const
+    {
+      if (!(info.mask & mask) ||
+	  (syllable && syllable != info.syllable ()))
+	return MATCH_NO;
+
+      if (match_func)
+        return match_func (info.codepoint, *glyph_data, match_data) ? MATCH_YES : MATCH_NO;
+
+      return MATCH_MAYBE;
+    }
+
+    enum may_skip_t {
+      SKIP_NO,
+      SKIP_YES,
+      SKIP_MAYBE
+    };
+
+    inline may_skip_t
+    may_skip (const hb_apply_context_t *c,
+	      const hb_glyph_info_t    &info) const
+    {
+      unsigned int property;
+
+      property = info.glyph_props();
+
+      if (!c->match_properties (info.codepoint, property, lookup_props))
+	return SKIP_YES;
+
+      if (unlikely (_hb_glyph_info_is_default_ignorable (&info) &&
+		    (ignore_zwnj || !_hb_glyph_info_is_zwnj (&info)) &&
+		    (ignore_zwj || !_hb_glyph_info_is_zwj (&info)) &&
+		    !is_a_ligature (info)))
+	return SKIP_MAYBE;
+
+      return SKIP_NO;
+    }
+
+    protected:
+    unsigned int lookup_props;
+    bool ignore_zwnj;
+    bool ignore_zwj;
+    hb_mask_t mask;
+    uint8_t syllable;
+    match_func_t match_func;
+    const void *match_data;
+  };
 
   struct skipping_forward_iterator_t
   {
     inline skipping_forward_iterator_t (hb_apply_context_t *c_,
 					unsigned int start_index_,
 					unsigned int num_items_,
-					bool context_match = false)
+					bool context_match = false) :
+					 idx (start_index_),
+					 c (c_),
+					 match_glyph_data (NULL),
+					 num_items (num_items_),
+					 end (c->buffer->len)
     {
-      c = c_;
-      idx = start_index_;
-      num_items = num_items_;
-      mask = context_match ? -1 : c->lookup_mask;
-      syllable = context_match ? 0 : c->buffer->cur().syllable ();
-      end = c->buffer->len;
+      matcher.set_lookup_props (c->lookup_props);
+      /* Ignore ZWNJ if we are matching GSUB context, or matching GPOS. */
+      matcher.set_ignore_zwnj (context_match || c->table_index == 1);
+      /* Ignore ZWJ if we are matching GSUB context, or matching GPOS, or if asked to. */
+      matcher.set_ignore_zwj (context_match || c->table_index == 1 || c->auto_zwj);
+      if (!context_match)
+	matcher.set_mask (c->lookup_mask);
+      matcher.set_syllable (start_index_ == c->buffer->idx ? c->buffer->cur().syllable () : 0);
     }
-    inline bool has_no_chance (void) const
+    inline void set_lookup_props (unsigned int lookup_props) { matcher.set_lookup_props (lookup_props); }
+    inline void set_syllable (unsigned int syllable) { matcher.set_syllable (syllable); }
+    inline void set_match_func (matcher_t::match_func_t match_func,
+				const void *match_data,
+				const USHORT glyph_data[])
     {
-      return unlikely (num_items && idx + num_items >= end);
+      matcher.set_match_func (match_func, match_data);
+      match_glyph_data = glyph_data;
     }
-    inline void reject (void)
-    {
-      num_items++;
-    }
-    inline bool next (unsigned int *property_out,
-		      unsigned int  lookup_props)
+
+    inline bool has_no_chance (void) const { return unlikely (num_items && idx + num_items >= end); }
+    inline void reject (void) { num_items++; match_glyph_data--; }
+    inline bool next (void)
     {
       assert (num_items > 0);
-      do
+      while (!has_no_chance ())
       {
-	if (has_no_chance ())
-	  return false;
 	idx++;
-      } while (c->should_skip (&c->buffer->info[idx], lookup_props, property_out));
-      num_items--;
-      return (c->buffer->info[idx].mask & mask) && (!syllable || syllable == c->buffer->info[idx].syllable ());
-    }
-    inline bool next (unsigned int *property_out = NULL)
-    {
-      return next (property_out, c->lookup_props);
+	const hb_glyph_info_t &info = c->buffer->info[idx];
+
+	matcher_t::may_skip_t skip = matcher.may_skip (c, info);
+	if (unlikely (skip == matcher_t::SKIP_YES))
+	  continue;
+
+	matcher_t::may_match_t match = matcher.may_match (info, match_glyph_data);
+	if (match == matcher_t::MATCH_YES ||
+	    (match == matcher_t::MATCH_MAYBE &&
+	     skip == matcher_t::SKIP_NO))
+	{
+	  num_items--;
+	  match_glyph_data++;
+	  return true;
+	}
+
+	if (skip == matcher_t::SKIP_NO)
+	  return false;
+      }
+      return false;
     }
 
     unsigned int idx;
     protected:
     hb_apply_context_t *c;
+    matcher_t matcher;
+    const USHORT *match_glyph_data;
+
     unsigned int num_items;
-    hb_mask_t mask;
-    uint8_t syllable;
     unsigned int end;
   };
 
   struct skipping_backward_iterator_t
   {
     inline skipping_backward_iterator_t (hb_apply_context_t *c_,
 					 unsigned int start_index_,
 					 unsigned int num_items_,
-					 hb_mask_t mask_ = 0,
-					 bool match_syllable_ = true)
+					 bool context_match = false) :
+					  idx (start_index_),
+					  c (c_),
+					  match_glyph_data (NULL),
+					  num_items (num_items_)
     {
-      c = c_;
-      idx = start_index_;
-      num_items = num_items_;
-      mask = mask_ ? mask_ : c->lookup_mask;
-      syllable = match_syllable_ ? c->buffer->cur().syllable () : 0;
+      matcher.set_lookup_props (c->lookup_props);
+      /* Ignore ZWNJ if we are matching GSUB context, or matching GPOS. */
+      matcher.set_ignore_zwnj (context_match || c->table_index == 1);
+      /* Ignore ZWJ if we are matching GSUB context, or matching GPOS, or if asked to. */
+      matcher.set_ignore_zwj (context_match || c->table_index == 1 || c->auto_zwj);
+      if (!context_match)
+	matcher.set_mask (c->lookup_mask);
+      matcher.set_syllable (start_index_ == c->buffer->idx ? c->buffer->cur().syllable () : 0);
     }
-    inline bool has_no_chance (void) const
+    inline void set_lookup_props (unsigned int lookup_props) { matcher.set_lookup_props (lookup_props); }
+    inline void set_syllable (unsigned int syllable) { matcher.set_syllable (syllable); }
+    inline void set_match_func (matcher_t::match_func_t match_func,
+				const void *match_data,
+				const USHORT glyph_data[])
     {
-      return unlikely (idx < num_items);
+      matcher.set_match_func (match_func, match_data);
+      match_glyph_data = glyph_data;
     }
-    inline void reject (void)
-    {
-      num_items++;
-    }
-    inline bool prev (unsigned int *property_out,
-		      unsigned int  lookup_props)
+
+    inline bool has_no_chance (void) const { return unlikely (idx < num_items); }
+    inline void reject (void) { num_items++; }
+    inline bool prev (void)
     {
       assert (num_items > 0);
-      do
+      while (!has_no_chance ())
       {
-	if (has_no_chance ())
-	  return false;
 	idx--;
-      } while (c->should_skip (&c->buffer->out_info[idx], lookup_props, property_out));
-      num_items--;
-      return (c->buffer->out_info[idx].mask & mask) && (!syllable || syllable == c->buffer->out_info[idx].syllable ());
-    }
-    inline bool prev (unsigned int *property_out = NULL)
-    {
-      return prev (property_out, c->lookup_props);
+	const hb_glyph_info_t &info = c->buffer->out_info[idx];
+
+	matcher_t::may_skip_t skip = matcher.may_skip (c, info);
+
+	if (unlikely (skip == matcher_t::SKIP_YES))
+	  continue;
+
+	matcher_t::may_match_t match = matcher.may_match (info, match_glyph_data);
+	if (match == matcher_t::MATCH_YES ||
+	    (match == matcher_t::MATCH_MAYBE &&
+	     skip == matcher_t::SKIP_NO))
+	{
+	  num_items--;
+	  match_glyph_data++;
+	  return true;
+	}
+
+	if (skip == matcher_t::SKIP_NO)
+	  return false;
+      }
+      return false;
     }
 
     unsigned int idx;
     protected:
     hb_apply_context_t *c;
+    matcher_t matcher;
+    const USHORT *match_glyph_data;
+
     unsigned int num_items;
-    hb_mask_t mask;
-    uint8_t syllable;
   };
 
   inline bool
   match_properties_mark (hb_codepoint_t  glyph,
 			 unsigned int    glyph_props,
 			 unsigned int    lookup_props) const
   {
     /* If using mark filtering sets, the high short of
@@ -418,47 +546,25 @@ struct hb_apply_context_t
     if (unlikely (glyph_props & HB_OT_LAYOUT_GLYPH_PROPS_MARK))
       return match_properties_mark (glyph, glyph_props, lookup_props);
 
     return true;
   }
 
   inline bool
   check_glyph_property (hb_glyph_info_t *info,
-			unsigned int  lookup_props,
-			unsigned int *property_out) const
+			unsigned int  lookup_props) const
   {
     unsigned int property;
 
     property = info->glyph_props();
-    *property_out = property;
 
     return match_properties (info->codepoint, property, lookup_props);
   }
 
-  inline bool
-  should_skip (hb_glyph_info_t *info,
-	       unsigned int  lookup_props,
-	       unsigned int *property_out) const
-  {
-    unsigned int property;
-
-    property = info->glyph_props();
-    if (property_out)
-      *property_out = property;
-
-    return !match_properties (info->codepoint, property, lookup_props);
-  }
-
-
-  inline bool should_skip_current_glyph (void) const
-  {
-    return should_skip (&buffer->cur(), lookup_props, NULL);
-  }
-
   inline void set_class (hb_codepoint_t glyph_index, unsigned int class_guess) const
   {
     if (likely (has_glyph_classes))
       buffer->cur().glyph_props() = gdef.get_glyph_props (glyph_index);
     else if (class_guess)
       buffer->cur().glyph_props() = class_guess;
   }
 
@@ -593,16 +699,17 @@ static inline bool match_input (hb_apply
 				const void *match_data,
 				unsigned int *end_offset = NULL,
 				bool *p_is_mark_ligature = NULL,
 				unsigned int *p_total_component_count = NULL)
 {
   TRACE_APPLY (NULL);
 
   hb_apply_context_t::skipping_forward_iterator_t skippy_iter (c, c->buffer->idx, count - 1);
+  skippy_iter.set_match_func (match_func, match_data, input);
   if (skippy_iter.has_no_chance ()) return TRACE_RETURN (false);
 
   /*
    * This is perhaps the trickiest part of OpenType...  Remarks:
    *
    * - If all components of the ligature were marks, we call this a mark ligature.
    *
    * - If there is no GDEF, and the ligature is NOT a mark ligature, we categorize
@@ -613,31 +720,27 @@ static inline bool match_input (hb_apply
    *   LAM,LAM,HEH form a ligature, leaving SHADDA,FATHA next to eachother.
    *   However, it would be wrong to ligate that SHADDA,FATHA sequence.o
    *   There is an exception to this: If a ligature tries ligating with marks that
    *   belong to it itself, go ahead, assuming that the font designer knows what
    *   they are doing (otherwise it can break Indic stuff when a matra wants to
    *   ligate with a conjunct...)
    */
 
-  bool is_mark_ligature = !!(c->property & HB_OT_LAYOUT_GLYPH_PROPS_MARK);
+  bool is_mark_ligature = !!(c->buffer->cur().glyph_props() & HB_OT_LAYOUT_GLYPH_PROPS_MARK);
 
   unsigned int total_component_count = 0;
   total_component_count += get_lig_num_comps (c->buffer->cur());
 
   unsigned int first_lig_id = get_lig_id (c->buffer->cur());
   unsigned int first_lig_comp = get_lig_comp (c->buffer->cur());
 
   for (unsigned int i = 1; i < count; i++)
   {
-    unsigned int property;
-
-    if (!skippy_iter.next (&property)) return TRACE_RETURN (false);
-
-    if (likely (!match_func (c->buffer->info[skippy_iter.idx].codepoint, input[i - 1], match_data))) return TRACE_RETURN (false);
+    if (!skippy_iter.next ()) return TRACE_RETURN (false);
 
     unsigned int this_lig_id = get_lig_id (c->buffer->info[skippy_iter.idx]);
     unsigned int this_lig_comp = get_lig_comp (c->buffer->info[skippy_iter.idx]);
 
     if (first_lig_id && first_lig_comp) {
       /* If first component was attached to a previous ligature component,
        * all subsequent components should be attached to the same ligature
        * component, otherwise we shouldn't ligate them. */
@@ -646,40 +749,44 @@ static inline bool match_input (hb_apply
     } else {
       /* If first component was NOT attached to a previous ligature component,
        * all subsequent components should also NOT be attached to any ligature
        * component, unless they are attached to the first component itself! */
       if (this_lig_id && this_lig_comp && (this_lig_id != first_lig_id))
 	return TRACE_RETURN (false);
     }
 
-    is_mark_ligature = is_mark_ligature && (property & HB_OT_LAYOUT_GLYPH_PROPS_MARK);
+    is_mark_ligature = is_mark_ligature && (c->buffer->info[skippy_iter.idx].glyph_props() & HB_OT_LAYOUT_GLYPH_PROPS_MARK);
     total_component_count += get_lig_num_comps (c->buffer->info[skippy_iter.idx]);
   }
 
   if (end_offset)
     *end_offset = skippy_iter.idx - c->buffer->idx + 1;
 
   if (p_is_mark_ligature)
     *p_is_mark_ligature = is_mark_ligature;
 
   if (p_total_component_count)
     *p_total_component_count = total_component_count;
 
   return TRACE_RETURN (true);
 }
 static inline void ligate_input (hb_apply_context_t *c,
 				 unsigned int count, /* Including the first glyph (not matched) */
-				 const USHORT input[] HB_UNUSED, /* Array of input values--start with second glyph */
+				 const USHORT input[], /* Array of input values--start with second glyph */
+				 match_func_t match_func,
+				 const void *match_data,
 				 hb_codepoint_t lig_glyph,
-				 match_func_t match_func HB_UNUSED,
-				 const void *match_data HB_UNUSED,
 				 bool is_mark_ligature,
 				 unsigned int total_component_count)
 {
+  hb_apply_context_t::skipping_forward_iterator_t skippy_iter (c, c->buffer->idx, count - 1);
+  skippy_iter.set_match_func (match_func, match_data, input);
+  if (skippy_iter.has_no_chance ()) return;
+
   /*
    * - If it *is* a mark ligature, we don't allocate a new ligature id, and leave
    *   the ligature to keep its old ligature id.  This will allow it to attach to
    *   a base ligature in GPOS.  Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
    *   and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA wit a
    *   ligature id and component value of 2.  Then if SHADDA,FATHA form a ligature
    *   later, we don't want them to lose their ligature id/component, otherwise
    *   GPOS will fail to correctly position the mark ligature on top of the
@@ -710,17 +817,19 @@ static inline void ligate_input (hb_appl
   unsigned int components_so_far = last_num_components;
 
   if (!is_mark_ligature)
     set_lig_props_for_ligature (c->buffer->cur(), lig_id, total_component_count);
   c->replace_glyph (lig_glyph, klass);
 
   for (unsigned int i = 1; i < count; i++)
   {
-    while (c->should_skip_current_glyph ())
+    if (!skippy_iter.next ()) return;
+
+    while (c->buffer->idx < skippy_iter.idx)
     {
       if (!is_mark_ligature) {
 	unsigned int new_lig_comp = components_so_far - last_num_components +
 				    MIN (MAX (get_lig_comp (c->buffer->cur()), 1u), last_num_components);
 	set_lig_props_for_mark (c->buffer->cur(), lig_id, new_lig_comp);
       }
       c->buffer->next_glyph ();
     }
@@ -750,53 +859,43 @@ static inline bool match_backtrack (hb_a
 				    unsigned int count,
 				    const USHORT backtrack[],
 				    match_func_t match_func,
 				    const void *match_data)
 {
   TRACE_APPLY (NULL);
 
   hb_apply_context_t::skipping_backward_iterator_t skippy_iter (c, c->buffer->backtrack_len (), count, true);
-  if (skippy_iter.has_no_chance ())
-    return TRACE_RETURN (false);
+  skippy_iter.set_match_func (match_func, match_data, backtrack);
+  if (skippy_iter.has_no_chance ()) return TRACE_RETURN (false);
 
   for (unsigned int i = 0; i < count; i++)
-  {
     if (!skippy_iter.prev ())
       return TRACE_RETURN (false);
 
-    if (likely (!match_func (c->buffer->out_info[skippy_iter.idx].codepoint, backtrack[i], match_data)))
-      return TRACE_RETURN (false);
-  }
-
   return TRACE_RETURN (true);
 }
 
 static inline bool match_lookahead (hb_apply_context_t *c,
 				    unsigned int count,
 				    const USHORT lookahead[],
 				    match_func_t match_func,
 				    const void *match_data,
 				    unsigned int offset)
 {
   TRACE_APPLY (NULL);
 
   hb_apply_context_t::skipping_forward_iterator_t skippy_iter (c, c->buffer->idx + offset - 1, count, true);
-  if (skippy_iter.has_no_chance ())
-    return TRACE_RETURN (false);
+  skippy_iter.set_match_func (match_func, match_data, lookahead);
+  if (skippy_iter.has_no_chance ()) return TRACE_RETURN (false);
 
   for (unsigned int i = 0; i < count; i++)
-  {
     if (!skippy_iter.next ())
       return TRACE_RETURN (false);
 
-    if (likely (!match_func (c->buffer->info[skippy_iter.idx].codepoint, lookahead[i], match_data)))
-      return TRACE_RETURN (false);
-  }
-
   return TRACE_RETURN (true);
 }
 
 
 
 struct LookupRecord
 {
   inline bool sanitize (hb_sanitize_context_t *c) {
@@ -819,64 +918,101 @@ static inline void recurse_lookups (cont
 				    const LookupRecord lookupRecord[] /* Array of LookupRecords--in design order */)
 {
   for (unsigned int i = 0; i < lookupCount; i++)
     c->recurse (lookupRecord->lookupListIndex);
 }
 
 static inline bool apply_lookup (hb_apply_context_t *c,
 				 unsigned int count, /* Including the first glyph */
+				 const USHORT input[], /* Array of input values--start with second glyph */
+				 match_func_t match_func,
+				 const void *match_data,
 				 unsigned int lookupCount,
 				 const LookupRecord lookupRecord[] /* Array of LookupRecords--in design order */)
 {
   TRACE_APPLY (NULL);
 
   unsigned int end = c->buffer->len;
   if (unlikely (count == 0 || c->buffer->idx + count > end))
     return TRACE_RETURN (false);
 
   /* TODO We don't support lookupRecord arrays that are not increasing:
    *      Should be easy for in_place ones at least. */
 
   /* Note: If sublookup is reverse, it will underflow after the first loop
    * and we jump out of it.  Not entirely disastrous.  So we don't check
    * for reverse lookup here.
    */
-  for (unsigned int i = 0; i < count; /* NOP */)
+
+  hb_apply_context_t::skipping_forward_iterator_t skippy_iter (c, c->buffer->idx, count - 1);
+  skippy_iter.set_match_func (match_func, match_data, input);
+  uint8_t syllable = c->buffer->cur().syllable();
+
+  unsigned int i = 0;
+  if (lookupCount && 0 == lookupRecord->sequenceIndex)
   {
-    if (unlikely (c->buffer->idx == end))
-      return TRACE_RETURN (true);
-    while (c->should_skip_current_glyph ())
+    unsigned int old_pos = c->buffer->idx;
+
+    /* Apply a lookup */
+    bool done = c->recurse (lookupRecord->lookupListIndex);
+
+    lookupRecord++;
+    lookupCount--;
+    /* Err, this is wrong if the lookup jumped over some glyphs */
+    i += c->buffer->idx - old_pos;
+
+    if (!done)
+      goto not_applied;
+    else
     {
-      /* No lookup applied for this index */
+      /* Reinitialize iterator. */
+      hb_apply_context_t::skipping_forward_iterator_t tmp (c, c->buffer->idx - 1, count - i);
+      tmp.set_syllable (syllable);
+      skippy_iter = tmp;
+    }
+  }
+  else
+  {
+  not_applied:
+    /* No lookup applied for this index */
+    c->buffer->next_glyph ();
+    i++;
+  }
+  while (i < count)
+  {
+    if (!skippy_iter.next ()) return TRACE_RETURN (true);
+    while (c->buffer->idx < skippy_iter.idx)
       c->buffer->next_glyph ();
-      if (unlikely (c->buffer->idx == end))
-	return TRACE_RETURN (true);
-    }
 
     if (lookupCount && i == lookupRecord->sequenceIndex)
     {
       unsigned int old_pos = c->buffer->idx;
 
       /* Apply a lookup */
       bool done = c->recurse (lookupRecord->lookupListIndex);
 
       lookupRecord++;
       lookupCount--;
       /* Err, this is wrong if the lookup jumped over some glyphs */
       i += c->buffer->idx - old_pos;
-      if (unlikely (c->buffer->idx == end))
-	return TRACE_RETURN (true);
 
       if (!done)
-	goto not_applied;
+	goto not_applied2;
+      else
+      {
+        /* Reinitialize iterator. */
+	hb_apply_context_t::skipping_forward_iterator_t tmp (c, c->buffer->idx - 1, count - i);
+	tmp.set_syllable (syllable);
+	skippy_iter = tmp;
+      }
     }
     else
     {
-    not_applied:
+    not_applied2:
       /* No lookup applied for this index */
       c->buffer->next_glyph ();
       i++;
     }
   }
 
   return TRACE_RETURN (true);
 }
@@ -948,17 +1084,18 @@ static inline bool context_apply_lookup 
 					 unsigned int lookupCount,
 					 const LookupRecord lookupRecord[],
 					 ContextApplyLookupContext &lookup_context)
 {
   return match_input (c,
 		      inputCount, input,
 		      lookup_context.funcs.match, lookup_context.match_data)
       && apply_lookup (c,
-		       inputCount,
+		       inputCount, input,
+		       lookup_context.funcs.match, lookup_context.match_data,
 		       lookupCount, lookupRecord);
 }
 
 struct Rule
 {
   inline void closure (hb_closure_context_t *c, ContextClosureLookupContext &lookup_context) const
   {
     TRACE_CLOSURE (this);
@@ -1343,23 +1480,23 @@ struct ContextFormat3
 					 * design order */
   public:
   DEFINE_SIZE_ARRAY2 (6, coverage, lookupRecordX);
 };
 
 struct Context
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
-    case 2: return TRACE_RETURN (c->process (u.format2));
-    case 3: return TRACE_RETURN (c->process (u.format3));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
+    case 2: return TRACE_RETURN (c->dispatch (u.format2));
+    case 3: return TRACE_RETURN (c->dispatch (u.format3));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -1484,17 +1621,18 @@ static inline bool chain_context_apply_l
       && match_backtrack (c,
 			  backtrackCount, backtrack,
 			  lookup_context.funcs.match, lookup_context.match_data[0])
       && match_lookahead (c,
 			  lookaheadCount, lookahead,
 			  lookup_context.funcs.match, lookup_context.match_data[2],
 			  lookahead_offset)
       && apply_lookup (c,
-		       inputCount,
+		       inputCount, input,
+		       lookup_context.funcs.match, lookup_context.match_data[1],
 		       lookupCount, lookupRecord);
 }
 
 struct ChainRule
 {
   inline void closure (hb_closure_context_t *c, ChainContextClosureLookupContext &lookup_context) const
   {
     TRACE_CLOSURE (this);
@@ -1958,23 +2096,23 @@ struct ChainContextFormat3
 					 * design order) */
   public:
   DEFINE_SIZE_MIN (10);
 };
 
 struct ChainContext
 {
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_PROCESS (this);
+    TRACE_DISPATCH (this);
     switch (u.format) {
-    case 1: return TRACE_RETURN (c->process (u.format1));
-    case 2: return TRACE_RETURN (c->process (u.format2));
-    case 3: return TRACE_RETURN (c->process (u.format3));
+    case 1: return TRACE_RETURN (c->dispatch (u.format1));
+    case 2: return TRACE_RETURN (c->dispatch (u.format2));
+    case 3: return TRACE_RETURN (c->dispatch (u.format3));
     default:return TRACE_RETURN (c->default_return_value ());
     }
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
@@ -2038,19 +2176,19 @@ struct Extension
   inline const X& get_subtable (void) const
   {
     unsigned int offset = get_offset ();
     if (unlikely (!offset)) return Null(typename T::LookupSubTable);
     return StructAtOffset<typename T::LookupSubTable> (this, offset);
   }
 
   template <typename context_t>
-  inline typename context_t::return_t process (context_t *c) const
+  inline typename context_t::return_t dispatch (context_t *c) const
   {
-    return get_subtable<typename T::LookupSubTable> ().process (c, get_type ());
+    return get_subtable<typename T::LookupSubTable> ().dispatch (c, get_type ());
   }
 
   inline bool sanitize_self (hb_sanitize_context_t *c) {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return TRACE_RETURN (false);
     switch (u.format) {
     case 1: return TRACE_RETURN (u.format1.sanitize (c));
     default:return TRACE_RETURN (true);
--- a/gfx/harfbuzz/src/hb-ot-layout-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-layout-private.hh
@@ -38,28 +38,80 @@
 #include "hb-set-private.hh"
 
 
 /* buffer var allocations, used during the GSUB/GPOS processing */
 #define glyph_props()		var1.u16[0] /* GDEF glyph properties */
 #define syllable()		var1.u8[2] /* GSUB/GPOS shaping boundaries */
 #define lig_props()		var1.u8[3] /* GSUB/GPOS ligature tracking */
 
+/* buffer var allocations, used during the entire shaping process */
+#define unicode_props0()	var2.u8[0]
+#define unicode_props1()	var2.u8[1]
+
+
+inline void
+_hb_glyph_info_set_unicode_props (hb_glyph_info_t *info, hb_unicode_funcs_t *unicode)
+{
+  info->unicode_props0() = ((unsigned int) unicode->general_category (info->codepoint)) |
+			   (unicode->is_default_ignorable (info->codepoint) ? 0x80 : 0) |
+			   (info->codepoint == 0x200C ? 0x40 : 0) |
+			   (info->codepoint == 0x200D ? 0x20 : 0);
+  info->unicode_props1() = unicode->modified_combining_class (info->codepoint);
+}
+
+inline hb_unicode_general_category_t
+_hb_glyph_info_get_general_category (const hb_glyph_info_t *info)
+{
+  return (hb_unicode_general_category_t) (info->unicode_props0() & 0x1F);
+}
+
+inline void
+_hb_glyph_info_set_modified_combining_class (hb_glyph_info_t *info, unsigned int modified_class)
+{
+  info->unicode_props1() = modified_class;
+}
+
+inline unsigned int
+_hb_glyph_info_get_modified_combining_class (const hb_glyph_info_t *info)
+{
+  return info->unicode_props1();
+}
+
+inline hb_bool_t
+_hb_glyph_info_is_default_ignorable (const hb_glyph_info_t *info)
+{
+  return !!(info->unicode_props0() & 0x80);
+}
+
+inline hb_bool_t
+_hb_glyph_info_is_zwnj (const hb_glyph_info_t *info)
+{
+  return !!(info->unicode_props0() & 0x40);
+}
+
+inline hb_bool_t
+_hb_glyph_info_is_zwj (const hb_glyph_info_t *info)
+{
+  return !!(info->unicode_props0() & 0x20);
+}
+
+
 #define hb_ot_layout_from_face(face) ((hb_ot_layout_t *) face->shaper_data.ot)
 
 /*
  * GDEF
  */
 
 typedef enum {
-  HB_OT_LAYOUT_GLYPH_PROPS_UNCLASSIFIED	= 0x0001,
-  HB_OT_LAYOUT_GLYPH_PROPS_BASE_GLYPH	= 0x0002,
-  HB_OT_LAYOUT_GLYPH_PROPS_LIGATURE	= 0x0004,
-  HB_OT_LAYOUT_GLYPH_PROPS_MARK		= 0x0008,
-  HB_OT_LAYOUT_GLYPH_PROPS_COMPONENT	= 0x0010
+  HB_OT_LAYOUT_GLYPH_PROPS_UNCLASSIFIED	= 1 << HB_OT_LAYOUT_GLYPH_CLASS_UNCLASSIFIED,
+  HB_OT_LAYOUT_GLYPH_PROPS_BASE_GLYPH	= 1 << HB_OT_LAYOUT_GLYPH_CLASS_BASE_GLYPH,
+  HB_OT_LAYOUT_GLYPH_PROPS_LIGATURE	= 1 << HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE,
+  HB_OT_LAYOUT_GLYPH_PROPS_MARK		= 1 << HB_OT_LAYOUT_GLYPH_CLASS_MARK,
+  HB_OT_LAYOUT_GLYPH_PROPS_COMPONENT	= 1 << HB_OT_LAYOUT_GLYPH_CLASS_COMPONENT
 } hb_ot_layout_glyph_class_mask_t;
 
 
 
 /*
  * GSUB/GPOS
  */
 
@@ -149,34 +201,36 @@ hb_ot_layout_lookup_would_substitute_fas
 HB_INTERNAL void
 hb_ot_layout_substitute_start (hb_font_t    *font,
 			       hb_buffer_t  *buffer);
 
 HB_INTERNAL hb_bool_t
 hb_ot_layout_substitute_lookup (hb_font_t    *font,
 				hb_buffer_t  *buffer,
 				unsigned int  lookup_index,
-				hb_mask_t     mask);
+				hb_mask_t     mask,
+				hb_bool_t     auto_zwj);
 
 /* Should be called after all the substitute_lookup's are done */
 HB_INTERNAL void
 hb_ot_layout_substitute_finish (hb_font_t    *font,
 				hb_buffer_t  *buffer);
 
 
 /* Should be called before all the position_lookup's are done.  Resets positions to zero. */
 HB_INTERNAL void
 hb_ot_layout_position_start (hb_font_t    *font,
 			     hb_buffer_t  *buffer);
 
 HB_INTERNAL hb_bool_t
 hb_ot_layout_position_lookup (hb_font_t    *font,
 			      hb_buffer_t  *buffer,
 			      unsigned int  lookup_index,
-			      hb_mask_t     mask);
+			      hb_mask_t     mask,
+			      hb_bool_t     auto_zwj);
 
 /* Should be called after all the position_lookup's are done */
 HB_INTERNAL void
 hb_ot_layout_position_finish (hb_font_t    *font,
 			      hb_buffer_t  *buffer);
 
 
 
--- a/gfx/harfbuzz/src/hb-ot-layout.cc
+++ b/gfx/harfbuzz/src/hb-ot-layout.cc
@@ -65,19 +65,25 @@ hb_ot_layout_t *
   if (unlikely ((layout->gsub_lookup_count && !layout->gsub_digests) ||
 		(layout->gpos_lookup_count && !layout->gpos_digests)))
   {
     _hb_ot_layout_destroy (layout);
     return NULL;
   }
 
   for (unsigned int i = 0; i < layout->gsub_lookup_count; i++)
+  {
+    layout->gsub_digests[i].init ();
     layout->gsub->get_lookup (i).add_coverage (&layout->gsub_digests[i]);
+  }
   for (unsigned int i = 0; i < layout->gpos_lookup_count; i++)
+  {
+    layout->gpos_digests[i].init ();
     layout->gpos->get_lookup (i).add_coverage (&layout->gpos_digests[i]);
+  }
 
   return layout;
 }
 
 void
 _hb_ot_layout_destroy (hb_ot_layout_t *layout)
 {
   hb_blob_destroy (layout->gdef_blob);
@@ -661,21 +667,22 @@ hb_ot_layout_substitute_start (hb_font_t
 {
   OT::GSUB::substitute_start (font, buffer);
 }
 
 hb_bool_t
 hb_ot_layout_substitute_lookup (hb_font_t    *font,
 				hb_buffer_t  *buffer,
 				unsigned int  lookup_index,
-				hb_mask_t     mask)
+				hb_mask_t     mask,
+				hb_bool_t     auto_zwj)
 {
   if (unlikely (lookup_index >= hb_ot_layout_from_face (font->face)->gsub_lookup_count)) return false;
 
-  OT::hb_apply_context_t c (font, buffer, mask);
+  OT::hb_apply_context_t c (0, font, buffer, mask, auto_zwj);
 
   const OT::SubstLookup& l = hb_ot_layout_from_face (font->face)->gsub->get_lookup (lookup_index);
 
   return l.apply_string (&c, &hb_ot_layout_from_face (font->face)->gsub_digests[lookup_index]);
 }
 
 void
 hb_ot_layout_substitute_finish (hb_font_t *font, hb_buffer_t *buffer)
@@ -710,21 +717,22 @@ hb_ot_layout_position_start (hb_font_t *
 {
   OT::GPOS::position_start (font, buffer);
 }
 
 hb_bool_t
 hb_ot_layout_position_lookup (hb_font_t    *font,
 			      hb_buffer_t  *buffer,
 			      unsigned int  lookup_index,
-			      hb_mask_t     mask)
+			      hb_mask_t     mask,
+			      hb_bool_t     auto_zwj)
 {
   if (unlikely (lookup_index >= hb_ot_layout_from_face (font->face)->gpos_lookup_count)) return false;
 
-  OT::hb_apply_context_t c (font, buffer, mask);
+  OT::hb_apply_context_t c (1, font, buffer, mask, auto_zwj);
 
   const OT::PosLookup& l = hb_ot_layout_from_face (font->face)->gpos->get_lookup (lookup_index);
 
   return l.apply_string (&c, &hb_ot_layout_from_face (font->face)->gpos_digests[lookup_index]);
 }
 
 void
 hb_ot_layout_position_finish (hb_font_t *font, hb_buffer_t *buffer)
--- a/gfx/harfbuzz/src/hb-ot-map-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-map-private.hh
@@ -44,24 +44,26 @@ struct hb_ot_map_t
 
   struct feature_map_t {
     hb_tag_t tag; /* should be first for our bsearch to work */
     unsigned int index[2]; /* GSUB/GPOS */
     unsigned int stage[2]; /* GSUB/GPOS */
     unsigned int shift;
     hb_mask_t mask;
     hb_mask_t _1_mask; /* mask for value=1, for quick access */
-    hb_bool_t needs_fallback;
+    unsigned int needs_fallback : 1;
+    unsigned int auto_zwj : 1;
 
     static int cmp (const feature_map_t *a, const feature_map_t *b)
     { return a->tag < b->tag ? -1 : a->tag > b->tag ? 1 : 0; }
   };
 
   struct lookup_map_t {
-    unsigned int index;
+    unsigned short index;
+    unsigned short auto_zwj : 1;
     hb_mask_t mask;
 
     static int cmp (const lookup_map_t *a, const lookup_map_t *b)
     { return a->index < b->index ? -1 : a->index > b->index ? 1 : 0; }
   };
 
   typedef void (*pause_func_t) (const struct hb_ot_shape_plan_t *plan, hb_font_t *font, hb_buffer_t *buffer);
 
@@ -131,37 +133,63 @@ struct hb_ot_map_t
   hb_tag_t chosen_script[2];
   bool found_script[2];
 
   private:
 
   HB_INTERNAL void add_lookups (hb_face_t    *face,
 				unsigned int  table_index,
 				unsigned int  feature_index,
-				hb_mask_t     mask);
+				hb_mask_t     mask,
+				bool          auto_zwj);
 
   hb_mask_t global_mask;
 
   hb_prealloced_array_t<feature_map_t, 8> features;
   hb_prealloced_array_t<lookup_map_t, 32> lookups[2]; /* GSUB/GPOS */
   hb_prealloced_array_t<pause_map_t, 1> pauses[2]; /* GSUB/GPOS */
 };
 
+enum hb_ot_map_feature_flags_t {
+  F_NONE		= 0x0000,
+  F_GLOBAL		= 0x0001,
+  F_HAS_FALLBACK	= 0x0002,
+  F_MANUAL_ZWJ		= 0x0004
+};
+/* Macro version for where const is desired. */
+#define F_COMBINE(l,r) (hb_ot_map_feature_flags_t ((unsigned int) (l) | (unsigned int) (r)))
+inline hb_ot_map_feature_flags_t
+operator | (hb_ot_map_feature_flags_t l, hb_ot_map_feature_flags_t r)
+{ return hb_ot_map_feature_flags_t ((unsigned int) l | (unsigned int) r); }
+inline hb_ot_map_feature_flags_t
+operator & (hb_ot_map_feature_flags_t l, hb_ot_map_feature_flags_t r)
+{ return hb_ot_map_feature_flags_t ((unsigned int) l & (unsigned int) r); }
+inline hb_ot_map_feature_flags_t
+operator ~ (hb_ot_map_feature_flags_t r)
+{ return hb_ot_map_feature_flags_t (~(unsigned int) r); }
+inline hb_ot_map_feature_flags_t&
+operator |= (hb_ot_map_feature_flags_t &l, hb_ot_map_feature_flags_t r)
+{ l = l | r; return l; }
+inline hb_ot_map_feature_flags_t&
+operator &= (hb_ot_map_feature_flags_t& l, hb_ot_map_feature_flags_t r)
+{ l = l & r; return l; }
+
 
 struct hb_ot_map_builder_t
 {
   public:
 
   HB_INTERNAL hb_ot_map_builder_t (hb_face_t *face_,
 				   const hb_segment_properties_t *props_);
 
-  HB_INTERNAL void add_feature (hb_tag_t tag, unsigned int value, bool global, bool has_fallback = false);
+  HB_INTERNAL void add_feature (hb_tag_t tag, unsigned int value,
+				hb_ot_map_feature_flags_t flags);
 
-  inline void add_bool_feature (hb_tag_t tag, bool global = true, bool has_fallback = false)
-  { add_feature (tag, 1, global, has_fallback); }
+  inline void add_global_bool_feature (hb_tag_t tag)
+  { add_feature (tag, 1, F_GLOBAL); }
 
   inline void add_gsub_pause (hb_ot_map_t::pause_func_t pause_func)
   { add_pause (0, pause_func); }
   inline void add_gpos_pause (hb_ot_map_t::pause_func_t pause_func)
   { add_pause (1, pause_func); }
 
   HB_INTERNAL void compile (struct hb_ot_map_t &m);
 
@@ -172,18 +200,17 @@ struct hb_ot_map_builder_t
   }
 
   private:
 
   struct feature_info_t {
     hb_tag_t tag;
     unsigned int seq; /* sequence#, used for stable sorting only */
     unsigned int max_value;
-    bool global; /* whether the feature applies value to every glyph in the buffer */
-    bool has_fallback; /* whether to allocate bits even if feature not found */
+    hb_ot_map_feature_flags_t flags;
     unsigned int default_value; /* for non-global features, what should the unset glyphs take */
     unsigned int stage[2]; /* GSUB/GPOS */
 
     static int cmp (const feature_info_t *a, const feature_info_t *b)
     { return (a->tag != b->tag) ?  (a->tag < b->tag ? -1 : 1) : (a->seq < b->seq ? -1 : 1); }
   };
 
   struct pause_info_t {
--- a/gfx/harfbuzz/src/hb-ot-map.cc
+++ b/gfx/harfbuzz/src/hb-ot-map.cc
@@ -28,17 +28,18 @@
 
 #include "hb-ot-map-private.hh"
 
 
 void
 hb_ot_map_t::add_lookups (hb_face_t    *face,
 			  unsigned int  table_index,
 			  unsigned int  feature_index,
-			  hb_mask_t     mask)
+			  hb_mask_t     mask,
+			  bool          auto_zwj)
 {
   unsigned int lookup_indices[32];
   unsigned int offset, len;
 
   offset = 0;
   do {
     len = ARRAY_LENGTH (lookup_indices);
     hb_ot_layout_feature_get_lookups (face,
@@ -48,16 +49,17 @@ hb_ot_map_t::add_lookups (hb_face_t    *
 				      lookup_indices);
 
     for (unsigned int i = 0; i < len; i++) {
       hb_ot_map_t::lookup_map_t *lookup = lookups[table_index].push ();
       if (unlikely (!lookup))
         return;
       lookup->mask = mask;
       lookup->index = lookup_indices[i];
+      lookup->auto_zwj = auto_zwj;
     }
 
     offset += len;
   } while (len == ARRAY_LENGTH (lookup_indices));
 }
 
 hb_ot_map_builder_t::hb_ot_map_builder_t (hb_face_t *face_,
 					  const hb_segment_properties_t *props_)
@@ -79,68 +81,77 @@ hb_ot_map_builder_t::hb_ot_map_builder_t
 
   for (unsigned int table_index = 0; table_index < 2; table_index++) {
     hb_tag_t table_tag = table_tags[table_index];
     found_script[table_index] = hb_ot_layout_table_choose_script (face, table_tag, script_tags, &script_index[table_index], &chosen_script[table_index]);
     hb_ot_layout_script_find_language (face, table_tag, script_index[table_index], language_tag, &language_index[table_index]);
   }
 }
 
-void hb_ot_map_builder_t::add_feature (hb_tag_t tag, unsigned int value, bool global, bool has_fallback)
+void hb_ot_map_builder_t::add_feature (hb_tag_t tag, unsigned int value,
+				       hb_ot_map_feature_flags_t flags)
 {
   feature_info_t *info = feature_infos.push();
   if (unlikely (!info)) return;
   info->tag = tag;
   info->seq = feature_infos.len;
   info->max_value = value;
-  info->global = global;
-  info->has_fallback = has_fallback;
-  info->default_value = global ? value : 0;
+  info->flags = flags;
+  info->default_value = (flags & F_GLOBAL) ? value : 0;
   info->stage[0] = current_stage[0];
   info->stage[1] = current_stage[1];
 }
 
 /* Keep the next two functions in sync. */
 
 void hb_ot_map_t::substitute (const hb_ot_shape_plan_t *plan, hb_font_t *font, hb_buffer_t *buffer) const
 {
   const unsigned int table_index = 0;
   unsigned int i = 0;
 
   for (unsigned int pause_index = 0; pause_index < pauses[table_index].len; pause_index++) {
     const pause_map_t *pause = &pauses[table_index][pause_index];
     for (; i < pause->num_lookups; i++)
-      hb_ot_layout_substitute_lookup (font, buffer, lookups[table_index][i].index, lookups[table_index][i].mask);
+      hb_ot_layout_substitute_lookup (font, buffer,
+				      lookups[table_index][i].index,
+				      lookups[table_index][i].mask,
+				      lookups[table_index][i].auto_zwj);
 
     buffer->clear_output ();
 
     if (pause->callback)
       pause->callback (plan, font, buffer);
   }
 
   for (; i < lookups[table_index].len; i++)
-    hb_ot_layout_substitute_lookup (font, buffer, lookups[table_index][i].index, lookups[table_index][i].mask);
+    hb_ot_layout_substitute_lookup (font, buffer, lookups[table_index][i].index,
+				    lookups[table_index][i].mask,
+				    lookups[table_index][i].auto_zwj);
 }
 
 void hb_ot_map_t::position (const hb_ot_shape_plan_t *plan, hb_font_t *font, hb_buffer_t *buffer) const
 {
   const unsigned int table_index = 1;
   unsigned int i = 0;
 
   for (unsigned int pause_index = 0; pause_index < pauses[table_index].len; pause_index++) {
     const pause_map_t *pause = &pauses[table_index][pause_index];
     for (; i < pause->num_lookups; i++)
-      hb_ot_layout_position_lookup (font, buffer, lookups[table_index][i].index, lookups[table_index][i].mask);
+      hb_ot_layout_position_lookup (font, buffer, lookups[table_index][i].index,
+				    lookups[table_index][i].mask,
+				    lookups[table_index][i].auto_zwj);
 
     if (pause->callback)
       pause->callback (plan, font, buffer);
   }
 
   for (; i < lookups[table_index].len; i++)
-    hb_ot_layout_position_lookup (font, buffer, lookups[table_index][i].index, lookups[table_index][i].mask);
+    hb_ot_layout_position_lookup (font, buffer, lookups[table_index][i].index,
+				  lookups[table_index][i].mask,
+				  lookups[table_index][i].auto_zwj);
 }
 
 void hb_ot_map_t::collect_lookups (unsigned int table_index, hb_set_t *lookups_out) const
 {
   for (unsigned int i = 0; i < lookups[table_index].len; i++)
     hb_set_add (lookups_out, lookups[table_index][i].index);
 }
 
@@ -171,41 +182,41 @@ hb_ot_map_builder_t::compile (hb_ot_map_
   /* Sort features and merge duplicates */
   {
     feature_infos.sort ();
     unsigned int j = 0;
     for (unsigned int i = 1; i < feature_infos.len; i++)
       if (feature_infos[i].tag != feature_infos[j].tag)
 	feature_infos[++j] = feature_infos[i];
       else {
-	if (feature_infos[i].global) {
-	  feature_infos[j].global = true;
+	if (feature_infos[i].flags & F_GLOBAL) {
+	  feature_infos[j].flags |= F_GLOBAL;
 	  feature_infos[j].max_value = feature_infos[i].max_value;
 	  feature_infos[j].default_value = feature_infos[i].default_value;
 	} else {
-	  feature_infos[j].global = false;
+	  feature_infos[j].flags &= ~F_GLOBAL;
 	  feature_infos[j].max_value = MAX (feature_infos[j].max_value, feature_infos[i].max_value);
+	  /* Inherit default_value from j */
 	}
-	feature_infos[j].has_fallback = feature_infos[j].has_fallback || feature_infos[i].has_fallback;
+	feature_infos[j].flags |= (feature_infos[i].flags & F_HAS_FALLBACK);
 	feature_infos[j].stage[0] = MIN (feature_infos[j].stage[0], feature_infos[i].stage[0]);
 	feature_infos[j].stage[1] = MIN (feature_infos[j].stage[1], feature_infos[i].stage[1]);
-	/* Inherit default_value from j */
       }
     feature_infos.shrink (j + 1);
   }
 
 
   /* Allocate bits now */
   unsigned int next_bit = 1;
   for (unsigned int i = 0; i < feature_infos.len; i++) {
     const feature_info_t *info = &feature_infos[i];
 
     unsigned int bits_needed;
 
-    if (info->global && info->max_value == 1)
+    if ((info->flags & F_GLOBAL) && info->max_value == 1)
       /* Uses the global bit */
       bits_needed = 0;
     else
       bits_needed = _hb_bit_storage (info->max_value);
 
     if (!info->max_value || next_bit + bits_needed > 8 * sizeof (hb_mask_t))
       continue; /* Feature disabled, or not enough bits. */
 
@@ -214,39 +225,39 @@ hb_ot_map_builder_t::compile (hb_ot_map_
     unsigned int feature_index[2];
     for (unsigned int table_index = 0; table_index < 2; table_index++)
       found |= hb_ot_layout_language_find_feature (face,
 						   table_tags[table_index],
 						   script_index[table_index],
 						   language_index[table_index],
 						   info->tag,
 						   &feature_index[table_index]);
-    if (!found && !info->has_fallback)
+    if (!found && !(info->flags & F_HAS_FALLBACK))
       continue;
 
 
     hb_ot_map_t::feature_map_t *map = m.features.push ();
     if (unlikely (!map))
       break;
 
     map->tag = info->tag;
     map->index[0] = feature_index[0];
     map->index[1] = feature_index[1];
     map->stage[0] = info->stage[0];
     map->stage[1] = info->stage[1];
-    if (info->global && info->max_value == 1) {
+    map->auto_zwj = !(info->flags & F_MANUAL_ZWJ);
+    if ((info->flags & F_GLOBAL) && info->max_value == 1) {
       /* Uses the global bit */
       map->shift = 0;
       map->mask = 1;
     } else {
       map->shift = next_bit;
       map->mask = (1 << (next_bit + bits_needed)) - (1 << next_bit);
       next_bit += bits_needed;
-      if (info->global)
-	m.global_mask |= (info->default_value << map->shift) & map->mask;
+      m.global_mask |= (info->default_value << map->shift) & map->mask;
     }
     map->_1_mask = (1 << map->shift) & map->mask;
     map->needs_fallback = !found;
 
   }
   feature_infos.shrink (0); /* Done with these */
 
 
@@ -259,37 +270,43 @@ hb_ot_map_builder_t::compile (hb_ot_map_
     /* Collect lookup indices for features */
 
     unsigned int required_feature_index;
     if (hb_ot_layout_language_get_required_feature_index (face,
 							  table_tag,
 							  script_index[table_index],
 							  language_index[table_index],
 							  &required_feature_index))
-      m.add_lookups (face, table_index, required_feature_index, 1);
+      m.add_lookups (face, table_index, required_feature_index, 1, true);
 
     unsigned int pause_index = 0;
     unsigned int last_num_lookups = 0;
     for (unsigned stage = 0; stage < current_stage[table_index]; stage++)
     {
       for (unsigned i = 0; i < m.features.len; i++)
         if (m.features[i].stage[table_index] == stage)
-	  m.add_lookups (face, table_index, m.features[i].index[table_index], m.features[i].mask);
+	  m.add_lookups (face, table_index,
+			 m.features[i].index[table_index],
+			 m.features[i].mask,
+			 m.features[i].auto_zwj);
 
       /* Sort lookups and merge duplicates */
       if (last_num_lookups < m.lookups[table_index].len)
       {
 	m.lookups[table_index].sort (last_num_lookups, m.lookups[table_index].len);
 
 	unsigned int j = last_num_lookups;
 	for (unsigned int i = j + 1; i < m.lookups[table_index].len; i++)
 	  if (m.lookups[table_index][i].index != m.lookups[table_index][j].index)
 	    m.lookups[table_index][++j] = m.lookups[table_index][i];
 	  else
+	  {
 	    m.lookups[table_index][j].mask |= m.lookups[table_index][i].mask;
+	    m.lookups[table_index][j].auto_zwj &= m.lookups[table_index][i].auto_zwj;
+	  }
 	m.lookups[table_index].shrink (j + 1);
       }
 
       last_num_lookups = m.lookups[table_index].len;
 
       if (pause_index < pauses[table_index].len && pauses[table_index][pause_index].stage == stage) {
 	hb_ot_map_t::pause_map_t *pause_map = m.pauses[table_index].push ();
 	if (likely (pause_map)) {
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-arabic-fallback.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-arabic-fallback.hh
@@ -207,17 +207,19 @@ static const arabic_fallback_plan_t arab
 static arabic_fallback_plan_t *
 arabic_fallback_plan_create (const hb_ot_shape_plan_t *plan,
 			     hb_font_t *font)
 {
   arabic_fallback_plan_t *fallback_plan = (arabic_fallback_plan_t *) calloc (1, sizeof (arabic_fallback_plan_t));
   if (unlikely (!fallback_plan))
     return const_cast<arabic_fallback_plan_t *> (&arabic_fallback_plan_nil);
 
-  for (unsigned int i = 0; i < ARABIC_NUM_FALLBACK_FEATURES; i++) {
+  for (unsigned int i = 0; i < ARABIC_NUM_FALLBACK_FEATURES; i++)
+  {
+    fallback_plan->digest_array[i].init ();
     fallback_plan->mask_array[i] = plan->map.get_1_mask (arabic_fallback_features[i]);
     if (fallback_plan->mask_array[i]) {
       fallback_plan->lookup_array[i] = arabic_fallback_synthesize_lookup (plan, font, i);
       if (fallback_plan->lookup_array[i])
 	fallback_plan->lookup_array[i]->add_coverage (&fallback_plan->digest_array[i]);
     }
   }
 
@@ -239,15 +241,15 @@ arabic_fallback_plan_destroy (arabic_fal
 
 static void
 arabic_fallback_plan_shape (arabic_fallback_plan_t *fallback_plan,
 			    hb_font_t *font,
 			    hb_buffer_t *buffer)
 {
   for (unsigned int i = 0; i < ARABIC_NUM_FALLBACK_FEATURES; i++)
     if (fallback_plan->lookup_array[i]) {
-      OT::hb_apply_context_t c (font, buffer, fallback_plan->mask_array[i]);
+      OT::hb_apply_context_t c (0, font, buffer, fallback_plan->mask_array[i], true/*auto_zwj*/);
       fallback_plan->lookup_array[i]->apply_string (&c, &fallback_plan->digest_array[i]);
     }
 }
 
 
 #endif /* HB_OT_SHAPE_COMPLEX_ARABIC_FALLBACK_HH */
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-arabic.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-arabic.cc
@@ -32,27 +32,26 @@
 #define arabic_shaping_action() complex_var_u8_0() /* arabic shaping action */
 
 
 /*
  * Bits used in the joining tables
  */
 enum {
   JOINING_TYPE_U		= 0,
-  JOINING_TYPE_R		= 1,
-  JOINING_TYPE_D		= 2,
+  JOINING_TYPE_L		= 1,
+  JOINING_TYPE_R		= 2,
+  JOINING_TYPE_D		= 3,
   JOINING_TYPE_C		= JOINING_TYPE_D,
-  JOINING_GROUP_ALAPH		= 3,
-  JOINING_GROUP_DALATH_RISH	= 4,
-  NUM_STATE_MACHINE_COLS	= 5,
+  JOINING_GROUP_ALAPH		= 4,
+  JOINING_GROUP_DALATH_RISH	= 5,
+  NUM_STATE_MACHINE_COLS	= 6,
 
-  /* We deliberately don't have a JOINING_TYPE_L since that's unused in Unicode. */
-
-  JOINING_TYPE_T = 6,
-  JOINING_TYPE_X = 7  /* means: use general-category to choose between U or T. */
+  JOINING_TYPE_T = 7,
+  JOINING_TYPE_X = 8  /* means: use general-category to choose between U or T. */
 };
 
 /*
  * Joining types:
  */
 
 #include "hb-ot-shape-complex-arabic-table.hh"
 
@@ -76,18 +75,17 @@ static unsigned int get_joining_type (hb
 	|| u == 0x1807 || u == 0x180A)
       return JOINING_TYPE_D;
   }
 
   /* 'Phags-pa joining data is not in ArabicJoining.txt yet. */
   if (unlikely (hb_in_range<hb_codepoint_t> (u, 0xA840, 0xA872)))
   {
       if (unlikely (u == 0xA872))
-	/* XXX Looks like this should be TYPE_L, but we don't support that yet! */
-	return JOINING_TYPE_R;
+	return JOINING_TYPE_L;
 
       return JOINING_TYPE_D;
   }
 
   if (unlikely (hb_in_range<hb_codepoint_t> (u, 0x200C, 0x200D)))
   {
     return u == 0x200C ? JOINING_TYPE_U : JOINING_TYPE_C;
   }
@@ -128,38 +126,38 @@ enum {
 };
 
 static const struct arabic_state_table_entry {
 	uint8_t prev_action;
 	uint8_t curr_action;
 	uint16_t next_state;
 } arabic_state_table[][NUM_STATE_MACHINE_COLS] =
 {
-  /*   jt_U,          jt_R,          jt_D,          jg_ALAPH,      jg_DALATH_RISH */
+  /*   jt_U,          jt_L,          jt_R,          jt_D,          jg_ALAPH,      jg_DALATH_RISH */
 
   /* State 0: prev was U, not willing to join. */
-  { {NONE,NONE,0}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,6}, },
+  { {NONE,NONE,0}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,6}, },
 
   /* State 1: prev was R or ISOL/ALAPH, not willing to join. */
-  { {NONE,NONE,0}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,FIN2,5}, {NONE,ISOL,6}, },
+  { {NONE,NONE,0}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,FIN2,5}, {NONE,ISOL,6}, },
 
-  /* State 2: prev was D/ISOL, willing to join. */
-  { {NONE,NONE,0}, {INIT,FINA,1}, {INIT,FINA,3}, {INIT,FINA,4}, {INIT,FINA,6}, },
+  /* State 2: prev was D/L in ISOL form, willing to join. */
+  { {NONE,NONE,0}, {NONE,ISOL,2}, {INIT,FINA,1}, {INIT,FINA,3}, {INIT,FINA,4}, {INIT,FINA,6}, },
 
-  /* State 3: prev was D/FINA, willing to join. */
-  { {NONE,NONE,0}, {MEDI,FINA,1}, {MEDI,FINA,3}, {MEDI,FINA,4}, {MEDI,FINA,6}, },
+  /* State 3: prev was D in FINA form, willing to join. */
+  { {NONE,NONE,0}, {NONE,ISOL,2}, {MEDI,FINA,1}, {MEDI,FINA,3}, {MEDI,FINA,4}, {MEDI,FINA,6}, },
 
   /* State 4: prev was FINA ALAPH, not willing to join. */
-  { {NONE,NONE,0}, {MED2,ISOL,1}, {MED2,ISOL,2}, {MED2,FIN2,5}, {MED2,ISOL,6}, },
+  { {NONE,NONE,0}, {NONE,ISOL,2}, {MED2,ISOL,1}, {MED2,ISOL,2}, {MED2,FIN2,5}, {MED2,ISOL,6}, },
 
   /* State 5: prev was FIN2/FIN3 ALAPH, not willing to join. */
-  { {NONE,NONE,0}, {ISOL,ISOL,1}, {ISOL,ISOL,2}, {ISOL,FIN2,5}, {ISOL,ISOL,6}, },
+  { {NONE,NONE,0}, {NONE,ISOL,2}, {ISOL,ISOL,1}, {ISOL,ISOL,2}, {ISOL,FIN2,5}, {ISOL,ISOL,6}, },
 
   /* State 6: prev was DALATH/RISH, not willing to join. */
-  { {NONE,NONE,0}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,FIN3,5}, {NONE,ISOL,6}, }
+  { {NONE,NONE,0}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,FIN3,5}, {NONE,ISOL,6}, }
 };
 
 
 static void
 arabic_fallback_shape (const hb_ot_shape_plan_t *plan,
 		       hb_font_t *font,
 		       hb_buffer_t *buffer);
 
@@ -173,35 +171,35 @@ collect_features_arabic (hb_ot_shape_pla
    * ligature work correctly. It's unfortunate though...
    *
    * This also makes Arial Bold in Windows7 work.  See:
    * https://bugzilla.mozilla.org/show_bug.cgi?id=644184
    *
    * TODO: Add test cases for these two.
    */
 
-  map->add_bool_feature (HB_TAG('c','c','m','p'));
-  map->add_bool_feature (HB_TAG('l','o','c','l'));
+  map->add_global_bool_feature (HB_TAG('c','c','m','p'));
+  map->add_global_bool_feature (HB_TAG('l','o','c','l'));
 
   map->add_gsub_pause (NULL);
 
   for (unsigned int i = 0; i < ARABIC_NUM_FEATURES; i++)
-    map->add_bool_feature (arabic_features[i], false, i < 4); /* The first four features have fallback. */
+    map->add_feature (arabic_features[i], 1, i < 4 ? F_HAS_FALLBACK : F_NONE); /* The first four features have fallback. */
 
   map->add_gsub_pause (NULL);
 
-  map->add_bool_feature (HB_TAG('r','l','i','g'), true, true);
+  map->add_feature (HB_TAG('r','l','i','g'), 1, F_GLOBAL|F_HAS_FALLBACK);
   map->add_gsub_pause (arabic_fallback_shape);
 
-  map->add_bool_feature (HB_TAG('c','a','l','t'));
+  map->add_global_bool_feature (HB_TAG('c','a','l','t'));
   map->add_gsub_pause (NULL);
 
-  map->add_bool_feature (HB_TAG('c','s','w','h'));
-  map->add_bool_feature (HB_TAG('d','l','i','g'));
-  map->add_bool_feature (HB_TAG('m','s','e','t'));
+  map->add_global_bool_feature (HB_TAG('c','s','w','h'));
+  map->add_global_bool_feature (HB_TAG('d','l','i','g'));
+  map->add_global_bool_feature (HB_TAG('m','s','e','t'));
 }
 
 #include "hb-ot-shape-complex-arabic-fallback.hh"
 
 struct arabic_shape_plan_t
 {
   ASSERT_POD ();
 
@@ -349,11 +347,11 @@ const hb_ot_complex_shaper_t _hb_ot_comp
   NULL, /* override_features */
   data_create_arabic,
   data_destroy_arabic,
   NULL, /* preprocess_text_arabic */
   NULL, /* normalization_preference */
   NULL, /* decompose */
   NULL, /* compose */
   setup_masks_arabic,
-  HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_UNICODE,
+  HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF,
   true, /* fallback_position */
 };
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-default.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-default.cc
@@ -63,28 +63,22 @@ collect_features_default (hb_ot_shape_pl
 
     /* Unicode-2.0 additions */
     case HB_SCRIPT_TIBETAN:
       script_features = tibetan_features;
       break;
   }
 
   for (; script_features && *script_features; script_features++)
-    plan->map.add_bool_feature (*script_features);
+    plan->map.add_global_bool_feature (*script_features);
 }
 
 static hb_ot_shape_normalization_mode_t
 normalization_preference_default (const hb_segment_properties_t *props)
 {
-  switch ((hb_tag_t) props->script)
-  {
-    /* Unicode-1.1 additions */
-    case HB_SCRIPT_HANGUL:
-      return HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_FULL;
-  }
   return HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS;
 }
 
 static bool
 compose_default (const hb_ot_shape_normalize_context_t *c,
 		 hb_codepoint_t  a,
 		 hb_codepoint_t  b,
 		 hb_codepoint_t *ab)
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.hh
@@ -1260,17 +1260,17 @@ static const int indic_syllable_machine_
     last = p+1; \
     syllable_serial++; \
     if (unlikely (syllable_serial == 16)) syllable_serial = 1; \
   } HB_STMT_END
 
 static void
 find_syllables (hb_buffer_t *buffer)
 {
-  unsigned int p, pe, eof, ts HB_UNUSED, te, act;
+  unsigned int p, pe, eof, ts HB_UNUSED, te HB_UNUSED, act HB_UNUSED;
   int cs;
   hb_glyph_info_t *info = buffer->info;
   
 #line 1273 "../../src/hb-ot-shape-complex-indic-machine.hh.tmp"
 	{
 	cs = indic_syllable_machine_start;
 	ts = 0;
 	te = 0;
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.rl
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.rl
@@ -98,17 +98,17 @@ main := |*
     last = p+1; \
     syllable_serial++; \
     if (unlikely (syllable_serial == 16)) syllable_serial = 1; \
   } HB_STMT_END
 
 static void
 find_syllables (hb_buffer_t *buffer)
 {
-  unsigned int p, pe, eof, ts HB_UNUSED, te, act;
+  unsigned int p, pe, eof, ts HB_UNUSED, te HB_UNUSED, act HB_UNUSED;
   int cs;
   hb_glyph_info_t *info = buffer->info;
   %%{
     write init;
     getkey info[p].indic_category();
   }%%
 
   p = 0;
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-indic.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-indic.cc
@@ -324,52 +324,52 @@ static const indic_config_t indic_config
 
 
 /*
  * Indic shaper.
  */
 
 struct feature_list_t {
   hb_tag_t tag;
-  hb_bool_t is_global;
+  hb_ot_map_feature_flags_t flags;
 };
 
 static const feature_list_t
 indic_features[] =
 {
   /*
    * Basic features.
    * These features are applied in order, one at a time, after initial_reordering.
    */
-  {HB_TAG('n','u','k','t'), true},
-  {HB_TAG('a','k','h','n'), true},
-  {HB_TAG('r','p','h','f'), false},
-  {HB_TAG('r','k','r','f'), true},
-  {HB_TAG('p','r','e','f'), false},
-  {HB_TAG('b','l','w','f'), false},
-  {HB_TAG('h','a','l','f'), false},
-  {HB_TAG('a','b','v','f'), false},
-  {HB_TAG('p','s','t','f'), false},
-  {HB_TAG('c','f','a','r'), false},
-  {HB_TAG('v','a','t','u'), true},
-  {HB_TAG('c','j','c','t'), true},
+  {HB_TAG('n','u','k','t'), F_GLOBAL},
+  {HB_TAG('a','k','h','n'), F_GLOBAL},
+  {HB_TAG('r','p','h','f'), F_NONE},
+  {HB_TAG('r','k','r','f'), F_GLOBAL},
+  {HB_TAG('p','r','e','f'), F_NONE},
+  {HB_TAG('b','l','w','f'), F_NONE},
+  {HB_TAG('h','a','l','f'), F_NONE},
+  {HB_TAG('a','b','v','f'), F_NONE},
+  {HB_TAG('p','s','t','f'), F_NONE},
+  {HB_TAG('c','f','a','r'), F_NONE},
+  {HB_TAG('v','a','t','u'), F_GLOBAL},
+  {HB_TAG('c','j','c','t'), F_GLOBAL},
   /*
    * Other features.
    * These features are applied all at once, after final_reordering.
    */
-  {HB_TAG('i','n','i','t'), false},
-  {HB_TAG('p','r','e','s'), true},
-  {HB_TAG('a','b','v','s'), true},
-  {HB_TAG('b','l','w','s'), true},
-  {HB_TAG('p','s','t','s'), true},
-  {HB_TAG('h','a','l','n'), true},
+  {HB_TAG('i','n','i','t'), F_NONE},
+  {HB_TAG('p','r','e','s'), F_GLOBAL},
+  {HB_TAG('a','b','v','s'), F_GLOBAL},
+  {HB_TAG('b','l','w','s'), F_GLOBAL},
+  {HB_TAG('p','s','t','s'), F_GLOBAL},
+  {HB_TAG('h','a','l','n'), F_GLOBAL},
   /* Positioning features, though we don't care about the types. */
-  {HB_TAG('d','i','s','t'), true},
-  {HB_TAG('a','b','v','m'), true},
-  {HB_TAG('b','l','w','m'), true},
+  {HB_TAG('d','i','s','t'), F_GLOBAL},
+  {HB_TAG('a','b','v','m'), F_GLOBAL},
+  {HB_TAG('b','l','w','m'), F_GLOBAL},
 };
 
 /*
  * Must be in the same order as the indic_features array.
  */
 enum {
   _NUKT,
   _AKHN,
@@ -414,58 +414,58 @@ final_reordering (const hb_ot_shape_plan
 static void
 collect_features_indic (hb_ot_shape_planner_t *plan)
 {
   hb_ot_map_builder_t *map = &plan->map;
 
   /* Do this before any lookups have been applied. */
   map->add_gsub_pause (setup_syllables);
 
-  map->add_bool_feature (HB_TAG('l','o','c','l'));
+  map->add_global_bool_feature (HB_TAG('l','o','c','l'));
   /* The Indic specs do not require ccmp, but we apply it here since if
    * there is a use of it, it's typically at the beginning. */
-  map->add_bool_feature (HB_TAG('c','c','m','p'));
+  map->add_global_bool_feature (HB_TAG('c','c','m','p'));
 
 
   unsigned int i = 0;
   map->add_gsub_pause (initial_reordering);
   for (; i < INDIC_BASIC_FEATURES; i++) {
-    map->add_bool_feature (indic_features[i].tag, indic_features[i].is_global);
+    map->add_feature (indic_features[i].tag, 1, indic_features[i].flags | F_MANUAL_ZWJ);
     map->add_gsub_pause (NULL);
   }
   map->add_gsub_pause (final_reordering);
   for (; i < INDIC_NUM_FEATURES; i++) {
-    map->add_bool_feature (indic_features[i].tag, indic_features[i].is_global);
+    map->add_feature (indic_features[i].tag, 1, indic_features[i].flags | F_MANUAL_ZWJ);
   }
 }
 
 static void
 override_features_indic (hb_ot_shape_planner_t *plan)
 {
   /* Uniscribe does not apply 'kern'. */
   if (hb_options ().uniscribe_bug_compatible)
-    plan->map.add_feature (HB_TAG('k','e','r','n'), 0, true);
+    plan->map.add_feature (HB_TAG('k','e','r','n'), 0, F_GLOBAL);
 
-  plan->map.add_feature (HB_TAG('l','i','g','a'), 0, true);
+  plan->map.add_feature (HB_TAG('l','i','g','a'), 0, F_GLOBAL);
 }
 
 
 struct would_substitute_feature_t
 {
   inline void init (const hb_ot_map_t *map, hb_tag_t feature_tag)
   {
     map->get_stage_lookups (0/*GSUB*/,
 			    map->get_feature_stage (0/*GSUB*/, feature_tag),
 			    &lookups, &count);
   }
 
-  inline bool would_substitute (hb_codepoint_t    *glyphs,
-				unsigned int       glyphs_count,
-				bool               zero_context,
-				hb_face_t         *face) const
+  inline bool would_substitute (const hb_codepoint_t *glyphs,
+				unsigned int          glyphs_count,
+				bool                  zero_context,
+				hb_face_t            *face) const
   {
     for (unsigned int i = 0; i < count; i++)
       if (hb_ot_layout_lookup_would_substitute_fast (face, lookups[i].index, glyphs, glyphs_count, zero_context))
 	return true;
     return false;
   }
 
   private:
@@ -527,36 +527,54 @@ data_create_indic (const hb_ot_shape_pla
   indic_plan->virama_glyph = (hb_codepoint_t) -1;
 
   indic_plan->rphf.init (&plan->map, HB_TAG('r','p','h','f'));
   indic_plan->pref.init (&plan->map, HB_TAG('p','r','e','f'));
   indic_plan->blwf.init (&plan->map, HB_TAG('b','l','w','f'));
   indic_plan->pstf.init (&plan->map, HB_TAG('p','s','t','f'));
 
   for (unsigned int i = 0; i < ARRAY_LENGTH (indic_plan->mask_array); i++)
-    indic_plan->mask_array[i] = indic_features[i].is_global ? 0 : plan->map.get_1_mask (indic_features[i].tag);
+    indic_plan->mask_array[i] = (indic_features[i].flags & F_GLOBAL) ?
+				 0 : plan->map.get_1_mask (indic_features[i].tag);
 
   return indic_plan;
 }
 
 static void
 data_destroy_indic (void *data)
 {
   free (data);
 }
 
 static indic_position_t
 consonant_position_from_face (const indic_shape_plan_t *indic_plan,
-			      hb_codepoint_t *glyphs, unsigned int glyphs_len,
-			      hb_face_t      *face)
+			      const hb_codepoint_t glyphs[2],
+			      hb_face_t *face)
 {
+  /* For old-spec, the order of glyphs is Consonant,Virama,
+   * whereas for new-spec, it's Virama,Consonant.  However,
+   * some broken fonts (like Free Sans) simply copied lookups
+   * from old-spec to new-spec without modification.
+   * And oddly enough, Uniscribe seems to respect those lookups.
+   * Eg. in the sequence U+0924,U+094D,U+0930, Uniscribe finds
+   * base at 0.  The font however, only has lookups matching
+   * 930,94D in 'blwf', not the expected 94D,930 (with new-spec
+   * table).  As such, we simply match both sequences.  Seems
+   * to work. */
   bool zero_context = indic_plan->is_old_spec ? false : true;
-  if (indic_plan->pref.would_substitute (glyphs, glyphs_len, zero_context, face)) return POS_BELOW_C;
-  if (indic_plan->blwf.would_substitute (glyphs, glyphs_len, zero_context, face)) return POS_BELOW_C;
-  if (indic_plan->pstf.would_substitute (glyphs, glyphs_len, zero_context, face)) return POS_POST_C;
+  hb_codepoint_t glyphs_r[2] = {glyphs[1], glyphs[0]};
+  if (indic_plan->pref.would_substitute (glyphs  , 2, zero_context, face) ||
+      indic_plan->pref.would_substitute (glyphs_r, 2, zero_context, face))
+    return POS_POST_C;
+  if (indic_plan->blwf.would_substitute (glyphs  , 2, zero_context, face) ||
+      indic_plan->blwf.would_substitute (glyphs_r, 2, zero_context, face))
+    return POS_BELOW_C;
+  if (indic_plan->pstf.would_substitute (glyphs  , 2, zero_context, face) ||
+      indic_plan->pstf.would_substitute (glyphs_r, 2, zero_context, face))
+    return POS_POST_C;
   return POS_BASE_C;
 }
 
 
 enum syllable_type_t {
   consonant_syllable,
   vowel_syllable,
   standalone_cluster,
@@ -604,26 +622,25 @@ compare_indic_order (const hb_glyph_info
 
 static void
 update_consonant_positions (const hb_ot_shape_plan_t *plan,
 			    hb_font_t         *font,
 			    hb_buffer_t       *buffer)
 {
   const indic_shape_plan_t *indic_plan = (const indic_shape_plan_t *) plan->data;
 
-  unsigned int consonant_pos = indic_plan->is_old_spec ? 0 : 1;
   hb_codepoint_t glyphs[2];
-  if (indic_plan->get_virama_glyph (font, &glyphs[1 - consonant_pos]))
+  if (indic_plan->get_virama_glyph (font, &glyphs[0]))
   {
     hb_face_t *face = font->face;
     unsigned int count = buffer->len;
     for (unsigned int i = 0; i < count; i++)
       if (buffer->info[i].indic_position() == POS_BASE_C) {
-	glyphs[consonant_pos] = buffer->info[i].codepoint;
-	buffer->info[i].indic_position() = consonant_position_from_face (indic_plan, glyphs, 2, face);
+	glyphs[1] = buffer->info[i].codepoint;
+	buffer->info[i].indic_position() = consonant_position_from_face (indic_plan, glyphs, face);
       }
   }
 }
 
 
 /* Rules from:
  * https://www.microsoft.com/typography/otfntdev/devanot/shaping.aspx */
 
@@ -711,17 +728,17 @@ initial_reordering_consonant_syllable (c
 	    }
 	    if (info[i].indic_position() == POS_BELOW_C)
 	      seen_below = true;
 
 	    /* -> or that is not a pre-base reordering Ra,
 	     *
 	     * IMPLEMENTATION NOTES:
 	     *
-	     * Our pre-base reordering Ra's are marked POS_BELOW, so will be skipped
+	     * Our pre-base reordering Ra's are marked POS_POST_C, so will be skipped
 	     * by the logic above already.
 	     */
 
 	    /* -> or arrive at the first consonant. The consonant stopped at will
 	     * be the base. */
 	    base = i;
 	  }
 	  else
@@ -997,18 +1014,19 @@ initial_reordering_consonant_syllable (c
   for (unsigned int i = start + 1; i < end; i++)
     if (is_joiner (info[i])) {
       bool non_joiner = info[i].indic_category() == OT_ZWNJ;
       unsigned int j = i;
 
       do {
 	j--;
 
-	/* A ZWJ disables CJCT, however, it's mere presence is enough
-	 * to disable ligation.  No explicit action needed. */
+	/* ZWJ/ZWNJ should disable CJCT.  They do that by simply
+	 * being there, since we don't skip them for the CJCT
+	 * feature (ie. F_MANUAL_ZWJ) */
 
 	/* A ZWNJ disables HALF. */
 	if (non_joiner)
 	  info[j].mask &= ~indic_plan->mask_array[HALF];
 
       } while (j > start && !is_consonant (info[j]));
     }
 }
@@ -1299,17 +1317,18 @@ final_reordering_syllable (const hb_ot_s
      *          fixed in shaping engine, there was no case where reph position
      *          will be found on this step.
      */
     {
       new_reph_pos = start + 1;
       while (new_reph_pos < base && !is_halant_or_coeng (info[new_reph_pos]))
 	new_reph_pos++;
 
-      if (new_reph_pos < base && is_halant_or_coeng (info[new_reph_pos])) {
+      if (new_reph_pos < base && is_halant_or_coeng (info[new_reph_pos]))
+      {
 	/* ->If ZWJ or ZWNJ are following this halant, position is moved after it. */
 	if (new_reph_pos + 1 < base && is_joiner (info[new_reph_pos + 1]))
 	  new_reph_pos++;
 	goto reph_move;
       }
     }
 
     /*       3. If reph should be repositioned after the main consonant: find the
@@ -1351,17 +1370,18 @@ final_reordering_syllable (const hb_ot_s
      */
     reph_step_5:
     {
       /* Copied from step 2. */
       new_reph_pos = start + 1;
       while (new_reph_pos < base && !is_halant_or_coeng (info[new_reph_pos]))
 	new_reph_pos++;
 
-      if (new_reph_pos < base && is_halant_or_coeng (info[new_reph_pos])) {
+      if (new_reph_pos < base && is_halant_or_coeng (info[new_reph_pos]))
+      {
 	/* ->If ZWJ or ZWNJ are following this halant, position is moved after it. */
 	if (new_reph_pos + 1 < base && is_joiner (info[new_reph_pos + 1]))
 	  new_reph_pos++;
 	goto reph_move;
       }
     }
 
     /*       6. Otherwise, reorder reph to the end of the syllable.
@@ -1450,19 +1470,21 @@ final_reordering_syllable (const hb_ot_s
 		{
 		  new_pos--;
 		  break;
 		}
 	    }
 	  }
 
 	  if (new_pos > start && is_halant_or_coeng (info[new_pos - 1]))
+	  {
 	    /* -> If ZWJ or ZWNJ follow this halant, position is moved after it. */
 	    if (new_pos < end && is_joiner (info[new_pos]))
 	      new_pos++;
+	  }
 
 	  {
 	    unsigned int old_pos = i;
 	    buffer->merge_clusters (new_pos, old_pos + 1);
 	    hb_glyph_info_t tmp = info[old_pos];
 	    memmove (&info[new_pos + 1], &info[new_pos], (old_pos - new_pos) * sizeof (info[0]));
 	    info[new_pos] = tmp;
 	    if (new_pos <= base && base < old_pos)
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar-machine.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar-machine.hh
@@ -275,17 +275,17 @@ static const int myanmar_syllable_machin
     last = p+1; \
     syllable_serial++; \
     if (unlikely (syllable_serial == 16)) syllable_serial = 1; \
   } HB_STMT_END
 
 static void
 find_syllables (hb_buffer_t *buffer)
 {
-  unsigned int p, pe, eof, ts HB_UNUSED, te, act;
+  unsigned int p, pe, eof, ts HB_UNUSED, te HB_UNUSED, act HB_UNUSED;
   int cs;
   hb_glyph_info_t *info = buffer->info;
   
 #line 288 "hb-ot-shape-complex-myanmar-machine.hh.tmp"
 	{
 	cs = myanmar_syllable_machine_start;
 	ts = 0;
 	te = 0;
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar-machine.rl
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar-machine.rl
@@ -97,17 +97,17 @@ main := |*
     last = p+1; \
     syllable_serial++; \
     if (unlikely (syllable_serial == 16)) syllable_serial = 1; \
   } HB_STMT_END
 
 static void
 find_syllables (hb_buffer_t *buffer)
 {
-  unsigned int p, pe, eof, ts HB_UNUSED, te, act;
+  unsigned int p, pe, eof, ts HB_UNUSED, te HB_UNUSED, act HB_UNUSED;
   int cs;
   hb_glyph_info_t *info = buffer->info;
   %%{
     write init;
     getkey info[p].myanmar_category();
   }%%
 
   p = 0;
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar.cc
@@ -31,57 +31,42 @@
 #define myanmar_position() complex_var_u8_1() /* myanmar_position_t */
 
 
 /*
  * Myanmar shaper.
  */
 
 static const hb_tag_t
-myanmar_features[] =
+basic_features[] =
 {
   /*
    * Basic features.
    * These features are applied in order, one at a time, after initial_reordering.
    */
   HB_TAG('r','p','h','f'),
   HB_TAG('p','r','e','f'),
   HB_TAG('b','l','w','f'),
   HB_TAG('p','s','t','f'),
+};
+static const hb_tag_t
+other_features[] =
+{
   /*
    * Other features.
    * These features are applied all at once, after final_reordering.
    */
   HB_TAG('p','r','e','s'),
   HB_TAG('a','b','v','s'),
   HB_TAG('b','l','w','s'),
   HB_TAG('p','s','t','s'),
   /* Positioning features, though we don't care about the types. */
   HB_TAG('d','i','s','t'),
 };
 
-/*
- * Must be in the same order as the myanmar_features array.
- */
-enum {
-  _RPHF,
-  _PREF,
-  _BLWF,
-  _PSTF,
-
-  _PRES,
-  _ABVS,
-  _BLWS,
-  _PSTS,
-  _DIST,
-
-  MYANMAR_NUM_FEATURES,
-  MYANMAR_BASIC_FEATURES = _PRES /* Don't forget to update this! */
-};
-
 static void
 setup_syllables (const hb_ot_shape_plan_t *plan,
 		 hb_font_t *font,
 		 hb_buffer_t *buffer);
 static void
 initial_reordering (const hb_ot_shape_plan_t *plan,
 		    hb_font_t *font,
 		    hb_buffer_t *buffer);
@@ -93,49 +78,48 @@ final_reordering (const hb_ot_shape_plan
 static void
 collect_features_myanmar (hb_ot_shape_planner_t *plan)
 {
   hb_ot_map_builder_t *map = &plan->map;
 
   /* Do this before any lookups have been applied. */
   map->add_gsub_pause (setup_syllables);
 
-  map->add_bool_feature (HB_TAG('l','o','c','l'));
+  map->add_global_bool_feature (HB_TAG('l','o','c','l'));
   /* The Indic specs do not require ccmp, but we apply it here since if
    * there is a use of it, it's typically at the beginning. */
-  map->add_bool_feature (HB_TAG('c','c','m','p'));
+  map->add_global_bool_feature (HB_TAG('c','c','m','p'));
 
 
-  unsigned int i = 0;
   map->add_gsub_pause (initial_reordering);
-  for (; i < MYANMAR_BASIC_FEATURES; i++) {
-    map->add_bool_feature (myanmar_features[i]);
+  for (unsigned int i = 0; i < ARRAY_LENGTH (basic_features); i++)
+  {
+    map->add_feature (basic_features[i], 1, F_GLOBAL | F_MANUAL_ZWJ);
     map->add_gsub_pause (NULL);
   }
   map->add_gsub_pause (final_reordering);
-  for (; i < MYANMAR_NUM_FEATURES; i++) {
-    map->add_bool_feature (myanmar_features[i]);
-  }
+  for (unsigned int i = 0; i < ARRAY_LENGTH (other_features); i++)
+    map->add_feature (other_features[i], 1, F_GLOBAL | F_MANUAL_ZWJ);
 }
 
 static void
 override_features_myanmar (hb_ot_shape_planner_t *plan)
 {
-  plan->map.add_feature (HB_TAG('l','i','g','a'), 0, true);
+  plan->map.add_feature (HB_TAG('l','i','g','a'), 0, F_GLOBAL);
 
   /*
    * Note:
    *
    * Spec says 'mark' is used, and the mmrtext.ttf font from
    * Windows 8 has lookups for it.  But testing suggests that
    * Windows 8 Uniscribe is NOT applying it.  It *is* applying
    * 'mkmk' however.
    */
   if (hb_options ().uniscribe_bug_compatible)
-    plan->map.add_feature (HB_TAG('m','a','r','k'), 0, true);
+    plan->map.add_feature (HB_TAG('m','a','r','k'), 0, F_GLOBAL);
 }
 
 
 enum syllable_type_t {
   consonant_syllable,
   broken_cluster,
   non_myanmar_cluster,
 };
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-private.hh
@@ -180,16 +180,30 @@ hb_ot_shape_complex_categorize (const hb
 
     /* Unicode-1.1 additions */
     case HB_SCRIPT_THAI:
     case HB_SCRIPT_LAO:
 
       return &_hb_ot_complex_shaper_thai;
 
 
+#if 0
+    /* Note:
+     * Currently we don't have a separate Hangul shaper.  The default shaper handles
+     * Hangul by enabling jamo features.  We may want to implement a separate shaper
+     * in the future.  See this thread for details of what such a shaper would do:
+     *
+     *   http://lists.freedesktop.org/archives/harfbuzz/2013-April/003070.html
+     */
+    /* Unicode-1.1 additions */
+    case HB_SCRIPT_HANGUL:
+
+      return &_hb_ot_complex_shaper_hangul;
+#endif
+
 
     /* ^--- Add new shapers here */
 
 
 #if 0
     /* Note:
      *
      * These disabled scripts are listed in ucd/IndicSyllabicCategory.txt, but according
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-sea-machine.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-sea-machine.hh
@@ -104,17 +104,17 @@ static const int sea_syllable_machine_en
     last = p+1; \
     syllable_serial++; \
     if (unlikely (syllable_serial == 16)) syllable_serial = 1; \
   } HB_STMT_END
 
 static void
 find_syllables (hb_buffer_t *buffer)
 {
-  unsigned int p, pe, eof, ts HB_UNUSED, te, act;
+  unsigned int p, pe, eof, ts HB_UNUSED, te HB_UNUSED, act HB_UNUSED;
   int cs;
   hb_glyph_info_t *info = buffer->info;
   
 #line 117 "hb-ot-shape-complex-sea-machine.hh.tmp"
 	{
 	cs = sea_syllable_machine_start;
 	ts = 0;
 	te = 0;
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-sea-machine.rl
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-sea-machine.rl
@@ -74,17 +74,17 @@ main := |*
     last = p+1; \
     syllable_serial++; \
     if (unlikely (syllable_serial == 16)) syllable_serial = 1; \
   } HB_STMT_END
 
 static void
 find_syllables (hb_buffer_t *buffer)
 {
-  unsigned int p, pe, eof, ts HB_UNUSED, te, act;
+  unsigned int p, pe, eof, ts HB_UNUSED, te HB_UNUSED, act HB_UNUSED;
   int cs;
   hb_glyph_info_t *info = buffer->info;
   %%{
     write init;
     getkey info[p].sea_category();
   }%%
 
   p = 0;
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-sea.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-sea.cc
@@ -33,57 +33,42 @@
 
 /*
  * South-East Asian shaper.
  * Loosely based on the Myanmar spec / shaper.
  * There is no OpenType spec for this.
  */
 
 static const hb_tag_t
-sea_features[] =
+basic_features[] =
 {
   /*
    * Basic features.
    * These features are applied in order, one at a time, after initial_reordering.
    */
   HB_TAG('p','r','e','f'),
   HB_TAG('a','b','v','f'),
   HB_TAG('b','l','w','f'),
   HB_TAG('p','s','t','f'),
+};
+static const hb_tag_t
+other_features[] =
+{
   /*
    * Other features.
    * These features are applied all at once, after final_reordering.
    */
   HB_TAG('p','r','e','s'),
   HB_TAG('a','b','v','s'),
   HB_TAG('b','l','w','s'),
   HB_TAG('p','s','t','s'),
   /* Positioning features, though we don't care about the types. */
   HB_TAG('d','i','s','t'),
 };
 
-/*
- * Must be in the same order as the sea_features array.
- */
-enum {
-  _PREF,
-  _ABVF,
-  _BLWF,
-  _PSTF,
-
-  _PRES,
-  _ABVS,
-  _BLWS,
-  _PSTS,
-  _DIST,
-
-  SEA_NUM_FEATURES,
-  SEA_BASIC_FEATURES = _PRES /* Don't forget to update this! */
-};
-
 static void
 setup_syllables (const hb_ot_shape_plan_t *plan,
 		 hb_font_t *font,
 		 hb_buffer_t *buffer);
 static void
 initial_reordering (const hb_ot_shape_plan_t *plan,
 		    hb_font_t *font,
 		    hb_buffer_t *buffer);
@@ -95,38 +80,36 @@ final_reordering (const hb_ot_shape_plan
 static void
 collect_features_sea (hb_ot_shape_planner_t *plan)
 {
   hb_ot_map_builder_t *map = &plan->map;
 
   /* Do this before any lookups have been applied. */
   map->add_gsub_pause (setup_syllables);
 
-  map->add_bool_feature (HB_TAG('l','o','c','l'));
+  map->add_global_bool_feature (HB_TAG('l','o','c','l'));
   /* The Indic specs do not require ccmp, but we apply it here since if
    * there is a use of it, it's typically at the beginning. */
-  map->add_bool_feature (HB_TAG('c','c','m','p'));
-
+  map->add_global_bool_feature (HB_TAG('c','c','m','p'));
 
-  unsigned int i = 0;
   map->add_gsub_pause (initial_reordering);
-  for (; i < SEA_BASIC_FEATURES; i++) {
-    map->add_bool_feature (sea_features[i]);
+  for (unsigned int i = 0; i < ARRAY_LENGTH (basic_features); i++)
+  {
+    map->add_feature (basic_features[i], 1, F_GLOBAL | F_MANUAL_ZWJ);
     map->add_gsub_pause (NULL);
   }
   map->add_gsub_pause (final_reordering);
-  for (; i < SEA_NUM_FEATURES; i++) {
-    map->add_bool_feature (sea_features[i]);
-  }
+  for (unsigned int i = 0; i < ARRAY_LENGTH (other_features); i++)
+    map->add_feature (other_features[i], 1, F_GLOBAL | F_MANUAL_ZWJ);
 }
 
 static void
 override_features_sea (hb_ot_shape_planner_t *plan)
 {
-  plan->map.add_feature (HB_TAG('l','i','g','a'), 0, true);
+  plan->map.add_feature (HB_TAG('l','i','g','a'), 0, F_GLOBAL);
 }
 
 
 enum syllable_type_t {
   consonant_syllable,
   broken_cluster,
   non_sea_cluster,
 };
--- a/gfx/harfbuzz/src/hb-ot-shape-fallback-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-fallback-private.hh
@@ -36,9 +36,14 @@ HB_INTERNAL void _hb_ot_shape_fallback_p
 						 hb_font_t *font,
 						 hb_buffer_t  *buffer);
 
 HB_INTERNAL void _hb_ot_shape_fallback_position_recategorize_marks (const hb_ot_shape_plan_t *plan,
 								    hb_font_t *font,
 								    hb_buffer_t  *buffer);
 
 
+HB_INTERNAL void _hb_ot_shape_fallback_kern (const hb_ot_shape_plan_t *plan,
+					     hb_font_t *font,
+					     hb_buffer_t  *buffer);
+
+
 #endif /* HB_OT_SHAPE_FALLBACK_PRIVATE_HH */
--- a/gfx/harfbuzz/src/hb-ot-shape-fallback.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-fallback.cc
@@ -20,16 +20,17 @@
  * 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
  */
 
 #include "hb-ot-shape-fallback-private.hh"
+#include "hb-ot-layout-gsubgpos-private.hh"
 
 static unsigned int
 recategorize_combining_class (hb_codepoint_t u,
 			      unsigned int klass)
 {
   if (klass >= 200)
     return klass;
 
@@ -402,8 +403,53 @@ void
   for (unsigned int i = 1; i < count; i++)
     if (buffer->info[i].cluster != last_cluster) {
       position_cluster (plan, font, buffer, start, i);
       start = i;
       last_cluster = buffer->info[i].cluster;
     }
   position_cluster (plan, font, buffer, start, count);
 }
+
+
+/* Performs old-style TrueType kerning. */
+void
+_hb_ot_shape_fallback_kern (const hb_ot_shape_plan_t *plan,
+			    hb_font_t *font,
+			    hb_buffer_t  *buffer)
+{
+  unsigned int count = buffer->len;
+  hb_mask_t kern_mask = plan->map.get_1_mask (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction) ?
+					      HB_TAG ('k','e','r','n') : HB_TAG ('v','k','r','n'));
+
+  OT::hb_apply_context_t c (1, font, buffer, kern_mask, true/*auto_zwj*/);
+  c.set_lookup_props (OT::LookupFlag::IgnoreMarks);
+
+  for (buffer->idx = 0; buffer->idx < count;)
+  {
+    OT::hb_apply_context_t::skipping_forward_iterator_t skippy_iter (&c, buffer->idx, 1);
+    if (!skippy_iter.next ())
+    {
+      buffer->idx++;
+      continue;
+    }
+
+    hb_position_t x_kern, y_kern, kern1, kern2;
+    font->get_glyph_kerning_for_direction (buffer->info[buffer->idx].codepoint,
+					   buffer->info[skippy_iter.idx].codepoint,
+					   buffer->props.direction,
+					   &x_kern, &y_kern);
+
+    kern1 = x_kern >> 1;
+    kern2 = x_kern - kern1;
+    buffer->pos[buffer->idx].x_advance += kern1;
+    buffer->pos[skippy_iter.idx].x_advance += kern2;
+    buffer->pos[skippy_iter.idx].x_offset += kern2;
+
+    kern1 = y_kern >> 1;
+    kern2 = y_kern - kern1;
+    buffer->pos[buffer->idx].y_advance += kern1;
+    buffer->pos[skippy_iter.idx].y_advance += kern2;
+    buffer->pos[skippy_iter.idx].y_offset += kern2;
+
+    buffer->idx = skippy_iter.idx;
+  }
+}
--- a/gfx/harfbuzz/src/hb-ot-shape-normalize-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-normalize-private.hh
@@ -36,17 +36,16 @@
 #define glyph_index()	var1.u32
 
 struct hb_ot_shape_plan_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 */
   HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT, /* always fully decomposes and then recompose back */
-  HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_FULL, /* including base-to-base composition */
 
   HB_OT_SHAPE_NORMALIZATION_MODE_DEFAULT = HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS
 };
 
 HB_INTERNAL void _hb_ot_shape_normalize (const hb_ot_shape_plan_t *shaper,
 					 hb_buffer_t *buffer,
 					 hb_font_t *font);
 
--- a/gfx/harfbuzz/src/hb-ot-shape-normalize.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-normalize.cc
@@ -165,17 +165,17 @@ decompose (const hb_ot_shape_normalize_c
     }
     return 1;
   }
 
   return 0;
 }
 
 /* Returns 0 if didn't decompose, number of resulting characters otherwise. */
-static inline bool
+static inline unsigned int
 decompose_compatibility (const hb_ot_shape_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 = c->buffer->unicode->decompose_compatibility (u, decomposed);
   if (!len)
@@ -186,17 +186,16 @@ decompose_compatibility (const hb_ot_sha
       return 0;
 
   for (i = 0; i < len; i++)
     output_char (c->buffer, decomposed[i], glyphs[i]);
 
   return len;
 }
 
-/* Returns true if recomposition may be benefitial. */
 static inline void
 decompose_current_character (const hb_ot_shape_normalize_context_t *c, bool shortest)
 {
   hb_buffer_t * const buffer = c->buffer;
   hb_codepoint_t glyph;
 
   /* Kind of a cute waterfall here... */
   if (shortest && c->font->get_glyph (buffer->cur().codepoint, 0, &glyph))
@@ -226,17 +225,16 @@ handle_variation_selector_cluster (const
     }
   }
   if (likely (buffer->idx < end)) {
     set_glyph (buffer->cur(), c->font);
     buffer->next_glyph ();
   }
 }
 
-/* Returns true if recomposition may be benefitial. */
 static inline void
 decompose_multi_char_cluster (const hb_ot_shape_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 (c, end);
@@ -348,22 +346,21 @@ void
 
   buffer->clear_output ();
   count = buffer->len;
   unsigned int starter = 0;
   buffer->next_glyph ();
   while (buffer->idx < count)
   {
     hb_codepoint_t composed, glyph;
-    if (/* If mode is NOT COMPOSED_FULL (ie. it's COMPOSED_DIACRITICS), we don't try to
-	 * compose a non-mark character with it's preceding starter.  This is just an
-	 * optimization to avoid trying to compose every two neighboring glyphs in most
-	 * scripts. */
-	(mode == HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_FULL ||
-	 HB_UNICODE_GENERAL_CATEGORY_IS_MARK (_hb_glyph_info_get_general_category (&buffer->cur()))) &&
+    if (/* We don't try to compose a non-mark character with it's preceding starter.
+	 * This is both an optimization to avoid trying to compose every two neighboring
+	 * glyphs in most scripts AND a desired feature for Hangul.  Apparently Hangul
+	 * fonts are not designed to mix-and-match pre-composed syllables and Jamo. */
+	HB_UNICODE_GENERAL_CATEGORY_IS_MARK (_hb_glyph_info_get_general_category (&buffer->cur())) &&
 	/* If there's anything between the starter and this char, they should have CCC
 	 * smaller than this character's. */
 	(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. */
 	c.compose (&c,
 		   buffer->out_info[starter].codepoint,
 		   buffer->cur().codepoint,
--- a/gfx/harfbuzz/src/hb-ot-shape-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-private.hh
@@ -28,20 +28,16 @@
 #define HB_OT_SHAPE_PRIVATE_HH
 
 #include "hb-private.hh"
 
 #include "hb-ot-map-private.hh"
 
 
 
-/* buffer var allocations, used during the entire shaping process */
-#define unicode_props0()	var2.u8[0]
-#define unicode_props1()	var2.u8[1]
-
 
 
 struct hb_ot_shape_plan_t
 {
   hb_segment_properties_t props;
   const struct hb_ot_complex_shaper_t *shaper;
   hb_ot_map_t map;
   const void *data;
@@ -84,42 +80,9 @@ struct hb_ot_shape_planner_t
     map.compile (plan.map);
   }
 
   private:
   NO_COPY (hb_ot_shape_planner_t);
 };
 
 
-
-inline void
-_hb_glyph_info_set_unicode_props (hb_glyph_info_t *info, hb_unicode_funcs_t *unicode)
-{
-  info->unicode_props0() = ((unsigned int) unicode->general_category (info->codepoint)) |
-			   (unicode->is_default_ignorable (info->codepoint) ? 0x80 : 0);
-  info->unicode_props1() = unicode->modified_combining_class (info->codepoint);
-}
-
-inline hb_unicode_general_category_t
-_hb_glyph_info_get_general_category (const hb_glyph_info_t *info)
-{
-  return (hb_unicode_general_category_t) (info->unicode_props0() & 0x7F);
-}
-
-inline void
-_hb_glyph_info_set_modified_combining_class (hb_glyph_info_t *info, unsigned int modified_class)
-{
-  info->unicode_props1() = modified_class;
-}
-
-inline unsigned int
-_hb_glyph_info_get_modified_combining_class (const hb_glyph_info_t *info)
-{
-  return info->unicode_props1();
-}
-
-inline hb_bool_t
-_hb_glyph_info_is_default_ignorable (const hb_glyph_info_t *info)
-{
-  return !!(info->unicode_props0() & 0x80);
-}
-
 #endif /* HB_OT_SHAPE_PRIVATE_HH */
--- a/gfx/harfbuzz/src/hb-ot-shape.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape.cc
@@ -78,54 +78,55 @@ hb_ot_shape_collect_features (hb_ot_shap
 			      const hb_segment_properties_t  *props,
 			      const hb_feature_t             *user_features,
 			      unsigned int                    num_user_features)
 {
   hb_ot_map_builder_t *map = &planner->map;
 
   switch (props->direction) {
     case HB_DIRECTION_LTR:
-      map->add_bool_feature (HB_TAG ('l','t','r','a'));
-      map->add_bool_feature (HB_TAG ('l','t','r','m'));
+      map->add_global_bool_feature (HB_TAG ('l','t','r','a'));
+      map->add_global_bool_feature (HB_TAG ('l','t','r','m'));
       break;
     case HB_DIRECTION_RTL:
-      map->add_bool_feature (HB_TAG ('r','t','l','a'));
-      map->add_bool_feature (HB_TAG ('r','t','l','m'), false);
+      map->add_global_bool_feature (HB_TAG ('r','t','l','a'));
+      map->add_feature (HB_TAG ('r','t','l','m'), 1, F_NONE);
       break;
     case HB_DIRECTION_TTB:
     case HB_DIRECTION_BTT:
     case HB_DIRECTION_INVALID:
     default:
       break;
   }
 
-#define ADD_FEATURES(array) \
-  HB_STMT_START { \
-    for (unsigned int i = 0; i < ARRAY_LENGTH (array); i++) \
-      map->add_bool_feature (array[i]); \
-  } HB_STMT_END
-
   if (planner->shaper->collect_features)
     planner->shaper->collect_features (planner);
 
-  ADD_FEATURES (common_features);
+  for (unsigned int i = 0; i < ARRAY_LENGTH (common_features); i++)
+    map->add_global_bool_feature (common_features[i]);
 
   if (HB_DIRECTION_IS_HORIZONTAL (props->direction))
-    ADD_FEATURES (horizontal_features);
+    for (unsigned int i = 0; i < ARRAY_LENGTH (horizontal_features); i++)
+      map->add_feature (horizontal_features[i], 1, F_GLOBAL |
+			(horizontal_features[i] == HB_TAG('k','e','r','n') ?
+			 F_HAS_FALLBACK : F_NONE));
   else
-    ADD_FEATURES (vertical_features);
+    for (unsigned int i = 0; i < ARRAY_LENGTH (vertical_features); i++)
+      map->add_feature (vertical_features[i], 1, F_GLOBAL |
+			(vertical_features[i] == HB_TAG('v','k','r','n') ?
+			 F_HAS_FALLBACK : F_NONE));
 
   if (planner->shaper->override_features)
     planner->shaper->override_features (planner);
 
-#undef ADD_FEATURES
-
   for (unsigned int i = 0; i < num_user_features; i++) {
     const hb_feature_t *feature = &user_features[i];
-    map->add_feature (feature->tag, feature->value, (feature->start == 0 && feature->end == (unsigned int) -1));
+    map->add_feature (feature->tag, feature->value,
+		      (feature->start == 0 && feature->end == (unsigned int) -1) ?
+		       F_GLOBAL : F_NONE);
   }
 }
 
 
 /*
  * shaper face data
  */
 
@@ -487,57 +488,32 @@ hb_ot_position_complex (hb_ot_shape_cont
   }
 
   hb_ot_layout_position_finish (c->font, c->buffer);
 
   return ret;
 }
 
 static inline void
-hb_ot_truetype_kern (hb_ot_shape_context_t *c)
-{
-  /* TODO Check for kern=0 */
-  unsigned int count = c->buffer->len;
-  for (unsigned int i = 1; i < count; i++) {
-    hb_position_t x_kern, y_kern, kern1, kern2;
-    c->font->get_glyph_kerning_for_direction (c->buffer->info[i - 1].codepoint, c->buffer->info[i].codepoint,
-					      c->buffer->props.direction,
-					      &x_kern, &y_kern);
-
-    kern1 = x_kern >> 1;
-    kern2 = x_kern - kern1;
-    c->buffer->pos[i - 1].x_advance += kern1;
-    c->buffer->pos[i].x_advance += kern2;
-    c->buffer->pos[i].x_offset += kern2;
-
-    kern1 = y_kern >> 1;
-    kern2 = y_kern - kern1;
-    c->buffer->pos[i - 1].y_advance += kern1;
-    c->buffer->pos[i].y_advance += kern2;
-    c->buffer->pos[i].y_offset += kern2;
-  }
-}
-
-static inline void
 hb_ot_position (hb_ot_shape_context_t *c)
 {
   hb_ot_position_default (c);
 
   hb_bool_t fallback = !hb_ot_position_complex (c);
 
   if (fallback && c->plan->shaper->fallback_position)
     _hb_ot_shape_fallback_position (c->plan, c->font, c->buffer);
 
   if (HB_DIRECTION_IS_BACKWARD (c->buffer->props.direction))
     hb_buffer_reverse (c->buffer);
 
   /* Visual fallback goes here. */
 
   if (fallback)
-    hb_ot_truetype_kern (c);
+    _hb_ot_shape_fallback_kern (c->plan, c->font, c->buffer);
 }
 
 
 /* Post-process */
 
 static void
 hb_ot_hide_default_ignorables (hb_ot_shape_context_t *c)
 {
@@ -646,18 +622,16 @@ void
 hb_ot_shape_glyphs_closure (hb_font_t          *font,
 			    hb_buffer_t        *buffer,
 			    const hb_feature_t *features,
 			    unsigned int        num_features,
 			    hb_set_t           *glyphs)
 {
   hb_ot_shape_plan_t plan;
 
-  buffer->guess_segment_properties ();
-
   const char *shapers[] = {"ot", NULL};
   hb_shape_plan_t *shape_plan = hb_shape_plan_create_cached (font->face, &buffer->props,
 							     features, num_features, shapers);
 
   bool mirror = hb_script_get_horizontal_direction (buffer->props.script) == HB_DIRECTION_RTL;
 
   unsigned int count = buffer->len;
   for (unsigned int i = 0; i < count; i++)
--- a/gfx/harfbuzz/src/hb-private.hh
+++ b/gfx/harfbuzz/src/hb-private.hh
@@ -371,16 +371,24 @@ struct hb_prealloced_array_t
   {
     if (array != static_array)
       free (array);
     array = NULL;
     allocated = len = 0;
   }
 };
 
+#define HB_AUTO_ARRAY_PREALLOCED 64
+template <typename Type>
+struct hb_auto_array_t : hb_prealloced_array_t <Type, HB_AUTO_ARRAY_PREALLOCED>
+{
+  hb_auto_array_t (void) { hb_prealloced_array_t<Type, HB_AUTO_ARRAY_PREALLOCED>::init (); }
+  ~hb_auto_array_t (void) { hb_prealloced_array_t<Type, HB_AUTO_ARRAY_PREALLOCED>::finish (); }
+};
+
 
 #define HB_LOCKABLE_SET_INIT {HB_PREALLOCED_ARRAY_INIT}
 template <typename item_t, typename lock_t>
 struct hb_lockable_set_t
 {
   hb_prealloced_array_t <item_t, 2> items;
 
   inline void init (void) { items.init (); }
@@ -504,27 +512,29 @@ static inline uint32_t hb_uint32_swap (c
 #define hb_be_uint16_put(v,V)	HB_STMT_START { v[0] = (V>>8); v[1] = (V); } HB_STMT_END
 #define hb_be_uint16_get(v)	(uint16_t) ((v[0] << 8) + v[1])
 #define hb_be_uint16_eq(a,b)	(a[0] == b[0] && a[1] == b[1])
 
 #define hb_be_uint32_put(v,V)	HB_STMT_START { v[0] = (V>>24); v[1] = (V>>16); v[2] = (V>>8); v[3] = (V); } HB_STMT_END
 #define hb_be_uint32_get(v)	(uint32_t) ((v[0] << 24) + (v[1] << 16) + (v[2] << 8) + v[3])
 #define hb_be_uint32_eq(a,b)	(a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3])
 
-#define hb_be_uint24_put(v,V)	HB_STMT_START { v[0] = (V>>16); v[1] = (V>>8); v[2] (V); } HB_STMT_END
+#define hb_be_uint24_put(v,V)	HB_STMT_START { v[0] = (V>>16); v[1] = (V>>8); v[2] = (V); } HB_STMT_END
 #define hb_be_uint24_get(v)	(uint32_t) ((v[0] << 16) + (v[1] << 8) + v[2])
 #define hb_be_uint24_eq(a,b)	(a[0] == b[0] && a[1] == b[1] && a[2] == b[2])
 
 
 /* ASCII tag/character handling */
 
-static inline unsigned char ISALPHA (unsigned char c)
+static inline bool ISALPHA (unsigned char c)
 { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }
-static inline unsigned char ISALNUM (unsigned char c)
+static inline bool ISALNUM (unsigned char c)
 { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); }
+static inline bool ISSPACE (unsigned char c)
+{ return c == ' ' || c =='\f'|| c =='\n'|| c =='\r'|| c =='\t'|| c =='\v'; }
 static inline unsigned char TOUPPER (unsigned char c)
 { return (c >= 'a' && c <= 'z') ? c - 'a' + 'A' : c; }
 static inline unsigned char TOLOWER (unsigned char c)
 { return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; }
 
 #define HB_TAG_CHAR4(s)   (HB_TAG(((const char *) s)[0], \
 				  ((const char *) s)[1], \
 				  ((const char *) s)[2], \
@@ -589,26 +599,30 @@ template <int max_level> static inline v
 	     level,
 	     bars + sizeof (bars) - 1 - MIN ((unsigned int) sizeof (bars), (unsigned int) (sizeof (VBAR) - 1) * level),
 	     level_dir ? (level_dir > 0 ? DLBAR : ULBAR) : LBAR);
   } else
     fprintf (stderr, "   " VRBAR LBAR);
 
   if (func)
   {
+    unsigned int func_len = strlen (func);
+#ifndef HB_DEBUG_VERBOSE
     /* Skip "typename" */
     if (0 == strncmp (func, "typename ", 9))
       func += 9;
     /* Skip return type */
     const char *space = strchr (func, ' ');
     if (space)
       func = space + 1;
     /* Skip parameter list */
     const char *paren = strchr (func, '(');
-    unsigned int func_len = paren ? paren - func : strlen (func);
+    if (paren)
+      func_len = paren - func;
+#endif
     fprintf (stderr, "%.*s: ", func_len, func);
   }
 
   if (message)
     vfprintf (stderr, message, ap);
 
   fprintf (stderr, "\n");
 }
@@ -836,18 +850,19 @@ hb_bubble_sort (T *array, unsigned int l
   hb_bubble_sort (array, len, compar, (int *) NULL);
 }
 
 static inline hb_bool_t
 hb_codepoint_parse (const char *s, unsigned int len, int base, hb_codepoint_t *out)
 {
   /* Pain because we don't know whether s is nul-terminated. */
   char buf[64];
-  strncpy (buf, s, MIN (ARRAY_LENGTH (buf) - 1, len));
-  buf[MIN (ARRAY_LENGTH (buf) - 1, len)] = '\0';
+  len = MIN (ARRAY_LENGTH (buf) - 1, len);
+  strncpy (buf, s, len);
+  buf[len] = '\0';
 
   char *end;
   errno = 0;
   unsigned long v = strtoul (buf, &end, base);
   if (errno) return false;
   if (*end) return false;
   *out = v;
   return true;
--- a/gfx/harfbuzz/src/hb-set-private.hh
+++ b/gfx/harfbuzz/src/hb-set-private.hh
@@ -27,115 +27,124 @@
 #ifndef HB_SET_PRIVATE_HH
 #define HB_SET_PRIVATE_HH
 
 #include "hb-private.hh"
 #include "hb-set.h"
 #include "hb-object-private.hh"
 
 
-struct hb_set_digest_common_bits_t
-{
-  ASSERT_POD ();
-
-  typedef unsigned int mask_t;
-
-  inline void init (void) {
-    mask = ~0;
-    value = (mask_t) -1;
-  }
-
-  inline void add (hb_codepoint_t g) {
-    if (unlikely (value == (mask_t) -1)) {
-      value = g;
-      return;
-    }
+/*
+ * The set digests here implement various "filters" that support
+ * "approximate member query".  Conceptually these are like Bloom
+ * Filter and Quotient Filter, however, much smaller, faster, and
+ * designed to fit the requirements of our uses for glyph coverage
+ * queries.  As a result, our filters have much higher.
+ */
 
-    mask ^= (g & mask) ^ value;
-    value &= mask;
-  }
-
-  inline void add_range (hb_codepoint_t a, hb_codepoint_t b) {
-    /* The negation here stands for ~(x-1). */
-    mask &= -(1 << _hb_bit_storage (a ^ b));
-    value &= mask;
-  }
-
-  inline bool may_have (hb_codepoint_t g) const {
-    return (g & mask) == value;
-  }
-
-  private:
-  mask_t mask;
-  mask_t value;
-};
-
+template <typename mask_t, unsigned int shift>
 struct hb_set_digest_lowest_bits_t
 {
   ASSERT_POD ();
 
-  typedef unsigned long mask_t;
+  static const unsigned int num_bits = 0
+				     + (sizeof (mask_t) >= 1 ? 3 : 0)
+				     + (sizeof (mask_t) >= 2 ? 1 : 0)
+				     + (sizeof (mask_t) >= 4 ? 1 : 0)
+				     + (sizeof (mask_t) >= 8 ? 1 : 0)
+				     + (sizeof (mask_t) >= 16? 1 : 0)
+				     + 0;
+
+  ASSERT_STATIC (shift < sizeof (hb_codepoint_t) * 8);
+  ASSERT_STATIC (shift + num_bits <= sizeof (hb_codepoint_t) * 8);
 
   inline void init (void) {
     mask = 0;
   }
 
   inline void add (hb_codepoint_t g) {
     mask |= mask_for (g);
   }
 
   inline void add_range (hb_codepoint_t a, hb_codepoint_t b) {
-    if (b - a >= sizeof (mask_t) * 8 - 1)
+    if ((b >> shift) - (a >> shift) >= sizeof (mask_t) * 8 - 1)
       mask = (mask_t) -1;
     else {
       mask_t ma = mask_for (a);
       mask_t mb = mask_for (b);
       mask |= mb + (mb - ma) - (mb < ma);
     }
   }
 
   inline bool may_have (hb_codepoint_t g) const {
     return !!(mask & mask_for (g));
   }
 
   private:
 
-  static inline mask_t mask_for (hb_codepoint_t g) { return ((mask_t) 1) << (g & (sizeof (mask_t) * 8 - 1)); }
+  static inline mask_t mask_for (hb_codepoint_t g) {
+    return ((mask_t) 1) << ((g >> shift) & (sizeof (mask_t) * 8 - 1));
+  }
   mask_t mask;
 };
 
-struct hb_set_digest_t
+template <typename head_t, typename tail_t>
+struct hb_set_digest_combiner_t
 {
   ASSERT_POD ();
 
   inline void init (void) {
-    digest1.init ();
-    digest2.init ();
+    head.init ();
+    tail.init ();
   }
 
   inline void add (hb_codepoint_t g) {
-    digest1.add (g);
-    digest2.add (g);
+    head.add (g);
+    tail.add (g);
   }
 
   inline void add_range (hb_codepoint_t a, hb_codepoint_t b) {
-    digest1.add_range (a, b);
-    digest2.add_range (a, b);
+    head.add_range (a, b);
+    tail.add_range (a, b);
   }
 
   inline bool may_have (hb_codepoint_t g) const {
-    return digest1.may_have (g) && digest2.may_have (g);
+    return head.may_have (g) && tail.may_have (g);
   }
 
   private:
-  hb_set_digest_common_bits_t digest1;
-  hb_set_digest_lowest_bits_t digest2;
+  head_t head;
+  tail_t tail;
 };
 
 
+/*
+ * hb_set_digest_t
+ *
+ * This is a combination of digests that performs "best".
+ * There is not much science to this: it's a result of intuition
+ * and testing.
+ */
+typedef hb_set_digest_combiner_t
+<
+  hb_set_digest_lowest_bits_t<unsigned long, 4>,
+  hb_set_digest_combiner_t
+  <
+    hb_set_digest_lowest_bits_t<unsigned long, 0>,
+    hb_set_digest_lowest_bits_t<unsigned long, 9>
+  >
+> hb_set_digest_t;
+
+
+
+/*
+ * hb_set_t
+ */
+
+
 /* TODO Make this faster and memmory efficient. */
 
 struct hb_set_t
 {
   hb_object_header_t header;
   ASSERT_POD ();
   bool in_error;
 
--- a/gfx/harfbuzz/src/hb-set.cc
+++ b/gfx/harfbuzz/src/hb-set.cc
@@ -22,17 +22,16 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #include "hb-set-private.hh"
 
 
-
 /* Public API */
 
 
 hb_set_t *
 hb_set_create (void)
 {
   hb_set_t *set;
 
--- a/gfx/harfbuzz/src/hb-shape-plan.cc
+++ b/gfx/harfbuzz/src/hb-shape-plan.cc
@@ -22,16 +22,17 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #include "hb-shape-plan-private.hh"
 #include "hb-shaper-private.hh"
 #include "hb-font-private.hh"
+#include "hb-buffer-private.hh"
 
 #define HB_SHAPER_IMPLEMENT(shaper) \
 	HB_SHAPER_DATA_ENSURE_DECLARE(shaper, face) \
 	HB_SHAPER_DATA_ENSURE_DECLARE(shaper, font)
 #include "hb-shaper-list.hh"
 #undef HB_SHAPER_IMPLEMENT
 
 
@@ -173,19 +174,24 @@ hb_shape_plan_get_user_data (hb_shape_pl
 
 hb_bool_t
 hb_shape_plan_execute (hb_shape_plan_t    *shape_plan,
 		       hb_font_t          *font,
 		       hb_buffer_t        *buffer,
 		       const hb_feature_t *features,
 		       unsigned int        num_features)
 {
-  if (unlikely (shape_plan->face != font->face))
+  if (unlikely (hb_object_is_inert (shape_plan) ||
+		hb_object_is_inert (font) ||
+		hb_object_is_inert (buffer)))
     return false;
 
+  assert (shape_plan->face == font->face);
+  assert (hb_segment_properties_equal (&shape_plan->props, &buffer->props));
+
 #define HB_SHAPER_EXECUTE(shaper) \
 	HB_STMT_START { \
 	  return HB_SHAPER_DATA (shaper, shape_plan) && \
 		 hb_##shaper##_shaper_font_data_ensure (font) && \
 		 _hb_##shaper##_shape (shape_plan, font, buffer, features, num_features); \
 	} HB_STMT_END
 
   if (0)
--- a/gfx/harfbuzz/src/hb-shape.cc
+++ b/gfx/harfbuzz/src/hb-shape.cc
@@ -33,20 +33,18 @@
 #include "hb-buffer-private.hh"
 #include "hb-font-private.hh"
 
 
 static void
 parse_space (const char **pp, const char *end)
 {
   char c;
-#define ISSPACE(c) ((c)==' '||(c)=='\f'||(c)=='\n'||(c)=='\r'||(c)=='\t'||(c)=='\v')
   while (*pp < end && (c = **pp, ISSPACE (c)))
     (*pp)++;
-#undef ISSPACE
 }
 
 static hb_bool_t
 parse_char (const char **pp, const char *end, char c)
 {
   parse_space (pp, end);
 
   if (*pp == end || **pp != c)
@@ -55,26 +53,29 @@ parse_char (const char **pp, const char 
   (*pp)++;
   return true;
 }
 
 static hb_bool_t
 parse_uint (const char **pp, const char *end, unsigned int *pv)
 {
   char buf[32];
-  strncpy (buf, *pp, end - *pp);
-  buf[ARRAY_LENGTH (buf) - 1] = '\0';
+  unsigned int len = MIN (ARRAY_LENGTH (buf) - 1, (unsigned int) (end - *pp));
+  strncpy (buf, *pp, len);
+  buf[len] = '\0';
 
   char *p = buf;
   char *pend = p;
   unsigned int v;
 
+  /* Intentionally use strtol instead of strtoul, such that
+   * -1 turns into "big number"... */
+  errno = 0;
   v = strtol (p, &pend, 0);
-
-  if (p == pend)
+  if (errno || p == pend)
     return false;
 
   *pv = v;
   *pp += pend - p;
   return true;
 }
 
 static hb_bool_t
@@ -250,18 +251,16 @@ hb_shape_full (hb_font_t          *font,
 	       unsigned int        num_features,
 	       const char * const *shaper_list)
 {
   if (unlikely (!buffer->len))
     return true;
 
   assert (buffer->content_type == HB_BUFFER_CONTENT_TYPE_UNICODE);
 
-  buffer->guess_segment_properties ();
-
   hb_shape_plan_t *shape_plan = hb_shape_plan_create_cached (font->face, &buffer->props, features, num_features, shaper_list);
   hb_bool_t res = hb_shape_plan_execute (shape_plan, font, buffer, features, num_features);
   hb_shape_plan_destroy (shape_plan);
 
   if (res)
     buffer->content_type = HB_BUFFER_CONTENT_TYPE_GLYPHS;
   return res;
 }
--- a/gfx/harfbuzz/src/hb-unicode-private.hh
+++ b/gfx/harfbuzz/src/hb-unicode-private.hh
@@ -124,30 +124,35 @@ HB_UNICODE_FUNCS_IMPLEMENT_CALLBACKS_SIM
 
   /* Default_Ignorable codepoints:
    *
    * Note that as of Oct 2012 (Unicode 6.2), U+180E MONGOLIAN VOWEL SEPARATOR
    * is NOT Default_Ignorable, but it really behaves in a way that it should
    * be.  That has been reported to the Unicode Technical Committee for
    * consideration.  As such, we include it here, since Uniscribe removes it.
    *
+   * Note: While U+115F and U+1160 are Default_Ignorable, we do NOT want to
+   * hide them, as the way Uniscribe has implemented them is with regular
+   * spacing glyphs, and that's the way fonts are made to work.  As such,
+   * we make exceptions for those two.
+   *
    * Gathered from:
    * http://unicode.org/cldr/utility/list-unicodeset.jsp?a=[:DI:]&abb=on&ucd=on&esc=on
    *
    * Last updated to the page with the following versions:
    * Version 3.6; ICU version: 50.0.1.0; Unicode version: 6.1.0.0
    *
    * 4,167 Code Points
    *
    * [\u00AD\u034F\u115F\u1160\u17B4\u17B5\u180B-\u180D\u200B-\u200F\u202A-\u202E\u2060-\u206F\u3164\uFE00-\uFE0F\uFEFF\uFFA0\uFFF0-\uFFF8\U0001D173-\U0001D17A\U000E0000-\U000E0FFF]
    *
    * 00AD ;SOFT HYPHEN
    * 034F ;COMBINING GRAPHEME JOINER
-   * 115F ;HANGUL CHOSEONG FILLER
-   * 1160 ;HANGUL JUNGSEONG FILLER
+   * #115F ;HANGUL CHOSEONG FILLER
+   * #1160 ;HANGUL JUNGSEONG FILLER
    * 17B4 ;KHMER VOWEL INHERENT AQ
    * 17B5 ;KHMER VOWEL INHERENT AA
    * 180B..180D ;MONGOLIAN FREE VARIATION SELECTOR THREE
    * 200B..200F ;RIGHT-TO-LEFT MARK
    * 202A..202E ;RIGHT-TO-LEFT OVERRIDE
    * 2060..206F ;NOMINAL DIGIT SHAPES
    * 3164 ;HANGUL FILLER
    * FE00..FE0F ;VARIATION SELECTOR-16
@@ -163,17 +168,16 @@ HB_UNICODE_FUNCS_IMPLEMENT_CALLBACKS_SIM
     hb_codepoint_t plane = ch >> 16;
     if (likely (plane == 0))
     {
       /* BMP */
       hb_codepoint_t page = ch >> 8;
       switch (page) {
 	case 0x00: return unlikely (ch == 0x00AD);
 	case 0x03: return unlikely (ch == 0x034F);
-	case 0x11: return hb_in_range<hb_codepoint_t> (ch, 0x115F, 0x1160);
 	case 0x17: return hb_in_range<hb_codepoint_t> (ch, 0x17B4, 0x17B5);
 	case 0x18: return hb_in_range<hb_codepoint_t> (ch, 0x180B, 0x180E);
 	case 0x20: return hb_in_ranges<hb_codepoint_t> (ch, 0x200B, 0x200F,
 							    0x202A, 0x202E,
 							    0x2060, 0x206F);
 	case 0x31: return unlikely (ch == 0x3164);
 	case 0xFE: return hb_in_range<hb_codepoint_t> (ch, 0xFE00, 0xFE0F) || ch == 0xFEFF;
 	case 0xFF: return hb_in_range<hb_codepoint_t> (ch, 0xFFF0, 0xFFF8) || ch == 0xFFA0;
--- a/gfx/harfbuzz/src/hb-version.h
+++ b/gfx/harfbuzz/src/hb-version.h
@@ -33,19 +33,19 @@
 
 #include "hb-common.h"
 
 HB_BEGIN_DECLS
 
 
 #define HB_VERSION_MAJOR 0
 #define HB_VERSION_MINOR 9
-#define HB_VERSION_MICRO 12
+#define HB_VERSION_MICRO 16
 
-#define HB_VERSION_STRING "0.9.12"
+#define HB_VERSION_STRING "0.9.16"
 
 #define HB_VERSION_CHECK(major,minor,micro) \
 	((major)*10000+(minor)*100+(micro) >= \
 	 HB_VERSION_MAJOR*10000+HB_VERSION_MINOR*100+HB_VERSION_MICRO)
 
 
 void
 hb_version (unsigned int *major,
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/test-buffer-serialize.cc
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2010,2011,2013  Google, Inc.
+ *
+ *  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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "hb.h"
+#ifdef HAVE_FREETYPE
+#include "hb-ft.h"
+#endif
+
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+  hb_blob_t *blob = NULL;
+
+  if (argc != 2) {
+    fprintf (stderr, "usage: %s font-file\n", argv[0]);
+    exit (1);
+  }
+
+  /* Create the blob */
+  {
+    const char *font_data;
+    unsigned int len;
+    hb_destroy_func_t destroy;
+    void *user_data;
+    hb_memory_mode_t mm;
+
+#ifdef HAVE_GLIB
+    GMappedFile *mf = g_mapped_file_new (argv[1], false, NULL);
+    font_data = g_mapped_file_get_contents (mf);
+    len = g_mapped_file_get_length (mf);
+    destroy = (hb_destroy_func_t) g_mapped_file_unref;
+    user_data = (void *) mf;
+    mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE;
+#else
+    FILE *f = fopen (argv[1], "rb");
+    fseek (f, 0, SEEK_END);
+    len = ftell (f);
+    fseek (f, 0, SEEK_SET);
+    font_data = (const char *) malloc (len);
+    if (!font_data) len = 0;
+    len = fread ((char *) font_data, 1, len, f);
+    destroy = free;
+    user_data = (void *) font_data;
+    fclose (f);
+    mm = HB_MEMORY_MODE_WRITABLE;
+#endif
+
+    blob = hb_blob_create (font_data, len, mm, user_data, destroy);
+  }
+
+  hb_face_t *face = hb_face_create (blob, 0 /* first face */);
+  hb_blob_destroy (blob);
+  blob = NULL;
+
+  unsigned int upem = hb_face_get_upem (face);
+  hb_font_t *font = hb_font_create (face);
+  hb_face_destroy (face);
+  hb_font_set_scale (font, upem, upem);
+#ifdef HAVE_FREETYPE
+  hb_ft_font_set_funcs (font);
+#endif
+
+  hb_buffer_t *buf;
+  buf = hb_buffer_create ();
+
+  bool ret = true;
+  char line[BUFSIZ], out[BUFSIZ];
+  while (fgets (line, sizeof(line), stdin) != 0)
+  {
+    hb_buffer_clear_contents (buf);
+
+    const char *p = line;
+    while (hb_buffer_deserialize_glyphs (buf,
+					 p, -1, &p,
+					 font,
+					 HB_BUFFER_SERIALIZE_FORMAT_JSON))
+      ;
+    if (*p && *p != '\n')
+      ret = false;
+
+    hb_buffer_serialize_glyphs (buf, 0, hb_buffer_get_length (buf),
+				out, sizeof (out), NULL,
+				font, HB_BUFFER_SERIALIZE_FORMAT_JSON,
+				HB_BUFFER_SERIALIZE_FLAGS_DEFAULT);
+    puts (out);
+  }
+
+  hb_buffer_destroy (buf);
+
+  hb_font_destroy (font);
+
+  return !ret;
+}
--- a/gfx/harfbuzz/src/test.cc
+++ b/gfx/harfbuzz/src/test.cc
@@ -95,16 +95,17 @@ main (int argc, char **argv)
 
 #ifdef HAVE_FREETYPE
   hb_ft_font_set_funcs (font);
 #endif
 
   hb_buffer_t *buffer = hb_buffer_create ();
 
   hb_buffer_add_utf8 (buffer, "\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\x95", -1, 0, -1);
+  hb_buffer_guess_segment_properties (buffer);
 
   hb_shape (font, buffer, NULL, 0);
 
   unsigned int count = hb_buffer_get_length (buffer);
   hb_glyph_info_t *infos = hb_buffer_get_glyph_infos (buffer, NULL);
   hb_glyph_position_t *positions = hb_buffer_get_glyph_positions (buffer, NULL);
 
   for (unsigned int i = 0; i < count; i++)
--- a/layout/reftests/font-features/reftest.list
+++ b/layout/reftests/font-features/reftest.list
@@ -46,19 +46,19 @@ HTTP(..) == font-features-turkish-overri
 HTTP(..) == font-features-turkish-override-3.html font-features-ref.html
 HTTP(..) == font-features-turkish-override-4.html font-features-ref.html
 skip-if(B2G) HTTP(..) == font-features-turkish-override-5.html font-features-turkish.html # bug 773482
 
 # check that last value wins if a feature is repeated
 HTTP(..) == font-features-order-1.html font-features-ref.html
 HTTP(..) == font-features-order-2.html font-features-noliga.html
 
-# sanity check for kerning - with no spaces, kerning should occur (XP version of Arial doesn't contain kerning data)
+# sanity check for kerning - with no spaces, kerning should occur
 HTTP(..) == kerning-sanity-check-kern.html kerning-sanity-check-default.html
-fails-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) HTTP(..) != kerning-sanity-check-nokern.html kerning-sanity-check-default.html
+HTTP(..) != kerning-sanity-check-nokern.html kerning-sanity-check-default.html
 
 # OpenType features should work across inter-word spaces
 HTTP(..) == font-features-across-space-1.html font-features-across-space-1-ref.html
 # requires Japanese font with feature support, WinXP lacks one
 random-if(!winWidget&&!cocoaWidget) fails-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) HTTP(..) == fwid-spaces.html fwid-spaces-ref.html
 # Arial/Times New Roman on Win7+/OSX 10.6+ have kerning pairs that include spaces
 random-if(!winWidget&&!cocoaWidget) fails-if(winWidget||cocoaWidget) random-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) HTTP(..) != kerning-spaces-arial-nokern.html kerning-spaces-arial-default.html
 random-if(!winWidget&&!cocoaWidget) fails-if(winWidget||cocoaWidget) random-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) HTTP(..) == kerning-spaces-arial-kern.html kerning-spaces-arial-default.html