Bug 1500356 - Update HarfBuzz to version 2.3.0. r=jfkthame
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 21 Dec 2018 16:10:52 -0500
changeset 452198 2d077b8e0b5b7ecc9187e5e3cba1022955363d84
parent 452197 c6e2a237b5940a7e607bc5fc19b73045692e0d7b
child 452199 27730939a1386619acfe4feb31e205977bde878a
push id35292
push userdvarga@mozilla.com
push dateMon, 31 Dec 2018 21:26:28 +0000
treeherdermozilla-central@d96e39ed615b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs1500356
milestone66.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 1500356 - Update HarfBuzz to version 2.3.0. r=jfkthame Differential Revision: https://phabricator.services.mozilla.com/D15238
gfx/harfbuzz/AUTHORS
gfx/harfbuzz/NEWS
gfx/harfbuzz/README
gfx/harfbuzz/README-mozilla
gfx/harfbuzz/TODO
gfx/harfbuzz/configure.ac
gfx/harfbuzz/src/HBIndicVowelConstraints.txt
gfx/harfbuzz/src/Makefile.am
gfx/harfbuzz/src/Makefile.sources
gfx/harfbuzz/src/check-static-inits.sh
gfx/harfbuzz/src/check-symbols.sh
gfx/harfbuzz/src/dump-emoji.cc
gfx/harfbuzz/src/dump-indic-data.cc
gfx/harfbuzz/src/dump-khmer-data.cc
gfx/harfbuzz/src/dump-myanmar-data.cc
gfx/harfbuzz/src/dump-use-data.cc
gfx/harfbuzz/src/gen-def.py
gfx/harfbuzz/src/gen-emoji-table.py
gfx/harfbuzz/src/gen-os2-unicode-ranges.py
gfx/harfbuzz/src/gen-tag-table.py
gfx/harfbuzz/src/gen-unicode-ranges.py
gfx/harfbuzz/src/gen-use-table.py
gfx/harfbuzz/src/gen-vowel-constraints.py
gfx/harfbuzz/src/harfbuzz-config.cmake.in
gfx/harfbuzz/src/hb-aat-fdsc-table.hh
gfx/harfbuzz/src/hb-aat-layout-ankr-table.hh
gfx/harfbuzz/src/hb-aat-layout-bsln-table.hh
gfx/harfbuzz/src/hb-aat-layout-common.hh
gfx/harfbuzz/src/hb-aat-layout-feat-table.hh
gfx/harfbuzz/src/hb-aat-layout-just-table.hh
gfx/harfbuzz/src/hb-aat-layout-kerx-table.hh
gfx/harfbuzz/src/hb-aat-layout-lcar-table.hh
gfx/harfbuzz/src/hb-aat-layout-morx-table.hh
gfx/harfbuzz/src/hb-aat-layout-trak-table.hh
gfx/harfbuzz/src/hb-aat-layout.cc
gfx/harfbuzz/src/hb-aat-layout.h
gfx/harfbuzz/src/hb-aat-layout.hh
gfx/harfbuzz/src/hb-aat-ltag-table.hh
gfx/harfbuzz/src/hb-aat-map.cc
gfx/harfbuzz/src/hb-aat-map.hh
gfx/harfbuzz/src/hb-aat.h
gfx/harfbuzz/src/hb-array.hh
gfx/harfbuzz/src/hb-atomic.hh
gfx/harfbuzz/src/hb-blob.cc
gfx/harfbuzz/src/hb-blob.hh
gfx/harfbuzz/src/hb-buffer-serialize.cc
gfx/harfbuzz/src/hb-buffer.cc
gfx/harfbuzz/src/hb-buffer.h
gfx/harfbuzz/src/hb-buffer.hh
gfx/harfbuzz/src/hb-cache.hh
gfx/harfbuzz/src/hb-cff-interp-common.hh
gfx/harfbuzz/src/hb-cff-interp-cs-common.hh
gfx/harfbuzz/src/hb-cff-interp-dict-common.hh
gfx/harfbuzz/src/hb-cff1-interp-cs.hh
gfx/harfbuzz/src/hb-cff2-interp-cs.hh
gfx/harfbuzz/src/hb-common.cc
gfx/harfbuzz/src/hb-common.h
gfx/harfbuzz/src/hb-coretext.cc
gfx/harfbuzz/src/hb-debug.hh
gfx/harfbuzz/src/hb-deprecated.h
gfx/harfbuzz/src/hb-directwrite.cc
gfx/harfbuzz/src/hb-dsalgs.hh
gfx/harfbuzz/src/hb-face.cc
gfx/harfbuzz/src/hb-face.hh
gfx/harfbuzz/src/hb-fallback-shape.cc
gfx/harfbuzz/src/hb-font.cc
gfx/harfbuzz/src/hb-font.h
gfx/harfbuzz/src/hb-font.hh
gfx/harfbuzz/src/hb-ft.cc
gfx/harfbuzz/src/hb-glib.cc
gfx/harfbuzz/src/hb-gobject-enums.cc.tmpl
gfx/harfbuzz/src/hb-gobject-enums.h.tmpl
gfx/harfbuzz/src/hb-gobject-structs.cc
gfx/harfbuzz/src/hb-graphite2.cc
gfx/harfbuzz/src/hb-graphite2.h
gfx/harfbuzz/src/hb-icu.cc
gfx/harfbuzz/src/hb-iter.hh
gfx/harfbuzz/src/hb-kern.hh
gfx/harfbuzz/src/hb-machinery.hh
gfx/harfbuzz/src/hb-map.cc
gfx/harfbuzz/src/hb-map.hh
gfx/harfbuzz/src/hb-mutex.hh
gfx/harfbuzz/src/hb-null.hh
gfx/harfbuzz/src/hb-object.hh
gfx/harfbuzz/src/hb-open-file.hh
gfx/harfbuzz/src/hb-open-type.hh
gfx/harfbuzz/src/hb-ot-cff-common.hh
gfx/harfbuzz/src/hb-ot-cff1-table.cc
gfx/harfbuzz/src/hb-ot-cff1-table.hh
gfx/harfbuzz/src/hb-ot-cff2-table.cc
gfx/harfbuzz/src/hb-ot-cff2-table.hh
gfx/harfbuzz/src/hb-ot-cmap-table.hh
gfx/harfbuzz/src/hb-ot-color-cbdt-table.hh
gfx/harfbuzz/src/hb-ot-color-colr-table.hh
gfx/harfbuzz/src/hb-ot-color-cpal-table.hh
gfx/harfbuzz/src/hb-ot-color-sbix-table.hh
gfx/harfbuzz/src/hb-ot-color-svg-table.hh
gfx/harfbuzz/src/hb-ot-color.cc
gfx/harfbuzz/src/hb-ot-color.h
gfx/harfbuzz/src/hb-ot-deprecated.h
gfx/harfbuzz/src/hb-ot-face.cc
gfx/harfbuzz/src/hb-ot-face.hh
gfx/harfbuzz/src/hb-ot-font.cc
gfx/harfbuzz/src/hb-ot-gasp-table.hh
gfx/harfbuzz/src/hb-ot-glyf-table.hh
gfx/harfbuzz/src/hb-ot-hdmx-table.hh
gfx/harfbuzz/src/hb-ot-head-table.hh
gfx/harfbuzz/src/hb-ot-hhea-table.hh
gfx/harfbuzz/src/hb-ot-hmtx-table.hh
gfx/harfbuzz/src/hb-ot-kern-table.hh
gfx/harfbuzz/src/hb-ot-layout-base-table.hh
gfx/harfbuzz/src/hb-ot-layout-common.hh
gfx/harfbuzz/src/hb-ot-layout-gdef-table.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.hh
gfx/harfbuzz/src/hb-ot-layout-jstf-table.hh
gfx/harfbuzz/src/hb-ot-layout.cc
gfx/harfbuzz/src/hb-ot-layout.h
gfx/harfbuzz/src/hb-ot-layout.hh
gfx/harfbuzz/src/hb-ot-map.cc
gfx/harfbuzz/src/hb-ot-map.hh
gfx/harfbuzz/src/hb-ot-math-table.hh
gfx/harfbuzz/src/hb-ot-math.cc
gfx/harfbuzz/src/hb-ot-maxp-table.hh
gfx/harfbuzz/src/hb-ot-name-language.cc
gfx/harfbuzz/src/hb-ot-name-language.hh
gfx/harfbuzz/src/hb-ot-name-table.hh
gfx/harfbuzz/src/hb-ot-name.cc
gfx/harfbuzz/src/hb-ot-name.h
gfx/harfbuzz/src/hb-ot-os2-table.hh
gfx/harfbuzz/src/hb-ot-os2-unicode-ranges.hh
gfx/harfbuzz/src/hb-ot-post-table.hh
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-hangul.cc
gfx/harfbuzz/src/hb-ot-shape-complex-hebrew.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-indic.hh
gfx/harfbuzz/src/hb-ot-shape-complex-khmer-machine.hh
gfx/harfbuzz/src/hb-ot-shape-complex-khmer-machine.rl
gfx/harfbuzz/src/hb-ot-shape-complex-khmer.cc
gfx/harfbuzz/src/hb-ot-shape-complex-khmer.hh
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-myanmar.hh
gfx/harfbuzz/src/hb-ot-shape-complex-thai.cc
gfx/harfbuzz/src/hb-ot-shape-complex-tibetan.cc
gfx/harfbuzz/src/hb-ot-shape-complex-use-machine.hh
gfx/harfbuzz/src/hb-ot-shape-complex-use-machine.rl
gfx/harfbuzz/src/hb-ot-shape-complex-use-table.cc
gfx/harfbuzz/src/hb-ot-shape-complex-use.cc
gfx/harfbuzz/src/hb-ot-shape-complex-use.hh
gfx/harfbuzz/src/hb-ot-shape-complex-vowel-constraints.cc
gfx/harfbuzz/src/hb-ot-shape-complex-vowel-constraints.hh
gfx/harfbuzz/src/hb-ot-shape-complex.hh
gfx/harfbuzz/src/hb-ot-shape-fallback.cc
gfx/harfbuzz/src/hb-ot-shape-fallback.hh
gfx/harfbuzz/src/hb-ot-shape-normalize.cc
gfx/harfbuzz/src/hb-ot-shape-normalize.hh
gfx/harfbuzz/src/hb-ot-shape.cc
gfx/harfbuzz/src/hb-ot-shape.hh
gfx/harfbuzz/src/hb-ot-stat-table.hh
gfx/harfbuzz/src/hb-ot-tag-table.hh
gfx/harfbuzz/src/hb-ot-tag.cc
gfx/harfbuzz/src/hb-ot-tag.h
gfx/harfbuzz/src/hb-ot-var-avar-table.hh
gfx/harfbuzz/src/hb-ot-var-fvar-table.hh
gfx/harfbuzz/src/hb-ot-var-hvar-table.hh
gfx/harfbuzz/src/hb-ot-var-mvar-table.hh
gfx/harfbuzz/src/hb-ot-var.cc
gfx/harfbuzz/src/hb-ot-var.h
gfx/harfbuzz/src/hb-ot-vorg-table.hh
gfx/harfbuzz/src/hb-ot.h
gfx/harfbuzz/src/hb-set-digest.hh
gfx/harfbuzz/src/hb-set.cc
gfx/harfbuzz/src/hb-set.hh
gfx/harfbuzz/src/hb-shape-plan.cc
gfx/harfbuzz/src/hb-shape-plan.hh
gfx/harfbuzz/src/hb-shape.cc
gfx/harfbuzz/src/hb-shaper-impl.hh
gfx/harfbuzz/src/hb-shaper-list.hh
gfx/harfbuzz/src/hb-shaper.cc
gfx/harfbuzz/src/hb-shaper.hh
gfx/harfbuzz/src/hb-static.cc
gfx/harfbuzz/src/hb-subset-cff-common.cc
gfx/harfbuzz/src/hb-subset-cff-common.hh
gfx/harfbuzz/src/hb-subset-cff1.cc
gfx/harfbuzz/src/hb-subset-cff1.hh
gfx/harfbuzz/src/hb-subset-cff2.cc
gfx/harfbuzz/src/hb-subset-cff2.hh
gfx/harfbuzz/src/hb-subset-glyf.cc
gfx/harfbuzz/src/hb-subset-glyf.hh
gfx/harfbuzz/src/hb-subset-input.cc
gfx/harfbuzz/src/hb-subset-input.hh
gfx/harfbuzz/src/hb-subset-plan.cc
gfx/harfbuzz/src/hb-subset-plan.hh
gfx/harfbuzz/src/hb-subset.cc
gfx/harfbuzz/src/hb-subset.h
gfx/harfbuzz/src/hb-subset.hh
gfx/harfbuzz/src/hb-ucdn.cc
gfx/harfbuzz/src/hb-unicode-emoji-table.hh
gfx/harfbuzz/src/hb-unicode.cc
gfx/harfbuzz/src/hb-unicode.h
gfx/harfbuzz/src/hb-unicode.hh
gfx/harfbuzz/src/hb-uniscribe.cc
gfx/harfbuzz/src/hb-utf.hh
gfx/harfbuzz/src/hb-vector.hh
gfx/harfbuzz/src/hb-version.h
gfx/harfbuzz/src/hb.h
gfx/harfbuzz/src/hb.hh
gfx/harfbuzz/src/moz.build
gfx/harfbuzz/src/sample.py
gfx/harfbuzz/src/test-name-table.cc
gfx/harfbuzz/src/test-ot-color.cc
gfx/harfbuzz/src/test-size-params.cc
gfx/harfbuzz/src/test-unicode-ranges.cc
gfx/harfbuzz/update.sh
--- a/gfx/harfbuzz/AUTHORS
+++ b/gfx/harfbuzz/AUTHORS
@@ -1,9 +1,11 @@
 Behdad Esfahbod
-Simon Hausmann
-Martin Hosken
+David Turner
+Ebrahim Byagowi
 Jonathan Kew
+Khaled Hosny
 Lars Knoll
-Werner Lemberg
+Martin Hosken
+Owen Taylor
 Roozbeh Pournader
-Owen Taylor
-David Turner
+Simon Hausmann
+Werner Lemberg
--- a/gfx/harfbuzz/NEWS
+++ b/gfx/harfbuzz/NEWS
@@ -1,8 +1,251 @@
+Overview of changes leading to 2.3.0
+Thursday, December 20, 2018
+====================================
+- Fix regression on big-endian architectures.  Ouch!
+- Misc bug and build fixes.
+- Fix subsetting of simple GSUB/GDEF.
+- Merge CFF / CFF2 support contributed by Adobe.  This mostly involves
+  the subsetter, but also get_glyph_extents on CFF fonts.
+
+New API in hb-aat.h:
++hb_aat_layout_has_substitution()
++hb_aat_layout_has_positioning()
++hb_aat_layout_has_tracking()
+
+
+Overview of changes leading to 2.2.0
+Thursday, November 29, 2018
+====================================
+- Misc shaping bug fixes.
+- Add font variations named-instance API.
+- Deprecate font variations axis enumeration API and add replacement.
+- AAT shaping improvements:
+  o Fixed 'kern' table Format 2 implementation.
+  o Implement 'feat' table API for feature detection.
+  o Blacklist 'GSUB' table of fonts from 'MUTF' foundry that also have 'morx'.
+
+New API:
++hb_aat_layout_feature_type_t
++hb_aat_layout_feature_selector_t
++hb_aat_layout_get_feature_types()
++hb_aat_layout_feature_type_get_name_id
++hb_aat_layout_feature_selector_info_t
++HB_AAT_LAYOUT_NO_SELECTOR_INDEX
++hb_aat_layout_feature_type_get_selector_infos()
++hb_ot_var_axis_flags_t
++hb_ot_var_axis_info_t
++hb_ot_var_get_axis_infos()
++hb_ot_var_find_axis_info()
++hb_ot_var_get_named_instance_count()
++hb_ot_var_named_instance_get_subfamily_name_id()
++hb_ot_var_named_instance_get_postscript_name_id()
++hb_ot_var_named_instance_get_design_coords()
+
+Deprecated API:
++HB_OT_VAR_NO_AXIS_INDEX
++hb_ot_var_axis_t
++hb_ot_var_get_axes()
++hb_ot_var_find_axis()
+
+
+Overview of changes leading to 2.1.3
+Friday, November 16, 2018
+====================================
+- Fix AAT 'mort' shaping, which was broken in 2.1.2
+
+
+Overview of changes leading to 2.1.2
+Friday, November 16, 2018
+====================================
+- Various internal changes.
+- AAT shaping improvements:
+  o Implement kern table Format 1 state-machine-based kerning.
+  o Implement cross-stream kerning (cursive positioning, etc).
+  o Ignore emptyish GSUB tables (zero scripts) if morx present.
+  o Don't apply GPOS if morx is being applied.  Matches Apple.
+
+
+-Overview of changes leading to 2.1.1
+Monday, November 5, 2018
+====================================
+- AAT improvements:
+  o Implement 'mort' table.
+  o Implement 'kern' subtables Format 1 and Format 3.
+
+
+Overview of changes leading to 2.1.0
+Tuesday, October 30, 2018
+====================================
+- AAT shaping improvements:
+  o Allow user controlling AAT features, for whole buffer only currently.
+  o Several 'morx' fixes.
+  o Implement tuple-kerns in 'kerx'; Fixes kerning with Apple default
+    San Francisco fonts.
+- Support for color fonts:
+  o COLR/CPAL API to fetch color layers.
+  o SVG table to fetch SVG documents.
+  o CBDT/sbix API to fetch PNG images.
+- New 'name' table API.
+- hb-ot-font now uses 'VORG' table to correctly position CFF glyphs
+  in vertical layout.
+- Various fuzzer-found bug fixes.
+
+Changed API:
+
+A type and a macro added in 2.0.0 were renamed:
+
+hb_name_id_t -> hb_ot_name_id_t
+HB_NAME_ID_INVALID -> HB_OT_NAME_ID_INVALID
+
+New API:
+
++hb_color_t
++HB_COLOR
++hb_color_get_alpha()
++hb_color_get_red()
++hb_color_get_green()
++hb_color_get_blue()
++hb_ot_color_has_palettes()
++hb_ot_color_palette_get_count()
++hb_ot_color_palette_get_name_id()
++hb_ot_color_palette_color_get_name_id()
++hb_ot_color_palette_flags_t
++hb_ot_color_palette_get_flags()
++hb_ot_color_palette_get_colors()
++hb_ot_color_has_layers()
++hb_ot_color_layer_t
++hb_ot_color_glyph_get_layers()
++hb_ot_color_has_svg()
++hb_ot_color_glyph_reference_svg()
++hb_ot_color_has_png()
++hb_ot_color_glyph_reference_png()
+
++hb_ot_name_id_t
++HB_OT_NAME_ID_INVALID
++HB_OT_NAME_ID_COPYRIGHT
++HB_OT_NAME_ID_FONT_FAMILY
++HB_OT_NAME_ID_FONT_SUBFAMILY
++HB_OT_NAME_ID_UNIQUE_ID
++HB_OT_NAME_ID_FULL_NAME
++HB_OT_NAME_ID_VERSION_STRING
++HB_OT_NAME_ID_POSTSCRIPT_NAME
++HB_OT_NAME_ID_TRADEMARK
++HB_OT_NAME_ID_MANUFACTURER
++HB_OT_NAME_ID_DESIGNER
++HB_OT_NAME_ID_DESCRIPTION
++HB_OT_NAME_ID_VENDOR_URL
++HB_OT_NAME_ID_DESIGNER_URL
++HB_OT_NAME_ID_LICENSE
++HB_OT_NAME_ID_LICENSE_URL
++HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY
++HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY
++HB_OT_NAME_ID_MAC_FULL_NAME
++HB_OT_NAME_ID_SAMPLE_TEXT
++HB_OT_NAME_ID_CID_FINDFONT_NAME
++HB_OT_NAME_ID_WWS_FAMILY
++HB_OT_NAME_ID_WWS_SUBFAMILY
++HB_OT_NAME_ID_LIGHT_BACKGROUND
++HB_OT_NAME_ID_DARK_BACKGROUND
++HB_OT_NAME_ID_VARIATIONS_PS_PREFIX
++hb_ot_name_entry_t
++hb_ot_name_list_names()
++hb_ot_name_get_utf8()
++hb_ot_name_get_utf16()
++hb_ot_name_get_utf32()
+
+
+Overview of changes leading to 2.0.2
+Saturday, October 20, 2018
+====================================
+- Fix two minor memory access issues in AAT tables.
+
+
+Overview of changes leading to 2.0.1
+Friday, October 19, 2018
+====================================
+- Fix hb-version.h reported release version that went wrong (1.8.0)
+  with previous release.
+- Fix extrapolation in 'trak' table.
+- Fix hb-font infinite-recursion issue with some font funcs and
+  subclassed fonts.
+- Implement variation-kerning format in kerx table, although without
+  variation.
+- Fix return value of hb_map_is_empty().
+
+
+Overview of changes leading to 2.0.0
+Thursday, October 18, 2018
+====================================
+- Added AAT shaping support (morx/kerx/trak).
+  Automatically used if GSUB/GPOS are not available respectively.
+  Set HB_OPTIONS=aat env var to have morx/kerx preferred over
+  GSUB/GPOS.
+- Apply TrueType kern table internally, instead of relying on
+  hb_font_t callbacks.
+- Khmer shaper significantly rewritten to better match Uniscribe.
+- Indic3 tags ('dev3', etc) are passed to USE shaper.
+- .dfont Mac font containers implemented.
+- Script- and language-mapping revamped to better use BCP 47.
+- Misc USE and Indic fixes.
+- Misc everything fixes.
+- Too many things to list.  Biggest release since 0.9.1, with
+  over 500 commits in just over 5 weeks!  Didn't intend it to
+  be a big release.  Just happened to become.
+- hb-ft now locks underlying FT_Face during use.
+
+API changes:
+
+- Newly-created hb_font_t's now have our internal "hb-ot-font"
+  callbacks set on them, so they should work out of the box
+  without any callbacks set.  If callbacks are set, everything
+  is back to what it was before, the fallback callbacks are
+  null.  If you to get the internal implementation modified,
+  sub_font it.
+
+- New hb_font_funcs_set_nominal_glyphs_func() allows speeding
+  up character to glyph mapping.
+
+New API:
++HB_FEATURE_GLOBAL_START
++HB_FEATURE_GLOBAL_END
++hb_buffer_set_invisible_glyph()
++hb_buffer_get_invisible_glyph()
++hb_font_funcs_set_nominal_glyphs_func()
++hb_ot_layout_table_select_script()
++hb_ot_layout_script_select_language()
++hb_ot_layout_feature_get_name_ids()
++hb_ot_layout_feature_get_characters()
++hb_name_id_t
++HB_NAME_ID_INVALID
++HB_OT_MAX_TAGS_PER_SCRIPT
++hb_ot_tags_from_script_and_language()
++hb_ot_tags_to_script_and_language()
+
+Deprecated API:
+-hb_font_funcs_set_glyph_func()
+-hb_unicode_eastasian_width_func_t
+-hb_unicode_funcs_set_eastasian_width_func()
+-hb_unicode_eastasian_width()
+-hb_unicode_decompose_compatibility_func_t
+-HB_UNICODE_MAX_DECOMPOSITION_LEN
+-hb_unicode_funcs_set_decompose_compatibility_func()
+-hb_unicode_decompose_compatibility()
+-hb_font_funcs_set_glyph_h_kerning_func()
+-hb_font_funcs_set_glyph_v_kerning_func()
+-hb_font_get_glyph_h_kerning()
+-hb_font_get_glyph_v_kerning()
+-hb_font_get_glyph_kerning_for_direction()
+-hb_ot_layout_table_choose_script()
+-hb_ot_layout_script_find_language()
+-hb_ot_tags_from_script()
+-hb_ot_tag_from_language()
+
+
 Overview of changes leading to 1.9.0
 Monday, September 10, 2018
 ====================================
 - Added 'cmap' API to hb_face_t.
 - Face-builder API.
 - hb-ot-font re-creation should be much leaner now, as the
   font tables it uses are cached on hb_face_t now.
 - Internal source header file name changes:
--- a/gfx/harfbuzz/README
+++ b/gfx/harfbuzz/README
@@ -1,14 +1,15 @@
-[![Build Status](https://travis-ci.org/harfbuzz/harfbuzz.svg)](https://travis-ci.org/harfbuzz/harfbuzz)
-[![Build status](https://ci.appveyor.com/api/projects/status/0t0flrxpstj9lb9w?svg=true)](https://ci.appveyor.com/project/harfbuzz/harfbuzz)
-[![CircleCI](https://circleci.com/gh/harfbuzz/harfbuzz.svg?style=svg)](https://circleci.com/gh/harfbuzz/harfbuzz)
-[![Coverity](https://img.shields.io/coverity/scan/5450.svg)](https://scan.coverity.com/projects/behdad-harfbuzz)
-[![Codacy Badge](https://api.codacy.com/project/badge/Grade/f17f1708783c447488bc8dd317150eaa)](https://app.codacy.com/app/behdad/harfbuzz)
-[![Coverage Status](https://img.shields.io/coveralls/harfbuzz/harfbuzz.svg)](https://coveralls.io/r/harfbuzz/harfbuzz)
+[![Travis Build Status](https://travis-ci.org/harfbuzz/harfbuzz.svg)](https://travis-ci.org/harfbuzz/harfbuzz)
+[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/0t0flrxpstj9lb9w?svg=true)](https://ci.appveyor.com/project/harfbuzz/harfbuzz)
+[![CircleCI Build Status](https://circleci.com/gh/harfbuzz/harfbuzz.svg?style=svg)](https://circleci.com/gh/harfbuzz/harfbuzz)
+[![Coverity Code Health](https://img.shields.io/coverity/scan/5450.svg)](https://scan.coverity.com/projects/behdad-harfbuzz)
+[![Codacy Code Health](https://api.codacy.com/project/badge/Grade/f17f1708783c447488bc8dd317150eaa)](https://app.codacy.com/app/behdad/harfbuzz)
+[![Codecov Code Coverage](https://codecov.io/gh/harfbuzz/harfbuzz/branch/master/graph/badge.svg)](https://codecov.io/gh/harfbuzz/harfbuzz)
+[![Coverals Code Coverage](https://img.shields.io/coveralls/harfbuzz/harfbuzz.svg)](https://coveralls.io/r/harfbuzz/harfbuzz)
 [ABI Tracker](http://abi-laboratory.pro/tracker/timeline/harfbuzz/)
 
 This is HarfBuzz, a text shaping library.
 
 For bug reports, mailing list, and other information please visit:
 
   http://harfbuzz.org/
 
--- a/gfx/harfbuzz/README-mozilla
+++ b/gfx/harfbuzz/README-mozilla
@@ -1,12 +1,12 @@
 This directory contains the HarfBuzz source from the upstream repo:
 https://github.com/harfbuzz/harfbuzz
 
-Current version: 1.9.0 [commit 54d332dd9b0263821376161cdffb60ffb3c7847f]
+Current version: 2.3.0 [commit 4941e95f10fe0fe658752134a42b58896fb19c42]
 
 UPDATING:
 
 Our in-tree copy of HarfBuzz does not depend on any generated files from the
 upstream build system. Therefore, it should be sufficient to simply overwrite
 the in-tree files one the updated ones from upstream to perform updates.
 
 To simplify this, the in-tree copy can be updated by running
--- a/gfx/harfbuzz/TODO
+++ b/gfx/harfbuzz/TODO
@@ -1,34 +1,24 @@
-General fixes:
-=============
-
-- Implement 'rand' feature.
-
-
 API issues:
 ===========
 
 - API to accept a list of languages?
 
 - Remove hb_ot_shape_glyphs_closure()?
 
 
 API additions
 =============
 
 - Language to/from script.
 
 - Add hb-cairo glue
 
-- Add sanitize API (and a cached version, that saves result on blob user-data)
-
-- BCP 47 language handling / API (language_matches?)
-
-- Add hb_font_create_unscaled()?
+- Add sanitize API.
 
 - Add query / enumeration API for aalt-like features?
 
 - Add segmentation API
 
 - Add hb-fribidi glue?
 
 
--- a/gfx/harfbuzz/configure.ac
+++ b/gfx/harfbuzz/configure.ac
@@ -1,11 +1,11 @@
 AC_PREREQ([2.64])
 AC_INIT([HarfBuzz],
-        [1.9.0],
+        [2.3.0],
         [https://github.com/harfbuzz/harfbuzz/issues/new],
         [harfbuzz],
         [http://harfbuzz.org/])
 
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_SRCDIR([src/harfbuzz.pc.in])
 AC_CONFIG_HEADERS([config.h])
 
@@ -14,17 +14,16 @@ AM_SILENT_RULES([yes])
 AX_CODE_COVERAGE
 
 # Initialize libtool
 m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
 LT_PREREQ([2.2])
 LT_INIT([disable-static])
 
 # Check for programs
-AC_USE_SYSTEM_EXTENSIONS
 AC_PROG_CC
 AC_PROG_CC_C99
 AM_PROG_CC_C_O
 AC_PROG_CXX
 dnl AX_CXX_COMPILE_STDCXX(11, noext, optional)
 AC_SYS_LARGEFILE
 PKG_PROG_PKG_CONFIG([0.20])
 AM_MISSING_PROG([RAGEL], [ragel])
@@ -143,22 +142,16 @@ if test "$hb_os_win32" = no; then
 fi
 if $have_pthread; then
 	AC_DEFINE(HAVE_PTHREAD, 1, [Have POSIX threads])
 fi
 AM_CONDITIONAL(HAVE_PTHREAD, $have_pthread)
 
 dnl ==========================================================================
 
-have_ot=true
-if $have_ot; then
-	AC_DEFINE(HAVE_OT, 1, [Have native OpenType Layout backend])
-fi
-AM_CONDITIONAL(HAVE_OT, $have_ot)
-
 have_fallback=true
 if $have_fallback; then
 	AC_DEFINE(HAVE_FALLBACK, 1, [Have simple TrueType Layout backend])
 fi
 AM_CONDITIONAL(HAVE_FALLBACK, $have_fallback)
 
 dnl ===========================================================================
 
@@ -325,17 +318,17 @@ AM_CONDITIONAL(HAVE_UCDN, $have_ucdn)
 
 dnl ==========================================================================
 
 AC_ARG_WITH(graphite2,
 	[AS_HELP_STRING([--with-graphite2=@<:@yes/no/auto@:>@],
 			[Use the graphite2 library @<:@default=no@:>@])],,
 	[with_graphite2=no])
 have_graphite2=false
-GRAPHITE2_DEPS="graphite2"
+GRAPHITE2_DEPS="graphite2 >= 1.2.0"
 AC_SUBST(GRAPHITE2_DEPS)
 if test "x$with_graphite2" = "xyes" -o "x$with_graphite2" = "xauto"; then
 	PKG_CHECK_MODULES(GRAPHITE2, $GRAPHITE2_DEPS, have_graphite2=true, :)
 	if test "x$have_graphite2" != "xtrue"; then
                 # If pkg-config is not available, graphite2 can still be there
 		ac_save_CFLAGS="$CFLAGS"
 		ac_save_CPPFLAGS="$CPPFLAGS"
 		CFLAGS="$CFLAGS $GRAPHITE2_CFLAGS"
@@ -509,16 +502,17 @@ src/Makefile
 src/harfbuzz-config.cmake
 src/hb-ucdn/Makefile
 util/Makefile
 test/Makefile
 test/api/Makefile
 test/fuzzing/Makefile
 test/shaping/Makefile
 test/shaping/data/Makefile
+test/shaping/data/aots/Makefile
 test/shaping/data/in-house/Makefile
 test/shaping/data/text-rendering-tests/Makefile
 test/subset/Makefile
 test/subset/data/Makefile
 docs/Makefile
 docs/version.xml
 ])
 
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/HBIndicVowelConstraints.txt
@@ -0,0 +1,97 @@
+# Copied from https://docs.microsoft.com/en-us/typography/script-development/use
+# On October 23, 2018; with documentd dated 02/07/2018.
+
+  0905 0946       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN SHORT E
+  0905 093E       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA
+  0930 094D 0907  ; # DEVANAGARI LETTER RA, DEVANAGARI SIGN VIRAMA, DEVANAGARI LETTER I
+  0909 0941       ; # DEVANAGARI LETTER U, DEVANAGARI VOWEL SIGN U
+  090F 0945       ; # DEVANAGARI LETTER E, DEVANAGARI VOWEL SIGN CANDRA E
+  090F 0946       ; # DEVANAGARI LETTER E, DEVANAGARI VOWEL SIGN SHORT E
+  090F 0947       ; # DEVANAGARI LETTER E, DEVANAGARI VOWEL SIGN E
+  0905 0949       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN CANDRA O
+  0906 0945       ; # DEVANAGARI LETTER AA, DEVANAGARI VOWEL SIGN CANDRA E
+  0905 094A       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN SHORT O
+  0906 0946       ; # DEVANAGARI LETTER AA, DEVANAGARI VOWEL SIGN SHORT E
+  0905 094B       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN O
+  0906 0947       ; # DEVANAGARI LETTER AA, DEVANAGARI VOWEL SIGN E
+  0905 094C       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AU
+  0906 0948       ; # DEVANAGARI LETTER AA, DEVANAGARI VOWEL SIGN AI
+  0905 0945       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN CANDRA E
+  0905 093A       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN OE
+  0905 093B       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN OOE
+  0906 093A       ; # DEVANAGARI LETTER AA, DEVANAGARI VOWEL SIGN OE
+  0905 094F       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AW
+  0905 0956       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN UE
+  0905 0957       ; # DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN UUE
+  0985 09BE       ; # BENGALI LETTER A, BENGALI VOWEL SIGN AA
+  098B 09C3       ; # BENGALI LETTER VOCALIC R, BENGALI VOWEL SIGN VOCALIC R
+  098C 09E2       ; # BENGALI LETTER VOCALIC L, BENGALI VOWEL SIGN VOCALIC L
+  0A05 0A3E       ; # GURMUKHI LETTER A, GURMUKHI VOWEL SIGN AA
+  0A72 0A3F       ; # GURMUKHI IRI, GURMUKHI VOWEL SIGN I
+  0A72 0A40       ; # GURMUKHI IRI, GURMUKHI VOWEL SIGN II
+  0A73 0A41       ; # GURMUKHI URA, GURMUKHI VOWEL SIGN U
+  0A73 0A42       ; # GURMUKHI URA, GURMUKHI VOWEL SIGN UU
+  0A72 0A47       ; # GURMUKHI IRI, GURMUKHI VOWEL SIGN EE
+  0A05 0A48       ; # GURMUKHI LETTER A, GURMUKHI VOWEL SIGN AI
+  0A73 0A4B       ; # GURMUKHI URA, GURMUKHI VOWEL SIGN OO
+  0A05 0A4C       ; # GURMUKHI LETTER A, GURMUKHI VOWEL SIGN AU
+  0A85 0ABE       ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN AA
+  0A85 0AC5       ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN CANDRA E
+  0A85 0AC7       ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN E
+  0A85 0AC8       ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN AI
+  0A85 0AC9       ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN CANDRA O
+  0A85 0ACB       ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN O
+  0A85 0ABE 0AC5  ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN AA, GUJARATI VOWEL SIGN CANDRA E
+  0A85 0ACC       ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN AU
+  0A85 0ABE 0AC8  ; # GUJARATI LETTER A, GUJARATI VOWEL SIGN AA, GUJARATI VOWEL SIGN AI
+  0AC5 0ABE       ; # GUJARATI VOWEL SIGN CANDRA E, GUJARATI VOWEL SIGN AA
+  0B05 0B3E       ; # ORIYA LETTER A, ORIYA VOWEL SIGN AA
+  0B0F 0B57       ; # ORIYA LETTER E, ORIYA AU LENGTH MARK
+  0B13 0B57       ; # ORIYA LETTER O, ORIYA AU LENGTH MARK
+  0C12 0C55       ; # TELUGU LETTER O, TELUGU LENGTH MARK
+  0C12 0C4C       ; # TELUGU LETTER O, TELUGU VOWEL SIGN AU
+  0C3F 0C55       ; # TELUGU VOWEL SIGN I, TELUGU LENGTH MARK
+  0C46 0C55       ; # TELUGU VOWEL SIGN E, TELUGU LENGTH MARK
+  0C4A 0C55       ; # TELUGU VOWEL SIGN O, TELUGU LENGTH MARK
+  0C89 0CBE       ; # KANNADA LETTER U, KANNADA VOWEL SIGN AA
+  0C92 0CCC       ; # KANNADA LETTER O, KANNADA VOWEL SIGN AU
+  0C8B 0CBE       ; # KANNADA LETTER VOCALIC R, KANNADA VOWEL SIGN AA
+  0D07 0D57       ; # MALAYALAM LETTER I, MALAYALAM AU LENGTH MARK
+  0D09 0D57       ; # MALAYALAM LETTER U, MALAYALAM AU LENGTH MARK
+  0D0E 0D46       ; # MALAYALAM LETTER E, MALAYALAM VOWEL SIGN E
+  0D12 0D3E       ; # MALAYALAM LETTER O, MALAYALAM VOWEL SIGN AA
+  0D12 0D57       ; # MALAYALAM LETTER O, MALAYALAM AU LENGTH MARK
+  0D85 0DCF       ; # SINHALA LETTER AYANNA, SINHALA VOWEL SIGN AELA-PILLA
+  0D85 0DD0       ; # SINHALA LETTER AYANNA, SINHALA VOWEL SIGN KETTI AEDA-PILLA
+  0D85 0DD1       ; # SINHALA LETTER AYANNA, SINHALA VOWEL SIGN DIGA AEDA-PILLA
+  0D8B 0DDF       ; # SINHALA LETTER UYANNA, SINHALA VOWEL SIGN GAYANUKITTA
+  0D8D 0DD8       ; # SINHALA LETTER IRUYANNA, SINHALA VOWEL SIGN GAETTA-PILLA
+  0D8F 0DDF       ; # SINHALA LETTER ILUYANNA, SINHALA VOWEL SIGN GAYANUKITTA
+  0D91 0DCA       ; # SINHALA LETTER EYANNA, SINHALA SIGN AL-LAKUNA
+  0D91 0DD9       ; # SINHALA LETTER EYANNA, SINHALA VOWEL SIGN KOMBUVA
+  0D91 0DDA       ; # SINHALA LETTER EYANNA, SINHALA VOWEL SIGN DIGA KOMBUVA
+  0D91 0DDC       ; # SINHALA LETTER EYANNA, SINHALA VOWEL SIGN KOMBUVA HAA AELA-PILLA
+  0D91 0DDD       ; # SINHALA LETTER EYANNA, SINHALA VOWEL SIGN KOMBUVA HAA DIGA AELA-PILLA
+  0D91 0DDD       ; # SINHALA LETTER EYANNA, SINHALA VOWEL SIGN KOMBUVA HAA DIGA AELA-PILLA
+  0D94 0DDF       ; # SINHALA LETTER OYANNA, SINHALA VOWEL SIGN GAYANUKITTA
+  11005 11038     ; # BRAHMI LETTER A, BRAHMI VOWEL SIGN AA
+  1100B 1103E     ; # BRAHMI LETTER VOCALIC R, BRAHMI VOWEL SIGN VOCALIC R
+  1100F 11042     ; # BRAHMI LETTER E, BRAHMI VOWEL SIGN E
+  11680 116AD     ; # TAKRI LETTER A, TAKRI VOWEL SIGN AA
+  11686 116B2     ; # TAKRI LETTER E, TAKRI VOWEL SIGN E
+  11680 116B4     ; # TAKRI LETTER A, TAKRI VOWEL SIGN O
+  11680 116B5     ; # TAKRI LETTER A, TAKRI VOWEL SIGN AU
+  112B0 112E0     ; # KHUDAWADI LETTER A, KHUDAWADI VOWEL SIGN AA
+  112B0 112E5     ; # KHUDAWADI LETTER A, KHUDAWADI VOWEL SIGN E
+  112B0 112E6     ; # KHUDAWADI LETTER A, KHUDAWADI VOWEL SIGN AI
+  112B0 112E7     ; # KHUDAWADI LETTER A, KHUDAWADI VOWEL SIGN O
+  112B0 112E8     ; # KHUDAWADI LETTER A, KHUDAWADI VOWEL SIGN AU
+  11481 114B0     ; # TIRHUTA LETTER A, TIRHUTA VOWEL SIGN AA
+  114AA 114B5     ; # TIRHUTA LETTER LA, TIRHUTA VOWEL SIGN VOCALIC R
+  114AA 114B6     ; # TIRHUTA LETTER LA, TIRHUTA VOWEL SIGN VOCALIC RR
+  1148B 114BA     ; # TIRHUTA LETTER E, TIRHUTA VOWEL SIGN SHORT E
+  1148D 114BA     ; # TIRHUTA LETTER O, TIRHUTA VOWEL SIGN SHORT E
+  11600 11639     ; # MODI LETTER A, MODI VOWEL SIGN E
+  11600 1163A     ; # MODI LETTER A, MODI VOWEL SIGN AI
+  11601 11639     ; # MODI LETTER AA, MODI VOWEL SIGN E
+  11601 1163A     ; # MODI LETTER AA, MODI VOWEL SIGN AI
--- a/gfx/harfbuzz/src/Makefile.am
+++ b/gfx/harfbuzz/src/Makefile.am
@@ -8,38 +8,31 @@ EXTRA_DIST =
 CLEANFILES =
 DISTCLEANFILES =
 MAINTAINERCLEANFILES =
 DISTCHECK_CONFIGURE_FLAGS = --enable-introspection
 TESTS =
 check_PROGRAMS =
 
 # Convenience targets:
-lib: $(BUILT_SOURCES) libharfbuzz.la libharfbuzz-subset.la
+lib: $(BUILT_SOURCES) libharfbuzz.la
 libs: $(BUILT_SOURCES) $(lib_LTLIBRARIES)
-fuzzing: $(BUILT_SOURCES) libharfbuzz-fuzzing.la libharfbuzz-subset-fuzzing.la
 
 lib_LTLIBRARIES = libharfbuzz.la
 
 include Makefile.sources
 
 HBCFLAGS =
 HBLIBS =
 HBNONPCLIBS =
 HBDEPS =
 HBSOURCES =  $(HB_BASE_sources)
 HBSOURCES += $(HB_BASE_RAGEL_GENERATED_sources)
 HBHEADERS = $(HB_BASE_headers)
 
-if HAVE_OT
-HBSOURCES += $(HB_OT_sources)
-HBSOURCES += $(HB_OT_RAGEL_GENERATED_sources)
-HBHEADERS += $(HB_OT_headers)
-endif
-
 if HAVE_FALLBACK
 HBSOURCES += $(HB_FALLBACK_sources)
 endif
 
 if HAVE_PTHREAD
 HBCFLAGS += $(PTHREAD_CFLAGS)
 HBNONPCLIBS += $(PTHREAD_LIBS)
 endif
@@ -166,47 +159,16 @@ libharfbuzz_subset_la_SOURCES = $(HB_SUB
 libharfbuzz_subset_la_CPPFLAGS = $(HBCFLAGS) $(CODE_COVERAGE_CFLAGS)
 libharfbuzz_subset_la_LDFLAGS = $(base_link_flags) $(export_symbols_subset) $(CODE_COVERAGE_LDFLAGS)
 libharfbuzz_subset_la_LIBADD = libharfbuzz.la
 EXTRA_libharfbuzz_subset_la_DEPENDENCIES = $(harfbuzz_subset_def_dependency)
 pkginclude_HEADERS += $(HB_SUBSET_headers)
 pkgconfig_DATA += harfbuzz-subset.pc
 EXTRA_DIST += harfbuzz-subset.pc.in
 
-FUZZING_CPPFLAGS = \
-	-DHB_NDEBUG \
-	-DHB_MAX_NESTING_LEVEL=3 \
-	-DHB_SANITIZE_MAX_EDITS=3 \
-	-DHB_SANITIZE_MAX_OPS_FACTOR=3 \
-	-DHB_SANITIZE_MAX_OPS_MIN=128 \
-	-DHB_BUFFER_MAX_LEN_FACTOR=3 \
-	-DHB_BUFFER_MAX_LEN_MIN=8 \
-	-DHB_BUFFER_MAX_LEN_DEFAULT=128 \
-	-DHB_BUFFER_MAX_OPS_FACTOR=8 \
-	-DHB_BUFFER_MAX_OPS_MIN=64 \
-	-DHB_BUFFER_MAX_OPS_DEFAULT=1024 \
-	$(NULL)
-EXTRA_LTLIBRARIES = libharfbuzz-fuzzing.la libharfbuzz-subset-fuzzing.la
-
-libharfbuzz_fuzzing_la_LINK = $(chosen_linker) $(libharfbuzz_fuzzing_la_LDFLAGS)
-libharfbuzz_fuzzing_la_SOURCES = $(libharfbuzz_la_SOURCES)
-libharfbuzz_fuzzing_la_CPPFLAGS = $(HBCFLAGS) $(FUZZING_CPPFLAGS)
-libharfbuzz_fuzzing_la_LDFLAGS = $(AM_LDFLAGS)
-libharfbuzz_fuzzing_la_LIBADD = $(libharfbuzz_la_LIBADD)
-EXTRA_libharfbuzz_fuzzing_la_DEPENDENCIES = $(EXTRA_libharfbuzz_la_DEPENDENCIES)
-CLEANFILES += libharfbuzz-fuzzing.la
-
-libharfbuzz_subset_fuzzing_la_LINK = $(chosen_linker) $(libharfbuzz_subset_fuzzing_la_LDFLAGS)
-libharfbuzz_subset_fuzzing_la_SOURCES = $(libharfbuzz_subset_la_SOURCES)
-libharfbuzz_subset_fuzzing_la_CPPFLAGS = $(HBCFLAGS) $(FUZZING_CPPFLAGS)
-libharfbuzz_subset_fuzzing_la_LDFLAGS = $(AM_LDFLAGS)
-libharfbuzz_subset_fuzzing_la_LIBADD = $(libharfbuzz_subset_la_LIBADD)
-EXTRA_libharfbuzz_subset_fuzzing_la_DEPENDENCIES = $(EXTRA_libharfbuzz_subset_la_DEPENDENCIES)
-CLEANFILES += libharfbuzz-subset-fuzzing.la
-
 if HAVE_ICU
 if HAVE_ICU_BUILTIN
 HBCFLAGS += $(ICU_CFLAGS)
 HBLIBS += $(ICU_LIBS)
 HBSOURCES += $(HB_ICU_sources)
 HBHEADERS += $(HB_ICU_headers)
 else
 lib_LTLIBRARIES += libharfbuzz-icu.la
@@ -266,101 +228,131 @@ EXTRA_DIST += \
 		-e 's@%requires_private%@$(HBDEPS)@g' \
 		-e 's@%VERSION%@$(VERSION)@g' \
 	"$<" > "$@" \
 	|| ($(RM) "$@"; false)
 
 CLEANFILES += $(pkgconfig_DATA)
 
 
-DEF_FILES = harfbuzz.def harfbuzz-subset.def harfbuzz-icu.def
+DEF_FILES = harfbuzz.def harfbuzz-subset.def harfbuzz-icu.def harfbuzz-deprecated-symbols.txt
 if HAVE_GOBJECT
 DEF_FILES += harfbuzz-gobject.def
 endif
 check: $(DEF_FILES) # For check-symbols.sh
 CLEANFILES += $(DEF_FILES)
 harfbuzz.def: $(HBHEADERS) $(HBNODISTHEADERS)
 	$(AM_V_GEN) $(srcdir)/gen-def.py "$@" $^
 harfbuzz-subset.def: $(HB_SUBSET_headers)
 	$(AM_V_GEN) $(srcdir)/gen-def.py "$@" $^
 harfbuzz-icu.def: $(HB_ICU_headers)
 	$(AM_V_GEN) $(srcdir)/gen-def.py "$@" $^
 harfbuzz-gobject.def: $(HB_GOBJECT_headers)
 	$(AM_V_GEN) $(srcdir)/gen-def.py "$@" $^
+harfbuzz-deprecated-symbols.txt: $(srcdir)/hb-deprecated.h
+	$(AM_V_GEN) PLAIN_LIST=1 $(srcdir)/gen-def.py "$@" $^
 
 
 GENERATORS = \
 	gen-arabic-table.py \
+	gen-def.py \
+	gen-emoji-table.py \
 	gen-indic-table.py \
+	gen-os2-unicode-ranges.py \
+	gen-tag-table.py \
 	gen-use-table.py \
-	gen-def.py \
+	gen-vowel-constraints.py \
 	$(NULL)
 EXTRA_DIST += $(GENERATORS)
 
-unicode-tables: arabic-table indic-table use-table
+unicode-tables: arabic-table indic-table tag-table use-table emoji-table
 
 arabic-table: gen-arabic-table.py ArabicShaping.txt UnicodeData.txt Blocks.txt
 	$(AM_V_GEN) $(builddir)/$^ > $(srcdir)/hb-ot-shape-complex-arabic-table.hh \
 	|| ($(RM) $(srcdir)/hb-ot-shape-complex-arabic-table.hh; false)
 
 indic-table: gen-indic-table.py IndicSyllabicCategory.txt IndicPositionalCategory.txt Blocks.txt
 	$(AM_V_GEN) $(builddir)/$^ > $(srcdir)/hb-ot-shape-complex-indic-table.cc \
 	|| ($(RM) $(srcdir)/hb-ot-shape-complex-indic-table.cc; false)
 
+tag-table: gen-tag-table.py languagetags language-subtag-registry
+	$(AM_V_GEN) $(builddir)/$^ > $(srcdir)/hb-ot-tag-table.hh \
+	|| ($(RM) $(srcdir)/hb-ot-tag-table.hh; false)
+
 use-table: gen-use-table.py IndicSyllabicCategory.txt IndicPositionalCategory.txt UnicodeData.txt Blocks.txt
 	$(AM_V_GEN) $(builddir)/$^ > $(srcdir)/hb-ot-shape-complex-use-table.cc \
 	|| ($(RM) $(srcdir)/hb-ot-shape-complex-use-table.cc; false)
 
+vowel-constraints: gen-vowel-constraints.py HBIndicVowelConstraints.txt Scripts.txt
+	$(AM_V_GEN) $(builddir)/$^ > $(srcdir)/hb-ot-shape-complex-vowel-constraints.cc \
+	|| ($(RM) $(srcdir)/hb-ot-shape-complex-vowel-constraints.cc; false)
+
+emoji-table: gen-emoji-table.py emoji-data.txt
+	$(AM_V_GEN) $(builddir)/$^ > $(srcdir)/hb-unicode-emoji-table.hh \
+	|| ($(RM) $(srcdir)/hb-unicode-emoji-table.hh; false)
+
 built-sources: $(BUILT_SOURCES)
 
-.PHONY: unicode-tables arabic-table indic-table use-table built-sources
+.PHONY: unicode-tables arabic-table indic-table tag-table use-table vowel-constraints emoji-table built-sources
 
 RAGEL_GENERATED = \
 	$(patsubst %,$(srcdir)/%,$(HB_BASE_RAGEL_GENERATED_sources)) \
-	$(patsubst %,$(srcdir)/%,$(HB_OT_RAGEL_GENERATED_sources)) \
 	$(NULL)
 BUILT_SOURCES += $(RAGEL_GENERATED)
 EXTRA_DIST += \
 	$(HB_BASE_RAGEL_sources) \
-	$(HB_OT_RAGEL_sources) \
 	$(NULL)
 # We decided to add ragel-generated files to git...
 #MAINTAINERCLEANFILES += $(RAGEL_GENERATED)
 $(srcdir)/%.hh: $(srcdir)/%.rl
 	$(AM_V_GEN)(cd $(srcdir) && $(RAGEL) -e -F1 -o "$*.hh" "$*.rl") \
 	|| ($(RM) "$@"; false)
 
 noinst_PROGRAMS = \
 	main \
 	test \
 	test-buffer-serialize \
+	test-name-table \
 	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)
 test_LDADD = libharfbuzz.la $(HBLIBS) $(FREETYPE_LIBS)
 
-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_buffer_serialize_SOURCES = test-buffer-serialize.cc
+test_buffer_serialize_CPPFLAGS = $(HBCFLAGS)
+test_buffer_serialize_LDADD = libharfbuzz.la $(HBLIBS)
+
+test_name_table_SOURCES = test-name-table.cc
+test_name_table_CPPFLAGS = $(HBCFLAGS)
+test_name_table_LDADD = libharfbuzz.la $(HBLIBS)
 
 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)
+test_would_substitute_SOURCES = test-would-substitute.cc
+test_would_substitute_CPPFLAGS = $(HBCFLAGS) $(FREETYPE_CFLAGS)
+test_would_substitute_LDADD = libharfbuzz.la $(HBLIBS) $(FREETYPE_LIBS)
+
+if HAVE_FREETYPE
+if HAVE_CAIRO_FT
+noinst_PROGRAMS += test-ot-color
+test_ot_color_SOURCES = test-ot-color.cc
+test_ot_color_CPPFLAGS = $(HBCFLAGS) $(FREETYPE_CFLAGS) $(CAIRO_FT_CFLAGS)
+test_ot_color_LDADD = libharfbuzz.la $(HBLIBS) $(FREETYPE_LIBS) $(CAIRO_LIBS) $(CAIRO_FT_LIBS)
+endif # HAVE_CAIRO_FT
+endif # HAVE_FREETYPE
 
 dist_check_SCRIPTS = \
 	check-c-linkage-decls.sh \
 	check-externs.sh \
 	check-header-guards.sh \
 	check-includes.sh \
 	check-static-inits.sh \
 	check-symbols.sh \
@@ -387,25 +379,16 @@ dump_khmer_data_CPPFLAGS = $(HBCFLAGS)
 dump_khmer_data_LDADD = libharfbuzz.la $(HBLIBS)
 dump_myanmar_data_SOURCES = dump-myanmar-data.cc hb-ot-shape-complex-indic-table.cc
 dump_myanmar_data_CPPFLAGS = $(HBCFLAGS)
 dump_myanmar_data_LDADD = libharfbuzz.la $(HBLIBS)
 dump_use_data_SOURCES = dump-use-data.cc hb-ot-shape-complex-use-table.cc
 dump_use_data_CPPFLAGS = $(HBCFLAGS)
 dump_use_data_LDADD = libharfbuzz.la $(HBLIBS)
 
-if HAVE_FREETYPE
-if HAVE_CAIRO_FT
-check_PROGRAMS += dump-emoji
-dump_emoji_SOURCES = dump-emoji.cc
-dump_emoji_CPPFLAGS = $(HBCFLAGS) $(FREETYPE_CFLAGS) $(CAIRO_FT_CFLAGS)
-dump_emoji_LDADD = libharfbuzz.la $(HBLIBS) $(FREETYPE_LIBS) $(CAIRO_LIBS) $(CAIRO_FT_LIBS)
-endif # HAVE_CAIRO_FT
-endif # HAVE_FREETYPE
-
 check_PROGRAMS += test-ot-tag test-unicode-ranges
 TESTS += test-ot-tag test-unicode-ranges
 
 test_ot_tag_SOURCES = hb-ot-tag.cc
 test_ot_tag_CPPFLAGS = $(HBCFLAGS) -DMAIN
 test_ot_tag_LDADD = libharfbuzz.la $(HBLIBS)
 
 test_unicode_ranges_SOURCES = test-unicode-ranges.cc
@@ -430,16 +413,18 @@ HarfBuzz-0.0.gir: libharfbuzz.la libharf
 HarfBuzz_0_0_gir_INCLUDES = GObject-2.0
 HarfBuzz_0_0_gir_CFLAGS = \
 	$(INCLUDES) \
 	$(HBCFLAGS) \
 	-DHB_H \
 	-DHB_H_IN \
 	-DHB_OT_H \
 	-DHB_OT_H_IN \
+	-DHB_AAT_H \
+	-DHB_AAT_H_IN \
 	-DHB_GOBJECT_H \
 	-DHB_GOBJECT_H_IN \
 	-DHB_EXTERN= \
 	$(NULL)
 HarfBuzz_0_0_gir_LIBS = \
 	libharfbuzz.la \
 	libharfbuzz-gobject.la \
 	$(NULL)
--- a/gfx/harfbuzz/src/Makefile.sources
+++ b/gfx/harfbuzz/src/Makefile.sources
@@ -1,186 +1,205 @@
 # Base and default-included sources and headers
 
 HB_BASE_sources = \
+	hb-aat-fdsc-table.hh \
+	hb-aat-layout-ankr-table.hh \
+	hb-aat-layout-bsln-table.hh \
+	hb-aat-layout-common.hh \
+	hb-aat-layout-feat-table.hh \
+	hb-aat-layout-just-table.hh \
+	hb-aat-layout-kerx-table.hh \
+	hb-aat-layout-lcar-table.hh \
+	hb-aat-layout-morx-table.hh \
+	hb-aat-layout-trak-table.hh \
+	hb-aat-layout.cc \
+	hb-aat-layout.hh \
+	hb-aat-ltag-table.hh \
+	hb-aat-map.cc \
+	hb-aat-map.hh \
+	hb-array.hh \
 	hb-atomic.hh \
+	hb-blob.cc \
 	hb-blob.hh \
-	hb-blob.cc \
-	hb-buffer.hh \
 	hb-buffer-serialize.cc \
 	hb-buffer.cc \
+	hb-buffer.hh \
+	hb-cache.hh \
+	hb-cff-interp-common.hh \
+	hb-cff-interp-cs-common.hh \
+	hb-cff-interp-dict-common.hh \
+	hb-cff1-interp-cs.hh \
+	hb-cff2-interp-cs.hh \
 	hb-common.cc \
 	hb-debug.hh \
 	hb-dsalgs.hh \
+	hb-face.cc \
 	hb-face.hh \
-	hb-face.cc \
-	hb-font.hh \
 	hb-font.cc \
-	hb-iter.hh \
+	hb-font.hh \
+	hb-kern.hh \
+	hb-machinery.hh \
+	hb-map.cc \
 	hb-map.hh \
-	hb-map.cc \
-	hb-machinery.hh \
 	hb-mutex.hh \
 	hb-null.hh \
 	hb-object.hh \
 	hb-open-file.hh \
 	hb-open-type.hh \
+	hb-ot-cff-common.hh \
+	hb-ot-cff1-table.cc \
+	hb-ot-cff1-table.hh \
+	hb-ot-cff2-table.cc \
+	hb-ot-cff2-table.hh \
+	hb-ot-cmap-table.hh \
 	hb-ot-color-cbdt-table.hh \
-	hb-ot-cmap-table.hh \
+	hb-ot-color-colr-table.hh \
+	hb-ot-color-cpal-table.hh \
+	hb-ot-color-sbix-table.hh \
+	hb-ot-color-svg-table.hh \
+	hb-ot-color.cc \
+	hb-ot-face.cc \
+	hb-ot-face.hh \
+	hb-ot-font.cc \
+	hb-ot-gasp-table.hh \
 	hb-ot-glyf-table.hh \
 	hb-ot-hdmx-table.hh \
 	hb-ot-head-table.hh \
 	hb-ot-hhea-table.hh \
 	hb-ot-hmtx-table.hh \
 	hb-ot-kern-table.hh \
+	hb-ot-layout-base-table.hh \
+	hb-ot-layout-common.hh \
+	hb-ot-layout-gdef-table.hh \
+	hb-ot-layout-gpos-table.hh \
+	hb-ot-layout-gsub-table.hh \
+	hb-ot-layout-gsubgpos.hh \
+	hb-ot-layout-jstf-table.hh \
+	hb-ot-layout.cc \
+	hb-ot-layout.hh \
+	hb-ot-map.cc \
+	hb-ot-map.hh \
+	hb-ot-math-table.hh \
+	hb-ot-math.cc \
 	hb-ot-maxp-table.hh \
+	hb-ot-name-language.cc \
+	hb-ot-name-language.hh \
 	hb-ot-name-table.hh \
+	hb-ot-name.cc \
 	hb-ot-os2-table.hh \
 	hb-ot-os2-unicode-ranges.hh \
 	hb-ot-post-macroman.hh \
 	hb-ot-post-table.hh \
+	hb-ot-shape-complex-arabic-fallback.hh \
+	hb-ot-shape-complex-arabic-table.hh \
+	hb-ot-shape-complex-arabic-win1256.hh \
+	hb-ot-shape-complex-arabic.cc \
+	hb-ot-shape-complex-arabic.hh \
+	hb-ot-shape-complex-default.cc \
+	hb-ot-shape-complex-hangul.cc \
+	hb-ot-shape-complex-hebrew.cc \
+	hb-ot-shape-complex-indic-table.cc \
+	hb-ot-shape-complex-indic.cc \
+	hb-ot-shape-complex-indic.hh \
+	hb-ot-shape-complex-khmer.cc \
+	hb-ot-shape-complex-khmer.hh \
+	hb-ot-shape-complex-myanmar.cc \
+	hb-ot-shape-complex-myanmar.hh \
+	hb-ot-shape-complex-thai.cc \
+	hb-ot-shape-complex-use-table.cc \
+	hb-ot-shape-complex-use.cc \
+	hb-ot-shape-complex-use.hh \
+	hb-ot-shape-complex-vowel-constraints.cc \
+	hb-ot-shape-complex-vowel-constraints.hh \
+	hb-ot-shape-complex.hh \
+	hb-ot-shape-fallback.cc \
+	hb-ot-shape-fallback.hh \
+	hb-ot-shape-normalize.cc \
+	hb-ot-shape-normalize.hh \
+	hb-ot-shape.cc \
+	hb-ot-shape.hh \
+	hb-ot-stat-table.hh \
+	hb-ot-tag-table.hh \
 	hb-ot-tag.cc \
-	hb.hh \
+	hb-ot-var-avar-table.hh \
+	hb-ot-var-fvar-table.hh \
+	hb-ot-var-hvar-table.hh \
+	hb-ot-var-mvar-table.hh \
+	hb-ot-var.cc \
+	hb-ot-vorg-table.hh \
 	hb-set-digest.hh \
+	hb-set.cc \
 	hb-set.hh \
-	hb-set.cc \
+	hb-shape-plan.cc \
+	hb-shape-plan.hh \
 	hb-shape.cc \
-	hb-shape-plan.hh \
-	hb-shape-plan.cc \
+	hb-shaper-impl.hh \
 	hb-shaper-list.hh \
-	hb-shaper-impl.hh \
+	hb-shaper.cc \
 	hb-shaper.hh \
-	hb-shaper.cc \
 	hb-static.cc \
 	hb-string-array.hh \
+	hb-unicode-emoji-table.hh \
+	hb-unicode.cc \
 	hb-unicode.hh \
-	hb-unicode.cc \
+	hb-utf.hh \
 	hb-vector.hh \
-	hb-utf.hh \
 	hb-warning.cc \
+	hb.hh \
 	$(NULL)
 
 HB_BASE_RAGEL_GENERATED_sources = \
 	hb-buffer-deserialize-json.hh \
 	hb-buffer-deserialize-text.hh \
+	hb-ot-shape-complex-indic-machine.hh \
+	hb-ot-shape-complex-khmer-machine.hh \
+	hb-ot-shape-complex-myanmar-machine.hh \
+	hb-ot-shape-complex-use-machine.hh \
 	$(NULL)
 HB_BASE_RAGEL_sources = \
 	hb-buffer-deserialize-json.rl \
 	hb-buffer-deserialize-text.rl \
+	hb-ot-shape-complex-indic-machine.rl \
+	hb-ot-shape-complex-khmer-machine.rl \
+	hb-ot-shape-complex-myanmar-machine.rl \
+	hb-ot-shape-complex-use-machine.rl \
 	$(NULL)
 
 HB_BASE_headers = \
-	hb.h \
+	hb-aat-layout.h \
+	hb-aat.h \
 	hb-blob.h \
 	hb-buffer.h \
 	hb-common.h \
 	hb-deprecated.h \
 	hb-face.h \
 	hb-font.h \
 	hb-map.h \
+	hb-ot-color.h \
+	hb-ot-deprecated.h \
+	hb-ot-font.h \
+	hb-ot-layout.h \
+	hb-ot-math.h \
+	hb-ot-name.h \
+	hb-ot-shape.h \
+	hb-ot-var.h \
+	hb-ot.h \
 	hb-set.h \
+	hb-shape-plan.h \
 	hb-shape.h \
-	hb-shape-plan.h \
 	hb-unicode.h \
 	hb-version.h \
+	hb.h \
 	$(NULL)
 
 HB_FALLBACK_sources = \
 	hb-fallback-shape.cc	\
 	$(NULL)
 
-HB_OT_sources = \
-	hb-aat-layout.cc \
-	hb-aat-layout-common.hh \
-	hb-aat-layout-ankr-table.hh \
-	hb-aat-layout-bsln-table.hh \
-	hb-aat-layout-feat-table.hh \
-	hb-aat-layout-kerx-table.hh \
-	hb-aat-layout-morx-table.hh \
-	hb-aat-layout-trak-table.hh \
-	hb-aat-layout.hh \
-	hb-aat-ltag-table.hh \
-	hb-ot-face.hh \
-	hb-ot-face.cc \
-	hb-ot-font.cc \
-	hb-ot-layout.cc \
-	hb-ot-layout-base-table.hh \
-	hb-ot-layout-common.hh \
-	hb-ot-layout-gdef-table.hh \
-	hb-ot-layout-gpos-table.hh \
-	hb-ot-layout-gsubgpos.hh \
-	hb-ot-layout-gsub-table.hh \
-	hb-ot-layout-jstf-table.hh \
-	hb-ot-layout.hh \
-	hb-ot-color.cc \
-	hb-ot-color-colr-table.hh \
-	hb-ot-color-cpal-table.hh \
-	hb-ot-color-sbix-table.hh \
-	hb-ot-color-svg-table.hh \
-	hb-ot-map.cc \
-	hb-ot-map.hh \
-	hb-ot-math.cc \
-	hb-ot-math-table.hh \
-	hb-ot-shape.cc \
-	hb-ot-shape-complex-arabic.cc \
-	hb-ot-shape-complex-arabic-fallback.hh \
-	hb-ot-shape-complex-arabic.hh \
-	hb-ot-shape-complex-arabic-table.hh \
-	hb-ot-shape-complex-arabic-win1256.hh \
-	hb-ot-shape-complex-default.cc \
-	hb-ot-shape-complex-hangul.cc \
-	hb-ot-shape-complex-hebrew.cc \
-	hb-ot-shape-complex-indic.cc \
-	hb-ot-shape-complex-indic.hh \
-	hb-ot-shape-complex-indic-table.cc \
-	hb-ot-shape-complex-khmer.hh \
-	hb-ot-shape-complex-khmer.cc \
-	hb-ot-shape-complex-myanmar.hh \
-	hb-ot-shape-complex-myanmar.cc \
-	hb-ot-shape-complex-thai.cc \
-	hb-ot-shape-complex-tibetan.cc \
-	hb-ot-shape-complex-use.cc \
-	hb-ot-shape-complex-use.hh \
-	hb-ot-shape-complex-use-table.cc \
-	hb-ot-shape-complex.hh \
-	hb-ot-shape-normalize.hh \
-	hb-ot-shape-normalize.cc \
-	hb-ot-shape-fallback.hh \
-	hb-ot-shape-fallback.cc \
-	hb-ot-shape.hh \
-	hb-ot-var.cc \
-	hb-ot-var-avar-table.hh \
-	hb-ot-var-fvar-table.hh \
-	hb-ot-var-hvar-table.hh \
-	hb-ot-var-mvar-table.hh \
-	$(NULL)
-
-HB_OT_RAGEL_GENERATED_sources = \
-	hb-ot-shape-complex-indic-machine.hh \
-	hb-ot-shape-complex-khmer-machine.hh \
-	hb-ot-shape-complex-myanmar-machine.hh \
-	hb-ot-shape-complex-use-machine.hh \
-	$(NULL)
-HB_OT_RAGEL_sources = \
-	hb-ot-shape-complex-indic-machine.rl \
-	hb-ot-shape-complex-khmer-machine.rl \
-	hb-ot-shape-complex-myanmar-machine.rl \
-	hb-ot-shape-complex-use-machine.rl \
-	$(NULL)
-
-HB_OT_headers = \
-	hb-ot.h \
-	hb-ot-font.h \
-	hb-ot-layout.h \
-	hb-ot-math.h \
-	hb-ot-shape.h \
-	hb-ot-tag.h \
-	hb-ot-var.h \
-	$(NULL)
-
 # Optional Sources and Headers with external deps
 
 HB_FT_sources = hb-ft.cc
 HB_FT_headers = hb-ft.h
 
 HB_GLIB_sources = hb-glib.cc
 HB_GLIB_headers = hb-glib.h
 
@@ -202,25 +221,36 @@ HB_UNISCRIBE_headers = hb-uniscribe.h
 HB_UCDN_sources  = hb-ucdn.cc
 
 # Sources for libharfbuzz-gobject and libharfbuzz-icu
 HB_ICU_sources = hb-icu.cc
 HB_ICU_headers = hb-icu.h
 
 # Sources for libharfbuzz-subset
 HB_SUBSET_sources = \
+	hb-ot-cff1-table.cc \
+	hb-ot-cff2-table.cc \
 	hb-static.cc \
-	hb-subset.cc \
-	hb-subset.hh \
+	hb-subset-cff-common.cc \
+	hb-subset-cff-common.hh \
+	hb-subset-cff1.cc \
+	hb-subset-cff1.hh \
+	hb-subset-cff2.cc \
+	hb-subset-cff2.hh \
 	hb-subset-glyf.cc \
 	hb-subset-glyf.hh \
+	hb-subset-glyf.hh \
 	hb-subset-input.cc \
 	hb-subset-input.hh \
 	hb-subset-plan.cc \
 	hb-subset-plan.hh \
+	hb-subset-plan.hh \
+	hb-subset.cc \
+	hb-subset.hh \
+	hb-subset.hh \
 	$(NULL)
 
 HB_SUBSET_headers = \
 	hb-subset.h \
 	$(NULL)
 
 HB_GOBJECT_DIST_sources = hb-gobject-structs.cc
 HB_GOBJECT_DIST_headers = hb-gobject.h hb-gobject-structs.h
--- a/gfx/harfbuzz/src/check-static-inits.sh
+++ b/gfx/harfbuzz/src/check-static-inits.sh
@@ -2,17 +2,16 @@
 
 LC_ALL=C
 export LC_ALL
 
 test -z "$srcdir" && srcdir=.
 test -z "$libs" && libs=.libs
 stat=0
 
-
 if which objdump 2>/dev/null >/dev/null; then
 	:
 else
 	echo "check-static-inits.sh: 'objdump' not found; skipping test"
 	exit 77
 fi
 
 OBJS=$libs/*.o
@@ -26,15 +25,16 @@ for obj in $OBJS; do
 	if objdump -t "$obj" | grep '[.][cd]tors' | grep -v '\<00*\>'; then
 		echo "Ouch, $obj has static initializers/finalizers"
 		stat=1
 	fi
 done
 
 echo "Checking that no object file has lazy static C++ constructors/destructors or other such stuff"
 for obj in $OBJS; do
-	if objdump -t "$obj" | grep '__cxa_'; then
+	if objdump -t "$obj" | grep -q '__cxa_' && ! objdump -t "$obj" | grep -q __ubsan_handle; then
+		objdump -t "$obj" | grep '__cxa_'
 		echo "Ouch, $obj has lazy static C++ constructors/destructors or other such stuff"
 		stat=1
 	fi
 done
 
 exit $stat
--- a/gfx/harfbuzz/src/check-symbols.sh
+++ b/gfx/harfbuzz/src/check-symbols.sh
@@ -21,17 +21,17 @@ for soname in harfbuzz harfbuzz-subset h
 	for suffix in so dylib; do
 		so=$libs/lib$soname.$suffix
 		if ! test -f "$so"; then continue; fi
 
 		# On macOS, C symbols are prefixed with _
 		symprefix=
 		if test $suffix = dylib; then symprefix=_; fi
 
-		EXPORTED_SYMBOLS="`nm "$so" | grep ' [BCDGINRSTVW] .' | grep -v " $symprefix\\($IGNORED_SYMBOLS\\>\\)" | cut -d' ' -f3 | c++filt`"
+		EXPORTED_SYMBOLS="`nm "$so" | grep ' [BCDGINRST] .' | grep -v " $symprefix\\($IGNORED_SYMBOLS\\>\\)" | cut -d' ' -f3 | c++filt`"
 
 		prefix=$symprefix`basename "$so" | sed 's/libharfbuzz/hb/; s/-/_/g; s/[.].*//'`
 
 		echo "Checking that $so does not expose internal symbols"
 		if echo "$EXPORTED_SYMBOLS" | grep -v "^${prefix}\(_\|$\)"; then
 			echo "Ouch, internal symbols exposed"
 			stat=1
 		fi
deleted file mode 100644
--- a/gfx/harfbuzz/src/dump-emoji.cc
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright © 2018  Ebrahim Byagowi
- *
- *  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.
- */
-
-#include "hb-static.cc"
-#include "hb-ot-color-cbdt-table.hh"
-#include "hb-ot-color-colr-table.hh"
-#include "hb-ot-color-cpal-table.hh"
-#include "hb-ot-color-sbix-table.hh"
-#include "hb-ot-color-svg-table.hh"
-
-#include "hb-ft.h"
-
-#include <ft2build.h>
-#include FT_FREETYPE_H
-#include FT_GLYPH_H
-
-#include <cairo.h>
-#include <cairo-ft.h>
-#include <cairo-svg.h>
-
-#ifdef HAVE_GLIB
-#include <glib.h>
-#endif
-#include <stdlib.h>
-#include <stdio.h>
-
-void cbdt_callback (const uint8_t* data, unsigned int length,
-                    unsigned int group, unsigned int gid)
-{
-  char output_path[255];
-  sprintf (output_path, "out/cbdt-%d-%d.png", group, gid);
-  FILE *f = fopen (output_path, "wb");
-  fwrite (data, 1, length, f);
-  fclose (f);
-}
-
-void sbix_callback (const uint8_t* data, unsigned int length,
-                    unsigned int group, unsigned int gid)
-{
-  char output_path[255];
-  sprintf (output_path, "out/sbix-%d-%d.png", group, gid);
-  FILE *f = fopen (output_path, "wb");
-  fwrite (data, 1, length, f);
-  fclose (f);
-}
-
-void svg_callback (const uint8_t* data, unsigned int length,
-                   unsigned int start_glyph, unsigned int end_glyph)
-{
-  char output_path[255];
-  if (start_glyph == end_glyph)
-    sprintf (output_path, "out/svg-%d.svg", start_glyph);
-  else
-    sprintf (output_path, "out/svg-%d-%d.svg", start_glyph, end_glyph);
-
-  // append "z" if the content is gzipped
-  if ((data[0] == 0x1F) && (data[1] == 0x8B))
-    strcat (output_path, "z");
-
-  FILE *f = fopen (output_path, "wb");
-  fwrite (data, 1, length, f);
-  fclose (f);
-}
-
-void colr_cpal_rendering (cairo_font_face_t *cairo_face, unsigned int upem, unsigned int num_glyphs,
-			  const OT::COLR *colr, const OT::CPAL *cpal)
-{
-  for (unsigned int i = 0; i < num_glyphs; ++i)
-  {
-    unsigned int first_layer_index, num_layers;
-    if (colr->get_base_glyph_record (i, &first_layer_index, &num_layers))
-    {
-      // Measure
-      cairo_text_extents_t extents;
-      {
-	cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
-	cairo_t *cr = cairo_create (surface);
-	cairo_set_font_face (cr, cairo_face);
-	cairo_set_font_size (cr, upem);
-
-	cairo_glyph_t *glyphs = (cairo_glyph_t *) calloc (num_layers, sizeof (cairo_glyph_t));
-	for (unsigned int j = 0; j < num_layers; ++j)
-	{
-	  hb_codepoint_t glyph_id;
-	  unsigned int color_index;
-	  colr->get_layer_record (first_layer_index + j, &glyph_id, &color_index);
-	  glyphs[j].index = glyph_id;
-	}
-	cairo_glyph_extents (cr, glyphs, num_layers, &extents);
-	free (glyphs);
-	cairo_surface_destroy (surface);
-	cairo_destroy (cr);
-      }
-
-      // Add a slight margin
-      extents.width += extents.width / 10;
-      extents.height += extents.height / 10;
-      extents.x_bearing -= extents.width / 20;
-      extents.y_bearing -= extents.height / 20;
-
-      // Render
-      unsigned int pallet_count = cpal->get_palette_count ();
-      for (unsigned int pallet = 0; pallet < pallet_count; ++pallet) {
-	char output_path[255];
-
-	// If we have more than one pallet, use a better namin
-	if (pallet_count == 1)
-	  sprintf (output_path, "out/colr-%d.svg", i);
-	else
-	  sprintf (output_path, "out/colr-%d-%d.svg", i, pallet);
-
-	cairo_surface_t *surface = cairo_svg_surface_create (output_path, extents.width, extents.height);
-	cairo_t *cr = cairo_create (surface);
-	cairo_set_font_face (cr, cairo_face);
-	cairo_set_font_size (cr, upem);
-
-	for (unsigned int j = 0; j < num_layers; ++j)
-	{
-	  hb_codepoint_t glyph_id;
-	  unsigned int color_index;
-	  colr->get_layer_record (first_layer_index + j, &glyph_id, &color_index);
-
-	  uint32_t color = cpal->get_color_record_argb (color_index, pallet);
-	  int alpha = color & 0xFF;
-	  int r = (color >> 8) & 0xFF;
-	  int g = (color >> 16) & 0xFF;
-	  int b = (color >> 24) & 0xFF;
-	  cairo_set_source_rgba (cr, r / 255.f, g / 255.f, b / 255.f, alpha);
-
-	  cairo_glyph_t glyph;
-	  glyph.index = glyph_id;
-	  glyph.x = -extents.x_bearing;
-	  glyph.y = -extents.y_bearing;
-	  cairo_show_glyphs (cr, &glyph, 1);
-	}
-
-	cairo_surface_destroy (surface);
-	cairo_destroy (cr);
-      }
-    }
-  }
-}
-
-void dump_glyphs (cairo_font_face_t *cairo_face, unsigned int upem, unsigned int num_glyphs)
-{
-  // Dump every glyph available on the font
-  return; // disabled for now
-  for (unsigned int i = 0; i < num_glyphs; ++i)
-  {
-    cairo_text_extents_t extents;
-    cairo_glyph_t glyph = {0};
-    glyph.index = i;
-
-    // Measure
-    {
-      cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
-      cairo_t *cr = cairo_create (surface);
-      cairo_set_font_face (cr, cairo_face);
-      cairo_set_font_size (cr, upem);
-
-      cairo_glyph_extents (cr, &glyph, 1, &extents);
-      cairo_surface_destroy (surface);
-      cairo_destroy (cr);
-    }
-
-    // Add a slight margin
-    extents.width += extents.width / 10;
-    extents.height += extents.height / 10;
-    extents.x_bearing -= extents.width / 20;
-    extents.y_bearing -= extents.height / 20;
-
-    // Render
-    {
-      char output_path[255];
-      sprintf (output_path, "out/%d.svg", i);
-      cairo_surface_t *surface = cairo_svg_surface_create (output_path, extents.width, extents.height);
-      cairo_t *cr = cairo_create (surface);
-      cairo_set_font_face (cr, cairo_face);
-      cairo_set_font_size (cr, upem);
-      glyph.x = -extents.x_bearing;
-      glyph.y = -extents.y_bearing;
-      cairo_show_glyphs (cr, &glyph, 1);
-      cairo_surface_destroy (surface);
-      cairo_destroy (cr);
-    }
-  }
-}
-
-int main (int argc, char **argv)
-{
-  if (argc != 2) {
-    fprintf (stderr, "usage: %s font-file.ttf\n", argv[0]);
-    exit (1);
-  }
-
-  hb_blob_t *blob = hb_blob_create_from_file (argv[1]);
-  hb_face_t *face = hb_face_create (blob, 0);
-  hb_font_t *font = hb_font_create (face);
-
-  OT::CBDT::accelerator_t cbdt;
-  cbdt.init (face);
-  cbdt.dump (cbdt_callback);
-  cbdt.fini ();
-
-  OT::sbix::accelerator_t sbix;
-  sbix.init (face);
-  sbix.dump (sbix_callback);
-  sbix.fini ();
-
-  OT::SVG::accelerator_t svg;
-  svg.init (face);
-  svg.dump (svg_callback);
-  svg.fini ();
-
-  hb_blob_t* colr_blob = hb_sanitize_context_t ().reference_table<OT::COLR> (face);
-  const OT::COLR *colr = colr_blob->as<OT::COLR> ();
-
-  hb_blob_t* cpal_blob = hb_sanitize_context_t ().reference_table<OT::CPAL> (face);
-  const OT::CPAL *cpal = cpal_blob->as<OT::CPAL> ();
-
-  cairo_font_face_t *cairo_face;
-  {
-    FT_Library library;
-    FT_Init_FreeType (&library);
-    FT_Face ftface;
-    FT_New_Face (library, argv[1], 0, &ftface);
-    cairo_face = cairo_ft_font_face_create_for_ft_face (ftface, 0);
-  }
-  unsigned int num_glyphs = hb_face_get_glyph_count (face);
-  unsigned int upem = hb_face_get_upem (face);
-  colr_cpal_rendering (cairo_face, upem, num_glyphs, colr, cpal);
-  dump_glyphs (cairo_face, upem, num_glyphs);
-
-
-  hb_font_destroy (font);
-  hb_face_destroy (face);
-  hb_blob_destroy (blob);
-
-  return 0;
-}
--- a/gfx/harfbuzz/src/dump-indic-data.cc
+++ b/gfx/harfbuzz/src/dump-indic-data.cc
@@ -22,17 +22,17 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #include "hb-ot-shape-complex-indic.hh"
 
 int
-main (void)
+main ()
 {
   for (hb_codepoint_t u = 0; u <= 0x10FFFF; u++)
   {
     hb_glyph_info_t info;
     info.codepoint = u;
     set_indic_properties (info);
     if (info.indic_category() != INDIC_SYLLABIC_CATEGORY_OTHER ||
 	info.indic_position() != INDIC_MATRA_CATEGORY_NOT_APPLICABLE)
--- a/gfx/harfbuzz/src/dump-khmer-data.cc
+++ b/gfx/harfbuzz/src/dump-khmer-data.cc
@@ -22,22 +22,20 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #include "hb-ot-shape-complex-khmer.hh"
 
 int
-main (void)
+main ()
 {
   for (hb_codepoint_t u = 0; u <= 0x10FFFF; u++)
   {
     hb_glyph_info_t info;
     info.codepoint = u;
     set_khmer_properties (info);
-    if (info.khmer_category() != INDIC_SYLLABIC_CATEGORY_OTHER ||
-	info.khmer_position() != INDIC_MATRA_CATEGORY_NOT_APPLICABLE)
-      printf("U+%04X	%u	%u\n", u,
-	     info.khmer_category(),
-	     info.khmer_position());
+    if (info.khmer_category() != INDIC_SYLLABIC_CATEGORY_OTHER)
+      printf("U+%04X	%u\n", u,
+	     info.khmer_category());
   }
 }
--- a/gfx/harfbuzz/src/dump-myanmar-data.cc
+++ b/gfx/harfbuzz/src/dump-myanmar-data.cc
@@ -22,17 +22,17 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #include "hb-ot-shape-complex-myanmar.hh"
 
 int
-main (void)
+main ()
 {
   for (hb_codepoint_t u = 0; u <= 0x10FFFF; u++)
   {
     hb_glyph_info_t info;
     info.codepoint = u;
     set_myanmar_properties (info);
     if (info.myanmar_category() != INDIC_SYLLABIC_CATEGORY_OTHER ||
 	info.myanmar_position() != INDIC_MATRA_CATEGORY_NOT_APPLICABLE)
--- a/gfx/harfbuzz/src/dump-use-data.cc
+++ b/gfx/harfbuzz/src/dump-use-data.cc
@@ -22,17 +22,17 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #include "hb-ot-shape-complex-use.hh"
 
 int
-main (void)
+main ()
 {
   for (hb_codepoint_t u = 0; u <= 0x10FFFF; u++)
   {
     unsigned int category = hb_use_get_category (u);
     if (category != USE_O)
       printf("U+%04X	%u\n", u, category);
   }
 }
--- a/gfx/harfbuzz/src/gen-def.py
+++ b/gfx/harfbuzz/src/gen-def.py
@@ -10,16 +10,15 @@ if len (sys.argv) < 3:
 output_file = sys.argv[1]
 header_paths = sys.argv[2:]
 
 headers_content = []
 for h in header_paths:
 	if h.endswith (".h"):
 		with io.open (h, encoding='utf-8') as f: headers_content.append (f.read ())
 
-result = """EXPORTS
+symbols = "\n".join (sorted (re.findall (r"^hb_\w+(?= \()", "\n".join (headers_content), re.M)))
+
+result = symbols if os.environ.get('PLAIN_LIST', '') else """EXPORTS
 %s
-LIBRARY lib%s-0.dll""" % (
-	"\n".join (sorted (re.findall (r"^hb_\w+(?= \()", "\n".join (headers_content), re.M))),
-	output_file.replace ('.def', '')
-)
+LIBRARY lib%s-0.dll""" % (symbols, output_file.replace ('.def', ''))
 
 with open (output_file, "w") as f: f.write (result)
new file mode 100755
--- /dev/null
+++ b/gfx/harfbuzz/src/gen-emoji-table.py
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+
+from __future__ import print_function, division, absolute_import
+import sys
+import os.path
+from collections import OrderedDict
+
+if len (sys.argv) != 2:
+	print("usage: ./gen-emoji-table.py emoji-data.txt", file=sys.stderr)
+	sys.exit (1)
+
+f = open(sys.argv[1])
+header = [f.readline () for _ in range(10)]
+
+ranges = OrderedDict()
+for line in f.readlines():
+	line = line.strip()
+	if not line or line[0] == '#':
+		continue
+	rang, typ = [s.strip() for s in line.split('#')[0].split(';')[:2]]
+
+	rang = [int(s, 16) for s in rang.split('..')]
+	if len(rang) > 1:
+		start, end = rang
+	else:
+		start = end = rang[0]
+
+	if typ not in ranges:
+		ranges[typ] = []
+	if ranges[typ] and ranges[typ][-1][1] == start - 1:
+		ranges[typ][-1] = (ranges[typ][-1][0], end)
+	else:
+		ranges[typ].append((start, end))
+
+
+
+print ("/* == Start of generated table == */")
+print ("/*")
+print (" * The following tables are generated by running:")
+print (" *")
+print (" *   ./gen-emoji-table.py emoji-data.txt")
+print (" *")
+print (" * on file with this header:")
+print (" *")
+for l in header:
+	print (" * %s" % (l.strip()))
+print (" */")
+print ()
+print ("#ifndef HB_UNICODE_EMOJI_TABLE_HH")
+print ("#define HB_UNICODE_EMOJI_TABLE_HH")
+print ()
+print ('#include "hb-unicode.hh"')
+print ()
+
+for typ,s in ranges.items():
+	if typ != "Extended_Pictographic": continue
+	print()
+	print("static const struct hb_unicode_range_t _hb_unicode_emoji_%s_table[] =" % typ)
+	print("{")
+	for pair in sorted(s):
+		print("  {0x%04X, 0x%04X}," % pair)
+	print("};")
+
+print ()
+print ("#endif /* HB_UNICODE_EMOJI_TABLE_HH */")
+print ()
+print ("/* == End of generated table == */")
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/gen-os2-unicode-ranges.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+# Generates the code for a sorted unicode range array as used in hb-ot-os2-unicode-ranges.hh
+# Input is a tab seperated list of unicode ranges from the otspec
+# (https://docs.microsoft.com/en-us/typography/opentype/spec/os2#ulunicoderange1).
+
+from __future__ import print_function, division, absolute_import
+
+import io
+import re
+import sys
+
+reload(sys)
+sys.setdefaultencoding('utf-8')
+
+print ("""static OS2Range _hb_os2_unicode_ranges[] =
+{""")
+
+args = sys.argv[1:]
+input_file = args[0]
+
+with io.open(input_file, mode="r", encoding="utf-8") as f:
+
+  all_ranges = [];
+  current_bit = 0
+  while True:
+    line = f.readline().strip()
+    if not line:
+      break
+    fields = re.split(r'\t+', line)
+    if len(fields) == 3:
+      current_bit = fields[0]
+      fields = fields[1:]
+    elif len(fields) > 3:
+      raise Error("bad input :(.")
+
+    name = fields[0]
+    ranges = re.split("-", fields[1])
+    if len(ranges) != 2:
+      raise Error("bad input :(.")
+
+    v = tuple((int(ranges[0], 16), int(ranges[1], 16), int(current_bit), name))
+    all_ranges.append(v)
+
+all_ranges = sorted(all_ranges, key=lambda t: t[0])
+
+for ranges in all_ranges:
+  start = ("0x%X" % ranges[0]).rjust(8)
+  end = ("0x%X" % ranges[1]).rjust(8)
+  bit = ("%s" % ranges[2]).rjust(3)
+
+  print ("  {%s, %s, %s}, // %s" % (start, end, bit, ranges[3]))
+
+print ("""};""")
new file mode 100755
--- /dev/null
+++ b/gfx/harfbuzz/src/gen-tag-table.py
@@ -0,0 +1,1126 @@
+#!/usr/bin/python
+
+"""Generator of the mapping from OpenType tags to BCP 47 tags and vice
+versa.
+
+It creates a ``const LangTag[]``, matching the tags from the OpenType
+languages system tag list to the language subtags of the BCP 47 language
+subtag registry, with some manual adjustments. The mappings are
+supplemented with macrolanguages' sublanguages and retired codes'
+replacements, according to BCP 47 and some manual additions where BCP 47
+omits a retired code entirely.
+
+Also generated is a function, ``hb_ot_ambiguous_tag_to_language``,
+intended for use by ``hb_ot_tag_to_language``. It maps OpenType tags
+back to BCP 47 tags. Ambiguous OpenType tags (those that correspond to
+multiple BCP 47 tags) are listed here, except when the alphabetically
+first BCP 47 tag happens to be the chosen disambiguated tag. In that
+case, the fallback behavior will choose the right tag anyway.
+"""
+
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import collections
+try:
+	from HTMLParser import HTMLParser
+	def write (s):
+		print (s.encode ('utf-8'), end='')
+except ImportError:
+	from html.parser import HTMLParser
+	def write (s):
+		sys.stdout.flush ()
+		sys.stdout.buffer.write (s.encode ('utf-8'))
+import io
+import itertools
+import re
+import sys
+import unicodedata
+
+if len (sys.argv) != 3:
+	print ('usage: ./gen-tag-table.py languagetags language-subtag-registry', file=sys.stderr)
+	sys.exit (1)
+
+try:
+	from html import unescape
+	def html_unescape (parser, entity):
+		return unescape (entity)
+except ImportError:
+	def html_unescape (parser, entity):
+		return parser.unescape (entity)
+
+def expect (condition, message=None):
+	if not condition:
+		if message is None:
+			raise AssertionError
+		raise AssertionError (message)
+
+# from http://www-01.sil.org/iso639-3/iso-639-3.tab
+ISO_639_3_TO_1 = {
+	'aar': 'aa',
+	'abk': 'ab',
+	'afr': 'af',
+	'aka': 'ak',
+	'amh': 'am',
+	'ara': 'ar',
+	'arg': 'an',
+	'asm': 'as',
+	'ava': 'av',
+	'ave': 'ae',
+	'aym': 'ay',
+	'aze': 'az',
+	'bak': 'ba',
+	'bam': 'bm',
+	'bel': 'be',
+	'ben': 'bn',
+	'bis': 'bi',
+	'bod': 'bo',
+	'bos': 'bs',
+	'bre': 'br',
+	'bul': 'bg',
+	'cat': 'ca',
+	'ces': 'cs',
+	'cha': 'ch',
+	'che': 'ce',
+	'chu': 'cu',
+	'chv': 'cv',
+	'cor': 'kw',
+	'cos': 'co',
+	'cre': 'cr',
+	'cym': 'cy',
+	'dan': 'da',
+	'deu': 'de',
+	'div': 'dv',
+	'dzo': 'dz',
+	'ell': 'el',
+	'eng': 'en',
+	'epo': 'eo',
+	'est': 'et',
+	'eus': 'eu',
+	'ewe': 'ee',
+	'fao': 'fo',
+	'fas': 'fa',
+	'fij': 'fj',
+	'fin': 'fi',
+	'fra': 'fr',
+	'fry': 'fy',
+	'ful': 'ff',
+	'gla': 'gd',
+	'gle': 'ga',
+	'glg': 'gl',
+	'glv': 'gv',
+	'grn': 'gn',
+	'guj': 'gu',
+	'hat': 'ht',
+	'hau': 'ha',
+	'hbs': 'sh',
+	'heb': 'he',
+	'her': 'hz',
+	'hin': 'hi',
+	'hmo': 'ho',
+	'hrv': 'hr',
+	'hun': 'hu',
+	'hye': 'hy',
+	'ibo': 'ig',
+	'ido': 'io',
+	'iii': 'ii',
+	'iku': 'iu',
+	'ile': 'ie',
+	'ina': 'ia',
+	'ind': 'id',
+	'ipk': 'ik',
+	'isl': 'is',
+	'ita': 'it',
+	'jav': 'jv',
+	'jpn': 'ja',
+	'kal': 'kl',
+	'kan': 'kn',
+	'kas': 'ks',
+	'kat': 'ka',
+	'kau': 'kr',
+	'kaz': 'kk',
+	'khm': 'km',
+	'kik': 'ki',
+	'kin': 'rw',
+	'kir': 'ky',
+	'kom': 'kv',
+	'kon': 'kg',
+	'kor': 'ko',
+	'kua': 'kj',
+	'kur': 'ku',
+	'lao': 'lo',
+	'lat': 'la',
+	'lav': 'lv',
+	'lim': 'li',
+	'lin': 'ln',
+	'lit': 'lt',
+	'ltz': 'lb',
+	'lub': 'lu',
+	'lug': 'lg',
+	'mah': 'mh',
+	'mal': 'ml',
+	'mar': 'mr',
+	'mkd': 'mk',
+	'mlg': 'mg',
+	'mlt': 'mt',
+	'mol': 'mo',
+	'mon': 'mn',
+	'mri': 'mi',
+	'msa': 'ms',
+	'mya': 'my',
+	'nau': 'na',
+	'nav': 'nv',
+	'nbl': 'nr',
+	'nde': 'nd',
+	'ndo': 'ng',
+	'nep': 'ne',
+	'nld': 'nl',
+	'nno': 'nn',
+	'nob': 'nb',
+	'nor': 'no',
+	'nya': 'ny',
+	'oci': 'oc',
+	'oji': 'oj',
+	'ori': 'or',
+	'orm': 'om',
+	'oss': 'os',
+	'pan': 'pa',
+	'pli': 'pi',
+	'pol': 'pl',
+	'por': 'pt',
+	'pus': 'ps',
+	'que': 'qu',
+	'roh': 'rm',
+	'ron': 'ro',
+	'run': 'rn',
+	'rus': 'ru',
+	'sag': 'sg',
+	'san': 'sa',
+	'sin': 'si',
+	'slk': 'sk',
+	'slv': 'sl',
+	'sme': 'se',
+	'smo': 'sm',
+	'sna': 'sn',
+	'snd': 'sd',
+	'som': 'so',
+	'sot': 'st',
+	'spa': 'es',
+	'sqi': 'sq',
+	'srd': 'sc',
+	'srp': 'sr',
+	'ssw': 'ss',
+	'sun': 'su',
+	'swa': 'sw',
+	'swe': 'sv',
+	'tah': 'ty',
+	'tam': 'ta',
+	'tat': 'tt',
+	'tel': 'te',
+	'tgk': 'tg',
+	'tgl': 'tl',
+	'tha': 'th',
+	'tir': 'ti',
+	'ton': 'to',
+	'tsn': 'tn',
+	'tso': 'ts',
+	'tuk': 'tk',
+	'tur': 'tr',
+	'twi': 'tw',
+	'uig': 'ug',
+	'ukr': 'uk',
+	'urd': 'ur',
+	'uzb': 'uz',
+	'ven': 've',
+	'vie': 'vi',
+	'vol': 'vo',
+	'wln': 'wa',
+	'wol': 'wo',
+	'xho': 'xh',
+	'yid': 'yi',
+	'yor': 'yo',
+	'zha': 'za',
+	'zho': 'zh',
+	'zul': 'zu',
+}
+
+class LanguageTag (object):
+	"""A BCP 47 language tag.
+
+	Attributes:
+		subtags (List[str]): The list of subtags in this tag.
+		grandfathered (bool): Whether this tag is grandfathered. If
+			``true``, the entire lowercased tag is the ``language``
+			and the other subtag fields are empty.
+		language (str): The language subtag.
+		script (str): The script subtag.
+		region (str): The region subtag.
+		variant (str): The variant subtag.
+
+	Args:
+		tag (str): A BCP 47 language tag.
+
+	"""
+	def __init__ (self, tag):
+		global bcp_47
+		self.subtags = tag.lower ().split ('-')
+		self.grandfathered = tag.lower () in bcp_47.grandfathered
+		if self.grandfathered:
+			self.language = tag.lower ()
+			self.script = ''
+			self.region = ''
+			self.variant = ''
+		else:
+			self.language = self.subtags[0]
+			self.script = self._find_first (lambda s: len (s) == 4 and s[0] > '9', self.subtags)
+			self.region = self._find_first (lambda s: len (s) == 2 and s[0] > '9' or len (s) == 3 and s[0] <= '9', self.subtags[1:])
+			self.variant = self._find_first (lambda s: len (s) > 4 or len (s) == 4 and s[0] <= '9', self.subtags)
+
+	def __str__(self):
+		return '-'.join(self.subtags)
+
+	def __repr__ (self):
+		return 'LanguageTag(%r)' % str(self)
+
+	@staticmethod
+	def _find_first (function, sequence):
+		try:
+			return next (iter (filter (function, sequence)))
+		except StopIteration:
+			return None
+
+	def is_complex (self):
+		"""Return whether this tag is too complex to represent as a
+		``LangTag`` in the generated code.
+
+		Complex tags need to be handled in
+		``hb_ot_tags_from_complex_language``.
+
+		Returns:
+			Whether this tag is complex.
+		"""
+		return not (len (self.subtags) == 1
+			or self.grandfathered
+			and len (self.subtags[1]) != 3
+			and ot.from_bcp_47[self.subtags[0]] == ot.from_bcp_47[self.language])
+
+	def get_group (self):
+		"""Return the group into which this tag should be categorized in
+		``hb_ot_tags_from_complex_language``.
+
+		The group is the first letter of the tag, or ``'und'`` if this tag
+		should not be matched in a ``switch`` statement in the generated
+		code.
+
+		Returns:
+			This tag's group.
+		"""
+		return ('und'
+			if (self.language == 'und'
+				or self.variant in bcp_47.prefixes and len (bcp_47.prefixes[self.variant]) == 1)
+			else self.language[0])
+
+class OpenTypeRegistryParser (HTMLParser):
+	"""A parser for the OpenType language system tag registry.
+
+	Attributes:
+		header (str): The "last updated" line of the registry.
+		names (Mapping[str, str]): A map of language system tags to the
+			names they are given in the registry.
+		ranks (DefaultDict[str, int]): A map of language system tags to
+			numbers. If a single BCP 47 tag corresponds to multiple
+			OpenType tags, the tags are ordered in increasing order by
+			rank. The rank is based on the number of BCP 47 tags
+			associated with a tag, though it may be manually modified.
+		to_bcp_47 (DefaultDict[str, AbstractSet[str]]): A map of
+			OpenType language system tags to sets of BCP 47 tags.
+		from_bcp_47 (DefaultDict[str, AbstractSet[str]]): ``to_bcp_47``
+			inverted. Its values start as unsorted sets;
+			``sort_languages`` converts them to sorted lists.
+
+	"""
+	def __init__ (self):
+		HTMLParser.__init__ (self)
+		self.header = ''
+		self.names = {}
+		self.ranks = collections.defaultdict (int)
+		self.to_bcp_47 = collections.defaultdict (set)
+		self.from_bcp_47 = collections.defaultdict (set)
+		# Whether the parser is in a <td> element
+		self._td = False
+		# The text of the <td> elements of the current <tr> element.
+		self._current_tr = []
+
+	def handle_starttag (self, tag, attrs):
+		if tag == 'meta':
+			for attr, value in attrs:
+				if attr == 'name' and value == 'updated_at':
+					self.header = self.get_starttag_text ()
+					break
+		elif tag == 'td':
+			self._td = True
+			self._current_tr.append ('')
+		elif tag == 'tr':
+			self._current_tr = []
+
+	def handle_endtag (self, tag):
+		if tag == 'td':
+			self._td = False
+		elif tag == 'tr' and self._current_tr:
+			expect (2 <= len (self._current_tr) <= 3)
+			name = self._current_tr[0].strip ()
+			tag = self._current_tr[1].strip ("\t\n\v\f\r '")
+			rank = 0
+			if len (tag) > 4:
+				expect (tag.endswith (' (deprecated)'), 'ill-formed OpenType tag: %s' % tag)
+				name += ' (deprecated)'
+				tag = tag.split (' ')[0]
+				rank = 1
+			self.names[tag] = re.sub (' languages$', '', name)
+			if not self._current_tr[2]:
+				return
+			iso_codes = self._current_tr[2].strip ()
+			self.to_bcp_47[tag].update (ISO_639_3_TO_1.get (code, code) for code in iso_codes.replace (' ', '').split (','))
+			rank += 2 * len (self.to_bcp_47[tag])
+			self.ranks[tag] = rank
+
+	def handle_data (self, data):
+		if self._td:
+			self._current_tr[-1] += data
+
+	def handle_charref (self, name):
+		self.handle_data (html_unescape (self, '&#%s;' % name))
+
+	def handle_entityref (self, name):
+		self.handle_data (html_unescape (self, '&%s;' % name))
+
+	def parse (self, filename):
+		"""Parse the OpenType language system tag registry.
+
+		Args:
+			filename (str): The file name of the registry.
+		"""
+		with io.open (filename, encoding='utf-8') as f:
+			self.feed (f.read ())
+		expect (self.header)
+		for tag, iso_codes in self.to_bcp_47.items ():
+			for iso_code in iso_codes:
+				self.from_bcp_47[iso_code].add (tag)
+
+	def add_language (self, bcp_47_tag, ot_tag):
+		"""Add a language as if it were in the registry.
+
+		Args:
+			bcp_47_tag (str): A BCP 47 tag. If the tag is more than just
+				a language subtag, and if the language subtag is a
+				macrolanguage, then new languages are added corresponding
+				to the macrolanguages' individual languages with the
+				remainder of the tag appended.
+			ot_tag (str): An OpenType language system tag.
+		"""
+		global bcp_47
+		self.to_bcp_47[ot_tag].add (bcp_47_tag)
+		self.from_bcp_47[bcp_47_tag].add (ot_tag)
+		if bcp_47_tag.lower () not in bcp_47.grandfathered:
+			try:
+				[macrolanguage, suffix] = bcp_47_tag.split ('-', 1)
+				if macrolanguage in bcp_47.macrolanguages:
+					s = set ()
+					for language in bcp_47.macrolanguages[macrolanguage]:
+						if language.lower () not in bcp_47.grandfathered:
+							s.add ('%s-%s' % (language, suffix))
+					bcp_47.macrolanguages['%s-%s' % (macrolanguage, suffix)] = s
+			except ValueError:
+				pass
+
+	@staticmethod
+	def _remove_language (tag_1, dict_1, dict_2):
+		for tag_2 in dict_1.pop (tag_1):
+			dict_2[tag_2].remove (tag_1)
+			if not dict_2[tag_2]:
+				del dict_2[tag_2]
+
+	def remove_language_ot (self, ot_tag):
+		"""Remove an OpenType tag from the registry.
+
+		Args:
+			ot_tag (str): An OpenType tag.
+		"""
+		self._remove_language (ot_tag, self.to_bcp_47, self.from_bcp_47)
+
+	def remove_language_bcp_47 (self, bcp_47_tag):
+		"""Remove a BCP 47 tag from the registry.
+
+		Args:
+			bcp_47_tag (str): A BCP 47 tag.
+		"""
+		self._remove_language (bcp_47_tag, self.from_bcp_47, self.to_bcp_47)
+
+	def inherit_from_macrolanguages (self):
+		"""Copy mappings from macrolanguages to individual languages.
+
+		If a BCP 47 tag for an individual mapping has no OpenType
+		mapping but its macrolanguage does, the mapping is copied to
+		the individual language. For example, als (Tosk Albanian) has no
+		explicit mapping, so it inherits from sq (Albanian) the mapping
+		to SQI.
+
+		If a BCP 47 tag for a macrolanguage has no OpenType mapping but
+		all of its individual languages do and they all map to the same
+		tags, the mapping is copied to the macrolanguage.
+		"""
+		global bcp_47
+		original_ot_from_bcp_47 = dict (self.from_bcp_47)
+		for macrolanguage, languages in dict (bcp_47.macrolanguages).items ():
+			ot_macrolanguages = set (original_ot_from_bcp_47.get (macrolanguage, set ()))
+			if ot_macrolanguages:
+				for ot_macrolanguage in ot_macrolanguages:
+					for language in languages:
+						# Remove the following condition if e.g. nn should map to NYN,NOR
+						# instead of just NYN.
+						if language not in original_ot_from_bcp_47:
+							self.add_language (language, ot_macrolanguage)
+							self.ranks[ot_macrolanguage] += 1
+			else:
+				for language in languages:
+					if language in original_ot_from_bcp_47:
+						if ot_macrolanguages:
+							ml = original_ot_from_bcp_47[language]
+							if ml:
+								ot_macrolanguages &= ml
+							else:
+								pass
+						else:
+							ot_macrolanguages |= original_ot_from_bcp_47[language]
+					else:
+						ot_macrolanguages.clear ()
+					if not ot_macrolanguages:
+						break
+				for ot_macrolanguage in ot_macrolanguages:
+					self.add_language (macrolanguage, ot_macrolanguage)
+
+	def sort_languages (self):
+		"""Sort the values of ``from_bcp_47`` in ascending rank order."""
+		for language, tags in self.from_bcp_47.items ():
+			self.from_bcp_47[language] = sorted (tags,
+					key=lambda t: (self.ranks[t] + rank_delta (language, t), t))
+
+ot = OpenTypeRegistryParser ()
+
+class BCP47Parser (object):
+	"""A parser for the BCP 47 subtag registry.
+
+	Attributes:
+		header (str): The "File-Date" line of the registry.
+		names (Mapping[str, str]): A map of subtags to the names they
+			are given in the registry. Each value is a
+			``'\\n'``-separated list of names.
+		scopes (Mapping[str, str]): A map of language subtags to strings
+			suffixed to language names, including suffixes to explain
+			language scopes.
+		macrolanguages (DefaultDict[str, AbstractSet[str]]): A map of
+			language subtags to the sets of language subtags which
+			inherit from them. See
+			``OpenTypeRegistryParser.inherit_from_macrolanguages``.
+		prefixes (DefaultDict[str, AbstractSet[str]]): A map of variant
+			subtags to their prefixes.
+		grandfathered (AbstractSet[str]): The set of grandfathered tags,
+			normalized to lowercase.
+
+	"""
+	def __init__ (self):
+		self.header = ''
+		self.names = {}
+		self.scopes = {}
+		self.macrolanguages = collections.defaultdict (set)
+		self.prefixes = collections.defaultdict (set)
+		self.grandfathered = set ()
+
+	def parse (self, filename):
+		"""Parse the BCP 47 subtag registry.
+
+		Args:
+			filename (str): The file name of the registry.
+		"""
+		with io.open (filename, encoding='utf-8') as f:
+			subtag_type = None
+			subtag = None
+			deprecated = False
+			has_preferred_value = False
+			line_buffer = ''
+			for line in itertools.chain (f, ['']):
+				line = line.rstrip ()
+				if line.startswith (' '):
+					line_buffer += line[1:]
+					continue
+				line, line_buffer = line_buffer, line
+				if line.startswith ('Type: '):
+					subtag_type = line.split (' ')[1]
+					deprecated = False
+					has_preferred_value = False
+				elif line.startswith ('Subtag: ') or line.startswith ('Tag: '):
+					subtag = line.split (' ')[1]
+					if subtag_type == 'grandfathered':
+						self.grandfathered.add (subtag.lower ())
+				elif line.startswith ('Description: '):
+					description = line.split (' ', 1)[1].replace (' (individual language)', '')
+					description = re.sub (' (\((individual |macro)language\)|languages)$', '',
+							description)
+					if subtag in self.names:
+						self.names[subtag] += '\n' + description
+					else:
+						self.names[subtag] = description
+				elif subtag_type == 'language' or subtag_type == 'grandfathered':
+					if line.startswith ('Scope: '):
+						scope = line.split (' ')[1]
+						if scope == 'macrolanguage':
+							scope = ' [macrolanguage]'
+						elif scope == 'collection':
+							scope = ' [family]'
+						else:
+							continue
+						self.scopes[subtag] = scope
+					elif line.startswith ('Deprecated: '):
+						self.scopes[subtag] = ' (retired code)' + self.scopes.get (subtag, '')
+						deprecated = True
+					elif deprecated and line.startswith ('Comments: see '):
+						# If a subtag is split into multiple replacement subtags,
+						# it essentially represents a macrolanguage.
+						for language in line.replace (',', '').split (' ')[2:]:
+							self._add_macrolanguage (subtag, language)
+					elif line.startswith ('Preferred-Value: '):
+						# If a subtag is deprecated in favor of a single replacement subtag,
+						# it is either a dialect or synonym of the preferred subtag. Either
+						# way, it is close enough to the truth to consider the replacement
+						# the macrolanguage of the deprecated language.
+						has_preferred_value = True
+						macrolanguage = line.split (' ')[1]
+						self._add_macrolanguage (macrolanguage, subtag)
+					elif not has_preferred_value and line.startswith ('Macrolanguage: '):
+						self._add_macrolanguage (line.split (' ')[1], subtag)
+				elif subtag_type == 'variant':
+					if line.startswith ('Prefix: '):
+						self.prefixes[subtag].add (line.split (' ')[1])
+				elif line.startswith ('File-Date: '):
+					self.header = line
+		expect (self.header)
+
+	def _add_macrolanguage (self, macrolanguage, language):
+		global ot
+		if language not in ot.from_bcp_47:
+			for l in self.macrolanguages.get (language, set ()):
+				self._add_macrolanguage (macrolanguage, l)
+		if macrolanguage not in ot.from_bcp_47:
+			for ls in list (self.macrolanguages.values ()):
+				if macrolanguage in ls:
+					ls.add (language)
+					return
+		self.macrolanguages[macrolanguage].add (language)
+
+	def remove_extra_macrolanguages (self):
+		"""Make every language have at most one macrolanguage."""
+		inverted = collections.defaultdict (list)
+		for macrolanguage, languages in self.macrolanguages.items ():
+			for language in languages:
+				inverted[language].append (macrolanguage)
+		for language, macrolanguages in inverted.items ():
+			if len (macrolanguages) > 1:
+				macrolanguages.sort (key=lambda ml: len (self.macrolanguages[ml]))
+				biggest_macrolanguage = macrolanguages.pop ()
+				for macrolanguage in macrolanguages:
+					self._add_macrolanguage (biggest_macrolanguage, macrolanguage)
+
+	def get_name (self, lt):
+		"""Return the names of the subtags in a language tag.
+
+		Args:
+			lt (LanguageTag): A BCP 47 language tag.
+
+		Returns:
+			The name form of ``lt``.
+		"""
+		name = self.names[lt.language].split ('\n')[0]
+		if lt.script:
+			name += '; ' + self.names[lt.script.title ()].split ('\n')[0]
+		if lt.region:
+			name += '; ' + self.names[lt.region.upper ()].split ('\n')[0]
+		if lt.variant:
+			name += '; ' + self.names[lt.variant].split ('\n')[0]
+		return name
+
+bcp_47 = BCP47Parser ()
+
+ot.parse (sys.argv[1])
+bcp_47.parse (sys.argv[2])
+
+ot.add_language ('ary', 'MOR')
+
+ot.add_language ('ath', 'ATH')
+
+ot.add_language ('bai', 'BML')
+
+ot.ranks['BAL'] = ot.ranks['KAR'] + 1
+
+ot.add_language ('ber', 'BBR')
+
+ot.remove_language_ot ('PGR')
+ot.add_language ('el-polyton', 'PGR')
+
+bcp_47.macrolanguages['et'] = {'ekk'}
+
+bcp_47.names['flm'] = 'Falam Chin'
+bcp_47.scopes['flm'] = ' (retired code)'
+bcp_47.macrolanguages['flm'] = {'cfm'}
+
+ot.ranks['FNE'] = ot.ranks['TNE'] + 1
+
+ot.add_language ('und-fonipa', 'IPPH')
+
+ot.add_language ('und-fonnapa', 'APPH')
+
+ot.remove_language_ot ('IRT')
+ot.add_language ('ga-Latg', 'IRT')
+
+ot.remove_language_ot ('KGE')
+ot.add_language ('und-Geok', 'KGE')
+
+ot.add_language ('guk', 'GUK')
+ot.names['GUK'] = 'Gumuz (SIL fonts)'
+ot.ranks['GUK'] = ot.ranks['GMZ'] + 1
+
+bcp_47.macrolanguages['id'] = {'in'}
+
+bcp_47.macrolanguages['ijo'] = {'ijc'}
+
+ot.add_language ('kht', 'KHN')
+ot.names['KHN'] = ot.names['KHT'] + ' (Microsoft fonts)'
+ot.names['KHT'] = ot.names['KHT'] + ' (OpenType spec and SIL fonts)'
+ot.ranks['KHN'] = ot.ranks['KHT']
+ot.ranks['KHT'] += 1
+
+ot.ranks['LCR'] = ot.ranks['MCR'] + 1
+
+ot.names['MAL'] = 'Malayalam Traditional'
+ot.ranks['MLR'] += 1
+
+bcp_47.names['mhv'] = 'Arakanese'
+bcp_47.scopes['mhv'] = ' (retired code)'
+
+ot.add_language ('no', 'NOR')
+
+ot.add_language ('oc-provenc', 'PRO')
+
+ot.add_language ('qu', 'QUZ')
+ot.add_language ('qub', 'QWH')
+ot.add_language ('qud', 'QVI')
+ot.add_language ('qug', 'QVI')
+ot.add_language ('qup', 'QVI')
+ot.add_language ('qur', 'QWH')
+ot.add_language ('qus', 'QUH')
+ot.add_language ('quw', 'QVI')
+ot.add_language ('qux', 'QWH')
+ot.add_language ('qva', 'QWH')
+ot.add_language ('qvh', 'QWH')
+ot.add_language ('qvj', 'QVI')
+ot.add_language ('qvl', 'QWH')
+ot.add_language ('qvm', 'QWH')
+ot.add_language ('qvn', 'QWH')
+ot.add_language ('qvo', 'QVI')
+ot.add_language ('qvp', 'QWH')
+ot.add_language ('qvw', 'QWH')
+ot.add_language ('qvz', 'QVI')
+ot.add_language ('qwa', 'QWH')
+ot.add_language ('qws', 'QWH')
+ot.add_language ('qxa', 'QWH')
+ot.add_language ('qxc', 'QWH')
+ot.add_language ('qxh', 'QWH')
+ot.add_language ('qxl', 'QVI')
+ot.add_language ('qxn', 'QWH')
+ot.add_language ('qxo', 'QWH')
+ot.add_language ('qxr', 'QVI')
+ot.add_language ('qxt', 'QWH')
+ot.add_language ('qxw', 'QWH')
+
+bcp_47.macrolanguages['ro'].remove ('mo')
+bcp_47.macrolanguages['ro-MD'].add ('mo')
+
+ot.add_language ('sgw', 'SGW')
+ot.names['SGW'] = ot.names['CHG'] + ' (SIL fonts)'
+ot.ranks['SGW'] = ot.ranks['CHG'] + 1
+
+ot.remove_language_ot ('SYRE')
+ot.remove_language_ot ('SYRJ')
+ot.remove_language_ot ('SYRN')
+ot.add_language ('und-Syre', 'SYRE')
+ot.add_language ('und-Syrj', 'SYRJ')
+ot.add_language ('und-Syrn', 'SYRN')
+
+bcp_47.names['xst'] = u"Silt'e"
+bcp_47.scopes['xst'] = ' (retired code)'
+bcp_47.macrolanguages['xst'] = {'stv', 'wle'}
+
+ot.add_language ('xwo', 'TOD')
+
+ot.remove_language_ot ('ZHH')
+ot.remove_language_ot ('ZHP')
+ot.remove_language_ot ('ZHT')
+bcp_47.macrolanguages['zh'].remove ('lzh')
+bcp_47.macrolanguages['zh'].remove ('yue')
+ot.add_language ('zh-Hant-MO', 'ZHH')
+ot.add_language ('zh-Hant-HK', 'ZHH')
+ot.add_language ('zh-Hans', 'ZHS')
+ot.add_language ('zh-Hant', 'ZHT')
+ot.add_language ('zh-HK', 'ZHH')
+ot.add_language ('zh-MO', 'ZHH')
+ot.add_language ('zh-TW', 'ZHT')
+ot.add_language ('lzh', 'ZHT')
+ot.add_language ('lzh-Hans', 'ZHS')
+ot.add_language ('yue', 'ZHH')
+ot.add_language ('yue-Hans', 'ZHS')
+
+bcp_47.macrolanguages['zom'] = {'yos'}
+
+def rank_delta (bcp_47, ot):
+	"""Return a delta to apply to a BCP 47 tag's rank.
+
+	Most OpenType tags have a constant rank, but a few have ranks that
+	depend on the BCP 47 tag.
+
+	Args:
+		bcp_47 (str): A BCP 47 tag.
+		ot (str): An OpenType tag to.
+
+	Returns:
+		A number to add to ``ot``'s rank when sorting ``bcp_47``'s
+		OpenType equivalents.
+	"""
+	if bcp_47 == 'ak' and ot == 'AKA':
+		return -1
+	if bcp_47 == 'tw' and ot == 'TWI':
+		return -1
+	return 0
+
+disambiguation = {
+	'ALT': 'alt',
+	'ARK': 'rki',
+	'BHI': 'bhb',
+	'BLN': 'bjt',
+	'BTI': 'beb',
+	'CCHN': 'cco',
+	'CMR': 'swb',
+	'CPP': 'crp',
+	'CRR': 'crx',
+	'DUJ': 'dwu',
+	'ECR': 'crj',
+	'HAL': 'cfm',
+	'HND': 'hnd',
+	'KIS': 'kqs',
+	'LRC': 'bqi',
+	'NDB': 'nd',
+	'NIS': 'njz',
+	'PLG': 'pce',
+	'PRO': 'pro',
+	'QIN': 'bgr',
+	'QUH': 'quh',
+	'QVI': 'qvi',
+	'QWH': 'qwh',
+	'SIG': 'stv',
+	'TNE': 'yrk',
+	'ZHH': 'zh-HK',
+	'ZHS': 'zh-Hans',
+	'ZHT': 'zh-Hant',
+}
+
+ot.inherit_from_macrolanguages ()
+bcp_47.remove_extra_macrolanguages ()
+ot.inherit_from_macrolanguages ()
+ot.sort_languages ()
+
+print ('/* == Start of generated table == */')
+print ('/*')
+print (' * The following table is generated by running:')
+print (' *')
+print (' *   %s languagetags language-subtag-registry' % sys.argv[0])
+print (' *')
+print (' * on files with these headers:')
+print (' *')
+print (' * %s' % ot.header.strip ())
+print (' * %s' % bcp_47.header)
+print (' */')
+print ()
+print ('#ifndef HB_OT_TAG_TABLE_HH')
+print ('#define HB_OT_TAG_TABLE_HH')
+print ()
+print ('static const LangTag ot_languages[] = {')
+
+def hb_tag (tag):
+	"""Convert a tag to ``HB_TAG`` form.
+
+	Args:
+		tag (str): An OpenType tag.
+
+	Returns:
+		A snippet of C++ representing ``tag``.
+	"""
+	return u"HB_TAG('%s','%s','%s','%s')" % tuple (('%-4s' % tag)[:4])
+
+def get_variant_set (name):
+	"""Return a set of variant language names from a name.
+
+	Args:
+		name (str): A list of language names from the BCP 47 registry,
+			joined on ``'\\n'``.
+
+	Returns:
+		A set of normalized language names.
+	"""
+	return set (unicodedata.normalize ('NFD', n.replace ('\u2019', u"'"))
+			.encode ('ASCII', 'ignore')
+			.strip ()
+			for n in re.split ('[\n(),]', name) if n)
+
+def language_name_intersection (a, b):
+	"""Return the names in common between two language names.
+
+	Args:
+		a (str): A list of language names from the BCP 47 registry,
+			joined on ``'\\n'``.
+		b (str): A list of language names from the BCP 47 registry,
+			joined on ``'\\n'``.
+
+	Returns:
+		The normalized language names shared by ``a`` and ``b``.
+	"""
+	return get_variant_set (a).intersection (get_variant_set (b))
+
+def get_matching_language_name (intersection, candidates):
+	return next (iter (c for c in candidates if not intersection.isdisjoint (get_variant_set (c))))
+
+maximum_tags = 0
+for language, tags in sorted (ot.from_bcp_47.items ()):
+	if language == '' or '-' in language:
+		continue
+	print ('  {\"%s\",\t{' % language, end='')
+	maximum_tags = max (maximum_tags, len (tags))
+	tag_count = len (tags)
+	for i, tag in enumerate (tags, start=1):
+		if i > 1:
+			print ('\t\t ', end='')
+		print (hb_tag (tag), end='')
+		if i == tag_count:
+			print ('}}', end='')
+		print (',\t/* ', end='')
+		bcp_47_name = bcp_47.names.get (language, '')
+		bcp_47_name_candidates = bcp_47_name.split ('\n')
+		intersection = language_name_intersection (bcp_47_name, ot.names[tag])
+		scope = bcp_47.scopes.get (language, '')
+		if not intersection:
+			write ('%s%s -> %s' % (bcp_47_name_candidates[0], scope, ot.names[tag]))
+		else:
+			name = get_matching_language_name (intersection, bcp_47_name_candidates)
+			bcp_47.names[language] = name
+			write ('%s%s' % (name if len (name) > len (ot.names[tag]) else ot.names[tag], scope))
+		print (' */')
+
+print ('};')
+print ()
+print ('static_assert (HB_OT_MAX_TAGS_PER_LANGUAGE == %iu, "");' % maximum_tags)
+print ()
+
+print ('/**')
+print (' * hb_ot_tags_from_complex_language:')
+print (' * @lang_str: a BCP 47 language tag to convert.')
+print (' * @limit: a pointer to the end of the substring of @lang_str to consider for')
+print (' * conversion.')
+print (' * @count: maximum number of language tags to retrieve (IN) and actual number of')
+print (' * language tags retrieved (OUT). If no tags are retrieved, it is not modified.')
+print (' * @tags: array of size at least @language_count to store the language tag')
+print (' * results')
+print (' *')
+print (' * Converts a multi-subtag BCP 47 language tag to language tags.')
+print (' *')
+print (' * Return value: Whether any language systems were retrieved.')
+print (' **/')
+print ('static bool')
+print ('hb_ot_tags_from_complex_language (const char   *lang_str,')
+print ('\t\t\t\t  const char   *limit,')
+print ('\t\t\t\t  unsigned int *count /* IN/OUT */,')
+print ('\t\t\t\t  hb_tag_t     *tags /* OUT */)')
+print ('{')
+
+def print_subtag_matches (subtag, new_line):
+	if subtag:
+		if new_line:
+			print ()
+			print ('\t&& ', end='')
+		print ('subtag_matches (lang_str, limit, "-%s")' % subtag, end='')
+
+complex_tags = collections.defaultdict (list)
+for initial, group in itertools.groupby ((lt_tags for lt_tags in [
+			(LanguageTag (language), tags)
+			for language, tags in sorted (ot.from_bcp_47.items (),
+				key=lambda i: (-len (i[0]), i[0]))
+		] if lt_tags[0].is_complex ()),
+		key=lambda lt_tags: lt_tags[0].get_group ()):
+	complex_tags[initial] += group
+
+for initial, items in sorted (complex_tags.items ()):
+	if initial != 'und':
+		continue
+	for lt, tags in items:
+		if lt.variant in bcp_47.prefixes:
+			expect (next (iter (bcp_47.prefixes[lt.variant])) == lt.language,
+					'%s is not a valid prefix of %s' % (lt.language, lt.variant))
+		print ('  if (', end='')
+		print_subtag_matches (lt.script, False)
+		print_subtag_matches (lt.region, False)
+		print_subtag_matches (lt.variant, False)
+		print (')')
+		print ('  {')
+		write ('    /* %s */' % bcp_47.get_name (lt))
+		print ()
+		if len (tags) == 1:
+			write ('    tags[0] = %s;  /* %s */' % (hb_tag (tags[0]), ot.names[tags[0]]))
+			print ()
+			print ('    *count = 1;')
+		else:
+			print ('    hb_tag_t possible_tags[] = {')
+			for tag in tags:
+				write ('      %s,  /* %s */' % (hb_tag (tag), ot.names[tag]))
+				print ()
+			print ('    };')
+			print ('    for (i = 0; i < %s && i < *count; i++)' % len (tags))
+			print ('      tags[i] = possible_tags[i];')
+			print ('    *count = i;')
+		print ('    return true;')
+		print ('  }')
+
+print ('  switch (lang_str[0])')
+print ('  {')
+for initial, items in sorted (complex_tags.items ()):
+	if initial == 'und':
+		continue
+	print ("  case '%s':" % initial)
+	for lt, tags in items:
+		print ('    if (', end='')
+		if lt.grandfathered:
+			print ('0 == strcmp (&lang_str[1], "%s")' % lt.language[1:], end='')
+		else:
+			string_literal = lt.language[1:] + '-'
+			if lt.script:
+				string_literal += lt.script
+				lt.script = None
+				if lt.region:
+					string_literal += '-' + lt.region
+					lt.region = None
+			if string_literal[-1] == '-':
+				print ('0 == strncmp (&lang_str[1], "%s", %i)' % (string_literal, len (string_literal)), end='')
+			else:
+				print ('lang_matches (&lang_str[1], "%s")' % string_literal, end='')
+		print_subtag_matches (lt.script, True)
+		print_subtag_matches (lt.region, True)
+		print_subtag_matches (lt.variant, True)
+		print (')')
+		print ('    {')
+		write ('      /* %s */' % bcp_47.get_name (lt))
+		print ()
+		if len (tags) == 1:
+			write ('      tags[0] = %s;  /* %s */' % (hb_tag (tags[0]), ot.names[tags[0]]))
+			print ()
+			print ('      *count = 1;')
+		else:
+			print ('      unsigned int i;')
+			print ('      hb_tag_t possible_tags[] = {')
+			for tag in tags:
+				write ('\t%s,  /* %s */' % (hb_tag (tag), ot.names[tag]))
+				print ()
+			print ('      };')
+			print ('      for (i = 0; i < %s && i < *count; i++)' % len (tags))
+			print ('\ttags[i] = possible_tags[i];')
+			print ('      *count = i;')
+		print ('      return true;')
+		print ('    }')
+	print ('    break;')
+
+print ('  }')
+print ('  return false;')
+print ('}')
+print ()
+print ('/**')
+print (' * hb_ot_ambiguous_tag_to_language')
+print (' * @tag: A language tag.')
+print (' *')
+print (' * Converts @tag to a BCP 47 language tag if it is ambiguous (it corresponds to')
+print (' * many language tags) and the best tag is not the alphabetically first, or if')
+print (' * the best tag consists of multiple subtags.')
+print (' *')
+print (' * Return value: The #hb_language_t corresponding to the BCP 47 language tag,')
+print (' * or #HB_LANGUAGE_INVALID if @tag is not ambiguous.')
+print (' **/')
+print ('static hb_language_t')
+print ('hb_ot_ambiguous_tag_to_language (hb_tag_t tag)')
+print ('{')
+print ('  switch (tag)')
+print ('  {')
+
+def verify_disambiguation_dict ():
+	"""Verify and normalize ``disambiguation``.
+
+	``disambiguation`` is a map of ambiguous OpenType language system
+	tags to the particular BCP 47 tags they correspond to. This function
+	checks that all its keys really are ambiguous and that each key's
+	value is valid for that key. It checks that no ambiguous tag is
+	missing, except when it can figure out which BCP 47 tag is the best
+	by itself.
+
+	It modifies ``disambiguation`` to remove keys whose values are the
+	same as those that the fallback would return anyway, and to add
+	ambiguous keys whose disambiguations it determined automatically.
+
+	Raises:
+		AssertionError: Verification failed.
+	"""
+	global bcp_47
+	global disambiguation
+	global ot
+	for ot_tag, bcp_47_tags in ot.to_bcp_47.items ():
+		primary_tags = list (t for t in bcp_47_tags if t not in bcp_47.grandfathered and ot.from_bcp_47.get (t)[0] == ot_tag)
+		if len (primary_tags) == 1:
+			expect (ot_tag not in disambiguation, 'unnecessary disambiguation for OT tag: %s' % ot_tag)
+			if '-' in primary_tags[0]:
+				disambiguation[ot_tag] = primary_tags[0]
+		elif len (primary_tags) == 0:
+			expect (ot_tag not in disambiguation, 'There is no possible valid disambiguation for %s' % ot_tag)
+		else:
+			macrolanguages = list (t for t in primary_tags if bcp_47.scopes.get (t) == ' [macrolanguage]')
+			if len (macrolanguages) != 1:
+				macrolanguages = list (t for t in primary_tags if bcp_47.scopes.get (t) == ' [family]')
+			if len (macrolanguages) != 1:
+				macrolanguages = list (t for t in primary_tags if 'retired code' not in bcp_47.scopes.get (t, ''))
+			if len (macrolanguages) != 1:
+				expect (ot_tag in disambiguation, 'ambiguous OT tag: %s %s' % (ot_tag, str (macrolanguages)))
+				expect (disambiguation[ot_tag] in bcp_47_tags,
+						'%s is not a valid disambiguation for %s' % (disambiguation[ot_tag], ot_tag))
+			elif ot_tag not in disambiguation:
+				disambiguation[ot_tag] = macrolanguages[0]
+			if disambiguation[ot_tag] == sorted (primary_tags)[0] and '-' not in disambiguation[ot_tag]:
+				del disambiguation[ot_tag]
+	for ot_tag in disambiguation.keys ():
+		expect (ot_tag in ot.to_bcp_47, 'unknown OT tag: %s' % ot_tag)
+
+verify_disambiguation_dict ()
+for ot_tag, bcp_47_tag in sorted (disambiguation.items ()):
+	write ('  case %s:  /* %s */' % (hb_tag (ot_tag), ot.names[ot_tag]))
+	print ()
+	write ('    return hb_language_from_string (\"%s\", -1);  /* %s */' % (bcp_47_tag, bcp_47.get_name (LanguageTag (bcp_47_tag))))
+	print ()
+
+print ('  default:')
+print ('    return HB_LANGUAGE_INVALID;')
+print ('  }')
+print ('}')
+
+print ()
+print ('#endif /* HB_OT_TAG_TABLE_HH */')
+print ()
+print ('/* == End of generated table == */')
+
deleted file mode 100644
--- a/gfx/harfbuzz/src/gen-unicode-ranges.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Generates the code for a sorted unicode range array as used in hb-ot-os2-unicode-ranges.hh
-# Input is a tab seperated list of unicode ranges from the otspec
-# (https://docs.microsoft.com/en-us/typography/opentype/spec/os2#ulunicoderange1).
-
-from __future__ import print_function, division, absolute_import
-
-import io
-import re
-import sys
-
-reload(sys)
-sys.setdefaultencoding('utf-8')
-
-print ("""static Range os2UnicodeRangesSorted[] =
-{""")
-
-args = sys.argv[1:]
-input_file = args[0]
-
-with io.open(input_file, mode="r", encoding="utf-8") as f:
-
-  all_ranges = [];
-  current_bit = 0
-  while True:
-    line = f.readline().strip()
-    if not line:
-      break
-    fields = re.split(r'\t+', line)
-    if len(fields) == 3:
-      current_bit = fields[0]
-      fields = fields[1:]
-    elif len(fields) > 3:
-      raise Error("bad input :(.")
-
-    name = fields[0]
-    ranges = re.split("-", fields[1])
-    if len(ranges) != 2:
-      raise Error("bad input :(.")
-
-    v = tuple((int(ranges[0], 16), int(ranges[1], 16), int(current_bit), name))
-    all_ranges.append(v)
-
-all_ranges = sorted(all_ranges, key=lambda t: t[0])
-
-for ranges in all_ranges:
-  start = ("0x%X" % ranges[0]).rjust(8)
-  end = ("0x%X" % ranges[1]).rjust(8)
-  bit = ("%s" % ranges[2]).rjust(3)
-
-  print ("  {%s, %s, %s}, // %s" % (start, end, bit, ranges[3]))
-
-print ("""};""")
--- a/gfx/harfbuzz/src/gen-use-table.py
+++ b/gfx/harfbuzz/src/gen-use-table.py
@@ -3,17 +3,17 @@
 from __future__ import print_function, division, absolute_import
 
 import io, sys
 
 if len (sys.argv) != 5:
 	print ("usage: ./gen-use-table.py IndicSyllabicCategory.txt IndicPositionalCategory.txt UnicodeData.txt Blocks.txt", file=sys.stderr)
 	sys.exit (1)
 
-BLACKLISTED_BLOCKS = ["Thai", "Lao", "Tibetan"]
+BLACKLISTED_BLOCKS = ["Thai", "Lao"]
 
 files = [io.open (x, encoding='utf-8') for x in sys.argv[1:]]
 
 headers = [[f.readline () for i in range (2)] for j,f in enumerate(files) if j != 2]
 headers.append (["UnicodeData.txt does not have a header."])
 
 data = [{} for f in files]
 values = [{} for f in files]
@@ -45,16 +45,18 @@ defaults = ('Other', 'Not_Applicable', '
 
 # TODO Characters that are not in Unicode Indic files, but used in USE
 data[0][0x034F] = defaults[0]
 data[0][0x2060] = defaults[0]
 data[0][0x20F0] = defaults[0]
 # TODO https://github.com/roozbehp/unicode-data/issues/9
 data[0][0x11C44] = 'Consonant_Placeholder'
 data[0][0x11C45] = 'Consonant_Placeholder'
+# TODO https://github.com/harfbuzz/harfbuzz/pull/1399
+data[0][0x111C8] = 'Consonant_Placeholder'
 for u in range (0xFE00, 0xFE0F + 1):
 	data[0][u] = defaults[0]
 
 # Merge data into one dict:
 for i,v in enumerate (defaults):
 	values[i][v] = values[i].get (v, 0) + 1
 combined = {}
 for i,d in enumerate (data):
@@ -163,17 +165,17 @@ def is_BASE(U, UISC, UGC):
 			Tone_Letter,
 			Vowel_Independent #SPEC-DRAFT
 			] or
 		(UGC == Lo and UISC in [Avagraha, Bindu, Consonant_Final, Consonant_Medial,
 					Consonant_Subjoined, Vowel, Vowel_Dependent]))
 def is_BASE_IND(U, UISC, UGC):
 	#SPEC-DRAFT return (UISC in [Consonant_Dead, Modifying_Letter] or UGC == Po)
 	return (UISC in [Consonant_Dead, Modifying_Letter] or
-		(UGC == Po and not U in [0x104B, 0x104E, 0x2022, 0x11A3F, 0x11A45, 0x11C44, 0x11C45]) or
+		(UGC == Po and not U in [0x104B, 0x104E, 0x2022, 0x111C8, 0x11A3F, 0x11A45, 0x11C44, 0x11C45]) or
 		False # SPEC-DRAFT-OUTDATED! U == 0x002D
 		)
 def is_BASE_NUM(U, UISC, UGC):
 	return UISC == Brahmi_Joining_Number
 def is_BASE_OTHER(U, UISC, UGC):
 	if UISC == Consonant_Placeholder: return True #SPEC-DRAFT
 	#SPEC-DRAFT return U in [0x00A0, 0x00D7, 0x2015, 0x2022, 0x25CC, 0x25FB, 0x25FC, 0x25FD, 0x25FE]
 	return U in [0x2015, 0x2022, 0x25FB, 0x25FC, 0x25FD, 0x25FE]
@@ -192,17 +194,21 @@ def is_CONS_MED(U, UISC, UGC):
 def is_CONS_MOD(U, UISC, UGC):
 	return UISC in [Nukta, Gemination_Mark, Consonant_Killer]
 def is_CONS_SUB(U, UISC, UGC):
 	#SPEC-DRAFT return UISC == Consonant_Subjoined
 	return UISC == Consonant_Subjoined and UGC != Lo
 def is_CONS_WITH_STACKER(U, UISC, UGC):
 	return UISC == Consonant_With_Stacker
 def is_HALANT(U, UISC, UGC):
-	return UISC in [Virama, Invisible_Stacker]
+	return UISC in [Virama, Invisible_Stacker] and not is_HALANT_OR_VOWEL_MODIFIER(U, UISC, UGC)
+def is_HALANT_OR_VOWEL_MODIFIER(U, UISC, UGC):
+	# https://github.com/harfbuzz/harfbuzz/issues/1102
+	# https://github.com/harfbuzz/harfbuzz/issues/1379
+	return U in [0x11046, 0x1134D]
 def is_HALANT_NUM(U, UISC, UGC):
 	return UISC == Number_Joiner
 def is_ZWNJ(U, UISC, UGC):
 	return UISC == Non_Joiner
 def is_ZWJ(U, UISC, UGC):
 	return UISC == Joiner
 def is_Word_Joiner(U, UISC, UGC):
 	return U == 0x2060
@@ -243,16 +249,17 @@ use_mapping = {
 	'CGJ':	is_CGJ,
 	'F':	is_CONS_FINAL,
 	'FM':	is_CONS_FINAL_MOD,
 	'M':	is_CONS_MED,
 	'CM':	is_CONS_MOD,
 	'SUB':	is_CONS_SUB,
 	'CS':	is_CONS_WITH_STACKER,
 	'H':	is_HALANT,
+	'HVM':	is_HALANT_OR_VOWEL_MODIFIER,
 	'HN':	is_HALANT_NUM,
 	'ZWNJ':	is_ZWNJ,
 	'ZWJ':	is_ZWJ,
 	'WJ':	is_Word_Joiner,
 	'O':	is_OTHER,
 	'Rsv':	is_Reserved,
 	'R':	is_REPHA,
 	'S':	is_SYM,
@@ -276,47 +283,65 @@ use_positions = {
 	},
 	'CM': {
 		'Abv': [Top],
 		'Blw': [Bottom],
 	},
 	'V': {
 		'Abv': [Top, Top_And_Bottom, Top_And_Bottom_And_Right, Top_And_Right],
 		'Blw': [Bottom, Overstruck, Bottom_And_Right],
-		'Pst': [Right],
-		'Pre': [Left, Top_And_Left, Top_And_Left_And_Right, Left_And_Right],
+		'Pst': [Right, Top_And_Left, Top_And_Left_And_Right, Left_And_Right],
+		'Pre': [Left],
 	},
 	'VM': {
 		'Abv': [Top],
 		'Blw': [Bottom, Overstruck],
 		'Pst': [Right],
 		'Pre': [Left],
 	},
 	'SM': {
 		'Abv': [Top],
 		'Blw': [Bottom],
 	},
 	'H': None,
+	'HVM': None,
 	'B': None,
 	'FM': None,
 	'SUB': None,
 }
 
 def map_to_use(data):
 	out = {}
 	items = use_mapping.items()
 	for U,(UISC,UIPC,UGC,UBlock) in data.items():
 
 		# Resolve Indic_Syllabic_Category
 
-		# TODO: These don't have UISC assigned in Unicode 8.0, but
-		# have UIPC
+		# TODO: These don't have UISC assigned in Unicode 8.0, but have UIPC
 		if U == 0x17DD: UISC = Vowel_Dependent
 		if 0x1CE2 <= U <= 0x1CE8: UISC = Cantillation_Mark
 
+		# Tibetan:
+		# TODO: These don't have UISC assigned in Unicode 11.0, but have UIPC
+		if 0x0F18 <= U <= 0x0F19 or 0x0F3E <= U <= 0x0F3F: UISC = Vowel_Dependent
+		if 0x0F86 <= U <= 0x0F87: UISC = Tone_Mark
+		# Overrides to allow NFC order matching syllable
+		# https://github.com/harfbuzz/harfbuzz/issues/1012
+		if UBlock == 'Tibetan' and is_VOWEL (U, UISC, UGC):
+			if UIPC == Top:
+				UIPC = Bottom
+
+		# TODO: https://github.com/harfbuzz/harfbuzz/pull/982
+		# also  https://github.com/harfbuzz/harfbuzz/issues/1012
+		if UBlock == 'Chakma' and is_VOWEL (U, UISC, UGC):
+			if UIPC == Top:
+				UIPC = Bottom
+			elif UIPC == Bottom:
+				UIPC = Top
+
 		# TODO: https://github.com/harfbuzz/harfbuzz/pull/627
 		if 0x1BF2 <= U <= 0x1BF3: UISC = Nukta; UIPC = Bottom
 
 		# TODO: U+1CED should only be allowed after some of
 		# the nasalization marks, maybe only for U+1CE9..U+1CF1.
 		if U == 0x1CED: UISC = Tone_Mark
 
 		# TODO: https://github.com/harfbuzz/harfbuzz/issues/525
@@ -326,16 +351,19 @@ def map_to_use(data):
 		if U == 0x20F0: UISC = Cantillation_Mark; UIPC = Top
 
 		# TODO: https://github.com/harfbuzz/harfbuzz/pull/626
 		if U == 0xA8B4: UISC = Consonant_Medial
 
 		# TODO: https://github.com/harfbuzz/harfbuzz/issues/1105
 		if U == 0x11134: UISC = Gemination_Mark
 
+		# TODO: https://github.com/harfbuzz/harfbuzz/pull/1399
+		if U == 0x111C9: UISC = Consonant_Final
+
 		values = [k for k,v in items if v(U,UISC,UGC)]
 		assert len(values) == 1, "%s %s %s %s" % (hex(U), UISC, UGC, values)
 		USE = values[0]
 
 		# Resolve Indic_Positional_Category
 
 		# TODO: Not in Unicode 8.0 yet, but in spec.
 		if U == 0x1B6C: UIPC = Bottom
@@ -354,23 +382,16 @@ def map_to_use(data):
 		if U == 0x11302: UIPC = Top
 		if U == 0x1133C: UIPC = Bottom
 		if U == 0x1171E: UIPC = Left # Correct?!
 		if 0x1CF2 <= U <= 0x1CF3: UIPC = Right
 		if 0x1CF8 <= U <= 0x1CF9: UIPC = Top
 		# https://github.com/roozbehp/unicode-data/issues/8
 		if U == 0x0A51: UIPC = Bottom
 
-		# TODO: https://github.com/harfbuzz/harfbuzz/pull/982
-		if UBlock == 'Chakma' and is_VOWEL (U, UISC, UGC):
-			if UIPC == Top:
-				UIPC = Bottom
-			elif UIPC == Bottom:
-				UIPC = Top
-
 		assert (UIPC in [Not_Applicable, Visual_Order_Left] or
 			USE in use_positions), "%s %s %s %s %s" % (hex(U), UIPC, USE, UISC, UGC)
 
 		pos_mapping = use_positions.get(USE, None)
 		if pos_mapping:
 			values = [k for k,v in pos_mapping.items() if v and UIPC in v]
 			assert len(values) == 1, "%s %s %s %s %s %s" % (hex(U), UIPC, USE, UISC, UGC, values)
 			USE = USE + values[0]
new file mode 100755
--- /dev/null
+++ b/gfx/harfbuzz/src/gen-vowel-constraints.py
@@ -0,0 +1,219 @@
+#!/usr/bin/python
+
+"""Generator of the function to prohibit certain vowel sequences.
+
+It creates ``_hb_preprocess_text_vowel_constraints``, which inserts dotted
+circles into sequences prohibited by the USE script development spec.
+This function should be used as the ``preprocess_text`` of an
+``hb_ot_complex_shaper_t``.
+"""
+
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import collections
+try:
+	from HTMLParser import HTMLParser
+	def write (s):
+		print (s.encode ('utf-8'), end='')
+except ImportError:
+	from html.parser import HTMLParser
+	def write (s):
+		sys.stdout.flush ()
+		sys.stdout.buffer.write (s.encode ('utf-8'))
+import itertools
+import io
+import sys
+
+if len (sys.argv) != 3:
+	print ('usage: ./gen-vowel-constraints.py HBIndicVowelConstraints.txt Scripts.txt', file=sys.stderr)
+	sys.exit (1)
+
+with io.open (sys.argv[2], encoding='utf-8') as f:
+	scripts_header = [f.readline () for i in range (2)]
+	scripts = {}
+	script_order = {}
+	for line in f:
+		j = line.find ('#')
+		if j >= 0:
+			line = line[:j]
+		fields = [x.strip () for x in line.split (';')]
+		if len (fields) == 1:
+			continue
+		uu = fields[0].split ('..')
+		start = int (uu[0], 16)
+		if len (uu) == 1:
+			end = start
+		else:
+			end = int (uu[1], 16)
+		script = fields[1]
+		for u in range (start, end + 1):
+			scripts[u] = script
+		if script not in script_order:
+			script_order[script] = start
+
+class ConstraintSet (object):
+	"""A set of prohibited code point sequences.
+
+	Args:
+		constraint (List[int]): A prohibited code point sequence.
+
+	"""
+	def __init__ (self, constraint):
+		# Either a list or a dictionary. As a list of code points, it
+		# represents a prohibited code point sequence. As a dictionary,
+		# it represents a set of prohibited sequences, where each item
+		# represents the set of prohibited sequences starting with the
+		# key (a code point) concatenated with any of the values
+		# (ConstraintSets).
+		self._c = constraint
+
+	def add (self, constraint):
+		"""Add a constraint to this set."""
+		if not constraint:
+			return
+		first = constraint[0]
+		rest = constraint[1:]
+		if isinstance (self._c, list):
+			if constraint == self._c[:len (constraint)]:
+				self._c = constraint
+			elif self._c != constraint[:len (self._c)]:
+				self._c = {self._c[0]: ConstraintSet (self._c[1:])}
+		if isinstance (self._c, dict):
+			if first in self._c:
+				self._c[first].add (rest)
+			else:
+				self._c[first] = ConstraintSet (rest)
+
+	def _indent (self, depth):
+		return ('  ' * depth).replace ('        ', '\t')
+
+	def __str__ (self, index=0, depth=4):
+		s = []
+		indent = self._indent (depth)
+		if isinstance (self._c, list):
+			if len (self._c) == 0:
+				s.append ('{}matched = true;\n'.format (indent))
+			elif len (self._c) == 1:
+				s.append ('{}matched = 0x{:04X}u == buffer->cur ({}).codepoint;\n'.format (indent, next (iter (self._c)), index or ''))
+			else:
+				s.append ('{}if (0x{:04X}u == buffer->cur ({}).codepoint &&\n'.format (indent, self._c[0], index))
+				s.append ('{}buffer->idx + {} < count &&\n'.format (self._indent (depth + 2), len (self._c)))
+				for i, cp in enumerate (self._c[1:], start=1):
+					s.append ('{}0x{:04X}u == buffer->cur ({}).codepoint{}\n'.format (
+						self._indent (depth + 2), cp, index + i, ')' if i == len (self._c) - 1 else ' &&'))
+				s.append ('{}{{\n'.format (indent))
+				for i in range (len (self._c)):
+					s.append ('{}buffer->next_glyph ();\n'.format (self._indent (depth + 1)))
+				s.append ('{}_output_dotted_circle (buffer);\n'.format (self._indent (depth + 1)))
+				s.append ('{}}}\n'.format (indent))
+		else:
+			s.append ('{}switch (buffer->cur ({}).codepoint)\n'.format(indent, index or ''))
+			s.append ('{}{{\n'.format (indent))
+			cases = collections.defaultdict (set)
+			for first, rest in sorted (self._c.items ()):
+				cases[rest.__str__ (index + 1, depth + 2)].add (first)
+			for body, labels in sorted (cases.items (), key=lambda b_ls: sorted (b_ls[1])[0]):
+				for i, cp in enumerate (sorted (labels)):
+					if i % 4 == 0:
+						s.append (self._indent (depth + 1))
+					else:
+						s.append (' ')
+					s.append ('case 0x{:04X}u:{}'.format (cp, '\n' if i % 4 == 3 else ''))
+				if len (labels) % 4 != 0:
+					s.append ('\n')
+				s.append (body)
+				s.append ('{}break;\n'.format (self._indent (depth + 2)))
+			s.append ('{}}}\n'.format (indent))
+		return ''.join (s)
+
+constraints = {}
+with io.open (sys.argv[1], encoding='utf-8') as f:
+	constraints_header = [f.readline ().strip () for i in range (2)]
+	for line in f:
+		j = line.find ('#')
+		if j >= 0:
+			line = line[:j]
+		constraint = [int (cp, 16) for cp in line.split (';')[0].split ()]
+		if not constraint: continue
+		assert 2 <= len (constraint), 'Prohibited sequence is too short: {}'.format (constraint)
+		script = scripts[constraint[0]]
+		if script in constraints:
+			constraints[script].add (constraint)
+		else:
+			constraints[script] = ConstraintSet (constraint)
+		assert constraints, 'No constraints found'
+
+print ('/* == Start of generated functions == */')
+print ('/*')
+print (' * The following functions are generated by running:')
+print (' *')
+print (' *   %s use Scripts.txt' % sys.argv[0])
+print (' *')
+print (' * on files with these headers:')
+print (' *')
+for line in constraints_header:
+	print (' * %s' % line.strip ())
+print (' *')
+for line in scripts_header:
+	print (' * %s' % line.strip ())
+print (' */')
+print ()
+print ('#include "hb-ot-shape-complex-vowel-constraints.hh"')
+print ()
+print ('static void')
+print ('_output_dotted_circle (hb_buffer_t *buffer)')
+print ('{')
+print ('  hb_glyph_info_t &dottedcircle = buffer->output_glyph (0x25CCu);')
+print ('  _hb_glyph_info_reset_continuation (&dottedcircle);')
+print ('}')
+print ()
+print ('static void')
+print ('_output_with_dotted_circle (hb_buffer_t *buffer)')
+print ('{')
+print ('  _output_dotted_circle (buffer);')
+print ('  buffer->next_glyph ();')
+print ('}')
+print ()
+
+print ('void')
+print ('_hb_preprocess_text_vowel_constraints (const hb_ot_shape_plan_t *plan HB_UNUSED,')
+print ('\t\t\t\t       hb_buffer_t              *buffer,')
+print ('\t\t\t\t       hb_font_t                *font HB_UNUSED)')
+print ('{')
+print ('  /* UGLY UGLY UGLY business of adding dotted-circle in the middle of')
+print ('   * vowel-sequences that look like another vowel.  Data for each script')
+print ('   * collected from the USE script development spec.')
+print ('   *')
+print ('   * https://github.com/harfbuzz/harfbuzz/issues/1019')
+print ('   */')
+print ('  bool processed = false;')
+print ('  buffer->clear_output ();')
+print ('  unsigned int count = buffer->len;')
+print ('  switch ((unsigned) buffer->props.script)')
+print ('  {')
+
+for script, constraints in sorted (constraints.items (), key=lambda s_c: script_order[s_c[0]]):
+	print ('    case HB_SCRIPT_{}:'.format (script.upper ()))
+	print ('      for (buffer->idx = 0; buffer->idx + 1 < count && buffer->successful;)')
+	print ('      {')
+	print ('\tbool matched = false;')
+	write (str (constraints))
+	print ('\tbuffer->next_glyph ();')
+	print ('\tif (matched) _output_with_dotted_circle (buffer);')
+	print ('      }')
+	print ('      processed = true;')
+	print ('      break;')
+	print ()
+
+print ('    default:')
+print ('      break;')
+print ('  }')
+print ('  if (processed)')
+print ('  {')
+print ('    if (buffer->idx < count)')
+print ('      buffer->next_glyph ();')
+print ('  }')
+print ('}')
+
+print ()
+print ('/* == End of generated functions == */')
--- a/gfx/harfbuzz/src/harfbuzz-config.cmake.in
+++ b/gfx/harfbuzz/src/harfbuzz-config.cmake.in
@@ -7,17 +7,21 @@ set(prefix "${_harfbuzz_remove_string}")
 # Compute the installation prefix by stripping components from our current
 # location.
 get_filename_component(_harfbuzz_prefix "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY)
 get_filename_component(_harfbuzz_prefix "${_harfbuzz_prefix}" DIRECTORY)
 set(_harfbuzz_libdir "@libdir@")
 string(REPLACE "${_harfbuzz_remove_string}/" "" _harfbuzz_libdir "${_harfbuzz_libdir}")
 set(_harfbuzz_libdir_iter "${_harfbuzz_libdir}")
 while (_harfbuzz_libdir_iter)
+  set(_harfbuzz_libdir_prev_iter "${_harfbuzz_libdir_iter}")
   get_filename_component(_harfbuzz_libdir_iter "${_harfbuzz_libdir_iter}" DIRECTORY)
+  if (_harfbuzz_libdir_prev_iter STREQUAL _harfbuzz_libdir_iter)
+    break()
+  endif ()
   get_filename_component(_harfbuzz_prefix "${_harfbuzz_prefix}" DIRECTORY)
 endwhile ()
 unset(_harfbuzz_libdir_iter)
 
 # Get the include subdir.
 set(_harfbuzz_includedir "@includedir@")
 string(REPLACE "${_harfbuzz_remove_string}/" "" _harfbuzz_includedir "${_harfbuzz_includedir}")
 
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-aat-fdsc-table.hh
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2018  Ebrahim Byagowi
+ *
+ *  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.
+ */
+
+#ifndef HB_AAT_FDSC_TABLE_HH
+#define HB_AAT_FDSC_TABLE_HH
+
+#include "hb-aat-layout-common.hh"
+#include "hb-open-type.hh"
+
+/*
+ * fdsc -- Font descriptors
+ * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fdsc.html
+ */
+#define HB_AAT_TAG_fdsc HB_TAG('f','d','s','c')
+
+
+namespace AAT {
+
+
+struct FontDescriptor
+{
+  bool has_data () const { return tag; }
+
+  int cmp (hb_tag_t a) const { return tag.cmp (a); }
+
+  float get_value () const { return u.value.to_float (); }
+
+  enum non_alphabetic_value_t {
+    Alphabetic		= 0,
+    Dingbats		= 1,
+    PiCharacters	= 2,
+    Fleurons		= 3,
+    DecorativeBorders	= 4,
+    InternationalSymbols= 5,
+    MathSymbols		= 6
+  };
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this));
+  }
+
+  protected:
+  Tag		tag;		/* The 4-byte table tag name. */
+  union {
+  Fixed		value;		/* The value for the descriptor tag. */
+  HBUINT32	nalfType;	/* If the tag is `nalf`, see non_alphabetic_value_t */
+  } u;
+  public:
+  DEFINE_SIZE_STATIC (8);
+};
+
+struct fdsc
+{
+  enum { tableTag = HB_AAT_TAG_fdsc };
+
+  enum {
+    Weight	 = HB_TAG ('w','g','h','t'),
+				/* Percent weight relative to regular weight.
+				 * (defaul value: 1.0) */
+    Width 	 = HB_TAG ('w','d','t','h'),
+				/* Percent width relative to regular width.
+				 * (default value: 1.0) */
+    Slant 	 = HB_TAG ('s','l','n','t'),
+				/* Angle of slant in degrees, where positive
+				 * is clockwise from straight up.
+				 * (default value: 0.0) */
+    OpticalSize  = HB_TAG ('o','p','s','z'),
+				/* Point size the font was designed for.
+				 * (default value: 12.0) */
+    NonAlphabetic= HB_TAG ('n','a','l','f')
+				/* These values are treated as integers,
+				 * not fixed32s. 0 means alphabetic, and greater
+				 * integers mean the font is non-alphabetic (e.g. symbols).
+				 * (default value: 0) */
+  };
+
+  const FontDescriptor &get_descriptor (hb_tag_t style) const
+  { return descriptors.lsearch (style); }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) &&
+		  descriptors.sanitize (c));
+  }
+
+  protected:
+  Fixed		version;	/* Version number of the font descriptors
+				 * table (0x00010000 for the current version). */
+  LArrayOf<FontDescriptor>
+		descriptors;	/* List of tagged-coordinate pairs style descriptors
+				 * that will be included to characterize this font.
+				 * Each descriptor consists of a <tag, value> pair.
+				 * These pairs are located in the gxFontDescriptor
+				 * array that follows. */
+  public:
+  DEFINE_SIZE_ARRAY (8, descriptors);
+};
+
+} /* namespace AAT */
+
+
+#endif /* HB_AAT_FDSC_TABLE_HH */
--- a/gfx/harfbuzz/src/hb-aat-layout-ankr-table.hh
+++ b/gfx/harfbuzz/src/hb-aat-layout-ankr-table.hh
@@ -31,51 +31,70 @@
  * ankr -- Anchor Point
  * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ankr.html
  */
 #define HB_AAT_TAG_ankr HB_TAG('a','n','k','r')
 
 
 namespace AAT {
 
+using namespace OT;
+
 
 struct Anchor
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this));
   }
 
+  public:
   FWORD		xCoordinate;
   FWORD		yCoordinate;
   public:
   DEFINE_SIZE_STATIC (4);
 };
 
+typedef LArrayOf<Anchor> GlyphAnchors;
+
 struct ankr
 {
-  static const hb_tag_t tableTag = HB_AAT_TAG_ankr;
+  enum { tableTag = HB_AAT_TAG_ankr };
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  const Anchor &get_anchor (hb_codepoint_t glyph_id,
+			    unsigned int i,
+			    unsigned int num_glyphs,
+			    const char *end) const
+  {
+    const Offset<HBUINT16, false> *offset = (this+lookupTable).get_value (glyph_id, num_glyphs);
+    if (!offset)
+      return Null(Anchor);
+    const GlyphAnchors &anchors = StructAtOffset<GlyphAnchors> (&(this+anchorData), *offset);
+    /* TODO Use sanitizer; to avoid overflows and more. */
+    if (unlikely ((const char *) &anchors + anchors.get_size () > end))
+      return Null(Anchor);
+    return anchors[i];
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this) &&
 			  version == 0 &&
-			  lookupTable.sanitize (c, this) &&
-			  anchors.sanitize (c, this)));
+			  lookupTable.sanitize (c, this)));
   }
 
   protected:
   HBUINT16	version; 	/* Version number (set to zero) */
   HBUINT16	flags;		/* Flags (currently unused; set to zero) */
-  LOffsetTo<Lookup<HBUINT16> >
+  LOffsetTo<Lookup<Offset<HBUINT16, false> >, false>
 		lookupTable;	/* Offset to the table's lookup table */
-  LOffsetTo<LArrayOf<Anchor> >
-		anchors;	/* Offset to the glyph data table */
+  LOffsetTo<HBUINT8, false>
+		anchorData;	/* Offset to the glyph data table */
 
   public:
   DEFINE_SIZE_STATIC (12);
 };
 
 } /* namespace AAT */
 
 
--- a/gfx/harfbuzz/src/hb-aat-layout-bsln-table.hh
+++ b/gfx/harfbuzz/src/hb-aat-layout-bsln-table.hh
@@ -34,17 +34,17 @@
 #define HB_AAT_TAG_bsln HB_TAG('b','s','l','n')
 
 
 namespace AAT {
 
 
 struct BaselineTableFormat0Part
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this)));
   }
 
   protected:
   // Roman, Ideographic centered, Ideographic low, Hanging and Math
   // are the default defined ones, but any other maybe accessed also.
@@ -52,17 +52,17 @@ struct BaselineTableFormat0Part
 				 * the font's natural baseline to the other
 				 * baselines used in the font. */
   public:
   DEFINE_SIZE_STATIC (64);
 };
 
 struct BaselineTableFormat1Part
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this) &&
 			  lookupTable.sanitize (c)));
   }
 
   protected:
   HBINT16	deltas[32];	/* ditto */
@@ -70,17 +70,17 @@ struct BaselineTableFormat1Part
 		lookupTable;	/* Lookup table that maps glyphs to their
 				 * baseline values. */
   public:
   DEFINE_SIZE_MIN (66);
 };
 
 struct BaselineTableFormat2Part
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this)));
   }
 
   protected:
   GlyphID	stdGlyph;	/* The specific glyph index number in this
 				 * font that is used to set the baseline values.
@@ -93,17 +93,17 @@ struct BaselineTableFormat2Part
 				 * A value of 0xFFFF means there is no corresponding
 				 * control point in the standard glyph. */
   public:
   DEFINE_SIZE_STATIC (66);
 };
 
 struct BaselineTableFormat3Part
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this) && lookupTable.sanitize (c));
   }
 
   protected:
   GlyphID	stdGlyph;	/* ditto */
   HBUINT16	ctlPoints[32];	/* ditto */
@@ -111,25 +111,26 @@ struct BaselineTableFormat3Part
 		lookupTable;	/* Lookup table that maps glyphs to their
 				 * baseline values. */
   public:
   DEFINE_SIZE_MIN (68);
 };
 
 struct bsln
 {
-  static const hb_tag_t tableTag = HB_AAT_TAG_bsln;
+  enum { tableTag = HB_AAT_TAG_bsln };
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     if (unlikely (!(c->check_struct (this) && defaultBaseline < 32)))
       return_trace (false);
 
-    switch (format) {
+    switch (format)
+    {
     case 0: return_trace (parts.format0.sanitize (c));
     case 1: return_trace (parts.format1.sanitize (c));
     case 2: return_trace (parts.format2.sanitize (c));
     case 3: return_trace (parts.format3.sanitize (c));
     default:return_trace (true);
     }
   }
 
--- a/gfx/harfbuzz/src/hb-aat-layout-common.hh
+++ b/gfx/harfbuzz/src/hb-aat-layout-common.hh
@@ -23,622 +23,828 @@
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #ifndef HB_AAT_LAYOUT_COMMON_HH
 #define HB_AAT_LAYOUT_COMMON_HH
 
 #include "hb-aat-layout.hh"
+#include "hb-open-type.hh"
 
 
 namespace AAT {
 
 using namespace OT;
 
 
 /*
- * Binary Searching Tables
- */
-
-struct BinSearchHeader
-{
-
-  inline bool sanitize (hb_sanitize_context_t *c) const
-  {
-    TRACE_SANITIZE (this);
-    return_trace (c->check_struct (this));
-  }
-
-  HBUINT16	unitSize;	/* Size of a lookup unit for this search in bytes. */
-  HBUINT16	nUnits;		/* Number of units of the preceding size to be searched. */
-  HBUINT16	searchRange;	/* The value of unitSize times the largest power of 2
-				 * that is less than or equal to the value of nUnits. */
-  HBUINT16	entrySelector;	/* The log base 2 of the largest power of 2 less than
-				 * or equal to the value of nUnits. */
-  HBUINT16	rangeShift;	/* The value of unitSize times the difference of the
-				 * value of nUnits minus the largest power of 2 less
-				 * than or equal to the value of nUnits. */
-  public:
-  DEFINE_SIZE_STATIC (10);
-};
-
-template <typename Type>
-struct BinSearchArrayOf
-{
-  inline const Type& operator [] (unsigned int i) const
-  {
-    if (unlikely (i >= header.nUnits)) return Null(Type);
-    return StructAtOffset<Type> (bytesZ, i * header.unitSize);
-  }
-  inline Type& operator [] (unsigned int i)
-  {
-    return StructAtOffset<Type> (bytesZ, i * header.unitSize);
-  }
-  inline unsigned int get_size (void) const
-  { return header.static_size + header.nUnits * header.unitSize; }
-
-  inline bool sanitize (hb_sanitize_context_t *c) const
-  {
-    TRACE_SANITIZE (this);
-    if (unlikely (!sanitize_shallow (c))) return_trace (false);
-
-    /* Note: for structs that do not reference other structs,
-     * we do not need to call their sanitize() as we already did
-     * a bound check on the aggregate array size.  We just include
-     * a small unreachable expression to make sure the structs
-     * pointed to do have a simple sanitize(), ie. they do not
-     * reference other structs via offsets.
-     */
-    (void) (false && StructAtOffset<Type> (bytesZ, 0).sanitize (c));
-
-    return_trace (true);
-  }
-  inline bool sanitize (hb_sanitize_context_t *c, const void *base) const
-  {
-    TRACE_SANITIZE (this);
-    if (unlikely (!sanitize_shallow (c))) return_trace (false);
-    unsigned int count = header.nUnits;
-    for (unsigned int i = 0; i < count; i++)
-      if (unlikely (!(*this)[i].sanitize (c, base)))
-        return_trace (false);
-    return_trace (true);
-  }
-
-  template <typename T>
-  inline const Type *bsearch (const T &key) const
-  {
-    unsigned int size = header.unitSize;
-    int min = 0, max = (int) header.nUnits - 1;
-    while (min <= max)
-    {
-      int mid = (min + max) / 2;
-      const Type *p = (const Type *) (((const char *) bytesZ) + (mid * size));
-      int c = p->cmp (key);
-      if (c < 0)
-	max = mid - 1;
-      else if (c > 0)
-	min = mid + 1;
-      else
-	return p;
-    }
-    return nullptr;
-  }
-
-  private:
-  inline bool sanitize_shallow (hb_sanitize_context_t *c) const
-  {
-    TRACE_SANITIZE (this);
-    return_trace (header.sanitize (c) &&
-		  Type::static_size >= header.unitSize &&
-		  c->check_array (bytesZ, header.unitSize, header.nUnits));
-  }
-
-  protected:
-  BinSearchHeader	header;
-  HBUINT8		bytesZ[VAR];
-  public:
-  DEFINE_SIZE_ARRAY (10, bytesZ);
-};
-
-
-/*
  * Lookup Table
  */
 
 template <typename T> struct Lookup;
 
 template <typename T>
 struct LookupFormat0
 {
   friend struct Lookup<T>;
 
   private:
-  inline const T* get_value (hb_codepoint_t glyph_id, unsigned int num_glyphs) const
+  const T* get_value (hb_codepoint_t glyph_id, unsigned int num_glyphs) const
   {
     if (unlikely (glyph_id >= num_glyphs)) return nullptr;
     return &arrayZ[glyph_id];
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (arrayZ.sanitize (c, c->get_num_glyphs ()));
   }
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (arrayZ.sanitize (c, c->get_num_glyphs (), base));
+  }
 
   protected:
   HBUINT16	format;		/* Format identifier--format = 0 */
   UnsizedArrayOf<T>
 		arrayZ;		/* Array of lookup values, indexed by glyph index. */
   public:
-  DEFINE_SIZE_ARRAY (2, arrayZ);
+  DEFINE_SIZE_UNBOUNDED (2);
 };
 
 
 template <typename T>
 struct LookupSegmentSingle
 {
-  inline int cmp (hb_codepoint_t g) const {
-    return g < first ? -1 : g <= last ? 0 : +1 ;
-  }
+  enum { TerminationWordCount = 2 };
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  int cmp (hb_codepoint_t g) const
+  { return g < first ? -1 : g <= last ? 0 : +1 ; }
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this) && value.sanitize (c));
   }
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && value.sanitize (c, base));
+  }
 
   GlyphID	last;		/* Last GlyphID in this segment */
   GlyphID	first;		/* First GlyphID in this segment */
   T		value;		/* The lookup value (only one) */
   public:
   DEFINE_SIZE_STATIC (4 + T::static_size);
 };
 
 template <typename T>
 struct LookupFormat2
 {
   friend struct Lookup<T>;
 
   private:
-  inline const T* get_value (hb_codepoint_t glyph_id) const
+  const T* get_value (hb_codepoint_t glyph_id) const
   {
     const LookupSegmentSingle<T> *v = segments.bsearch (glyph_id);
     return v ? &v->value : nullptr;
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (segments.sanitize (c));
   }
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (segments.sanitize (c, base));
+  }
 
   protected:
   HBUINT16	format;		/* Format identifier--format = 2 */
-  BinSearchArrayOf<LookupSegmentSingle<T> >
+  VarSizedBinSearchArrayOf<LookupSegmentSingle<T> >
 		segments;	/* The actual segments. These must already be sorted,
 				 * according to the first word in each one (the last
 				 * glyph in each segment). */
   public:
   DEFINE_SIZE_ARRAY (8, segments);
 };
 
 template <typename T>
 struct LookupSegmentArray
 {
-  inline const T* get_value (hb_codepoint_t glyph_id, const void *base) const
+  enum { TerminationWordCount = 2 };
+
+  const T* get_value (hb_codepoint_t glyph_id, const void *base) const
   {
     return first <= glyph_id && glyph_id <= last ? &(base+valuesZ)[glyph_id - first] : nullptr;
   }
 
-  inline int cmp (hb_codepoint_t g) const {
-    return g < first ? -1 : g <= last ? 0 : +1 ;
-  }
+  int cmp (hb_codepoint_t g) const
+  { return g < first ? -1 : g <= last ? 0 : +1; }
 
-  inline bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this) &&
 		  first <= last &&
 		  valuesZ.sanitize (c, base, last - first + 1));
   }
+  template <typename T2>
+  bool sanitize (hb_sanitize_context_t *c, const void *base, T2 user_data) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) &&
+		  first <= last &&
+		  valuesZ.sanitize (c, base, last - first + 1, user_data));
+  }
 
   GlyphID	last;		/* Last GlyphID in this segment */
   GlyphID	first;		/* First GlyphID in this segment */
-  OffsetTo<UnsizedArrayOf<T> >
+  OffsetTo<UnsizedArrayOf<T>, HBUINT16, false>
 		valuesZ;	/* A 16-bit offset from the start of
 				 * the table to the data. */
   public:
   DEFINE_SIZE_STATIC (6);
 };
 
 template <typename T>
 struct LookupFormat4
 {
   friend struct Lookup<T>;
 
   private:
-  inline const T* get_value (hb_codepoint_t glyph_id) const
+  const T* get_value (hb_codepoint_t glyph_id) const
   {
     const LookupSegmentArray<T> *v = segments.bsearch (glyph_id);
     return v ? v->get_value (glyph_id, this) : nullptr;
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (segments.sanitize (c, this));
   }
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (segments.sanitize (c, this, base));
+  }
 
   protected:
-  HBUINT16	format;		/* Format identifier--format = 2 */
-  BinSearchArrayOf<LookupSegmentArray<T> >
+  HBUINT16	format;		/* Format identifier--format = 4 */
+  VarSizedBinSearchArrayOf<LookupSegmentArray<T> >
 		segments;	/* The actual segments. These must already be sorted,
 				 * according to the first word in each one (the last
 				 * glyph in each segment). */
   public:
   DEFINE_SIZE_ARRAY (8, segments);
 };
 
 template <typename T>
 struct LookupSingle
 {
-  inline int cmp (hb_codepoint_t g) const { return glyph.cmp (g); }
+  enum { TerminationWordCount = 1 };
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  int cmp (hb_codepoint_t g) const { return glyph.cmp (g); }
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this) && value.sanitize (c));
   }
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && value.sanitize (c, base));
+  }
 
   GlyphID	glyph;		/* Last GlyphID */
   T		value;		/* The lookup value (only one) */
   public:
-  DEFINE_SIZE_STATIC (4 + T::static_size);
+  DEFINE_SIZE_STATIC (2 + T::static_size);
 };
 
 template <typename T>
 struct LookupFormat6
 {
   friend struct Lookup<T>;
 
   private:
-  inline const T* get_value (hb_codepoint_t glyph_id) const
+  const T* get_value (hb_codepoint_t glyph_id) const
   {
     const LookupSingle<T> *v = entries.bsearch (glyph_id);
     return v ? &v->value : nullptr;
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (entries.sanitize (c));
   }
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (entries.sanitize (c, base));
+  }
 
   protected:
   HBUINT16	format;		/* Format identifier--format = 6 */
-  BinSearchArrayOf<LookupSingle<T> >
+  VarSizedBinSearchArrayOf<LookupSingle<T> >
 		entries;	/* The actual entries, sorted by glyph index. */
   public:
   DEFINE_SIZE_ARRAY (8, entries);
 };
 
 template <typename T>
 struct LookupFormat8
 {
   friend struct Lookup<T>;
 
   private:
-  inline const T* get_value (hb_codepoint_t glyph_id) const
+  const T* get_value (hb_codepoint_t glyph_id) const
   {
-    return firstGlyph <= glyph_id && glyph_id - firstGlyph < glyphCount ? &valueArrayZ[glyph_id - firstGlyph] : nullptr;
+    return firstGlyph <= glyph_id && glyph_id - firstGlyph < glyphCount ?
+	   &valueArrayZ[glyph_id - firstGlyph] : nullptr;
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this) && valueArrayZ.sanitize (c, glyphCount));
   }
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && valueArrayZ.sanitize (c, glyphCount, base));
+  }
 
   protected:
-  HBUINT16	format;		/* Format identifier--format = 6 */
+  HBUINT16	format;		/* Format identifier--format = 8 */
   GlyphID	firstGlyph;	/* First glyph index included in the trimmed array. */
   HBUINT16	glyphCount;	/* Total number of glyphs (equivalent to the last
 				 * glyph minus the value of firstGlyph plus 1). */
   UnsizedArrayOf<T>
 		valueArrayZ;	/* The lookup values (indexed by the glyph index
 				 * minus the value of firstGlyph). */
   public:
   DEFINE_SIZE_ARRAY (6, valueArrayZ);
 };
 
 template <typename T>
+struct LookupFormat10
+{
+  friend struct Lookup<T>;
+
+  private:
+  const typename T::type get_value_or_null (hb_codepoint_t glyph_id) const
+  {
+    if (!(firstGlyph <= glyph_id && glyph_id - firstGlyph < glyphCount))
+      return Null(T);
+
+    const HBUINT8 *p = &valueArrayZ[(glyph_id - firstGlyph) * valueSize];
+
+    unsigned int v = 0;
+    unsigned int count = valueSize;
+    for (unsigned int i = 0; i < count; i++)
+      v = (v << 8) | *p++;
+
+    return v;
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) &&
+		  valueSize <= 4 &&
+		  valueArrayZ.sanitize (c, glyphCount * valueSize));
+  }
+
+  protected:
+  HBUINT16	format;		/* Format identifier--format = 8 */
+  HBUINT16	valueSize;	/* Byte size of each value. */
+  GlyphID	firstGlyph;	/* First glyph index included in the trimmed array. */
+  HBUINT16	glyphCount;	/* Total number of glyphs (equivalent to the last
+				 * glyph minus the value of firstGlyph plus 1). */
+  UnsizedArrayOf<HBUINT8>
+		valueArrayZ;	/* The lookup values (indexed by the glyph index
+				 * minus the value of firstGlyph). */
+  public:
+  DEFINE_SIZE_ARRAY (8, valueArrayZ);
+};
+
+template <typename T>
 struct Lookup
 {
-  inline const T* get_value (hb_codepoint_t glyph_id, unsigned int num_glyphs) const
+  const T* get_value (hb_codepoint_t glyph_id, unsigned int num_glyphs) const
   {
     switch (u.format) {
     case 0: return u.format0.get_value (glyph_id, num_glyphs);
     case 2: return u.format2.get_value (glyph_id);
     case 4: return u.format4.get_value (glyph_id);
     case 6: return u.format6.get_value (glyph_id);
     case 8: return u.format8.get_value (glyph_id);
     default:return nullptr;
     }
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  const typename T::type get_value_or_null (hb_codepoint_t glyph_id, unsigned int num_glyphs) const
+  {
+    switch (u.format) {
+      /* Format 10 cannot return a pointer. */
+      case 10: return u.format10.get_value_or_null (glyph_id);
+      default:
+      const T *v = get_value (glyph_id, num_glyphs);
+      return v ? *v : Null(T);
+    }
+  }
+
+  typename T::type get_class (hb_codepoint_t glyph_id,
+			      unsigned int num_glyphs,
+			      unsigned int outOfRange) const
+  {
+    const T *v = get_value (glyph_id, num_glyphs);
+    return v ? *v : outOfRange;
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     if (!u.format.sanitize (c)) return_trace (false);
     switch (u.format) {
     case 0: return_trace (u.format0.sanitize (c));
     case 2: return_trace (u.format2.sanitize (c));
     case 4: return_trace (u.format4.sanitize (c));
     case 6: return_trace (u.format6.sanitize (c));
     case 8: return_trace (u.format8.sanitize (c));
+    case 10: return_trace (u.format10.sanitize (c));
+    default:return_trace (true);
+    }
+  }
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    if (!u.format.sanitize (c)) return_trace (false);
+    switch (u.format) {
+    case 0: return_trace (u.format0.sanitize (c, base));
+    case 2: return_trace (u.format2.sanitize (c, base));
+    case 4: return_trace (u.format4.sanitize (c, base));
+    case 6: return_trace (u.format6.sanitize (c, base));
+    case 8: return_trace (u.format8.sanitize (c, base));
+    case 10: return_trace (false); /* No need to support format10 apparently */
     default:return_trace (true);
     }
   }
 
   protected:
   union {
   HBUINT16		format;		/* Format identifier */
   LookupFormat0<T>	format0;
   LookupFormat2<T>	format2;
   LookupFormat4<T>	format4;
   LookupFormat6<T>	format6;
   LookupFormat8<T>	format8;
+  LookupFormat10<T>	format10;
   } u;
   public:
   DEFINE_SIZE_UNION (2, format);
 };
+/* Lookup 0 has unbounded size (dependant on num_glyphs).  So we need to defined
+ * special NULL objects for Lookup<> objects, but since it's template our macros
+ * don't work.  So we have to hand-code them here.  UGLY. */
+} /* Close namespace. */
+/* Ugly hand-coded null objects for template Lookup<> :(. */
+extern HB_INTERNAL const unsigned char _hb_Null_AAT_Lookup[2];
+template <>
+/*static*/ inline const AAT::Lookup<OT::HBUINT16>& Null<AAT::Lookup<OT::HBUINT16> > ()
+{ return *reinterpret_cast<const AAT::Lookup<OT::HBUINT16> *> (_hb_Null_AAT_Lookup); }
+template <>
+/*static*/ inline const AAT::Lookup<OT::HBUINT32>& Null<AAT::Lookup<OT::HBUINT32> > ()
+{ return *reinterpret_cast<const AAT::Lookup<OT::HBUINT32> *> (_hb_Null_AAT_Lookup); }
+template <>
+/*static*/ inline const AAT::Lookup<OT::Offset<OT::HBUINT16, false> >& Null<AAT::Lookup<OT::Offset<OT::HBUINT16, false> > > ()
+{ return *reinterpret_cast<const AAT::Lookup<OT::Offset<OT::HBUINT16, false> > *> (_hb_Null_AAT_Lookup); }
+namespace AAT {
 
+enum { DELETED_GLYPH = 0xFFFF };
 
 /*
- * Extended State Table
+ * (Extended) State Table
  */
 
 template <typename T>
 struct Entry
 {
-  inline bool sanitize (hb_sanitize_context_t *c, unsigned int count) const
+  bool sanitize (hb_sanitize_context_t *c, unsigned int count) const
   {
     TRACE_SANITIZE (this);
     /* Note, we don't recurse-sanitize data because we don't access it.
      * That said, in our DEFINE_SIZE_STATIC we access T::static_size,
      * which ensures that data has a simple sanitize(). To be determined
-     * if I need to remove that as well. */
+     * if I need to remove that as well.
+     *
+     * HOWEVER! Because we are a template, our DEFINE_SIZE_STATIC
+     * assertion wouldn't be checked, hence the line below. */
+    static_assert (T::static_size, "");
+
     return_trace (c->check_struct (this));
   }
 
   public:
   HBUINT16	newState;	/* Byte offset from beginning of state table
 				 * to the new state. Really?!?! Or just state
 				 * number?  The latter in morx for sure. */
   HBUINT16	flags;		/* Table specific. */
   T		data;		/* Optional offsets to per-glyph tables. */
   public:
   DEFINE_SIZE_STATIC (4 + T::static_size);
 };
 
 template <>
 struct Entry<void>
 {
-  inline bool sanitize (hb_sanitize_context_t *c, unsigned int count) const
+  bool sanitize (hb_sanitize_context_t *c, unsigned int count /*XXX Unused?*/) const
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this));
   }
 
   public:
   HBUINT16	newState;	/* Byte offset from beginning of state table to the new state. */
   HBUINT16	flags;		/* Table specific. */
   public:
   DEFINE_SIZE_STATIC (4);
 };
 
-template <typename Extra>
+template <typename Types, typename Extra>
 struct StateTable
 {
-  inline unsigned int get_class (hb_codepoint_t glyph_id, unsigned int num_glyphs) const
+  typedef typename Types::HBUINT HBUINT;
+  typedef typename Types::HBUSHORT HBUSHORT;
+  typedef typename Types::ClassTypeNarrow ClassType;
+
+  enum State
+  {
+    STATE_START_OF_TEXT = 0,
+    STATE_START_OF_LINE = 1,
+  };
+  enum Class
   {
-    const HBUINT16 *v = (this+classTable).get_value (glyph_id, num_glyphs);
-    return v ? *v : 1;
+    CLASS_END_OF_TEXT = 0,
+    CLASS_OUT_OF_BOUNDS = 1,
+    CLASS_DELETED_GLYPH = 2,
+    CLASS_END_OF_LINE = 3,
+  };
+
+  int new_state (unsigned int newState) const
+  { return Types::extended ? newState : ((int) newState - (int) stateArrayTable) / (int) nClasses; }
+
+  unsigned int get_class (hb_codepoint_t glyph_id, unsigned int num_glyphs) const
+  {
+    if (unlikely (glyph_id == DELETED_GLYPH)) return CLASS_DELETED_GLYPH;
+    return (this+classTable).get_class (glyph_id, num_glyphs, 1);
   }
 
-  inline const Entry<Extra> *get_entries () const
-  {
-    return (this+entryTable).arrayZ;
-  }
+  const Entry<Extra> *get_entries () const
+  { return (this+entryTable).arrayZ; }
 
-  inline const Entry<Extra> *get_entryZ (unsigned int state, unsigned int klass) const
+  const Entry<Extra> *get_entryZ (int state, unsigned int klass) const
   {
     if (unlikely (klass >= nClasses)) return nullptr;
 
-    const HBUINT16 *states = (this+stateArrayTable).arrayZ;
+    const HBUSHORT *states = (this+stateArrayTable).arrayZ;
     const Entry<Extra> *entries = (this+entryTable).arrayZ;
 
     unsigned int entry = states[state * nClasses + klass];
+    DEBUG_MSG (APPLY, nullptr, "e%u", entry);
 
     return &entries[entry];
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c,
-			unsigned int *num_entries_out = nullptr) const
+  bool sanitize (hb_sanitize_context_t *c,
+		 unsigned int *num_entries_out = nullptr) const
   {
     TRACE_SANITIZE (this);
     if (unlikely (!(c->check_struct (this) &&
 		    classTable.sanitize (c, this)))) return_trace (false);
 
-    const HBUINT16 *states = (this+stateArrayTable).arrayZ;
+    const HBUSHORT *states = (this+stateArrayTable).arrayZ;
     const Entry<Extra> *entries = (this+entryTable).arrayZ;
 
-    unsigned int num_states = 1;
+    unsigned int num_classes = nClasses;
+    if (unlikely (hb_unsigned_mul_overflows (num_classes, states[0].static_size)))
+      return_trace (false);
+    unsigned int row_stride = num_classes * states[0].static_size;
+
+    /* Apple 'kern' table has this peculiarity:
+     *
+     * "Because the stateTableOffset in the state table header is (strictly
+     * speaking) redundant, some 'kern' tables use it to record an initial
+     * state where that should not be StartOfText. To determine if this is
+     * done, calculate what the stateTableOffset should be. If it's different
+     * from the actual stateTableOffset, use it as the initial state."
+     *
+     * We implement this by calling the initial state zero, but allow *negative*
+     * states if the start state indeed was not the first state.  Since the code
+     * is shared, this will also apply to 'mort' table.  The 'kerx' / 'morx'
+     * tables are not affected since those address states by index, not offset.
+     */
+
+    int min_state = 0;
+    int max_state = 0;
     unsigned int num_entries = 0;
 
-    unsigned int state = 0;
+    int state_pos = 0;
+    int state_neg = 0;
     unsigned int entry = 0;
-    while (state < num_states)
+    while (min_state < state_neg || state_pos <= max_state)
     {
-      if (unlikely (!c->check_array (states,
-				     states[0].static_size * nClasses,
-				     num_states)))
-	return_trace (false);
-      { /* Sweep new states. */
-	const HBUINT16 *stop = &states[num_states * nClasses];
-	for (const HBUINT16 *p = &states[state * nClasses]; p < stop; p++)
-	  num_entries = MAX<unsigned int> (num_entries, *p + 1);
-	state = num_states;
+      if (min_state < state_neg)
+      {
+	/* Negative states. */
+	if (unlikely (hb_unsigned_mul_overflows (min_state, num_classes)))
+	  return_trace (false);
+	if (unlikely (!c->check_range (&states[min_state * num_classes],
+				       -min_state,
+				       row_stride)))
+	  return_trace (false);
+	if ((c->max_ops -= state_neg - min_state) < 0)
+	  return_trace (false);
+	{ /* Sweep new states. */
+	  const HBUSHORT *stop = &states[min_state * num_classes];
+	  if (unlikely (stop > states))
+	    return_trace (false);
+	  for (const HBUSHORT *p = states; stop < p; p--)
+	    num_entries = MAX<unsigned int> (num_entries, *(p - 1) + 1);
+	  state_neg = min_state;
+	}
       }
 
-      if (unlikely (!c->check_array (entries,
-				     entries[0].static_size,
-				     num_entries)))
+      if (state_pos <= max_state)
+      {
+	/* Positive states. */
+	if (unlikely (!c->check_range (states,
+				       max_state + 1,
+				       row_stride)))
+	  return_trace (false);
+	if ((c->max_ops -= max_state - state_pos + 1) < 0)
+	  return_trace (false);
+	{ /* Sweep new states. */
+	  if (unlikely (hb_unsigned_mul_overflows ((max_state + 1), num_classes)))
+	    return_trace (false);
+	  const HBUSHORT *stop = &states[(max_state + 1) * num_classes];
+	  if (unlikely (stop < states))
+	    return_trace (false);
+	  for (const HBUSHORT *p = &states[state_pos * num_classes]; p < stop; p++)
+	    num_entries = MAX<unsigned int> (num_entries, *p + 1);
+	  state_pos = max_state + 1;
+	}
+      }
+
+      if (unlikely (!c->check_array (entries, num_entries)))
+	return_trace (false);
+      if ((c->max_ops -= num_entries - entry) < 0)
 	return_trace (false);
       { /* Sweep new entries. */
 	const Entry<Extra> *stop = &entries[num_entries];
 	for (const Entry<Extra> *p = &entries[entry]; p < stop; p++)
-	  num_states = MAX<unsigned int> (num_states, p->newState + 1);
+	{
+	  int newState = new_state (p->newState);
+	  min_state = MIN (min_state, newState);
+	  max_state = MAX (max_state, newState);
+	}
 	entry = num_entries;
       }
     }
 
     if (num_entries_out)
       *num_entries_out = num_entries;
 
     return_trace (true);
   }
 
   protected:
-  HBUINT32	nClasses;	/* Number of classes, which is the number of indices
+  HBUINT	nClasses;	/* Number of classes, which is the number of indices
 				 * in a single line in the state array. */
-  LOffsetTo<Lookup<HBUINT16> >
+  OffsetTo<ClassType, HBUINT, false>
 		classTable;	/* Offset to the class table. */
-  LOffsetTo<UnsizedArrayOf<HBUINT16> >
+  OffsetTo<UnsizedArrayOf<HBUSHORT>, HBUINT, false>
 		stateArrayTable;/* Offset to the state array. */
-  LOffsetTo<UnsizedArrayOf<Entry<Extra> > >
+  OffsetTo<UnsizedArrayOf<Entry<Extra> >, HBUINT, false>
 		entryTable;	/* Offset to the entry array. */
 
   public:
-  DEFINE_SIZE_STATIC (16);
+  DEFINE_SIZE_STATIC (4 * sizeof (HBUINT));
+};
+
+template <typename HBUCHAR>
+struct ClassTable
+{
+  unsigned int get_class (hb_codepoint_t glyph_id, unsigned int outOfRange) const
+  {
+    unsigned int i = glyph_id - firstGlyph;
+    return i >= classArray.len ? outOfRange : classArray.arrayZ[i];
+  }
+  unsigned int get_class (hb_codepoint_t glyph_id,
+			  unsigned int num_glyphs HB_UNUSED,
+			  unsigned int outOfRange) const
+  {
+    return get_class (glyph_id, outOfRange);
+  }
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this) && classArray.sanitize (c));
+  }
+  protected:
+  GlyphID		firstGlyph;	/* First glyph index included in the trimmed array. */
+  ArrayOf<HBUCHAR>	classArray;	/* The class codes (indexed by glyph index minus
+					 * firstGlyph). */
+  public:
+  DEFINE_SIZE_ARRAY (4, classArray);
 };
 
-template <typename EntryData>
+struct ObsoleteTypes
+{
+  enum { extended = false };
+  typedef HBUINT16 HBUINT;
+  typedef HBUINT8 HBUSHORT;
+  typedef ClassTable<HBUINT8> ClassTypeNarrow;
+  typedef ClassTable<HBUINT16> ClassTypeWide;
+
+  template <typename T>
+  static unsigned int offsetToIndex (unsigned int offset,
+				     const void *base,
+				     const T *array)
+  {
+    return (offset - ((const char *) array - (const char *) base)) / sizeof (T);
+  }
+  template <typename T>
+  static unsigned int byteOffsetToIndex (unsigned int offset,
+					 const void *base,
+					 const T *array)
+  {
+    return offsetToIndex (offset, base, array);
+  }
+  template <typename T>
+  static unsigned int wordOffsetToIndex (unsigned int offset,
+					 const void *base,
+					 const T *array)
+  {
+    return offsetToIndex (2 * offset, base, array);
+  }
+};
+struct ExtendedTypes
+{
+  enum { extended = true };
+  typedef HBUINT32 HBUINT;
+  typedef HBUINT16 HBUSHORT;
+  typedef Lookup<HBUINT16> ClassTypeNarrow;
+  typedef Lookup<HBUINT16> ClassTypeWide;
+
+  template <typename T>
+  static unsigned int offsetToIndex (unsigned int offset,
+				     const void *base,
+				     const T *array)
+  {
+    return offset;
+  }
+  template <typename T>
+  static unsigned int byteOffsetToIndex (unsigned int offset,
+					 const void *base,
+					 const T *array)
+  {
+    return offset / 2;
+  }
+  template <typename T>
+  static unsigned int wordOffsetToIndex (unsigned int offset,
+					 const void *base,
+					 const T *array)
+  {
+    return offset;
+  }
+};
+
+template <typename Types, typename EntryData>
 struct StateTableDriver
 {
-  inline StateTableDriver (const StateTable<EntryData> &machine_,
-			   hb_buffer_t *buffer_,
-			   hb_face_t *face_) :
+  StateTableDriver (const StateTable<Types, EntryData> &machine_,
+		    hb_buffer_t *buffer_,
+		    hb_face_t *face_) :
 	      machine (machine_),
 	      buffer (buffer_),
 	      num_glyphs (face_->get_num_glyphs ()) {}
 
   template <typename context_t>
-  inline void drive (context_t *c)
+  void drive (context_t *c)
   {
-    hb_glyph_info_t *info = buffer->info;
-
     if (!c->in_place)
       buffer->clear_output ();
 
-    unsigned int state = 0;
+    int state = StateTable<Types, EntryData>::STATE_START_OF_TEXT;
     bool last_was_dont_advance = false;
-    for (buffer->idx = 0;;)
+    for (buffer->idx = 0; buffer->successful;)
     {
       unsigned int klass = buffer->idx < buffer->len ?
-			   machine.get_class (info[buffer->idx].codepoint, num_glyphs) :
-			   0 /* End of text */;
+			   machine.get_class (buffer->info[buffer->idx].codepoint, num_glyphs) :
+			   (unsigned) StateTable<Types, EntryData>::CLASS_END_OF_TEXT;
+      DEBUG_MSG (APPLY, nullptr, "c%u at %u", klass, buffer->idx);
       const Entry<EntryData> *entry = machine.get_entryZ (state, klass);
       if (unlikely (!entry))
 	break;
 
       /* Unsafe-to-break before this if not in state 0, as things might
-       * go differently if we start from state 0 here. */
-      if (state && buffer->idx)
+       * go differently if we start from state 0 here.
+       *
+       * Ugh.  The indexing here is ugly... */
+      if (state && buffer->backtrack_len () && buffer->idx < buffer->len)
       {
 	/* If there's no action and we're just epsilon-transitioning to state 0,
 	 * safe to break. */
 	if (c->is_actionable (this, entry) ||
-	    !(entry->newState == 0 && entry->flags == context_t::DontAdvance))
-	  buffer->unsafe_to_break (buffer->idx - 1, buffer->idx + 1);
+	    !(entry->newState == StateTable<Types, EntryData>::STATE_START_OF_TEXT &&
+	      entry->flags == context_t::DontAdvance))
+	  buffer->unsafe_to_break_from_outbuffer (buffer->backtrack_len () - 1, buffer->idx + 1);
       }
 
       /* Unsafe-to-break if end-of-text would kick in here. */
       if (buffer->idx + 2 <= buffer->len)
       {
 	const Entry<EntryData> *end_entry = machine.get_entryZ (state, 0);
 	if (c->is_actionable (this, end_entry))
 	  buffer->unsafe_to_break (buffer->idx, buffer->idx + 2);
       }
 
       if (unlikely (!c->transition (this, entry)))
-        break;
+	break;
 
       last_was_dont_advance = (entry->flags & context_t::DontAdvance) && buffer->max_ops-- > 0;
 
-      state = entry->newState;
+      state = machine.new_state (entry->newState);
+      DEBUG_MSG (APPLY, nullptr, "s%d", state);
 
       if (buffer->idx == buffer->len)
-        break;
+	break;
 
       if (!last_was_dont_advance)
-        buffer->next_glyph ();
+	buffer->next_glyph ();
     }
 
     if (!c->in_place)
     {
-      for (; buffer->idx < buffer->len;)
-        buffer->next_glyph ();
+      for (; buffer->successful && buffer->idx < buffer->len;)
+	buffer->next_glyph ();
       buffer->swap_buffers ();
     }
   }
 
   public:
-  const StateTable<EntryData> &machine;
+  const StateTable<Types, EntryData> &machine;
   hb_buffer_t *buffer;
   unsigned int num_glyphs;
 };
 
 
+struct ankr;
 
 struct hb_aat_apply_context_t :
        hb_dispatch_context_t<hb_aat_apply_context_t, bool, HB_DEBUG_APPLY>
 {
-  inline const char *get_name (void) { return "APPLY"; }
+  const char *get_name () { return "APPLY"; }
   template <typename T>
-  inline return_t dispatch (const T &obj) { return obj.apply (this); }
-  static return_t default_return_value (void) { return false; }
+  return_t dispatch (const T &obj) { return obj.apply (this); }
+  static return_t default_return_value () { return false; }
   bool stop_sublookup_iteration (return_t r) const { return r; }
 
+  const hb_ot_shape_plan_t *plan;
   hb_font_t *font;
   hb_face_t *face;
   hb_buffer_t *buffer;
   hb_sanitize_context_t sanitizer;
+  const ankr *ankr_table;
+  const char *ankr_end;
 
   /* Unused. For debug tracing only. */
   unsigned int lookup_index;
   unsigned int debug_depth;
 
-  inline hb_aat_apply_context_t (hb_font_t *font_,
-				 hb_buffer_t *buffer_,
-				 hb_blob_t *table) :
-		font (font_), face (font->face), buffer (buffer_),
-		sanitizer (), lookup_index (0), debug_depth (0)
-  {
-    sanitizer.init (table);
-    sanitizer.set_num_glyphs (face->get_num_glyphs ());
-    sanitizer.start_processing ();
-  }
+  HB_INTERNAL hb_aat_apply_context_t (const hb_ot_shape_plan_t *plan_,
+				      hb_font_t *font_,
+				      hb_buffer_t *buffer_,
+				      hb_blob_t *blob = const_cast<hb_blob_t *> (&Null(hb_blob_t)));
 
-  inline void set_lookup_index (unsigned int i) { lookup_index = i; }
+  HB_INTERNAL ~hb_aat_apply_context_t ();
 
-  inline ~hb_aat_apply_context_t (void)
-  {
-    sanitizer.end_processing ();
-  }
+  HB_INTERNAL void set_ankr_table (const AAT::ankr *ankr_table_, const char *ankr_end_);
+
+  void set_lookup_index (unsigned int i) { lookup_index = i; }
 };
 
 
 } /* namespace AAT */
 
 
 #endif /* HB_AAT_LAYOUT_COMMON_HH */
--- a/gfx/harfbuzz/src/hb-aat-layout-feat-table.hh
+++ b/gfx/harfbuzz/src/hb-aat-layout-feat-table.hh
@@ -34,87 +34,185 @@
 #define HB_AAT_TAG_feat HB_TAG('f','e','a','t')
 
 
 namespace AAT {
 
 
 struct SettingName
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  friend struct FeatureName;
+
+  int cmp (hb_aat_layout_feature_selector_t key) const
+  { return (int) key - (int) setting; }
+
+  hb_aat_layout_feature_selector_t get_selector () const
+  { return (hb_aat_layout_feature_selector_t) (unsigned) setting; }
+
+  void get_info (hb_aat_layout_feature_selector_info_t *s,
+			hb_aat_layout_feature_selector_t default_selector) const
+  {
+    s->name_id = nameIndex;
+
+    s->enable = (hb_aat_layout_feature_selector_t) (unsigned int) setting;
+    s->disable = default_selector == HB_AAT_LAYOUT_FEATURE_SELECTOR_INVALID ?
+		 (hb_aat_layout_feature_selector_t) (s->enable + 1) :
+		 default_selector;
+
+    s->reserved = 0;
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this)));
   }
 
   protected:
   HBUINT16	setting;	/* The setting. */
   NameID	nameIndex;	/* The name table index for the setting's name. */
   public:
   DEFINE_SIZE_STATIC (4);
 };
+DECLARE_NULL_NAMESPACE_BYTES (AAT, SettingName);
+
+struct feat;
 
 struct FeatureName
 {
-  inline bool sanitize (hb_sanitize_context_t *c, const void *base) const
-  {
-    TRACE_SANITIZE (this);
-    return_trace (likely (c->check_struct (this) &&
-			  (base+settingTable).sanitize (c, nSettings)));
-  }
+  int cmp (hb_aat_layout_feature_type_t key) const
+  { return (int) key - (int) feature; }
 
   enum {
-    Exclusive = 0x8000,		/* If set, the feature settings are mutually exclusive. */
-    NotDefault = 0x4000,	/* If clear, then the setting with an index of 0 in
+    Exclusive	= 0x8000,	/* If set, the feature settings are mutually exclusive. */
+    NotDefault	= 0x4000,	/* If clear, then the setting with an index of 0 in
 				 * the setting name array for this feature should
 				 * be taken as the default for the feature
 				 * (if one is required). If set, then bits 0-15 of this
 				 * featureFlags field contain the index of the setting
 				 * which is to be taken as the default. */
-    IndexMask = 0x00FF		/* If bits 30 and 31 are set, then these sixteen bits
+    IndexMask	= 0x00FF	/* If bits 30 and 31 are set, then these sixteen bits
 				 * indicate the index of the setting in the setting name
 				 * array for this feature which should be taken
 				 * as the default. */
   };
 
+  unsigned int get_selector_infos (unsigned int                           start_offset,
+				   unsigned int                          *selectors_count, /* IN/OUT.  May be NULL. */
+				   hb_aat_layout_feature_selector_info_t *selectors,       /* OUT.     May be NULL. */
+				   unsigned int                          *pdefault_index,  /* OUT.     May be NULL. */
+				   const void *base) const
+  {
+    hb_array_t< const SettingName> settings_table = (base+settingTableZ).as_array (nSettings);
+
+    static_assert (Index::NOT_FOUND_INDEX == HB_AAT_LAYOUT_NO_SELECTOR_INDEX, "");
+
+    hb_aat_layout_feature_selector_t default_selector = HB_AAT_LAYOUT_FEATURE_SELECTOR_INVALID;
+    unsigned int default_index = Index::NOT_FOUND_INDEX;
+    if (featureFlags & Exclusive)
+    {
+      default_index = (featureFlags & NotDefault) ? featureFlags & IndexMask : 0;
+      default_selector = settings_table[default_index].get_selector ();
+    }
+    if (pdefault_index)
+      *pdefault_index = default_index;
+
+    if (selectors_count)
+    {
+      hb_array_t<const SettingName> arr = settings_table.sub_array (start_offset, selectors_count);
+      unsigned int count = arr.len;
+      for (unsigned int i = 0; i < count; i++)
+        settings_table[start_offset + i].get_info (&selectors[i], default_selector);
+    }
+    return settings_table.len;
+  }
+
+  hb_aat_layout_feature_type_t get_feature_type () const
+  { return (hb_aat_layout_feature_type_t) (unsigned int) feature; }
+
+  hb_ot_name_id_t get_feature_name_id () const { return nameIndex; }
+
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this) &&
+			  (base+settingTableZ).sanitize (c, nSettings)));
+  }
+
   protected:
   HBUINT16	feature;	/* Feature type. */
   HBUINT16	nSettings;	/* The number of records in the setting name array. */
-  LOffsetTo<UnsizedArrayOf<SettingName> >
-		settingTable;	/* Offset in bytes from the beginning of this table to
+  LOffsetTo<UnsizedArrayOf<SettingName>, false>
+		settingTableZ;	/* Offset in bytes from the beginning of this table to
 				 * this feature's setting name array. The actual type of
 				 * record this offset refers to will depend on the
 				 * exclusivity value, as described below. */
   HBUINT16	featureFlags;	/* Single-bit flags associated with the feature type. */
   HBINT16	nameIndex;	/* The name table index for the feature's name.
 				 * This index has values greater than 255 and
 				 * less than 32768. */
   public:
   DEFINE_SIZE_STATIC (12);
 };
 
 struct feat
 {
-  static const hb_tag_t tableTag = HB_AAT_TAG_feat;
+  enum { tableTag = HB_AAT_TAG_feat };
+
+  bool has_data () const { return version.to_int (); }
+
+  unsigned int get_feature_types (unsigned int                  start_offset,
+				  unsigned int                 *count,
+				  hb_aat_layout_feature_type_t *features) const
+  {
+    unsigned int feature_count = featureNameCount;
+    if (count && *count)
+    {
+      unsigned int len = MIN (feature_count - start_offset, *count);
+      for (unsigned int i = 0; i < len; i++)
+	features[i] = namesZ[i + start_offset].get_feature_type ();
+      *count = len;
+    }
+    return featureNameCount;
+  }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  const FeatureName& get_feature (hb_aat_layout_feature_type_t feature_type) const
+  {
+    return namesZ.bsearch (featureNameCount, feature_type);
+  }
+
+  hb_ot_name_id_t get_feature_name_id (hb_aat_layout_feature_type_t feature) const
+  { return get_feature (feature).get_feature_name_id (); }
+
+  unsigned int get_selector_infos (hb_aat_layout_feature_type_t           feature_type,
+				   unsigned int                           start_offset,
+				   unsigned int                          *selectors_count, /* IN/OUT.  May be NULL. */
+				   hb_aat_layout_feature_selector_info_t *selectors,       /* OUT.     May be NULL. */
+				   unsigned int                          *default_index    /* OUT.     May be NULL. */) const
+  {
+    return get_feature (feature_type).get_selector_infos (start_offset, selectors_count, selectors,
+							  default_index, this);
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this) &&
-			  names.sanitize (c, featureNameCount, this)));
+			  version.major == 1 &&
+			  namesZ.sanitize (c, featureNameCount, this)));
   }
 
   protected:
   FixedVersion<>version;	/* Version number of the feature name table
 				 * (0x00010000 for the current version). */
   HBUINT16	featureNameCount;
 				/* The number of entries in the feature name array. */
   HBUINT16	reserved1;	/* Reserved (set to zero). */
   HBUINT32	reserved2;	/* Reserved (set to zero). */
-  UnsizedArrayOf<FeatureName>
-		names;		/* The feature name array. */
+  SortedUnsizedArrayOf<FeatureName>
+		namesZ;		/* The feature name array. */
   public:
   DEFINE_SIZE_STATIC (24);
 };
 
 } /* namespace AAT */
 
 #endif /* HB_AAT_LAYOUT_FEAT_TABLE_HH */
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-aat-layout-just-table.hh
@@ -0,0 +1,417 @@
+/*
+ * Copyright © 2018  Ebrahim Byagowi
+ *
+ *  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.
+ */
+
+#ifndef HB_AAT_LAYOUT_JUST_TABLE_HH
+#define HB_AAT_LAYOUT_JUST_TABLE_HH
+
+#include "hb-aat-layout-common.hh"
+#include "hb-ot-layout.hh"
+#include "hb-open-type.hh"
+
+#include "hb-aat-layout-morx-table.hh"
+
+/*
+ * just -- Justification
+ * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6just.html
+ */
+#define HB_AAT_TAG_just HB_TAG('j','u','s','t')
+
+
+namespace AAT {
+
+using namespace OT;
+
+
+struct ActionSubrecordHeader
+{
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this)));
+  }
+
+  HBUINT16 	actionClass; 	/* The JustClass value associated with this
+				 * ActionSubrecord. */
+  HBUINT16 	actionType; 	/* The type of postcompensation action. */
+  HBUINT16 	actionLength;	/* Length of this ActionSubrecord record, which
+				 * must be a multiple of 4. */
+  public:
+  DEFINE_SIZE_STATIC (6);
+};
+
+struct DecompositionAction
+{
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this)));
+  }
+
+  ActionSubrecordHeader
+		header;
+  Fixed		lowerLimit; 	/* If the distance factor is less than this value,
+				 * then the ligature is decomposed. */
+  Fixed		upperLimit; 	/* If the distance factor is greater than this value,
+				 * then the ligature is decomposed. */
+  HBUINT16 	order;		/* Numerical order in which this ligature will
+				 * be decomposed; you may want infrequent ligatures
+				 * to decompose before more frequent ones. The ligatures
+				 * on the line of text will decompose in increasing
+				 * value of this field. */
+  ArrayOf<HBUINT16>
+		decomposedglyphs;
+				/* Number of 16-bit glyph indexes that follow;
+				 * the ligature will be decomposed into these glyphs.
+				 *
+				 * Array of decomposed glyphs. */
+  public:
+  DEFINE_SIZE_ARRAY (18, decomposedglyphs);
+};
+
+struct UnconditionalAddGlyphAction
+{
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this));
+  }
+
+  protected:
+  ActionSubrecordHeader
+		header;
+  GlyphID	addGlyph;	/* Glyph that should be added if the distance factor
+				 * is growing. */
+
+  public:
+  DEFINE_SIZE_STATIC (8);
+};
+
+struct ConditionalAddGlyphAction
+{
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this)));
+  }
+
+  protected:
+  ActionSubrecordHeader
+		header;
+  Fixed 	substThreshold; /* Distance growth factor (in ems) at which
+				 * this glyph is replaced and the growth factor
+				 * recalculated. */
+  GlyphID 	addGlyph; 	/* Glyph to be added as kashida. If this value is
+				 * 0xFFFF, no extra glyph will be added. Note that
+				 * generally when a glyph is added, justification
+				 * will need to be redone. */
+  GlyphID 	substGlyph; 	/* Glyph to be substituted for this glyph if the
+				 * growth factor equals or exceeds the value of
+				 * substThreshold. */
+  public:
+  DEFINE_SIZE_STATIC (14);
+};
+
+struct DuctileGlyphAction
+{
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this)));
+  }
+
+  protected:
+  ActionSubrecordHeader
+		header;
+  HBUINT32 	variationAxis;	/* The 4-byte tag identifying the ductile axis.
+				 * This would normally be 0x64756374 ('duct'),
+				 * but you may use any axis the font contains. */
+  Fixed 	minimumLimit; 	/* The lowest value for the ductility axis tha
+				 * still yields an acceptable appearance. Normally
+				 * this will be 1.0. */
+  Fixed 	noStretchValue; /* This is the default value that corresponds to
+				 * no change in appearance. Normally, this will
+				 * be 1.0. */
+  Fixed 	maximumLimit; 	/* The highest value for the ductility axis that
+				 * still yields an acceptable appearance. */
+  public:
+  DEFINE_SIZE_STATIC (22);
+};
+
+struct RepeatedAddGlyphAction
+{
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this)));
+  }
+
+  protected:
+  ActionSubrecordHeader
+		header;
+  HBUINT16 	flags;		/* Currently unused; set to 0. */
+  GlyphID 	glyph;		/* Glyph that should be added if the distance factor
+				 * is growing. */
+  public:
+  DEFINE_SIZE_STATIC (10);
+};
+
+struct ActionSubrecord
+{
+  unsigned int get_length () const { return u.header.actionLength; }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    if (unlikely (!c->check_struct (this)))
+      return_trace (false);
+
+    switch (u.header.actionType)
+    {
+    case 0:  return_trace (u.decompositionAction.sanitize (c));
+    case 1:  return_trace (u.unconditionalAddGlyphAction.sanitize (c));
+    case 2:  return_trace (u.conditionalAddGlyphAction.sanitize (c));
+    // case 3: return_trace (u.stretchGlyphAction.sanitize (c));
+    case 4:  return_trace (u.decompositionAction.sanitize (c));
+    case 5:  return_trace (u.decompositionAction.sanitize (c));
+    default: return_trace (true);
+    }
+  }
+
+  protected:
+  union	{
+  ActionSubrecordHeader		header;
+  DecompositionAction		decompositionAction;
+  UnconditionalAddGlyphAction	unconditionalAddGlyphAction;
+  ConditionalAddGlyphAction	conditionalAddGlyphAction;
+  /* StretchGlyphAction stretchGlyphAction; -- Not supported by CoreText */
+  DuctileGlyphAction		ductileGlyphAction;
+  RepeatedAddGlyphAction	repeatedAddGlyphAction;
+  } u;				/* Data. The format of this data depends on
+				 * the value of the actionType field. */
+  public:
+  DEFINE_SIZE_UNION (6, header);
+};
+
+struct PostcompensationActionChain
+{
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    if (unlikely (!c->check_struct (this)))
+      return_trace (false);
+
+    unsigned int offset = min_size;
+    for (unsigned int i = 0; i < count; i++)
+    {
+      const ActionSubrecord& subrecord = StructAtOffset<ActionSubrecord> (this, offset);
+      if (unlikely (!subrecord.sanitize (c))) return_trace (false);
+      offset += subrecord.get_length ();
+    }
+
+    return_trace (true);
+  }
+
+  protected:
+  HBUINT32	count;
+
+  public:
+  DEFINE_SIZE_STATIC (4);
+};
+
+struct JustWidthDeltaEntry
+{
+  enum Flags
+  {
+    Reserved1		=0xE000,/* Reserved. You should set these bits to zero. */
+    UnlimiteGap		=0x1000,/* The glyph can take unlimited gap. When this
+				 * glyph participates in the justification process,
+				 * it and any other glyphs on the line having this
+				 * bit set absorb all the remaining gap. */
+    Reserved2		=0x0FF0,/* Reserved. You should set these bits to zero. */
+    Priority		=0x000F /* The justification priority of the glyph. */
+  };
+
+  enum Priority
+  {
+    Kashida		= 0,	/* Kashida priority. This is the highest priority
+				 * during justification. */
+    Whitespace		= 1,	/* Whitespace priority. Any whitespace glyphs (as
+				 * identified in the glyph properties table) will
+				 * get this priority. */
+    InterCharacter	= 2,	/* Inter-character priority. Give this to any
+				 * remaining glyphs. */
+    NullPriority	= 3	/* Null priority. You should set this priority for
+				 * glyphs that only participate in justification
+				 * after the above priorities. Normally all glyphs
+				 * have one of the previous three values. If you
+				 * don't want a glyph to participate in justification,
+				 * and you don't want to set its factors to zero,
+				 * you may instead assign it to the null priority. */
+  };
+
+  protected:
+  Fixed		beforeGrowLimit;/* The ratio by which the advance width of the
+				 * glyph is permitted to grow on the left or top side. */
+  Fixed		beforeShrinkLimit;
+				/* The ratio by which the advance width of the
+				 * glyph is permitted to shrink on the left or top side. */
+  Fixed		afterGrowLimit;	/* The ratio by which the advance width of the glyph
+				 * is permitted to shrink on the left or top side. */
+  Fixed		afterShrinkLimit;
+				/* The ratio by which the advance width of the glyph
+				 * is at most permitted to shrink on the right or
+				 * bottom side. */
+  HBUINT16	growFlags;	/* Flags controlling the grow case. */
+  HBUINT16	shrinkFlags;	/* Flags controlling the shrink case. */
+
+  public:
+  DEFINE_SIZE_STATIC (20);
+};
+
+struct WidthDeltaPair
+{
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this)));
+  }
+
+  protected:
+  HBUINT32	justClass;	/* The justification category associated
+				 * with the wdRecord field. Only 7 bits of
+				 * this field are used. (The other bits are
+				 * used as padding to guarantee longword
+				 * alignment of the following record). */
+  JustWidthDeltaEntry
+		wdRecord;	/* The actual width delta record. */
+
+  public:
+  DEFINE_SIZE_STATIC (24);
+};
+  
+typedef OT::LArrayOf<WidthDeltaPair> WidthDeltaCluster;
+
+struct JustificationCategory
+{
+  typedef void EntryData;
+
+  enum Flags
+  {
+    SetMark		=0x8000,/* If set, make the current glyph the marked
+				 * glyph. */
+    DontAdvance		=0x4000,/* If set, don't advance to the next glyph before
+				 * going to the new state. */
+    MarkCategory	=0x3F80,/* The justification category for the marked
+				 * glyph if nonzero. */
+    CurrentCategory	=0x007F /* The justification category for the current
+				 * glyph if nonzero. */
+  };
+
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this) &&
+			  morphHeader.sanitize (c) &&
+			  stHeader.sanitize (c)));
+  }
+
+  protected:
+  ChainSubtable<ObsoleteTypes>
+		morphHeader;	/* Metamorphosis-style subtable header. */
+  StateTable<ObsoleteTypes, EntryData>
+		stHeader;	/* The justification insertion state table header */
+  public:
+  DEFINE_SIZE_STATIC (30);
+};
+
+struct JustificationHeader
+{
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this) &&
+			  justClassTable.sanitize (c, base, base) &&
+			  wdcTable.sanitize (c, base) &&
+			  pcTable.sanitize (c, base) &&
+			  lookupTable.sanitize (c, base)));
+  }
+
+  protected:
+  OffsetTo<JustificationCategory>
+		justClassTable;	/* Offset to the justification category state table. */
+  OffsetTo<WidthDeltaCluster>
+  		wdcTable;	/* Offset from start of justification table to start
+				 * of the subtable containing the width delta factors
+				 * for the glyphs in your font.
+				 *
+				 * The width delta clusters table. */
+  OffsetTo<PostcompensationActionChain>
+		pcTable;	/* Offset from start of justification table to start
+				 * of postcompensation subtable (set to zero if none).
+				 *
+				 * The postcompensation subtable, if present in the font. */
+  Lookup<OffsetTo<WidthDeltaCluster> >
+  		lookupTable;	/* Lookup table associating glyphs with width delta
+				 * clusters. See the description of Width Delta Clusters
+				 * table for details on how to interpret the lookup values. */
+
+  public:
+  DEFINE_SIZE_MIN (8);
+};
+
+struct just
+{
+  enum { tableTag = HB_AAT_TAG_just };
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+
+    return_trace (likely (c->check_struct (this) &&
+			  version.major == 1 &&
+			  horizData.sanitize (c, this, this) &&
+			  vertData.sanitize (c, this, this)));
+  }
+
+  protected:
+  FixedVersion<>version;	/* Version of the justification table
+				 * (0x00010000u for version 1.0). */
+  HBUINT16	format; 	/* Format of the justification table (set to 0). */
+  OffsetTo<JustificationHeader>
+		horizData;	/* Byte offset from the start of the justification table
+				 * to the header for tables that contain justification
+				 * information for horizontal text.
+				 * If you are not including this information,
+				 * store 0. */
+  OffsetTo<JustificationHeader>
+		vertData;	/* ditto, vertical */
+
+  public:
+  DEFINE_SIZE_STATIC (10);
+};
+
+} /* namespace AAT */
+
+
+#endif /* HB_AAT_LAYOUT_JUST_TABLE_HH */
--- a/gfx/harfbuzz/src/hb-aat-layout-kerx-table.hh
+++ b/gfx/harfbuzz/src/hb-aat-layout-kerx-table.hh
@@ -23,324 +23,988 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #ifndef HB_AAT_LAYOUT_KERX_TABLE_HH
 #define HB_AAT_LAYOUT_KERX_TABLE_HH
 
-#include "hb-open-type.hh"
-#include "hb-aat-layout-common.hh"
+#include "hb-kern.hh"
 #include "hb-aat-layout-ankr-table.hh"
 
 /*
  * kerx -- Extended Kerning
  * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kerx.html
  */
 #define HB_AAT_TAG_kerx HB_TAG('k','e','r','x')
 
 
 namespace AAT {
 
 using namespace OT;
 
 
-struct KerxFormat0Records
+static inline int
+kerxTupleKern (int value,
+	       unsigned int tupleCount,
+	       const void *base,
+	       hb_aat_apply_context_t *c)
+{
+  if (likely (!tupleCount || !c)) return value;
+
+  unsigned int offset = value;
+  const FWORD *pv = &StructAtOffset<FWORD> (base, offset);
+  if (unlikely (!c->sanitizer.check_array (pv, tupleCount))) return 0;
+  return *pv;
+}
+
+
+struct hb_glyph_pair_t
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  hb_codepoint_t left;
+  hb_codepoint_t right;
+};
+
+struct KernPair
+{
+  int get_kerning () const { return value; }
+
+  int cmp (const hb_glyph_pair_t &o) const
+  {
+    int ret = left.cmp (o.left);
+    if (ret) return ret;
+    return right.cmp (o.right);
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
-    return_trace (likely (c->check_struct (this)));
+    return_trace (c->check_struct (this));
   }
 
   protected:
   GlyphID	left;
   GlyphID	right;
   FWORD		value;
   public:
   DEFINE_SIZE_STATIC (6);
 };
 
+template <typename KernSubTableHeader>
 struct KerxSubTableFormat0
 {
-  // TODO(ebraminio) Enable when we got suitable BinSearchArrayOf
-  // inline int get_kerning (hb_codepoint_t left, hb_codepoint_t right) const
-  // {
-  //   hb_glyph_pair_t pair = {left, right};
-  //   int i = pairs.bsearch (pair);
-  //   if (i == -1)
-  //     return 0;
-  //   return pairs[i].get_kerning ();
-  // }
-
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  int get_kerning (hb_codepoint_t left, hb_codepoint_t right,
+		   hb_aat_apply_context_t *c = nullptr) const
   {
-    TRACE_SANITIZE (this);
-    return_trace (likely (c->check_struct (this) &&
-			  recordsZ.sanitize (c, nPairs)));
+    hb_glyph_pair_t pair = {left, right};
+    int v = pairs.bsearch (pair).get_kerning ();
+    return kerxTupleKern (v, header.tuple_count (), this, c);
   }
 
-  protected:
-  // TODO(ebraminio): A custom version of "BinSearchArrayOf<KerxPair> pairs;" is
-  // needed here to use HBUINT32 instead
-  HBUINT32	nPairs;		/* The number of kerning pairs in this subtable */
-  HBUINT32	searchRange;	/* The largest power of two less than or equal to the value of nPairs,
-				 * multiplied by the size in bytes of an entry in the subtable. */
-  HBUINT32	entrySelector;	/* This is calculated as log2 of the largest power of two less
-				 * than or equal to the value of nPairs. */
-  HBUINT32	rangeShift;	/* The value of nPairs minus the largest power of two less than or equal to nPairs. */
-  UnsizedArrayOf<KerxFormat0Records>
-		recordsZ;	/* VAR=nPairs */
-  public:
-  DEFINE_SIZE_ARRAY (16, recordsZ);
-};
+  bool apply (hb_aat_apply_context_t *c) const
+  {
+    TRACE_APPLY (this);
+
+    if (!c->plan->requested_kerning)
+      return false;
+
+    if (header.coverage & header.Backwards)
+      return false;
+
+    accelerator_t accel (*this, c);
+    hb_kern_machine_t<accelerator_t> machine (accel, header.coverage & header.CrossStream);
+    machine.kern (c->font, c->buffer, c->plan->kern_mask);
 
-struct KerxSubTableFormat1
-{
-  inline bool sanitize (hb_sanitize_context_t *c) const
+    return_trace (true);
+  }
+
+  struct accelerator_t
+  {
+    const KerxSubTableFormat0 &table;
+    hb_aat_apply_context_t *c;
+
+    accelerator_t (const KerxSubTableFormat0 &table_,
+		   hb_aat_apply_context_t *c_) :
+		     table (table_), c (c_) {}
+
+    int get_kerning (hb_codepoint_t left, hb_codepoint_t right) const
+    { return table.get_kerning (left, right, c); }
+  };
+
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
-    return_trace (likely (c->check_struct (this) &&
-			  stateHeader.sanitize (c)));
+    return_trace (likely (pairs.sanitize (c)));
   }
 
   protected:
-  StateTable<HBUINT16>		stateHeader;
-  LOffsetTo<ArrayOf<HBUINT16> >	valueTable;
+  KernSubTableHeader	header;
+  BinSearchArrayOf<KernPair, typename KernSubTableHeader::Types::HBUINT>
+			pairs;	/* Sorted kern records. */
   public:
-  DEFINE_SIZE_STATIC (20);
+  DEFINE_SIZE_ARRAY (KernSubTableHeader::static_size + 16, pairs);
 };
 
-// TODO(ebraminio): Maybe this can be replaced with Lookup<HBUINT16>?
-struct KerxClassTable
+
+template <bool extended>
+struct Format1Entry;
+
+template <>
+struct Format1Entry<true>
 {
-  inline unsigned int get_class (hb_codepoint_t g) const { return classes[g - firstGlyph]; }
+  enum Flags
+  {
+    Push		= 0x8000,	/* If set, push this glyph on the kerning stack. */
+    DontAdvance		= 0x4000,	/* If set, don't advance to the next glyph
+					 * before going to the new state. */
+    Reset		= 0x2000,	/* If set, reset the kerning data (clear the stack) */
+    Reserved		= 0x1FFF,	/* Not used; set to 0. */
+  };
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  struct EntryData
   {
-    TRACE_SANITIZE (this);
-    return_trace (likely (firstGlyph.sanitize (c) &&
-			  classes.sanitize (c)));
-  }
+    HBUINT16	kernActionIndex;/* Index into the kerning value array. If
+				 * this index is 0xFFFF, then no kerning
+				 * is to be performed. */
+    public:
+    DEFINE_SIZE_STATIC (2);
+  };
+
+  static bool performAction (const Entry<EntryData> *entry)
+  { return entry->data.kernActionIndex != 0xFFFF; }
 
-  protected:
-  HBUINT16		firstGlyph;	/* First glyph in class range. */
-  ArrayOf<HBUINT16>	classes;	/* Glyph classes. */
-  public:
-  DEFINE_SIZE_ARRAY (4, classes);
+  static unsigned int kernActionIndex (const Entry<EntryData> *entry)
+  { return entry->data.kernActionIndex; }
+};
+template <>
+struct Format1Entry<false>
+{
+  enum Flags
+  {
+    Push		= 0x8000,	/* If set, push this glyph on the kerning stack. */
+    DontAdvance		= 0x4000,	/* If set, don't advance to the next glyph
+					 * before going to the new state. */
+    Offset		= 0x3FFF,	/* Byte offset from beginning of subtable to the
+					 * value table for the glyphs on the kerning stack. */
+
+    Reset		= 0x0000,	/* Not supported? */
+  };
+
+  typedef void EntryData;
+
+  static bool performAction (const Entry<EntryData> *entry)
+  { return entry->flags & Offset; }
+
+  static unsigned int kernActionIndex (const Entry<EntryData> *entry)
+  { return entry->flags & Offset; }
 };
 
-struct KerxSubTableFormat2
+template <typename KernSubTableHeader>
+struct KerxSubTableFormat1
 {
-  inline int get_kerning (hb_codepoint_t left, hb_codepoint_t right, const char *end) const
+  typedef typename KernSubTableHeader::Types Types;
+  typedef typename Types::HBUINT HBUINT;
+
+  typedef Format1Entry<Types::extended> Format1EntryT;
+  typedef typename Format1EntryT::EntryData EntryData;
+
+  struct driver_context_t
   {
-    unsigned int l = (this+leftClassTable).get_class (left);
-    unsigned int r = (this+leftClassTable).get_class (left);
-    unsigned int offset = l * rowWidth + r * sizeof (FWORD);
-    const FWORD *arr = &(this+array);
-    if (unlikely ((const void *) arr < (const void *) this || (const void *) arr >= (const void *) end))
-      return 0;
-    const FWORD *v = &StructAtOffset<FWORD> (arr, offset);
-    if (unlikely ((const void *) v < (const void *) arr || (const void *) (v + 1) > (const void *) end))
-      return 0;
-    return *v;
+    enum { in_place = true };
+    enum
+    {
+      DontAdvance	= Format1EntryT::DontAdvance,
+    };
+
+    driver_context_t (const KerxSubTableFormat1 *table_,
+		      hb_aat_apply_context_t *c_) :
+	c (c_),
+	table (table_),
+	/* Apparently the offset kernAction is from the beginning of the state-machine,
+	 * similar to offsets in morx table, NOT from beginning of this table, like
+	 * other subtables in kerx.  Discovered via testing. */
+	kernAction (&table->machine + table->kernAction),
+	depth (0),
+	crossStream (table->header.coverage & table->header.CrossStream) {}
+
+    bool is_actionable (StateTableDriver<Types, EntryData> *driver HB_UNUSED,
+			const Entry<EntryData> *entry)
+    {
+      return Format1EntryT::performAction (entry);
+    }
+    bool transition (StateTableDriver<Types, EntryData> *driver,
+		     const Entry<EntryData> *entry)
+    {
+      hb_buffer_t *buffer = driver->buffer;
+      unsigned int flags = entry->flags;
+
+      if (flags & Format1EntryT::Reset)
+	depth = 0;
+
+      if (flags & Format1EntryT::Push)
+      {
+	if (likely (depth < ARRAY_LENGTH (stack)))
+	  stack[depth++] = buffer->idx;
+	else
+	  depth = 0; /* Probably not what CoreText does, but better? */
+      }
+
+      if (Format1EntryT::performAction (entry) && depth)
+      {
+	unsigned int tuple_count = MAX (1u, table->header.tuple_count ());
+
+	unsigned int kern_idx = Format1EntryT::kernActionIndex (entry);
+	kern_idx = Types::byteOffsetToIndex (kern_idx, &table->machine, kernAction.arrayZ);
+	const FWORD *actions = &kernAction[kern_idx];
+	if (!c->sanitizer.check_array (actions, depth, tuple_count))
+	{
+	  depth = 0;
+	  return false;
+	}
+
+	hb_mask_t kern_mask = c->plan->kern_mask;
+
+	/* From Apple 'kern' spec:
+	 * "Each pops one glyph from the kerning stack and applies the kerning value to it.
+	 * The end of the list is marked by an odd value... */
+	bool last = false;
+	while (!last && depth)
+	{
+	  unsigned int idx = stack[--depth];
+	  int v = *actions;
+	  actions += tuple_count;
+	  if (idx >= buffer->len) continue;
+
+	  /* "The end of the list is marked by an odd value..." */
+	  last = v & 1;
+	  v &= ~1;
+
+	  hb_glyph_position_t &o = buffer->pos[idx];
+
+	  /* Testing shows that CoreText only applies kern (cross-stream or not)
+	   * if none has been applied by previous subtables.  That is, it does
+	   * NOT seem to accumulate as otherwise implied by specs. */
+
+	  /* The following flag is undocumented in the spec, but described
+	   * in the 'kern' table example. */
+	  if (v == -0x8000)
+	  {
+	    o.attach_type() = ATTACH_TYPE_NONE;
+	    o.attach_chain() = 0;
+	    o.x_offset = o.y_offset = 0;
+	  }
+	  else if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction))
+	  {
+	    if (crossStream)
+	    {
+	      if (buffer->pos[idx].attach_type() && !buffer->pos[idx].y_offset)
+	      {
+		o.y_offset = c->font->em_scale_y (v);
+		buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
+	      }
+	    }
+	    else if (buffer->info[idx].mask & kern_mask)
+	    {
+	      if (!buffer->pos[idx].x_offset)
+	      {
+		buffer->pos[idx].x_advance += c->font->em_scale_x (v);
+		buffer->pos[idx].x_offset += c->font->em_scale_x (v);
+	      }
+	    }
+	  }
+	  else
+	  {
+	    if (crossStream)
+	    {
+	      /* CoreText doesn't do crossStream kerning in vertical.  We do. */
+	      if (buffer->pos[idx].attach_type() && !buffer->pos[idx].x_offset)
+	      {
+		o.x_offset = c->font->em_scale_x (v);
+		buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
+	      }
+	    }
+	    else if (buffer->info[idx].mask & kern_mask)
+	    {
+	      if (!buffer->pos[idx].y_offset)
+	      {
+		buffer->pos[idx].y_advance += c->font->em_scale_y (v);
+		buffer->pos[idx].y_offset += c->font->em_scale_y (v);
+	      }
+	    }
+	  }
+	}
+      }
+
+      return true;
+    }
+
+    private:
+    hb_aat_apply_context_t *c;
+    const KerxSubTableFormat1 *table;
+    const UnsizedArrayOf<FWORD> &kernAction;
+    unsigned int stack[8];
+    unsigned int depth;
+    bool crossStream;
+  };
+
+  bool apply (hb_aat_apply_context_t *c) const
+  {
+    TRACE_APPLY (this);
+
+    if (!c->plan->requested_kerning &&
+	!(header.coverage & header.CrossStream))
+      return false;
+
+    driver_context_t dc (this, c);
+
+    StateTableDriver<Types, EntryData> driver (machine, c->buffer, c->font->face);
+    driver.drive (&dc);
+
+    return_trace (true);
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
+    /* The rest of array sanitizations are done at run-time. */
     return_trace (likely (c->check_struct (this) &&
-			  rowWidth.sanitize (c) &&
-			  leftClassTable.sanitize (c, this) &&
-			  rightClassTable.sanitize (c, this) &&
-			  array.sanitize (c, this)));
+			  machine.sanitize (c)));
   }
 
   protected:
-  HBUINT32	rowWidth;	/* The width, in bytes, of a row in the table. */
-  LOffsetTo<KerxClassTable>
-		leftClassTable;	/* Offset from beginning of this subtable to
-				 * left-hand class table. */
-  LOffsetTo<KerxClassTable>
-		rightClassTable;/* Offset from beginning of this subtable to
-				 * right-hand class table. */
-  LOffsetTo<FWORD>
-		array;		/* Offset from beginning of this subtable to
-				 * the start of the kerning array. */
+  KernSubTableHeader				header;
+  StateTable<Types, EntryData>			machine;
+  OffsetTo<UnsizedArrayOf<FWORD>, HBUINT, false>kernAction;
   public:
-  DEFINE_SIZE_STATIC (16);
+  DEFINE_SIZE_STATIC (KernSubTableHeader::static_size + 5 * sizeof (HBUINT));
 };
 
-struct KerxSubTableFormat4
+template <typename KernSubTableHeader>
+struct KerxSubTableFormat2
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  typedef typename KernSubTableHeader::Types Types;
+  typedef typename Types::HBUINT HBUINT;
+
+  int get_kerning (hb_codepoint_t left, hb_codepoint_t right,
+		   hb_aat_apply_context_t *c) const
+  {
+    unsigned int num_glyphs = c->sanitizer.get_num_glyphs ();
+    unsigned int l = (this+leftClassTable).get_class (left, num_glyphs, 0);
+    unsigned int r = (this+rightClassTable).get_class (right, num_glyphs, 0);
+
+    const UnsizedArrayOf<FWORD> &arrayZ = this+array;
+    unsigned int kern_idx = l + r;
+    kern_idx = Types::offsetToIndex (kern_idx, this, &arrayZ);
+    const FWORD *v = &arrayZ[kern_idx];
+    if (unlikely (!v->sanitize (&c->sanitizer))) return 0;
+
+    return kerxTupleKern (*v, header.tuple_count (), this, c);
+  }
+
+  bool apply (hb_aat_apply_context_t *c) const
+  {
+    TRACE_APPLY (this);
+
+    if (!c->plan->requested_kerning)
+      return false;
+
+    if (header.coverage & header.Backwards)
+      return false;
+
+    accelerator_t accel (*this, c);
+    hb_kern_machine_t<accelerator_t> machine (accel, header.coverage & header.CrossStream);
+    machine.kern (c->font, c->buffer, c->plan->kern_mask);
+
+    return_trace (true);
+  }
+
+  struct accelerator_t
+  {
+    const KerxSubTableFormat2 &table;
+    hb_aat_apply_context_t *c;
+
+    accelerator_t (const KerxSubTableFormat2 &table_,
+		   hb_aat_apply_context_t *c_) :
+		     table (table_), c (c_) {}
+
+    int get_kerning (hb_codepoint_t left, hb_codepoint_t right) const
+    { return table.get_kerning (left, right, c); }
+  };
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this) &&
-			  rowWidth.sanitize (c) &&
 			  leftClassTable.sanitize (c, this) &&
 			  rightClassTable.sanitize (c, this) &&
-			  array.sanitize (c, this)));
-  }
-
-  protected:
-  HBUINT32	rowWidth;	/* The width, in bytes, of a row in the table. */
-  LOffsetTo<KerxClassTable>
-		leftClassTable;	/* Offset from beginning of this subtable to
-				 * left-hand class table. */
-  LOffsetTo<KerxClassTable>
-		rightClassTable;/* Offset from beginning of this subtable to
-				 * right-hand class table. */
-  LOffsetTo<FWORD>
-		array;		/* Offset from beginning of this subtable to
-				 * the start of the kerning array. */
-  public:
-  DEFINE_SIZE_STATIC (16);
-};
-
-struct KerxSubTableFormat6
-{
-  inline bool sanitize (hb_sanitize_context_t *c) const
-  {
-    TRACE_SANITIZE (this);
-    return_trace (likely (c->check_struct (this) &&
-			  rowIndexTable.sanitize (c, this) &&
-			  columnIndexTable.sanitize (c, this) &&
-			  kerningArray.sanitize (c, this) &&
-			  kerningVector.sanitize (c, this)));
+			  c->check_range (this, array)));
   }
 
   protected:
-  HBUINT32	flags;
-  HBUINT16	rowCount;
-  HBUINT16	columnCount;
-  LOffsetTo<Lookup<HBUINT16> >	rowIndexTable;
-  LOffsetTo<Lookup<HBUINT16> >	columnIndexTable;
-  LOffsetTo<Lookup<HBUINT16> >	kerningArray;
-  LOffsetTo<Lookup<HBUINT16> >	kerningVector;
+  KernSubTableHeader	header;
+  HBUINT		rowWidth;	/* The width, in bytes, of a row in the table. */
+  OffsetTo<typename Types::ClassTypeWide, HBUINT, false>
+			leftClassTable;	/* Offset from beginning of this subtable to
+					 * left-hand class table. */
+  OffsetTo<typename Types::ClassTypeWide, HBUINT, false>
+			rightClassTable;/* Offset from beginning of this subtable to
+					 * right-hand class table. */
+  OffsetTo<UnsizedArrayOf<FWORD>, HBUINT, false>
+			 array;		/* Offset from beginning of this subtable to
+					 * the start of the kerning array. */
   public:
-  DEFINE_SIZE_STATIC (24);
-};
-
-enum coverage_flags_t
-{
-  COVERAGE_VERTICAL_FLAG	= 0x80u,
-  COVERAGE_CROSSSTREAM_FLAG	= 0x40u,
-  COVERAGE_VARIATION_FLAG	= 0x20u,
-  COVERAGE_PROCESS_DIRECTION	= 0x10u,
+  DEFINE_SIZE_STATIC (KernSubTableHeader::static_size + 4 * sizeof (HBUINT));
 };
 
-struct KerxTable
+template <typename KernSubTableHeader>
+struct KerxSubTableFormat4
 {
-  inline bool apply (hb_aat_apply_context_t *c, const AAT::ankr *ankr) const
+  typedef ExtendedTypes Types;
+
+  struct EntryData
+  {
+    HBUINT16	ankrActionIndex;/* Either 0xFFFF (for no action) or the index of
+				 * the action to perform. */
+    public:
+    DEFINE_SIZE_STATIC (2);
+  };
+
+  struct driver_context_t
+  {
+    enum { in_place = true };
+    enum Flags
+    {
+      Mark		= 0x8000,	/* If set, remember this glyph as the marked glyph. */
+      DontAdvance	= 0x4000,	/* If set, don't advance to the next glyph before
+					 * going to the new state. */
+      Reserved		= 0x3FFF,	/* Not used; set to 0. */
+    };
+
+    enum SubTableFlags
+    {
+      ActionType	= 0xC0000000,	/* A two-bit field containing the action type. */
+      Unused		= 0x3F000000,	/* Unused - must be zero. */
+      Offset		= 0x00FFFFFF,	/* Masks the offset in bytes from the beginning
+					 * of the subtable to the beginning of the control
+					 * point table. */
+    };
+
+    driver_context_t (const KerxSubTableFormat4 *table,
+			     hb_aat_apply_context_t *c_) :
+	c (c_),
+	action_type ((table->flags & ActionType) >> 30),
+	ankrData ((HBUINT16 *) ((const char *) &table->machine + (table->flags & Offset))),
+	mark_set (false),
+	mark (0) {}
+
+    bool is_actionable (StateTableDriver<Types, EntryData> *driver HB_UNUSED,
+			const Entry<EntryData> *entry)
+    {
+      return entry->data.ankrActionIndex != 0xFFFF;
+    }
+    bool transition (StateTableDriver<Types, EntryData> *driver,
+		     const Entry<EntryData> *entry)
+    {
+      hb_buffer_t *buffer = driver->buffer;
+
+      if (mark_set && entry->data.ankrActionIndex != 0xFFFF && buffer->idx < buffer->len)
+      {
+	hb_glyph_position_t &o = buffer->cur_pos();
+	switch (action_type)
+	{
+	  case 0: /* Control Point Actions.*/
+	  {
+	    /* indexed into glyph outline. */
+	    const HBUINT16 *data = &ankrData[entry->data.ankrActionIndex];
+	    if (!c->sanitizer.check_array (data, 2))
+	      return false;
+	    HB_UNUSED unsigned int markControlPoint = *data++;
+	    HB_UNUSED unsigned int currControlPoint = *data++;
+	    hb_position_t markX = 0;
+	    hb_position_t markY = 0;
+	    hb_position_t currX = 0;
+	    hb_position_t currY = 0;
+	    if (!c->font->get_glyph_contour_point_for_origin (c->buffer->info[mark].codepoint,
+							      markControlPoint,
+							      HB_DIRECTION_LTR /*XXX*/,
+							      &markX, &markY) ||
+		!c->font->get_glyph_contour_point_for_origin (c->buffer->cur ().codepoint,
+							      currControlPoint,
+							      HB_DIRECTION_LTR /*XXX*/,
+							      &currX, &currY))
+	      return true; /* True, such that the machine continues. */
+
+	    o.x_offset = markX - currX;
+	    o.y_offset = markY - currY;
+	  }
+	  break;
+
+	  case 1: /* Anchor Point Actions. */
+	  {
+	   /* Indexed into 'ankr' table. */
+	    const HBUINT16 *data = &ankrData[entry->data.ankrActionIndex];
+	    if (!c->sanitizer.check_array (data, 2))
+	      return false;
+	    unsigned int markAnchorPoint = *data++;
+	    unsigned int currAnchorPoint = *data++;
+	    const Anchor markAnchor = c->ankr_table->get_anchor (c->buffer->info[mark].codepoint,
+								 markAnchorPoint,
+								 c->sanitizer.get_num_glyphs (),
+								 c->ankr_end);
+	    const Anchor currAnchor = c->ankr_table->get_anchor (c->buffer->cur ().codepoint,
+								 currAnchorPoint,
+								 c->sanitizer.get_num_glyphs (),
+								 c->ankr_end);
+
+	    o.x_offset = c->font->em_scale_x (markAnchor.xCoordinate) - c->font->em_scale_x (currAnchor.xCoordinate);
+	    o.y_offset = c->font->em_scale_y (markAnchor.yCoordinate) - c->font->em_scale_y (currAnchor.yCoordinate);
+	  }
+	  break;
+
+	  case 2: /* Control Point Coordinate Actions. */
+	  {
+	    const FWORD *data = (const FWORD *) &ankrData[entry->data.ankrActionIndex];
+	    if (!c->sanitizer.check_array (data, 4))
+	      return false;
+	    int markX = *data++;
+	    int markY = *data++;
+	    int currX = *data++;
+	    int currY = *data++;
+
+	    o.x_offset = c->font->em_scale_x (markX) - c->font->em_scale_x (currX);
+	    o.y_offset = c->font->em_scale_y (markY) - c->font->em_scale_y (currY);
+	  }
+	  break;
+	}
+	o.attach_type() = ATTACH_TYPE_MARK;
+	o.attach_chain() = (int) mark - (int) buffer->idx;
+	buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
+      }
+
+      if (entry->flags & Mark)
+      {
+	mark_set = true;
+	mark = buffer->idx;
+      }
+
+      return true;
+    }
+
+    private:
+    hb_aat_apply_context_t *c;
+    unsigned int action_type;
+    const HBUINT16 *ankrData;
+    bool mark_set;
+    unsigned int mark;
+  };
+
+  bool apply (hb_aat_apply_context_t *c) const
   {
     TRACE_APPLY (this);
-    /* TODO */
-    return_trace (false);
+
+    driver_context_t dc (this, c);
+
+    StateTableDriver<Types, EntryData> driver (machine, c->buffer, c->font->face);
+    driver.drive (&dc);
+
+    return_trace (true);
   }
 
-  inline unsigned int get_size (void) const { return length; }
-
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
-    if (unlikely (!c->check_struct (this)))
-      return_trace (false);
+    /* The rest of array sanitizations are done at run-time. */
+    return_trace (likely (c->check_struct (this) &&
+			  machine.sanitize (c)));
+  }
+
+  protected:
+  KernSubTableHeader		header;
+  StateTable<Types, EntryData>	machine;
+  HBUINT32			flags;
+  public:
+  DEFINE_SIZE_STATIC (KernSubTableHeader::static_size + 20);
+};
+
+template <typename KernSubTableHeader>
+struct KerxSubTableFormat6
+{
+  enum Flags
+  {
+    ValuesAreLong	= 0x00000001,
+  };
+
+  bool is_long () const { return flags & ValuesAreLong; }
 
-    switch (format) {
-    case 0: return u.format0.sanitize (c);
-    case 1: return u.format1.sanitize (c);
-    case 2: return u.format2.sanitize (c);
-    case 4: return u.format4.sanitize (c);
-    case 6: return u.format6.sanitize (c);
-    default:return_trace (false);
+  int get_kerning (hb_codepoint_t left, hb_codepoint_t right,
+			  hb_aat_apply_context_t *c) const
+  {
+    unsigned int num_glyphs = c->sanitizer.get_num_glyphs ();
+    if (is_long ())
+    {
+      const typename U::Long &t = u.l;
+      unsigned int l = (this+t.rowIndexTable).get_value_or_null (left, num_glyphs);
+      unsigned int r = (this+t.columnIndexTable).get_value_or_null (right, num_glyphs);
+      unsigned int offset = l + r;
+      if (unlikely (offset < l)) return 0; /* Addition overflow. */
+      if (unlikely (hb_unsigned_mul_overflows (offset, sizeof (FWORD32)))) return 0;
+      const FWORD32 *v = &StructAtOffset<FWORD32> (&(this+t.array), offset * sizeof (FWORD32));
+      if (unlikely (!v->sanitize (&c->sanitizer))) return 0;
+      return kerxTupleKern (*v, header.tuple_count (), &(this+vector), c);
+    }
+    else
+    {
+      const typename U::Short &t = u.s;
+      unsigned int l = (this+t.rowIndexTable).get_value_or_null (left, num_glyphs);
+      unsigned int r = (this+t.columnIndexTable).get_value_or_null (right, num_glyphs);
+      unsigned int offset = l + r;
+      const FWORD *v = &StructAtOffset<FWORD> (&(this+t.array), offset * sizeof (FWORD));
+      if (unlikely (!v->sanitize (&c->sanitizer))) return 0;
+      return kerxTupleKern (*v, header.tuple_count (), &(this+vector), c);
     }
   }
 
-protected:
-  HBUINT32	length;
-  HBUINT8	coverage;
-  HBUINT16	unused;
-  HBUINT8	format;
-  HBUINT32	tupleIndex;
-  union {
-  KerxSubTableFormat0	format0;
-  KerxSubTableFormat1	format1;
-  KerxSubTableFormat2	format2;
-  KerxSubTableFormat4	format4;
-  KerxSubTableFormat6	format6;
+  bool apply (hb_aat_apply_context_t *c) const
+  {
+    TRACE_APPLY (this);
+
+    if (!c->plan->requested_kerning)
+      return false;
+
+    if (header.coverage & header.Backwards)
+      return false;
+
+    accelerator_t accel (*this, c);
+    hb_kern_machine_t<accelerator_t> machine (accel, header.coverage & header.CrossStream);
+    machine.kern (c->font, c->buffer, c->plan->kern_mask);
+
+    return_trace (true);
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this) &&
+			  (is_long () ?
+			   (
+			     u.l.rowIndexTable.sanitize (c, this) &&
+			     u.l.columnIndexTable.sanitize (c, this) &&
+			     c->check_range (this, u.l.array)
+			   ) : (
+			     u.s.rowIndexTable.sanitize (c, this) &&
+			     u.s.columnIndexTable.sanitize (c, this) &&
+			     c->check_range (this, u.s.array)
+			   )) &&
+			  (header.tuple_count () == 0 ||
+			   c->check_range (this, vector))));
+  }
+
+  struct accelerator_t
+  {
+    const KerxSubTableFormat6 &table;
+    hb_aat_apply_context_t *c;
+
+    accelerator_t (const KerxSubTableFormat6 &table_,
+		   hb_aat_apply_context_t *c_) :
+		     table (table_), c (c_) {}
+
+    int get_kerning (hb_codepoint_t left, hb_codepoint_t right) const
+    { return table.get_kerning (left, right, c); }
+  };
+
+  protected:
+  KernSubTableHeader		header;
+  HBUINT32			flags;
+  HBUINT16			rowCount;
+  HBUINT16			columnCount;
+  union U
+  {
+    struct Long
+    {
+      LOffsetTo<Lookup<HBUINT32>, false>	rowIndexTable;
+      LOffsetTo<Lookup<HBUINT32>, false>	columnIndexTable;
+      LOffsetTo<UnsizedArrayOf<FWORD32>, false>	array;
+    } l;
+    struct Short
+    {
+      LOffsetTo<Lookup<HBUINT16>, false>	rowIndexTable;
+      LOffsetTo<Lookup<HBUINT16>, false>	columnIndexTable;
+      LOffsetTo<UnsizedArrayOf<FWORD>, false>	array;
+    } s;
   } u;
-public:
-  DEFINE_SIZE_MIN (12);
+  LOffsetTo<UnsizedArrayOf<FWORD>, false>	vector;
+  public:
+  DEFINE_SIZE_STATIC (KernSubTableHeader::static_size + 24);
 };
 
-struct SubtableGlyphCoverageArray
+
+struct KerxSubTableHeader
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  typedef ExtendedTypes Types;
+
+  unsigned int tuple_count () const { return tupleCount; }
+  bool is_horizontal () const       { return !(coverage & Vertical); }
+
+  enum Coverage
+  {
+    Vertical	= 0x80000000u,	/* Set if table has vertical kerning values. */
+    CrossStream	= 0x40000000u,	/* Set if table has cross-stream kerning values. */
+    Variation	= 0x20000000u,	/* Set if table has variation kerning values. */
+    Backwards	= 0x10000000u,	/* If clear, process the glyphs forwards, that
+				 * is, from first to last in the glyph stream.
+				 * If we, process them from last to first.
+				 * This flag only applies to state-table based
+				 * 'kerx' subtables (types 1 and 4). */
+    Reserved	= 0x0FFFFF00u,	/* Reserved, set to zero. */
+    SubtableType= 0x000000FFu,	/* Subtable type. */
+  };
+
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this)));
   }
 
-  protected:
+  public:
   HBUINT32	length;
   HBUINT32	coverage;
   HBUINT32	tupleCount;
   public:
   DEFINE_SIZE_STATIC (12);
 };
 
-struct kerx
+struct KerxSubTable
 {
-  static const hb_tag_t tableTag = HB_AAT_TAG_kerx;
+  friend struct kerx;
+
+  unsigned int get_size () const { return u.header.length; }
+  unsigned int get_type () const { return u.header.coverage & u.header.SubtableType; }
 
-  inline bool apply (hb_aat_apply_context_t *c, const AAT::ankr *ankr) const
+  template <typename context_t>
+  typename context_t::return_t dispatch (context_t *c) const
   {
-    TRACE_APPLY (this);
-    const KerxTable &table = StructAfter<KerxTable> (*this);
-    return_trace (table.apply (c, ankr));
+    unsigned int subtable_type = get_type ();
+    TRACE_DISPATCH (this, subtable_type);
+    switch (subtable_type) {
+    case 0:	return_trace (c->dispatch (u.format0));
+    case 1:	return_trace (c->dispatch (u.format1));
+    case 2:	return_trace (c->dispatch (u.format2));
+    case 4:	return_trace (c->dispatch (u.format4));
+    case 6:	return_trace (c->dispatch (u.format6));
+    default:	return_trace (c->default_return_value ());
+    }
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
-    if (unlikely (!(c->check_struct (this))))
-     return_trace (false);
-
-    /* TODO: Something like `morx`s ChainSubtable should be done here instead */
-    const KerxTable *table = &StructAfter<KerxTable> (*this);
-    if (unlikely (!(table->sanitize (c))))
+    if (!u.header.sanitize (c) ||
+	u.header.length <= u.header.static_size ||
+	!c->check_range (this, u.header.length))
       return_trace (false);
 
-    for (unsigned int i = 0; i < nTables - 1; ++i)
+    return_trace (dispatch (c));
+  }
+
+  public:
+  union {
+  KerxSubTableHeader				header;
+  KerxSubTableFormat0<KerxSubTableHeader>	format0;
+  KerxSubTableFormat1<KerxSubTableHeader>	format1;
+  KerxSubTableFormat2<KerxSubTableHeader>	format2;
+  KerxSubTableFormat4<KerxSubTableHeader>	format4;
+  KerxSubTableFormat6<KerxSubTableHeader>	format6;
+  } u;
+  public:
+  DEFINE_SIZE_MIN (12);
+};
+
+
+/*
+ * The 'kerx' Table
+ */
+
+template <typename T>
+struct KerxTable
+{
+  /* https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern */
+  const T* thiz () const { return static_cast<const T *> (this); }
+
+  bool has_state_machine () const
+  {
+    typedef typename T::SubTable SubTable;
+
+    const SubTable *st = &thiz()->firstSubTable;
+    unsigned int count = thiz()->tableCount;
+    for (unsigned int i = 0; i < count; i++)
+    {
+      if (st->get_type () == 1)
+        return true;
+      st = &StructAfter<SubTable> (*st);
+    }
+    return false;
+  }
+
+  bool has_cross_stream () const
+  {
+    typedef typename T::SubTable SubTable;
+
+    const SubTable *st = &thiz()->firstSubTable;
+    unsigned int count = thiz()->tableCount;
+    for (unsigned int i = 0; i < count; i++)
+    {
+      if (st->u.header.coverage & st->u.header.CrossStream)
+        return true;
+      st = &StructAfter<SubTable> (*st);
+    }
+    return false;
+  }
+
+  int get_h_kerning (hb_codepoint_t left, hb_codepoint_t right) const
+  {
+    typedef typename T::SubTable SubTable;
+
+    int v = 0;
+    const SubTable *st = &thiz()->firstSubTable;
+    unsigned int count = thiz()->tableCount;
+    for (unsigned int i = 0; i < count; i++)
     {
-      table = &StructAfter<KerxTable> (*table);
-      if (unlikely (!(table->sanitize (c))))
-        return_trace (false);
+      if ((st->u.header.coverage & (st->u.header.Variation | st->u.header.CrossStream)) ||
+	  !st->u.header.is_horizontal ())
+        continue;
+      v += st->get_kerning (left, right);
+      st = &StructAfter<SubTable> (*st);
+    }
+    return v;
+  }
+
+  bool apply (AAT::hb_aat_apply_context_t *c) const
+  {
+    typedef typename T::SubTable SubTable;
+
+    bool ret = false;
+    bool seenCrossStream = false;
+    c->set_lookup_index (0);
+    const SubTable *st = &thiz()->firstSubTable;
+    unsigned int count = thiz()->tableCount;
+    for (unsigned int i = 0; i < count; i++)
+    {
+      bool reverse;
+
+      if (!T::Types::extended && (st->u.header.coverage & st->u.header.Variation))
+        goto skip;
+
+      if (HB_DIRECTION_IS_HORIZONTAL (c->buffer->props.direction) != st->u.header.is_horizontal ())
+	goto skip;
+
+      reverse = bool (st->u.header.coverage & st->u.header.Backwards) !=
+		HB_DIRECTION_IS_BACKWARD (c->buffer->props.direction);
+
+      if (!c->buffer->message (c->font, "start %c%c%c%c subtable %d", HB_UNTAG (thiz()->tableTag), c->lookup_index))
+	goto skip;
+
+      if (!seenCrossStream &&
+	  (st->u.header.coverage & st->u.header.CrossStream))
+      {
+        /* Attach all glyphs into a chain. */
+        seenCrossStream = true;
+	hb_glyph_position_t *pos = c->buffer->pos;
+	unsigned int count = c->buffer->len;
+	for (unsigned int i = 0; i < count; i++)
+	{
+	  pos[i].attach_type() = ATTACH_TYPE_CURSIVE;
+	  pos[i].attach_chain() = HB_DIRECTION_IS_FORWARD (c->buffer->props.direction) ? -1 : +1;
+	  /* We intentionally don't set HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT,
+	   * since there needs to be a non-zero attachment for post-positioning to
+	   * be needed. */
+	}
+      }
+
+      if (reverse)
+	c->buffer->reverse ();
+
+      {
+	/* See comment in sanitize() for conditional here. */
+	hb_sanitize_with_object_t with (&c->sanitizer, i < count - 1 ? st : (const SubTable *) nullptr);
+	ret |= st->dispatch (c);
+      }
+
+      if (reverse)
+	c->buffer->reverse ();
+
+      (void) c->buffer->message (c->font, "end %c%c%c%c subtable %d", HB_UNTAG (thiz()->tableTag), c->lookup_index);
+
+    skip:
+      st = &StructAfter<SubTable> (*st);
+      c->set_lookup_index (c->lookup_index + 1);
     }
 
-    // If version is less than 3, we are done here; otherwise better to check footer also
-    if (version < 3)
-      return_trace (true);
+    return ret;
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    if (unlikely (!thiz()->version.sanitize (c) ||
+		  (unsigned) thiz()->version < (unsigned) T::minVersion ||
+		  !thiz()->tableCount.sanitize (c)))
+      return_trace (false);
+
+    typedef typename T::SubTable SubTable;
 
-    // TODO: Investigate why this just work on some fonts no matter of version
-    // const SubtableGlyphCoverageArray &footer =
-    //   StructAfter<SubtableGlyphCoverageArray> (*table);
-    // return_trace (footer.sanitize (c));
+    const SubTable *st = &thiz()->firstSubTable;
+    unsigned int count = thiz()->tableCount;
+    for (unsigned int i = 0; i < count; i++)
+    {
+      if (unlikely (!st->u.header.sanitize (c)))
+	return_trace (false);
+      /* OpenType kern table has 2-byte subtable lengths.  That's limiting.
+       * MS implementation also only supports one subtable, of format 0,
+       * anyway.  Certain versions of some fonts, like Calibry, contain
+       * kern subtable that exceeds 64kb.  Looks like, the subtable length
+       * is simply ignored.  Which makes sense.  It's only needed if you
+       * have multiple subtables.  To handle such fonts, we just ignore
+       * the length for the last subtable. */
+      hb_sanitize_with_object_t with (c, i < count - 1 ? st : (const SubTable *) nullptr);
+
+      if (unlikely (!st->sanitize (c)))
+	return_trace (false);
+
+      st = &StructAfter<SubTable> (*st);
+    }
 
     return_trace (true);
   }
+};
+
+struct kerx : KerxTable<kerx>
+{
+  friend struct KerxTable<kerx>;
+
+  enum { tableTag = HB_AAT_TAG_kerx };
+  enum { minVersion = 2u };
+
+  typedef KerxSubTableHeader SubTableHeader;
+  typedef SubTableHeader::Types Types;
+  typedef KerxSubTable SubTable;
+
+  bool has_data () const { return version; }
 
   protected:
-  HBUINT16		version;
-  HBUINT16		padding;
-  HBUINT32		nTables;
-/*KerxTable tablesZ[VAR]; XXX ArrayOf??? */
-/*SubtableGlyphCoverageArray coverage_array;*/
+  HBUINT16	version;	/* The version number of the extended kerning table
+				 * (currently 2, 3, or 4). */
+  HBUINT16	unused;		/* Set to 0. */
+  HBUINT32	tableCount;	/* The number of subtables included in the extended kerning
+				 * table. */
+  SubTable	firstSubTable;	/* Subtables. */
+/*subtableGlyphCoverageArray*/	/* Only if version >= 3. We don't use. */
+
   public:
-  DEFINE_SIZE_STATIC (8);
+  DEFINE_SIZE_MIN (8);
 };
 
+
 } /* namespace AAT */
 
 
 #endif /* HB_AAT_LAYOUT_KERX_TABLE_HH */
new file mode 100644
--- /dev/null
+++ b/gfx/harfbuzz/src/hb-aat-layout-lcar-table.hh
@@ -0,0 +1,93 @@
+/*
+ * Copyright © 2018  Ebrahim Byagowi
+ *
+ *  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.
+ */
+#ifndef HB_AAT_LAYOUT_LCAR_TABLE_HH
+#define HB_AAT_LAYOUT_LCAR_TABLE_HH
+
+#include "hb-open-type.hh"
+#include "hb-aat-layout-common.hh"
+
+/*
+ * lcar -- Ligature caret
+ * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6lcar.html
+ */
+#define HB_AAT_TAG_lcar HB_TAG('l','c','a','r')
+
+
+namespace AAT {
+
+typedef ArrayOf<HBINT16> LigCaretClassEntry;
+
+struct lcar
+{
+  enum { tableTag = HB_AAT_TAG_lcar };
+
+  unsigned int get_lig_carets (hb_font_t      *font,
+			       hb_direction_t  direction,
+			       hb_codepoint_t  glyph,
+			       unsigned int    start_offset,
+			       unsigned int   *caret_count /* IN/OUT */,
+			       hb_position_t  *caret_array /* OUT */) const
+  {
+    const OffsetTo<LigCaretClassEntry>* entry_offset = lookup.get_value (glyph,
+									 font->face->get_num_glyphs ());
+    const LigCaretClassEntry& array = entry_offset ? this+*entry_offset : Null (LigCaretClassEntry);
+    if (caret_count)
+    {
+      hb_array_t<const HBINT16> arr = array.sub_array (start_offset, caret_count);
+      unsigned int count = arr.len;
+      for (unsigned int i = 0; i < count; ++i)
+	switch (format)
+	{
+	case 0: caret_array[i] = font->em_scale_dir (arr[i], direction); break;
+	case 1:
+	  hb_position_t x, y;
+	  font->get_glyph_contour_point_for_origin (glyph, arr[i], direction, &x, &y);
+	  caret_array[i] = HB_DIRECTION_IS_HORIZONTAL (direction) ? x : y;
+	  break;
+	}
+    }
+    return array.len;
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this) &&
+			  version.major == 1 &&
+			  lookup.sanitize (c, this)));
+  }
+
+  protected:
+  FixedVersion<>version;	/* Version number of the ligature caret table */
+  HBUINT16	format;		/* Format of the ligature caret table. */
+  Lookup<OffsetTo<LigCaretClassEntry> >
+		lookup;		/* data Lookup table associating glyphs */
+
+  public:
+  DEFINE_SIZE_MIN (8);
+};
+
+} /* namespace AAT */
+
+#endif /* HB_AAT_LAYOUT_LCAR_TABLE_HH */
--- a/gfx/harfbuzz/src/hb-aat-layout-morx-table.hh
+++ b/gfx/harfbuzz/src/hb-aat-layout-morx-table.hh
@@ -25,60 +25,66 @@
  */
 
 #ifndef HB_AAT_LAYOUT_MORX_TABLE_HH
 #define HB_AAT_LAYOUT_MORX_TABLE_HH
 
 #include "hb-open-type.hh"
 #include "hb-aat-layout-common.hh"
 #include "hb-ot-layout-common.hh"
+#include "hb-aat-map.hh"
 
 /*
  * morx -- Extended Glyph Metamorphosis
  * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
+ * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html
  */
 #define HB_AAT_TAG_morx HB_TAG('m','o','r','x')
+#define HB_AAT_TAG_mort HB_TAG('m','o','r','t')
 
 
 namespace AAT {
 
 using namespace OT;
 
-
+template <typename Types>
 struct RearrangementSubtable
 {
+  typedef typename Types::HBUINT HBUINT;
+
   typedef void EntryData;
 
   struct driver_context_t
   {
-    static const bool in_place = true;
-    enum Flags {
+    enum { in_place = true };
+    enum Flags
+    {
       MarkFirst		= 0x8000,	/* If set, make the current glyph the first
 					 * glyph to be rearranged. */
       DontAdvance	= 0x4000,	/* If set, don't advance to the next glyph
 					 * before going to the new state. This means
 					 * that the glyph index doesn't change, even
 					 * if the glyph at that index has changed. */
       MarkLast		= 0x2000,	/* If set, make the current glyph the last
 					 * glyph to be rearranged. */
       Reserved		= 0x1FF0,	/* These bits are reserved and should be set to 0. */
       Verb		= 0x000F,	/* The type of rearrangement specified. */
     };
 
-    inline driver_context_t (const RearrangementSubtable *table) :
+    driver_context_t (const RearrangementSubtable *table HB_UNUSED) :
 	ret (false),
 	start (0), end (0) {}
 
-    inline bool is_actionable (StateTableDriver<EntryData> *driver,
-			       const Entry<EntryData> *entry)
+    bool is_actionable (StateTableDriver<Types, EntryData> *driver HB_UNUSED,
+			const Entry<EntryData> *entry)
     {
       return (entry->flags & Verb) && start < end;
     }
-    inline bool transition (StateTableDriver<EntryData> *driver,
-			    const Entry<EntryData> *entry)
+    bool transition (StateTableDriver<Types, EntryData> *driver,
+		     const Entry<EntryData> *entry)
     {
       hb_buffer_t *buffer = driver->buffer;
       unsigned int flags = entry->flags;
 
       if (flags & MarkFirst)
 	start = buffer->idx;
 
       if (flags & MarkLast)
@@ -152,149 +158,185 @@ struct RearrangementSubtable
 
     public:
     bool ret;
     private:
     unsigned int start;
     unsigned int end;
   };
 
-  inline bool apply (hb_aat_apply_context_t *c) const
+  bool apply (hb_aat_apply_context_t *c) const
   {
     TRACE_APPLY (this);
 
     driver_context_t dc (this);
 
-    StateTableDriver<void> driver (machine, c->buffer, c->face);
+    StateTableDriver<Types, EntryData> driver (machine, c->buffer, c->face);
     driver.drive (&dc);
 
     return_trace (dc.ret);
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (machine.sanitize (c));
   }
 
   protected:
-  StateTable<EntryData>	machine;
+  StateTable<Types, EntryData>	machine;
   public:
   DEFINE_SIZE_STATIC (16);
 };
 
+template <typename Types>
 struct ContextualSubtable
 {
+  typedef typename Types::HBUINT HBUINT;
+
   struct EntryData
   {
     HBUINT16	markIndex;	/* Index of the substitution table for the
 				 * marked glyph (use 0xFFFF for none). */
     HBUINT16	currentIndex;	/* Index of the substitution table for the
 				 * current glyph (use 0xFFFF for none). */
     public:
     DEFINE_SIZE_STATIC (4);
   };
 
   struct driver_context_t
   {
-    static const bool in_place = true;
-    enum Flags {
+    enum { in_place = true };
+    enum Flags
+    {
       SetMark		= 0x8000,	/* If set, make the current glyph the marked glyph. */
       DontAdvance	= 0x4000,	/* If set, don't advance to the next glyph before
 					 * going to the new state. */
       Reserved		= 0x3FFF,	/* These bits are reserved and should be set to 0. */
     };
 
-    inline driver_context_t (const ContextualSubtable *table) :
+    driver_context_t (const ContextualSubtable *table_,
+			     hb_aat_apply_context_t *c_) :
 	ret (false),
+	c (c_),
 	mark_set (false),
 	mark (0),
+	table (table_),
 	subs (table+table->substitutionTables) {}
 
-    inline bool is_actionable (StateTableDriver<EntryData> *driver,
-			       const Entry<EntryData> *entry)
+    bool is_actionable (StateTableDriver<Types, EntryData> *driver,
+			const Entry<EntryData> *entry)
     {
       hb_buffer_t *buffer = driver->buffer;
 
       if (buffer->idx == buffer->len && !mark_set)
         return false;
 
       return entry->data.markIndex != 0xFFFF || entry->data.currentIndex != 0xFFFF;
     }
-    inline bool transition (StateTableDriver<EntryData> *driver,
-			    const Entry<EntryData> *entry)
+    bool transition (StateTableDriver<Types, EntryData> *driver,
+		     const Entry<EntryData> *entry)
     {
       hb_buffer_t *buffer = driver->buffer;
 
       /* Looks like CoreText applies neither mark nor current substitution for
        * end-of-text if mark was not explicitly set. */
       if (buffer->idx == buffer->len && !mark_set)
         return true;
 
-      if (entry->data.markIndex != 0xFFFF)
+      const GlyphID *replacement;
+
+      replacement = nullptr;
+      if (Types::extended)
       {
-	const Lookup<GlyphID> &lookup = subs[entry->data.markIndex];
-	hb_glyph_info_t *info = buffer->info;
-	const GlyphID *replacement = lookup.get_value (info[mark].codepoint, driver->num_glyphs);
-	if (replacement)
+	if (entry->data.markIndex != 0xFFFF)
 	{
-	  buffer->unsafe_to_break (mark, MIN (buffer->idx + 1, buffer->len));
-	  info[mark].codepoint = *replacement;
-	  ret = true;
+	  const Lookup<GlyphID> &lookup = subs[entry->data.markIndex];
+	  replacement = lookup.get_value (buffer->info[mark].codepoint, driver->num_glyphs);
 	}
       }
-      if (entry->data.currentIndex != 0xFFFF)
+      else
+      {
+	unsigned int offset = entry->data.markIndex + buffer->info[mark].codepoint;
+	const UnsizedArrayOf<GlyphID> &subs_old = (const UnsizedArrayOf<GlyphID> &) subs;
+	replacement = &subs_old[Types::wordOffsetToIndex (offset, table, subs_old.arrayZ)];
+	if (!replacement->sanitize (&c->sanitizer) || !*replacement)
+	  replacement = nullptr;
+      }
+      if (replacement)
+      {
+	buffer->unsafe_to_break (mark, MIN (buffer->idx + 1, buffer->len));
+	buffer->info[mark].codepoint = *replacement;
+	ret = true;
+      }
+
+      replacement = nullptr;
+      unsigned int idx = MIN (buffer->idx, buffer->len - 1);
+      if (Types::extended)
       {
-        unsigned int idx = MIN (buffer->idx, buffer->len - 1);
-	const Lookup<GlyphID> &lookup = subs[entry->data.currentIndex];
-	hb_glyph_info_t *info = buffer->info;
-	const GlyphID *replacement = lookup.get_value (info[idx].codepoint, driver->num_glyphs);
-	if (replacement)
+	if (entry->data.currentIndex != 0xFFFF)
 	{
-	  info[idx].codepoint = *replacement;
-	  ret = true;
+	  const Lookup<GlyphID> &lookup = subs[entry->data.currentIndex];
+	  replacement = lookup.get_value (buffer->info[idx].codepoint, driver->num_glyphs);
 	}
       }
+      else
+      {
+	unsigned int offset = entry->data.currentIndex + buffer->info[idx].codepoint;
+	const UnsizedArrayOf<GlyphID> &subs_old = (const UnsizedArrayOf<GlyphID> &) subs;
+	replacement = &subs_old[Types::wordOffsetToIndex (offset, table, subs_old.arrayZ)];
+	if (!replacement->sanitize (&c->sanitizer) || !*replacement)
+	  replacement = nullptr;
+      }
+      if (replacement)
+      {
+	buffer->info[idx].codepoint = *replacement;
+	ret = true;
+      }
 
       if (entry->flags & SetMark)
       {
 	mark_set = true;
 	mark = buffer->idx;
       }
 
       return true;
     }
 
     public:
     bool ret;
     private:
+    hb_aat_apply_context_t *c;
     bool mark_set;
     unsigned int mark;
-    const UnsizedOffsetListOf<Lookup<GlyphID>, HBUINT32> &subs;
+    const ContextualSubtable *table;
+    const UnsizedOffsetListOf<Lookup<GlyphID>, HBUINT, false> &subs;
   };
 
-  inline bool apply (hb_aat_apply_context_t *c) const
+  bool apply (hb_aat_apply_context_t *c) const
   {
     TRACE_APPLY (this);
 
-    driver_context_t dc (this);
+    driver_context_t dc (this, c);
 
-    StateTableDriver<EntryData> driver (machine, c->buffer, c->face);
+    StateTableDriver<Types, EntryData> driver (machine, c->buffer, c->face);
     driver.drive (&dc);
 
     return_trace (dc.ret);
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
 
     unsigned int num_entries = 0;
     if (unlikely (!machine.sanitize (c, &num_entries))) return_trace (false);
 
+    if (!Types::extended) return_trace (true);
+
     unsigned int num_lookups = 0;
 
     const Entry<EntryData> *entries = machine.get_entries ();
     for (unsigned int i = 0; i < num_entries; i++)
     {
       const EntryData &data = entries[i].data;
 
       if (data.markIndex != 0xFFFF)
@@ -302,191 +344,271 @@ struct ContextualSubtable
       if (data.currentIndex != 0xFFFF)
 	num_lookups = MAX<unsigned int> (num_lookups, 1 + data.currentIndex);
     }
 
     return_trace (substitutionTables.sanitize (c, this, num_lookups));
   }
 
   protected:
-  StateTable<EntryData>
+  StateTable<Types, EntryData>
 		machine;
-  LOffsetTo<UnsizedOffsetListOf<Lookup<GlyphID>, HBUINT32> >
+  OffsetTo<UnsizedOffsetListOf<Lookup<GlyphID>, HBUINT, false>, HBUINT, false>
 		substitutionTables;
   public:
   DEFINE_SIZE_STATIC (20);
 };
 
-struct LigatureSubtable
+
+template <bool extended>
+struct LigatureEntry;
+
+template <>
+struct LigatureEntry<true>
 {
+  enum Flags
+  {
+    SetComponent	= 0x8000,	/* Push this glyph onto the component stack for
+					 * eventual processing. */
+    DontAdvance		= 0x4000,	/* Leave the glyph pointer at this glyph for the
+					   next iteration. */
+    PerformAction	= 0x2000,	/* Use the ligActionIndex to process a ligature
+					 * group. */
+    Reserved		= 0x1FFF,	/* These bits are reserved and should be set to 0. */
+  };
+
   struct EntryData
   {
     HBUINT16	ligActionIndex;	/* Index to the first ligActionTable entry
 				 * for processing this group, if indicated
 				 * by the flags. */
     public:
     DEFINE_SIZE_STATIC (2);
   };
 
+  static bool performAction (const Entry<EntryData> *entry)
+  { return entry->flags & PerformAction; }
+
+  static unsigned int ligActionIndex (const Entry<EntryData> *entry)
+  { return entry->data.ligActionIndex; }
+};
+template <>
+struct LigatureEntry<false>
+{
+  enum Flags
+  {
+    SetComponent	= 0x8000,	/* Push this glyph onto the component stack for
+					 * eventual processing. */
+    DontAdvance		= 0x4000,	/* Leave the glyph pointer at this glyph for the
+					   next iteration. */
+    Offset		= 0x3FFF,	/* Byte offset from beginning of subtable to the
+					 * ligature action list. This value must be a
+					 * multiple of 4. */
+  };
+
+  typedef void EntryData;
+
+  static bool performAction (const Entry<EntryData> *entry)
+  { return entry->flags & Offset; }
+
+  static unsigned int ligActionIndex (const Entry<EntryData> *entry)
+  { return entry->flags & Offset; }
+};
+
+
+template <typename Types>
+struct LigatureSubtable
+{
+  typedef typename Types::HBUINT HBUINT;
+
+  typedef LigatureEntry<Types::extended> LigatureEntryT;
+  typedef typename LigatureEntryT::EntryData EntryData;
+
   struct driver_context_t
   {
-    static const bool in_place = false;
-    enum Flags {
-      SetComponent	= 0x8000,	/* Push this glyph onto the component stack for
-					 * eventual processing. */
-      DontAdvance	= 0x4000,	/* Leave the glyph pointer at this glyph for the
-					   next iteration. */
-      PerformAction	= 0x2000,	/* Use the ligActionIndex to process a ligature
-					 * group. */
-      Reserved		= 0x1FFF,	/* These bits are reserved and should be set to 0. */
+    enum { in_place = false };
+    enum
+    {
+      DontAdvance	= LigatureEntryT::DontAdvance,
     };
-    enum LigActionFlags {
+    enum LigActionFlags
+    {
       LigActionLast	= 0x80000000,	/* This is the last action in the list. This also
 					 * implies storage. */
       LigActionStore	= 0x40000000,	/* Store the ligature at the current cumulated index
 					 * in the ligature table in place of the marked
 					 * (i.e. currently-popped) glyph. */
       LigActionOffset	= 0x3FFFFFFF,	/* A 30-bit value which is sign-extended to 32-bits
 					 * and added to the glyph ID, resulting in an index
 					 * into the component table. */
     };
 
-    inline driver_context_t (const LigatureSubtable *table,
-			     hb_aat_apply_context_t *c_) :
+    driver_context_t (const LigatureSubtable *table_,
+		      hb_aat_apply_context_t *c_) :
 	ret (false),
 	c (c_),
+	table (table_),
 	ligAction (table+table->ligAction),
 	component (table+table->component),
 	ligature (table+table->ligature),
 	match_length (0) {}
 
-    inline bool is_actionable (StateTableDriver<EntryData> *driver,
-			       const Entry<EntryData> *entry)
+    bool is_actionable (StateTableDriver<Types, EntryData> *driver HB_UNUSED,
+			const Entry<EntryData> *entry)
     {
-      return !!(entry->flags & PerformAction);
+      return LigatureEntryT::performAction (entry);
     }
-    inline bool transition (StateTableDriver<EntryData> *driver,
-			    const Entry<EntryData> *entry)
+    bool transition (StateTableDriver<Types, EntryData> *driver,
+		     const Entry<EntryData> *entry)
     {
       hb_buffer_t *buffer = driver->buffer;
-      unsigned int flags = entry->flags;
 
-      if (flags & SetComponent)
+      DEBUG_MSG (APPLY, nullptr, "Ligature transition at %u", buffer->idx);
+      if (entry->flags & LigatureEntryT::SetComponent)
       {
         if (unlikely (match_length >= ARRAY_LENGTH (match_positions)))
 	  return false;
 
 	/* Never mark same index twice, in case DontAdvance was used... */
 	if (match_length && match_positions[match_length - 1] == buffer->out_len)
 	  match_length--;
 
 	match_positions[match_length++] = buffer->out_len;
+	DEBUG_MSG (APPLY, nullptr, "Set component at %u", buffer->out_len);
       }
 
-      if (flags & PerformAction)
+      if (LigatureEntryT::performAction (entry))
       {
+	DEBUG_MSG (APPLY, nullptr, "Perform action with %u", match_length);
 	unsigned int end = buffer->out_len;
-	unsigned int action_idx = entry->data.ligActionIndex;
+
+	if (unlikely (!match_length))
+	  return true;
+
+	if (buffer->idx >= buffer->len)
+	  return false; // TODO Work on previous instead?
+
+	unsigned int cursor = match_length;
+
+	unsigned int action_idx = LigatureEntryT::ligActionIndex (entry);
+	action_idx = Types::offsetToIndex (action_idx, table, ligAction.arrayZ);
+	const HBUINT32 *actionData = &ligAction[action_idx];
+
+	unsigned int ligature_idx = 0;
 	unsigned int action;
-	unsigned int ligature_idx = 0;
         do
 	{
-	  if (unlikely (!match_length))
-	    return false;
+	  if (unlikely (!cursor))
+	  {
+	    /* Stack underflow.  Clear the stack. */
+	    DEBUG_MSG (APPLY, nullptr, "Stack underflow");
+	    match_length = 0;
+	    break;
+	  }
 
-	  buffer->move_to (match_positions[--match_length]);
+	  DEBUG_MSG (APPLY, nullptr, "Moving to stack position %u", cursor - 1);
+	  buffer->move_to (match_positions[--cursor]);
 
-	  const HBUINT32 &actionData = ligAction[action_idx];
-	  if (unlikely (!actionData.sanitize (&c->sanitizer))) return false;
-	  action = actionData;
+	  if (unlikely (!actionData->sanitize (&c->sanitizer))) return false;
+	  action = *actionData;
 
 	  uint32_t uoffset = action & LigActionOffset;
 	  if (uoffset & 0x20000000)
-	    uoffset += 0xC0000000;
+	    uoffset |= 0xC0000000; /* Sign-extend. */
 	  int32_t offset = (int32_t) uoffset;
 	  unsigned int component_idx = buffer->cur().codepoint + offset;
-
+	  component_idx = Types::wordOffsetToIndex (component_idx, table, component.arrayZ);
 	  const HBUINT16 &componentData = component[component_idx];
 	  if (unlikely (!componentData.sanitize (&c->sanitizer))) return false;
 	  ligature_idx += componentData;
 
+	  DEBUG_MSG (APPLY, nullptr, "Action store %u last %u",
+		     bool (action & LigActionStore),
+		     bool (action & LigActionLast));
 	  if (action & (LigActionStore | LigActionLast))
 	  {
+	    ligature_idx = Types::offsetToIndex (ligature_idx, table, ligature.arrayZ);
 	    const GlyphID &ligatureData = ligature[ligature_idx];
 	    if (unlikely (!ligatureData.sanitize (&c->sanitizer))) return false;
 	    hb_codepoint_t lig = ligatureData;
 
-	    match_positions[match_length++] = buffer->out_len;
+	    DEBUG_MSG (APPLY, nullptr, "Produced ligature %u", lig);
 	    buffer->replace_glyph (lig);
 
-	    //ligature_idx = 0; // XXX Yes or no?
+	    unsigned int lig_end = match_positions[match_length - 1] + 1;
+	    /* Now go and delete all subsequent components. */
+	    while (match_length - 1 > cursor)
+	    {
+	      DEBUG_MSG (APPLY, nullptr, "Skipping ligature component");
+	      buffer->move_to (match_positions[--match_length]);
+	      buffer->replace_glyph (DELETED_GLYPH);
+	    }
+
+	    buffer->move_to (lig_end);
+	    buffer->merge_out_clusters (match_positions[cursor], buffer->out_len);
 	  }
-	  else
-	  {
-	    buffer->skip_glyph ();
-	    end--;
-	  }
-	  /* TODO merge_clusters / unsafe_to_break */
 
-	  action_idx++;
+	  actionData++;
 	}
 	while (!(action & LigActionLast));
 	buffer->move_to (end);
       }
 
       return true;
     }
 
     public:
     bool ret;
     private:
     hb_aat_apply_context_t *c;
+    const LigatureSubtable *table;
     const UnsizedArrayOf<HBUINT32> &ligAction;
     const UnsizedArrayOf<HBUINT16> &component;
     const UnsizedArrayOf<GlyphID> &ligature;
     unsigned int match_length;
     unsigned int match_positions[HB_MAX_CONTEXT_LENGTH];
   };
 
-  inline bool apply (hb_aat_apply_context_t *c) const
+  bool apply (hb_aat_apply_context_t *c) const
   {
     TRACE_APPLY (this);
 
     driver_context_t dc (this, c);
 
-    StateTableDriver<EntryData> driver (machine, c->buffer, c->face);
+    StateTableDriver<Types, EntryData> driver (machine, c->buffer, c->face);
     driver.drive (&dc);
 
     return_trace (dc.ret);
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     /* The rest of array sanitizations are done at run-time. */
     return_trace (c->check_struct (this) && machine.sanitize (c) &&
 		  ligAction && component && ligature);
   }
 
   protected:
-  StateTable<EntryData>
+  StateTable<Types, EntryData>
 		machine;
-  LOffsetTo<UnsizedArrayOf<HBUINT32> >
+  OffsetTo<UnsizedArrayOf<HBUINT32>, HBUINT, false>
 		ligAction;	/* Offset to the ligature action table. */
-  LOffsetTo<UnsizedArrayOf<HBUINT16> >
+  OffsetTo<UnsizedArrayOf<HBUINT16>, HBUINT, false>
 		component;	/* Offset to the component table. */
-  LOffsetTo<UnsizedArrayOf<GlyphID> >
+  OffsetTo<UnsizedArrayOf<GlyphID>, HBUINT, false>
 		ligature;	/* Offset to the actual ligature lists. */
   public:
   DEFINE_SIZE_STATIC (28);
 };
 
+template <typename Types>
 struct NoncontextualSubtable
 {
-  inline bool apply (hb_aat_apply_context_t *c) const
+  bool apply (hb_aat_apply_context_t *c) const
   {
     TRACE_APPLY (this);
 
     bool ret = false;
     unsigned int num_glyphs = c->face->get_num_glyphs ();
 
     hb_glyph_info_t *info = c->buffer->info;
     unsigned int count = c->buffer->len;
@@ -498,49 +620,234 @@ struct NoncontextualSubtable
 	info[i].codepoint = *replacement;
 	ret = true;
       }
     }
 
     return_trace (ret);
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (substitute.sanitize (c));
   }
 
   protected:
   Lookup<GlyphID>	substitute;
   public:
   DEFINE_SIZE_MIN (2);
 };
 
+template <typename Types>
 struct InsertionSubtable
 {
-  inline bool apply (hb_aat_apply_context_t *c) const
+  typedef typename Types::HBUINT HBUINT;
+
+  struct EntryData
+  {
+    HBUINT16	currentInsertIndex;	/* Zero-based index into the insertion glyph table.
+					 * The number of glyphs to be inserted is contained
+					 * in the currentInsertCount field in the flags.
+					 * A value of 0xFFFF indicates no insertion is to
+					 * be done. */
+    HBUINT16	markedInsertIndex;	/* Zero-based index into the insertion glyph table.
+					 * The number of glyphs to be inserted is contained
+					 * in the markedInsertCount field in the flags.
+					 * A value of 0xFFFF indicates no insertion is to
+					 * be done. */
+    public:
+    DEFINE_SIZE_STATIC (4);
+  };
+
+  struct driver_context_t
+  {
+    enum { in_place = false };
+    enum Flags
+    {
+      SetMark		= 0x8000,	/* If set, mark the current glyph. */
+      DontAdvance	= 0x4000,	/* If set, don't advance to the next glyph before
+					 * going to the new state.  This does not mean
+					 * that the glyph pointed to is the same one as
+					 * before. If you've made insertions immediately
+					 * downstream of the current glyph, the next glyph
+					 * processed would in fact be the first one
+					 * inserted. */
+      CurrentIsKashidaLike= 0x2000,	/* If set, and the currentInsertList is nonzero,
+					 * then the specified glyph list will be inserted
+					 * as a kashida-like insertion, either before or
+					 * after the current glyph (depending on the state
+					 * of the currentInsertBefore flag). If clear, and
+					 * the currentInsertList is nonzero, then the
+					 * specified glyph list will be inserted as a
+					 * split-vowel-like insertion, either before or
+					 * after the current glyph (depending on the state
+					 * of the currentInsertBefore flag). */
+      MarkedIsKashidaLike= 0x1000,	/* If set, and the markedInsertList is nonzero,
+					 * then the specified glyph list will be inserted
+					 * as a kashida-like insertion, either before or
+					 * after the marked glyph (depending on the state
+					 * of the markedInsertBefore flag). If clear, and
+					 * the markedInsertList is nonzero, then the
+					 * specified glyph list will be inserted as a
+					 * split-vowel-like insertion, either before or
+					 * after the marked glyph (depending on the state
+					 * of the markedInsertBefore flag). */
+      CurrentInsertBefore= 0x0800,	/* If set, specifies that insertions are to be made
+					 * to the left of the current glyph. If clear,
+					 * they're made to the right of the current glyph. */
+      MarkedInsertBefore= 0x0400,	/* If set, specifies that insertions are to be
+					 * made to the left of the marked glyph. If clear,
+					 * they're made to the right of the marked glyph. */
+      CurrentInsertCount= 0x3E0,	/* This 5-bit field is treated as a count of the
+					 * number of glyphs to insert at the current
+					 * position. Since zero means no insertions, the
+					 * largest number of insertions at any given
+					 * current location is 31 glyphs. */
+      MarkedInsertCount= 0x001F,	/* This 5-bit field is treated as a count of the
+					 * number of glyphs to insert at the marked
+					 * position. Since zero means no insertions, the
+					 * largest number of insertions at any given
+					 * marked location is 31 glyphs. */
+    };
+
+    driver_context_t (const InsertionSubtable *table,
+		      hb_aat_apply_context_t *c_) :
+	ret (false),
+	c (c_),
+	mark_set (false),
+	mark (0),
+	insertionAction (table+table->insertionAction) {}
+
+    bool is_actionable (StateTableDriver<Types, EntryData> *driver HB_UNUSED,
+			const Entry<EntryData> *entry)
+    {
+      return (entry->flags & (CurrentInsertCount | MarkedInsertCount)) &&
+	     (entry->data.currentInsertIndex != 0xFFFF ||entry->data.markedInsertIndex != 0xFFFF);
+    }
+    bool transition (StateTableDriver<Types, EntryData> *driver,
+		     const Entry<EntryData> *entry)
+    {
+      hb_buffer_t *buffer = driver->buffer;
+      unsigned int flags = entry->flags;
+
+      if (entry->data.markedInsertIndex != 0xFFFF && mark_set)
+      {
+	unsigned int count = (flags & MarkedInsertCount);
+	unsigned int start = entry->data.markedInsertIndex;
+	const GlyphID *glyphs = &insertionAction[start];
+	if (unlikely (!c->sanitizer.check_array (glyphs, count))) return false;
+
+	bool before = flags & MarkedInsertBefore;
+
+	unsigned int end = buffer->out_len;
+	buffer->move_to (mark);
+
+	if (buffer->idx < buffer->len && !before)
+	  buffer->copy_glyph ();
+	/* TODO We ignore KashidaLike setting. */
+	for (unsigned int i = 0; i < count; i++)
+	  buffer->output_glyph (glyphs[i]);
+	if (buffer->idx < buffer->len && !before)
+	  buffer->skip_glyph ();
+
+	buffer->move_to (end + count);
+
+	buffer->unsafe_to_break_from_outbuffer (mark, MIN (buffer->idx + 1, buffer->len));
+      }
+
+      if (entry->data.currentInsertIndex != 0xFFFF)
+      {
+	unsigned int count = (flags & CurrentInsertCount) >> 5;
+	unsigned int start = entry->data.currentInsertIndex;
+	const GlyphID *glyphs = &insertionAction[start];
+	if (unlikely (!c->sanitizer.check_array (glyphs, count))) return false;
+
+	bool before = flags & CurrentInsertBefore;
+
+	unsigned int end = buffer->out_len;
+
+	if (buffer->idx < buffer->len && !before)
+	  buffer->copy_glyph ();
+	/* TODO We ignore KashidaLike setting. */
+	for (unsigned int i = 0; i < count; i++)
+	  buffer->output_glyph (glyphs[i]);
+	if (buffer->idx < buffer->len && !before)
+	  buffer->skip_glyph ();
+
+	/* Humm. Not sure where to move to.  There's this wording under
+	 * DontAdvance flag:
+	 *
+	 * "If set, don't update the glyph index before going to the new state.
+	 * This does not mean that the glyph pointed to is the same one as
+	 * before. If you've made insertions immediately downstream of the
+	 * current glyph, the next glyph processed would in fact be the first
+	 * one inserted."
+	 *
+	 * This suggests that if DontAdvance is NOT set, we should move to
+	 * end+count.  If it *was*, then move to end, such that newly inserted
+	 * glyphs are now visible.
+	 *
+	 * https://github.com/harfbuzz/harfbuzz/issues/1224#issuecomment-427691417
+	 */
+	buffer->move_to ((flags & DontAdvance) ? end : end + count);
+      }
+
+      if (flags & SetMark)
+      {
+	mark_set = true;
+	mark = buffer->out_len;
+      }
+
+      return true;
+    }
+
+    public:
+    bool ret;
+    private:
+    hb_aat_apply_context_t *c;
+    bool mark_set;
+    unsigned int mark;
+    const UnsizedArrayOf<GlyphID> &insertionAction;
+  };
+
+  bool apply (hb_aat_apply_context_t *c) const
   {
     TRACE_APPLY (this);
-    /* TODO */
-    return_trace (false);
+
+    driver_context_t dc (this, c);
+
+    StateTableDriver<Types, EntryData> driver (machine, c->buffer, c->face);
+    driver.drive (&dc);
+
+    return_trace (dc.ret);
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
-    /* TODO */
-    return_trace (true);
+    /* The rest of array sanitizations are done at run-time. */
+    return_trace (c->check_struct (this) && machine.sanitize (c) &&
+		  insertionAction);
   }
+
+  protected:
+  StateTable<Types, EntryData>
+		machine;
+  OffsetTo<UnsizedArrayOf<GlyphID>, HBUINT, false>
+		insertionAction;	/* Byte offset from stateHeader to the start of
+					 * the insertion glyph table. */
+  public:
+  DEFINE_SIZE_STATIC (20);
 };
 
 
 struct Feature
 {
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this));
   }
 
   public:
   HBUINT16	featureType;	/* The type of feature. */
   HBUINT16	featureSetting;	/* The feature's setting (aka selector). */
@@ -548,188 +855,318 @@ struct Feature
 				 * and setting enables. */
   HBUINT32	disableFlags;	/* Complement of flags for the settings that this
 				 * feature and setting disable. */
 
   public:
   DEFINE_SIZE_STATIC (12);
 };
 
-
+template <typename Types>
 struct ChainSubtable
 {
+  typedef typename Types::HBUINT HBUINT;
+
+  template <typename T>
   friend struct Chain;
 
-  inline unsigned int get_size (void) const { return length; }
-  inline unsigned int get_type (void) const { return coverage & 0xFF; }
+  unsigned int get_size () const     { return length; }
+  unsigned int get_type () const     { return coverage & 0xFF; }
+  unsigned int get_coverage () const { return coverage >> (sizeof (HBUINT) * 8 - 8); }
 
-  enum Type {
+  enum Coverage
+  {
+    Vertical		= 0x80,	/* If set, this subtable will only be applied
+				 * to vertical text. If clear, this subtable
+				 * will only be applied to horizontal text. */
+    Backwards		= 0x40,	/* If set, this subtable will process glyphs
+				 * in descending order. If clear, it will
+				 * process the glyphs in ascending order. */
+    AllDirections	= 0x20,	/* If set, this subtable will be applied to
+				 * both horizontal and vertical text (i.e.
+				 * the state of bit 0x80000000 is ignored). */
+    Logical		= 0x10,	/* If set, this subtable will process glyphs
+				 * in logical order (or reverse logical order,
+				 * depending on the value of bit 0x80000000). */
+  };
+  enum Type
+  {
     Rearrangement	= 0,
     Contextual		= 1,
     Ligature		= 2,
     Noncontextual	= 4,
     Insertion		= 5
   };
 
-  inline void apply (hb_aat_apply_context_t *c) const
-  {
-    dispatch (c);
-  }
-
   template <typename context_t>
-  inline typename context_t::return_t dispatch (context_t *c) const
+  typename context_t::return_t dispatch (context_t *c) const
   {
     unsigned int subtable_type = get_type ();
     TRACE_DISPATCH (this, subtable_type);
     switch (subtable_type) {
     case Rearrangement:		return_trace (c->dispatch (u.rearrangement));
     case Contextual:		return_trace (c->dispatch (u.contextual));
     case Ligature:		return_trace (c->dispatch (u.ligature));
     case Noncontextual:		return_trace (c->dispatch (u.noncontextual));
     case Insertion:		return_trace (c->dispatch (u.insertion));
     default:			return_trace (c->default_return_value ());
     }
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  bool apply (hb_aat_apply_context_t *c) const
+  {
+    TRACE_APPLY (this);
+    hb_sanitize_with_object_t with (&c->sanitizer, this);
+    return_trace (dispatch (c));
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    if (!length.sanitize (c) ||
+	length <= min_size ||
+	!c->check_range (this, length))
+      return_trace (false);
+
+    hb_sanitize_with_object_t with (c, this);
+    return_trace (dispatch (c));
+  }
+
+  protected:
+  HBUINT	length;		/* Total subtable length, including this header. */
+  HBUINT	coverage;	/* Coverage flags and subtable type. */
+  HBUINT32	subFeatureFlags;/* The 32-bit mask identifying which subtable this is. */
+  union {
+  RearrangementSubtable<Types>	rearrangement;
+  ContextualSubtable<Types>	contextual;
+  LigatureSubtable<Types>	ligature;
+  NoncontextualSubtable<Types>	noncontextual;
+  InsertionSubtable<Types>	insertion;
+  } u;
+  public:
+  DEFINE_SIZE_MIN (2 * sizeof (HBUINT) + 4);
+};
+
+template <typename Types>
+struct Chain
+{
+  typedef typename Types::HBUINT HBUINT;
+
+  hb_mask_t compile_flags (const hb_aat_map_builder_t *map) const
+  {
+    hb_mask_t flags = defaultFlags;
+    {
+      unsigned int count = featureCount;
+      for (unsigned i = 0; i < count; i++)
+      {
+	const Feature &feature = featureZ[i];
+	hb_aat_layout_feature_type_t type = (hb_aat_layout_feature_type_t) (unsigned int) feature.featureType;
+	hb_aat_layout_feature_selector_t setting = (hb_aat_layout_feature_selector_t) (unsigned int) feature.featureSetting;
+      retry:
+	const hb_aat_map_builder_t::feature_info_t *info = map->features.bsearch ((uint16_t) type);
+	if (info && info->setting == setting)
+	{
+	  flags &= feature.disableFlags;
+	  flags |= feature.enableFlags;
+	}
+	else if (type == HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE && setting == HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS)
+	{
+	  /* Deprecated. https://github.com/harfbuzz/harfbuzz/issues/1342 */
+	  type = HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE;
+	  setting = HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS;
+	  goto retry;
+	}
+      }
+    }
+    return flags;
+  }
+
+  void apply (hb_aat_apply_context_t *c,
+		     hb_mask_t flags) const
+  {
+    const ChainSubtable<Types> *subtable = &StructAfter<ChainSubtable<Types> > (featureZ.as_array (featureCount));
+    unsigned int count = subtableCount;
+    for (unsigned int i = 0; i < count; i++)
+    {
+      bool reverse;
+
+      if (!(subtable->subFeatureFlags & flags))
+        goto skip;
+
+      if (!(subtable->get_coverage() & ChainSubtable<Types>::AllDirections) &&
+	  HB_DIRECTION_IS_VERTICAL (c->buffer->props.direction) !=
+	  bool (subtable->get_coverage() & ChainSubtable<Types>::Vertical))
+        goto skip;
+
+      /* Buffer contents is always in logical direction.  Determine if
+       * we need to reverse before applying this subtable.  We reverse
+       * back after if we did reverse indeed.
+       *
+       * Quoting the spac:
+       * """
+       * Bits 28 and 30 of the coverage field control the order in which
+       * glyphs are processed when the subtable is run by the layout engine.
+       * Bit 28 is used to indicate if the glyph processing direction is
+       * the same as logical order or layout order. Bit 30 is used to
+       * indicate whether glyphs are processed forwards or backwards within
+       * that order.
+
+		Bit 30	Bit 28	Interpretation for Horizontal Text
+		0	0	The subtable is processed in layout order
+				(the same order as the glyphs, which is
+				always left-to-right).
+		1	0	The subtable is processed in reverse layout order
+				(the order opposite that of the glyphs, which is
+				always right-to-left).
+		0	1	The subtable is processed in logical order
+				(the same order as the characters, which may be
+				left-to-right or right-to-left).
+		1	1	The subtable is processed in reverse logical order
+				(the order opposite that of the characters, which
+				may be right-to-left or left-to-right).
+       */
+      reverse = subtable->get_coverage () & ChainSubtable<Types>::Logical ?
+		bool (subtable->get_coverage () & ChainSubtable<Types>::Backwards) :
+		bool (subtable->get_coverage () & ChainSubtable<Types>::Backwards) !=
+		HB_DIRECTION_IS_BACKWARD (c->buffer->props.direction);
+
+      if (!c->buffer->message (c->font, "start chain subtable %d", c->lookup_index))
+        goto skip;
+
+      if (reverse)
+        c->buffer->reverse ();
+
+      subtable->apply (c);
+
+      if (reverse)
+        c->buffer->reverse ();
+
+      (void) c->buffer->message (c->font, "end chain subtable %d", c->lookup_index);
+
+      if (unlikely (!c->buffer->successful)) return;
+
+    skip:
+      subtable = &StructAfter<ChainSubtable<Types> > (*subtable);
+      c->set_lookup_index (c->lookup_index + 1);
+    }
+  }
+
+  unsigned int get_size () const { return length; }
+
+  bool sanitize (hb_sanitize_context_t *c, unsigned int version HB_UNUSED) const
   {
     TRACE_SANITIZE (this);
     if (!length.sanitize (c) ||
 	length < min_size ||
 	!c->check_range (this, length))
       return_trace (false);
 
-    return_trace (dispatch (c));
-  }
-
-  protected:
-  HBUINT32	length;		/* Total subtable length, including this header. */
-  HBUINT32	coverage;	/* Coverage flags and subtable type. */
-  HBUINT32	subFeatureFlags;/* The 32-bit mask identifying which subtable this is. */
-  union {
-  RearrangementSubtable		rearrangement;
-  ContextualSubtable		contextual;
-  LigatureSubtable		ligature;
-  NoncontextualSubtable		noncontextual;
-  InsertionSubtable		insertion;
-  } u;
-  public:
-  DEFINE_SIZE_MIN (12);
-};
-
-struct Chain
-{
-  inline void apply (hb_aat_apply_context_t *c) const
-  {
-    const ChainSubtable *subtable = &StructAtOffset<ChainSubtable> (featureZ, featureZ[0].static_size * featureCount);
-    unsigned int count = subtableCount;
-    for (unsigned int i = 0; i < count; i++)
-    {
-      if (!c->buffer->message (c->font, "start chain subtable %d", c->lookup_index))
-      {
-	c->set_lookup_index (c->lookup_index + 1);
-	continue;
-      }
-
-      subtable->apply (c);
-      subtable = &StructAfter<ChainSubtable> (*subtable);
-
-      (void) c->buffer->message (c->font, "end chain subtable %d", c->lookup_index);
-
-      c->set_lookup_index (c->lookup_index + 1);
-    }
-  }
-
-  inline unsigned int get_size (void) const { return length; }
-
-  inline bool sanitize (hb_sanitize_context_t *c, unsigned int major) const
-  {
-    TRACE_SANITIZE (this);
-    if (!length.sanitize (c) ||
-	length < min_size ||
-	!c->check_range (this, length))
+    if (!c->check_array (featureZ.arrayZ, featureCount))
       return_trace (false);
 
-    if (!c->check_array (featureZ, featureZ[0].static_size, featureCount))
-      return_trace (false);
-
-    const ChainSubtable *subtable = &StructAtOffset<ChainSubtable> (featureZ, featureZ[0].static_size * featureCount);
+    const ChainSubtable<Types> *subtable = &StructAfter<ChainSubtable<Types> > (featureZ.as_array (featureCount));
     unsigned int count = subtableCount;
     for (unsigned int i = 0; i < count; i++)
     {
       if (!subtable->sanitize (c))
 	return_trace (false);
-      subtable = &StructAfter<ChainSubtable> (*subtable);
+      subtable = &StructAfter<ChainSubtable<Types> > (*subtable);
     }
 
     return_trace (true);
   }
 
   protected:
   HBUINT32	defaultFlags;	/* The default specification for subtables. */
   HBUINT32	length;		/* Total byte count, including this header. */
-  HBUINT32	featureCount;	/* Number of feature subtable entries. */
-  HBUINT32	subtableCount;	/* The number of subtables in the chain. */
+  HBUINT	featureCount;	/* Number of feature subtable entries. */
+  HBUINT	subtableCount;	/* The number of subtables in the chain. */
 
-  Feature	featureZ[VAR];	/* Features. */
-/*ChainSubtable	subtableX[VAR];*//* Subtables. */
-/*subtableGlyphCoverageArray*/	/* Only if major == 3. */
+  UnsizedArrayOf<Feature>	featureZ;	/* Features. */
+/*ChainSubtable	firstSubtable;*//* Subtables. */
+/*subtableGlyphCoverageArray*/	/* Only if version >= 3. We don't use. */
 
   public:
-  DEFINE_SIZE_MIN (16);
+  DEFINE_SIZE_MIN (8 + 2 * sizeof (HBUINT));
 };
 
 
 /*
- * The 'mort'/'morx' Tables
+ * The 'mort'/'morx' Table
  */
 
-struct morx
+template <typename Types>
+struct mortmorx
 {
-  static const hb_tag_t tableTag = HB_AAT_TAG_morx;
+  enum { tableTag = HB_AAT_TAG_morx };
 
-  inline void apply (hb_aat_apply_context_t *c) const
+  bool has_data () const { return version != 0; }
+
+  void compile_flags (const hb_aat_map_builder_t *mapper,
+		      hb_aat_map_t *map) const
   {
-    c->set_lookup_index (0);
-    const Chain *chain = chainsZ;
+    const Chain<Types> *chain = &firstChain;
     unsigned int count = chainCount;
     for (unsigned int i = 0; i < count; i++)
     {
-      chain->apply (c);
-      chain = &StructAfter<Chain> (*chain);
+      map->chain_flags.push (chain->compile_flags (mapper));
+      chain = &StructAfter<Chain<Types> > (*chain);
     }
   }
 
-  inline bool sanitize (hb_sanitize_context_t *c) const
+  void apply (hb_aat_apply_context_t *c) const
   {
-    TRACE_SANITIZE (this);
-    if (!version.sanitize (c) ||
-	(version.major >> (sizeof (HBUINT32) == 4 ? 1 : 0)) != 1 ||
-	!chainCount.sanitize (c))
-      return_trace (false);
-
-    const Chain *chain = chainsZ;
+    if (unlikely (!c->buffer->successful)) return;
+    c->set_lookup_index (0);
+    const Chain<Types> *chain = &firstChain;
     unsigned int count = chainCount;
     for (unsigned int i = 0; i < count; i++)
     {
-      if (!chain->sanitize (c, version.major))
+      chain->apply (c, c->plan->aat_map.chain_flags[i]);
+      if (unlikely (!c->buffer->successful)) return;
+      chain = &StructAfter<Chain<Types> > (*chain);
+    }
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    if (!version.sanitize (c) || !version || !chainCount.sanitize (c))
+      return_trace (false);
+
+    const Chain<Types> *chain = &firstChain;
+    unsigned int count = chainCount;
+    for (unsigned int i = 0; i < count; i++)
+    {
+      if (!chain->sanitize (c, version))
 	return_trace (false);
-      chain = &StructAfter<Chain> (*chain);
+      chain = &StructAfter<Chain<Types> > (*chain);
     }
 
     return_trace (true);
   }
 
   protected:
-  FixedVersion<>version;	/* Version number of the glyph metamorphosis table.
-				 * 1 for mort, 2 or 3 for morx. */
+  HBUINT16	version;	/* Version number of the glyph metamorphosis table.
+				 * 1, 2, or 3. */
+  HBUINT16	unused;		/* Set to 0. */
   HBUINT32	chainCount;	/* Number of metamorphosis chains contained in this
 				 * table. */
-  Chain		chainsZ[VAR];	/* Chains. */
+  Chain<Types>	firstChain;	/* Chains. */
 
   public:
   DEFINE_SIZE_MIN (8);
 };
 
+struct morx : mortmorx<ExtendedTypes>
+{
+  enum { tableTag = HB_AAT_TAG_morx };
+};
+struct mort : mortmorx<ObsoleteTypes>
+{
+  enum { tableTag = HB_AAT_TAG_mort };
+};
+
+
 } /* namespace AAT */
 
 
 #endif /* HB_AAT_LAYOUT_MORX_TABLE_HH */
--- a/gfx/harfbuzz/src/hb-aat-layout-trak-table.hh
+++ b/gfx/harfbuzz/src/hb-aat-layout-trak-table.hh
@@ -41,171 +41,197 @@
 
 namespace AAT {
 
 
 struct TrackTableEntry
 {
   friend struct TrackData;
 
-  inline bool sanitize (hb_sanitize_context_t *c, const void *base,
-			unsigned int size) const
+  float get_track_value () const { return track.to_float (); }
+
+  int get_value (const void *base, unsigned int index,
+		 unsigned int table_size) const
+  { return (base+valuesZ).as_array (table_size)[index]; }
+
+  public:
+  bool sanitize (hb_sanitize_context_t *c, const void *base,
+		 unsigned int table_size) const
   {
     TRACE_SANITIZE (this);
     return_trace (likely (c->check_struct (this) &&
-			  (valuesZ.sanitize (c, base, size))));
-  }
-
-  private:
-  inline float get_track_value () const
-  {
-    return track.to_float ();
-  }
-
-  inline int get_value (const void *base, unsigned int index) const
-  {
-    return (base+valuesZ)[index];
+			  (valuesZ.sanitize (c, base, table_size))));
   }
 
   protected:
   Fixed		track;		/* Track value for this record. */
-  NameID	trackNameID;	/* The 'name' table index for this track */
-  OffsetTo<UnsizedArrayOf<FWORD> >
+  NameID	trackNameID;	/* The 'name' table index for this track.
+				 * (a short word or phrase like "loose"
+				 * or "very tight") */
+  OffsetTo<UnsizedArrayOf<FWORD>, HBUINT16, false>
 		valuesZ;	/* Offset from start of tracking table to
 				 * per-size tracking values for this track. */
 
   public:
   DEFINE_SIZE_STATIC (8);
 };
 
 struct TrackData
 {
-  inline bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  float interpolate_at (unsigned int idx,
+			float target_size,
+			const TrackTableEntry &trackTableEntry,
+			const void *base) const
   {
-    TRACE_SANITIZE (this);
-    return_trace (c->check_struct (this) &&
-		  sizeTable.sanitize (c, base, nSizes) &&
-		  trackTable.sanitize (c, nTracks, base, nSizes));
+    unsigned int sizes = nSizes;
+    hb_array_t<const Fixed> size_table ((base+sizeTable).arrayZ, sizes);
+
+    float s0 = size_table[idx].to_float ();
+    float s1 = size_table[idx + 1].to_float ();
+    float t = unlikely (s0 == s1) ? 0.f : (target_size - s0) / (s1 - s0);
+    return t * trackTableEntry.get_value (base, idx + 1, sizes) +
+	   (1.f - t) * trackTableEntry.get_value (base, idx, sizes);
   }
 
-  inline float get_tracking (const void *base, float ptem) const
+  int get_tracking (const void *base, float ptem) const
   {
     /* CoreText points are CSS pixels (96 per inch),
      * NOT typographic points (72 per inch).
      *
      * https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html
      */
     float csspx = ptem * 96.f / 72.f;
-    Fixed fixed_size;
-    fixed_size.set_float (csspx);
-
-    /* XXX Clean this up. Make it work with nSizes==1 and 0. */
-
-    unsigned int sizes = nSizes;
 
+    /*
+     * Choose track.
+     */
     const TrackTableEntry *trackTableEntry = nullptr;
-    for (unsigned int i = 0; i < sizes; ++i)
-      // For now we only seek for track entries with zero tracking value
+    unsigned int count = nTracks;
+    for (unsigned int i = 0; i < count; i++)
+    {
+      /* Note: Seems like the track entries are sorted by values.  But the
+       * spec doesn't explicitly say that.  It just mentions it in the example. */
+
+      /* For now we only seek for track entries with zero tracking value */
+
       if (trackTable[i].get_track_value () == 0.f)
-        trackTableEntry = &trackTable[0];
-
-    // We couldn't match any, exit
+      {
+	trackTableEntry = &trackTable[i];
+	break;
+      }
+    }
     if (!trackTableEntry) return 0.;
 
-    /* TODO bfind() */
+    /*
+     * Choose size.
+     */
+    unsigned int sizes = nSizes;
+    if (!sizes) return 0.;
+    if (sizes == 1) return trackTableEntry->get_value (base, 0, sizes);
+
+    hb_array_t<const Fixed> size_table ((base+sizeTable).arrayZ, sizes);
     unsigned int size_index;
-    UnsizedArrayOf<Fixed> size_table = base+sizeTable;
-    for (size_index = 0; size_index < sizes; ++size_index)
-      if (size_table[size_index] >= fixed_size)
+    for (size_index = 0; size_index < sizes - 1; size_index++)
+      if (size_table[size_index].to_float () >= csspx)
         break;
 
-    // TODO(ebraminio): We don't attempt to extrapolate to larger or
-    // smaller values for now but we should do, per spec
-    if (size_index == sizes)
-      return trackTableEntry->get_value (base, sizes - 1);
-    if (size_index == 0 || size_table[size_index] == fixed_size)
-      return trackTableEntry->get_value (base, size_index);
+    return round (interpolate_at (size_index ? size_index - 1 : 0, csspx,
+				  *trackTableEntry, base));
+  }
 
-    float s0 = size_table[size_index - 1].to_float ();
-    float s1 = size_table[size_index].to_float ();
-    float t = (csspx - s0) / (s1 - s0);
-    return (float) t * trackTableEntry->get_value (base, size_index) +
-	   ((float) 1.0 - t) * trackTableEntry->get_value (base, size_index - 1);
+  bool sanitize (hb_sanitize_context_t *c, const void *base) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (c->check_struct (this) &&
+			  sizeTable.sanitize (c, base, nSizes) &&
+			  trackTable.sanitize (c, nTracks, base, nSizes)));
   }
 
   protected:
   HBUINT16	nTracks;	/* Number of separate tracks included in this table. */
   HBUINT16	nSizes;		/* Number of point sizes included in this table. */
-  LOffsetTo<UnsizedArrayOf<Fixed> >
-		sizeTable;	/* Offset to array[nSizes] of size values. */
+  LOffsetTo<UnsizedArrayOf<Fixed>, false>
+		sizeTable;	/* Offset from start of the tracking table to
+				 * Array[nSizes] of size values.. */
   UnsizedArrayOf<TrackTableEntry>
 		trackTable;	/* Array[nTracks] of TrackTableEntry records. */
 
   public:
   DEFINE_SIZE_ARRAY (8, trackTable);
 };
 
 struct trak
 {
-  static const hb_tag_t tableTag = HB_AAT_TAG_trak;
-
-  inline bool sanitize (hb_sanitize_context_t *c) const
-  {
-    TRACE_SANITIZE (this);
+  enum { tableTag = HB_AAT_TAG_trak };
 
-    return_trace (unlikely (c->check_struct (this) &&
-			    horizData.sanitize (c, this, this) &&
-			    vertData.sanitize (c, this, this)));
-  }
+  bool has_data () const { return version.to_int (); }
 
-  inline bool apply (hb_aat_apply_context_t *c) const
+  bool apply (hb_aat_apply_context_t *c) const
   {
     TRACE_APPLY (this);
 
+    hb_mask_t trak_mask = c->plan->trak_mask;
+
     const float ptem = c->font->ptem;
     if (unlikely (ptem <= 0.f))
       return_trace (false);
 
     hb_buffer_t *buffer = c->buffer;
     if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction))
     {
       const TrackData &trackData = this+horizData;
-      float tracking = trackData.get_tracking (this, ptem);
-      hb_position_t advance_to_add = c->font->em_scalef_x (tracking / 2);
+      int tracking = trackData.get_tracking (this, ptem);
+      hb_position_t offset_to_add = c->font->em_scalef_x (tracking / 2);
+      hb_position_t advance_to_add = c->font->em_scalef_x (tracking);
       foreach_grapheme (buffer, start, end)
       {
-	buffer->pos[start].x_offset += advance_to_add;
+        if (!(buffer->info[start].mask & trak_mask)) continue;
 	buffer->pos[start].x_advance += advance_to_add;
-	buffer->pos[end].x_advance += advance_to_add;
+	buffer->pos[start].x_offset += offset_to_add;
       }
     }
     else
     {
       const TrackData &trackData = this+vertData;
-      float tracking = trackData.get_tracking (this, ptem);
-      hb_position_t advance_to_add = c->font->em_scalef_y (tracking / 2);
+      int tracking = trackData.get_tracking (this, ptem);
+      hb_position_t offset_to_add = c->font->em_scalef_y (tracking / 2);
+      hb_position_t advance_to_add = c->font->em_scalef_y (tracking);
       foreach_grapheme (buffer, start, end)
       {
-	buffer->pos[start].y_offset += advance_to_add;
+        if (!(buffer->info[start].mask & trak_mask)) continue;
 	buffer->pos[start].y_advance += advance_to_add;
-	buffer->pos[end].y_advance += advance_to_add;
+	buffer->pos[start].y_offset += offset_to_add;
       }
     }
 
     return_trace (true);
   }
 
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+
+    return_trace (likely (c->check_struct (this) &&
+			  version.major == 1 &&
+			  horizData.sanitize (c, this, this) &&
+			  vertData.sanitize (c, this, this)));
+  }
+
   protected:
-  FixedVersion<>	version;	/* Version of the tracking table--currently
-					 * 0x00010000u for version 1.0. */
-  HBUINT16		format; 	/* Format of the tracking table */
-  OffsetTo<TrackData>	horizData;	/* TrackData for horizontal text */
-  OffsetTo<TrackData>	vertData;	/* TrackData for vertical text */
-  HBUINT16		reserved;	/* Reserved. Set to 0. */
+  FixedVersion<>version;	/* Version of the tracking table
+					 * (0x00010000u for version 1.0). */
+  HBUINT16	format; 	/* Format of the tracking table (set to 0). */
+  OffsetTo<TrackData>
+		horizData;	/* Offset from start of tracking table to TrackData
+				 * for horizontal text (or 0 if none). */
+  OffsetTo<TrackData>
+		vertData;	/* Offset from start of tracking table to TrackData
+				 * for vertical text (or 0 if none). */
+  HBUINT16	reserved;	/* Reserved. Set to 0. */
 
   public:
-  DEFINE_SIZE_MIN (12);
+  DEFINE_SIZE_STATIC (12);
 };
 
 } /* namespace AAT */
 
 
 #endif /* HB_AAT_LAYOUT_TRAK_TABLE_HH */
--- a/gfx/harfbuzz/src/hb-aat-layout.cc
+++ b/gfx/harfbuzz/src/hb-aat-layout.cc
@@ -1,10 +1,11 @@
 /*
  * Copyright © 2017  Google, Inc.
+ * Copyright © 2018  Ebrahim Byagowi
  *
  *  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.
@@ -23,59 +24,369 @@
  *
  * Google Author(s): Behdad Esfahbod
  */
 
 #include "hb-open-type.hh"
 
 #include "hb-ot-face.hh"
 #include "hb-aat-layout.hh"
+#include "hb-aat-fdsc-table.hh" // Just so we compile it; unused otherwise.
 #include "hb-aat-layout-ankr-table.hh"
 #include "hb-aat-layout-bsln-table.hh" // Just so we compile it; unused otherwise.
-#include "hb-aat-layout-feat-table.hh" // Just so we compile it; unused otherwise.
+#include "hb-aat-layout-feat-table.hh"
+#include "hb-aat-layout-just-table.hh" // Just so we compile it; unused otherwise.
 #include "hb-aat-layout-kerx-table.hh"
 #include "hb-aat-layout-morx-table.hh"
 #include "hb-aat-layout-trak-table.hh"
-#include "hb-aat-ltag-table.hh" // Just so we compile it; unused otherwise.
+#include "hb-aat-ltag-table.hh"
+
+
+/**
+ * SECTION:hb-aat-layout
+ * @title: hb-aat-layout
+ * @short_description: Apple Advanced Typography Layout
+ * @include: hb-aat.h
+ *
+ * Functions for querying OpenType Layout features in the font face.
+ **/
+
+
+/* Table data courtesy of Apple.  Converted from mnemonics to integers
+ * when moving to this file. */
+static const hb_aat_feature_mapping_t feature_mappings[] =
+{
+  {HB_TAG ('a','f','r','c'), HB_AAT_LAYOUT_FEATURE_TYPE_FRACTIONS,               HB_AAT_LAYOUT_FEATURE_SELECTOR_VERTICAL_FRACTIONS,             HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_FRACTIONS},
+  {HB_TAG ('c','2','p','c'), HB_AAT_LAYOUT_FEATURE_TYPE_UPPER_CASE,              HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_PETITE_CAPS,         HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_UPPER_CASE},
+  {HB_TAG ('c','2','s','c'), HB_AAT_LAYOUT_FEATURE_TYPE_UPPER_CASE,              HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_SMALL_CAPS,          HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_UPPER_CASE},
+  {HB_TAG ('c','a','l','t'), HB_AAT_LAYOUT_FEATURE_TYPE_CONTEXTUAL_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_ALTERNATES_ON,       HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_ALTERNATES_OFF},
+  {HB_TAG ('c','a','s','e'), HB_AAT_LAYOUT_FEATURE_TYPE_CASE_SENSITIVE_LAYOUT,   HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_LAYOUT_ON,       HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_LAYOUT_OFF},
+  {HB_TAG ('c','l','i','g'), HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES,               HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_LIGATURES_ON,        HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_LIGATURES_OFF},
+  {HB_TAG ('c','p','s','p'), HB_AAT_LAYOUT_FEATURE_TYPE_CASE_SENSITIVE_LAYOUT,   HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_SPACING_ON,      HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_SPACING_OFF},
+  {HB_TAG ('c','s','w','h'), HB_AAT_LAYOUT_FEATURE_TYPE_CONTEXTUAL_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_SWASH_ALTERNATES_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_SWASH_ALTERNATES_OFF},
+  {HB_TAG ('d','l','i','g'), HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES,               HB_AAT_LAYOUT_FEATURE_SELECTOR_RARE_LIGATURES_ON,              HB_AAT_LAYOUT_FEATURE_SELECTOR_RARE_LIGATURES_OFF},
+  {HB_TAG ('e','x','p','t'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_EXPERT_CHARACTERS,              (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('f','r','a','c'), HB_AAT_LAYOUT_FEATURE_TYPE_FRACTIONS,               HB_AAT_LAYOUT_FEATURE_SELECTOR_DIAGONAL_FRACTIONS,             HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_FRACTIONS},
+  {HB_TAG ('f','w','i','d'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_MONOSPACED_TEXT,                (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('h','a','l','t'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_HALF_WIDTH_TEXT,            (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('h','i','s','t'), HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES,               HB_AAT_LAYOUT_FEATURE_SELECTOR_HISTORICAL_LIGATURES_ON,        HB_AAT_LAYOUT_FEATURE_SELECTOR_HISTORICAL_LIGATURES_OFF},
+  {HB_TAG ('h','k','n','a'), HB_AAT_LAYOUT_FEATURE_TYPE_ALTERNATE_KANA,          HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_HORIZ_KANA_ON,        HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_HORIZ_KANA_OFF},
+  {HB_TAG ('h','l','i','g'), HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES,               HB_AAT_LAYOUT_FEATURE_SELECTOR_HISTORICAL_LIGATURES_ON,        HB_AAT_LAYOUT_FEATURE_SELECTOR_HISTORICAL_LIGATURES_OFF},
+  {HB_TAG ('h','n','g','l'), HB_AAT_LAYOUT_FEATURE_TYPE_TRANSLITERATION,         HB_AAT_LAYOUT_FEATURE_SELECTOR_HANJA_TO_HANGUL,                HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_TRANSLITERATION},
+  {HB_TAG ('h','o','j','o'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_HOJO_CHARACTERS,                (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('h','w','i','d'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_HALF_WIDTH_TEXT,                (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('i','t','a','l'), HB_AAT_LAYOUT_FEATURE_TYPE_ITALIC_CJK_ROMAN,        HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN_ON,            HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN_OFF},
+  {HB_TAG ('j','p','0','4'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS2004_CHARACTERS,             (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('j','p','7','8'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1978_CHARACTERS,             (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('j','p','8','3'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1983_CHARACTERS,             (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('j','p','9','0'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1990_CHARACTERS,             (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('l','i','g','a'), HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES,               HB_AAT_LAYOUT_FEATURE_SELECTOR_COMMON_LIGATURES_ON,            HB_AAT_LAYOUT_FEATURE_SELECTOR_COMMON_LIGATURES_OFF},
+  {HB_TAG ('l','n','u','m'), HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_CASE,             HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_NUMBERS,             (hb_aat_layout_feature_selector_t) 2},
+  {HB_TAG ('m','g','r','k'), HB_AAT_LAYOUT_FEATURE_TYPE_MATHEMATICAL_EXTRAS,     HB_AAT_LAYOUT_FEATURE_SELECTOR_MATHEMATICAL_GREEK_ON,          HB_AAT_LAYOUT_FEATURE_SELECTOR_MATHEMATICAL_GREEK_OFF},
+  {HB_TAG ('n','l','c','k'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_NLCCHARACTERS,                  (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('o','n','u','m'), HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_CASE,             HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_NUMBERS,             (hb_aat_layout_feature_selector_t) 2},
+  {HB_TAG ('o','r','d','n'), HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION,       HB_AAT_LAYOUT_FEATURE_SELECTOR_ORDINALS,                       HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION},
+  {HB_TAG ('p','a','l','t'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_PROPORTIONAL_TEXT,          (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('p','c','a','p'), HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE,              HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_PETITE_CAPS,         HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_LOWER_CASE},
+  {HB_TAG ('p','k','n','a'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_TEXT,              (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('p','n','u','m'), HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_SPACING,          HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_NUMBERS,           (hb_aat_layout_feature_selector_t) 4},
+  {HB_TAG ('p','w','i','d'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_TEXT,              (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('q','w','i','d'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_QUARTER_WIDTH_TEXT,             (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('r','u','b','y'), HB_AAT_LAYOUT_FEATURE_TYPE_RUBY_KANA,               HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA_ON,                   HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA_OFF},
+  {HB_TAG ('s','i','n','f'), HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION,       HB_AAT_LAYOUT_FEATURE_SELECTOR_SCIENTIFIC_INFERIORS,           HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION},
+  {HB_TAG ('s','m','c','p'), HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE,              HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS,          HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_LOWER_CASE},
+  {HB_TAG ('s','m','p','l'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_SIMPLIFIED_CHARACTERS,          (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('s','s','0','1'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ONE_ON,           HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ONE_OFF},
+  {HB_TAG ('s','s','0','2'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWO_ON,           HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWO_OFF},
+  {HB_TAG ('s','s','0','3'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THREE_ON,         HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THREE_OFF},
+  {HB_TAG ('s','s','0','4'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOUR_ON,          HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOUR_OFF},
+  {HB_TAG ('s','s','0','5'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIVE_ON,          HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIVE_OFF},
+  {HB_TAG ('s','s','0','6'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIX_ON,           HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIX_OFF},
+  {HB_TAG ('s','s','0','7'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVEN_ON,         HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVEN_OFF},
+  {HB_TAG ('s','s','0','8'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHT_ON,         HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHT_OFF},
+  {HB_TAG ('s','s','0','9'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINE_ON,          HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINE_OFF},
+  {HB_TAG ('s','s','1','0'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TEN_ON,           HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TEN_OFF},
+  {HB_TAG ('s','s','1','1'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ELEVEN_ON,        HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ELEVEN_OFF},
+  {HB_TAG ('s','s','1','2'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWELVE_ON,        HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWELVE_OFF},
+  {HB_TAG ('s','s','1','3'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THIRTEEN_ON,      HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THIRTEEN_OFF},
+  {HB_TAG ('s','s','1','4'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOURTEEN_ON,      HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOURTEEN_OFF},
+  {HB_TAG ('s','s','1','5'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIFTEEN_ON,       HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIFTEEN_OFF},
+  {HB_TAG ('s','s','1','6'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIXTEEN_ON,       HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIXTEEN_OFF},
+  {HB_TAG ('s','s','1','7'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVENTEEN_ON,     HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVENTEEN_OFF},
+  {HB_TAG ('s','s','1','8'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHTEEN_ON,      HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHTEEN_OFF},
+  {HB_TAG ('s','s','1','9'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINETEEN_ON,      HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINETEEN_OFF},
+  {HB_TAG ('s','s','2','0'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES,  HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWENTY_ON,        HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWENTY_OFF},
+  {HB_TAG ('s','u','b','s'), HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION,       HB_AAT_LAYOUT_FEATURE_SELECTOR_INFERIORS,                      HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION},
+  {HB_TAG ('s','u','p','s'), HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION,       HB_AAT_LAYOUT_FEATURE_SELECTOR_SUPERIORS,                      HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION},
+  {HB_TAG ('s','w','s','h'), HB_AAT_LAYOUT_FEATURE_TYPE_CONTEXTUAL_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_SWASH_ALTERNATES_ON,            HB_AAT_LAYOUT_FEATURE_SELECTOR_SWASH_ALTERNATES_OFF},
+  {HB_TAG ('t','i','t','l'), HB_AAT_LAYOUT_FEATURE_TYPE_STYLE_OPTIONS,           HB_AAT_LAYOUT_FEATURE_SELECTOR_TITLING_CAPS,                   HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_STYLE_OPTIONS},
+  {HB_TAG ('t','n','a','m'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_NAMES_CHARACTERS,   (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('t','n','u','m'), HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_SPACING,          HB_AAT_LAYOUT_FEATURE_SELECTOR_MONOSPACED_NUMBERS,             (hb_aat_layout_feature_selector_t) 4},
+  {HB_TAG ('t','r','a','d'), HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE,         HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_CHARACTERS,         (hb_aat_layout_feature_selector_t) 16},
+  {HB_TAG ('t','w','i','d'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_THIRD_WIDTH_TEXT,               (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('u','n','i','c'), HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE,             (hb_aat_layout_feature_selector_t) 14,                 (hb_aat_layout_feature_selector_t) 15},
+  {HB_TAG ('v','a','l','t'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_PROPORTIONAL_TEXT,          (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('v','e','r','t'), HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_SUBSTITUTION,   HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_ON,   HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_OFF},
+  {HB_TAG ('v','h','a','l'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_HALF_WIDTH_TEXT,            (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('v','k','n','a'), HB_AAT_LAYOUT_FEATURE_TYPE_ALTERNATE_KANA,          HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_VERT_KANA_ON,         HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_VERT_KANA_OFF},
+  {HB_TAG ('v','p','a','l'), HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING,            HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_PROPORTIONAL_TEXT,          (hb_aat_layout_feature_selector_t) 7},
+  {HB_TAG ('v','r','t','2'), HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_SUBSTITUTION,   HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_ON,   HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_OFF},
+  {HB_TAG ('z','e','r','o'), HB_AAT_LAYOUT_FEATURE_TYPE_TYPOGRAPHIC_EXTRAS,      HB_AAT_LAYOUT_FEATURE_SELECTOR_SLASHED_ZERO_ON,                HB_AAT_LAYOUT_FEATURE_SELECTOR_SLASHED_ZERO_OFF},
+};
+
+const hb_aat_feature_mapping_t *
+hb_aat_layout_find_feature_mapping (hb_tag_t tag)
+{
+  return (const hb_aat_feature_mapping_t *) bsearch (&tag,
+						     feature_mappings,
+						     ARRAY_LENGTH (feature_mappings),
+						     sizeof (feature_mappings[0]),
+						     hb_aat_feature_mapping_t::cmp);
+}
+
 
 /*
- * morx/kerx/trak/ankr
+ * hb_aat_apply_context_t
  */
 
-static inline const AAT::morx&
-_get_morx (hb_face_t *face, hb_blob_t **blob = nullptr)
+AAT::hb_aat_apply_context_t::hb_aat_apply_context_t (const hb_ot_shape_plan_t *plan_,
+						     hb_font_t *font_,
+						     hb_buffer_t *buffer_,
+						     hb_blob_t *blob) :
+						       plan (plan_),
+						       font (font_),
+						       face (font->face),
+						       buffer (buffer_),
+						       sanitizer (),
+						       ankr_table (&Null(AAT::ankr)),
+						       ankr_end (nullptr),
+						       lookup_index (0),
+						       debug_depth (0)
 {
-  if (unlikely (!hb_ot_shaper_face_data_ensure (face)))
+  sanitizer.init (blob);
+  sanitizer.set_num_glyphs (face->get_num_glyphs ());
+  sanitizer.start_processing ();
+  sanitizer.set_max_ops (HB_SANITIZE_MAX_OPS_MAX);
+}
+
+AAT::hb_aat_apply_context_t::~hb_aat_apply_context_t ()
+{ sanitizer.end_processing (); }
+
+void
+AAT::hb_aat_apply_context_t::set_ankr_table (const AAT::ankr *ankr_table_,
+					     const char      *ankr_end_)
+{
+  ankr_table = ankr_table_;
+  ankr_end = ankr_end_;
+}
+
+
+/*
+ * mort/morx/kerx/trak
+ */
+
+
+void
+hb_aat_layout_compile_map (const hb_aat_map_builder_t *mapper,
+			   hb_aat_map_t *map)
+{
+  const AAT::morx& morx = *mapper->face->table.morx;
+  if (morx.has_data ())
   {
-    if (blob)
-      *blob = hb_blob_get_empty ();
-    return Null(AAT::morx);
+    morx.compile_flags (mapper, map);
+    return;
+  }
+
+  const AAT::mort& mort = *mapper->face->table.mort;
+  if (mort.has_data ())
+  {
+    mort.compile_flags (mapper, map);
+    return;
   }
-  const AAT::morx& morx = *(hb_ot_face_data (face)->morx.get ());
-  if (blob)
-    *blob = hb_ot_face_data (face)->morx.get_blob ();
-  return morx;
+}
+
+
+/*
+ * hb_aat_layout_has_substitution:
+ * @face:
+ *
+ * Returns:
+ * Since: 2.3.0
+ */
+hb_bool_t
+hb_aat_layout_has_substitution (hb_face_t *face)
+{
+  return face->table.morx->has_data () ||
+	 face->table.mort->has_data ();
 }
 
 void
-hb_aat_layout_substitute (hb_font_t *font, hb_buffer_t *buffer)
+hb_aat_layout_substitute (const hb_ot_shape_plan_t *plan,
+			  hb_font_t *font,
+			  hb_buffer_t *buffer)
+{
+  hb_blob_t *morx_blob = font->face->table.morx.get_blob ();
+  const AAT::morx& morx = *morx_blob->as<AAT::morx> ();
+  if (morx.has_data ())
+  {
+    AAT::hb_aat_apply_context_t c (plan, font, buffer, morx_blob);
+    morx.apply (&c);
+    return;
+  }
+
+  hb_blob_t *mort_blob = font->face->table.mort.get_blob ();
+  const AAT::mort& mort = *mort_blob->as<AAT::mort> ();
+  if (mort.has_data ())
+  {
+    AAT::hb_aat_apply_context_t c (plan, font, buffer, mort_blob);
+    mort.apply (&c);
+    return;
+  }
+}
+
+void
+hb_aat_layout_zero_width_deleted_glyphs (hb_buffer_t *buffer)
 {
-  hb_blob_t *blob;
-  const AAT::morx& morx = _get_morx (font->face, &blob);
+  unsigned int count = buffer->len;
+  hb_glyph_info_t *info = buffer->info;
+  hb_glyph_position_t *pos = buffer->pos;
+  for (unsigned int i = 0; i < count; i++)
+    if (unlikely (info[i].codepoint == AAT::DELETED_GLYPH))
+      pos[i].x_advance = pos[i].y_advance = pos[i].x_offset = pos[i].y_offset = 0;
+}
+
+static bool
+is_deleted_glyph (const hb_glyph_info_t *info)
+{
+  return info->codepoint == AAT::DELETED_GLYPH;
+}
 
-  AAT::hb_aat_apply_context_t c (font, buffer, blob);
-  morx.apply (&c);
+void
+hb_aat_layout_remove_deleted_glyphs (hb_buffer_t *buffer)
+{
+  hb_ot_layout_delete_glyphs_inplace (buffer, is_deleted_glyph);
+}
+
+/*
+ * hb_aat_layout_has_positioning:
+ * @face:
+ *
+ * Returns:
+ * Since: 2.3.0
+ */
+hb_bool_t
+hb_aat_layout_has_positioning (hb_face_t *face)
+{
+  return face->table.kerx->has_data ();
 }
 
 void
-hb_aat_layout_position (hb_font_t *font, hb_buffer_t *buffer)
+hb_aat_layout_position (const hb_ot_shape_plan_t *plan,
+			hb_font_t *font,
+			hb_buffer_t *buffer)
+{
+  hb_blob_t *kerx_blob = font->face->table.kerx.get_blob ();
+  const AAT::kerx& kerx = *kerx_blob->as<AAT::kerx> ();
+
+  hb_blob_t *ankr_blob = font->face->table.ankr.get_blob ();;
+  const AAT::ankr& ankr = *font->face->table.ankr;
+
+  AAT::hb_aat_apply_context_t c (plan, font, buffer, kerx_blob);
+  c.set_ankr_table (&ankr, ankr_blob->data + ankr_blob->length);
+  kerx.apply (&c);
+}
+
+
+/*
+ * hb_aat_layout_has_tracking:
+ * @face:
+ *
+ * Returns:
+ * Since: 2.3.0
+ */
+hb_bool_t
+hb_aat_layout_has_tracking (hb_face_t *face)
+{
+  return face->table.trak->has_data ();
+}
+
+void
+hb_aat_layout_track (const hb_ot_shape_plan_t *plan,
+		     hb_font_t *font,
+		     hb_buffer_t *buffer)
+{
+  const AAT::trak& trak = *font->face->table.trak;
+
+  AAT::hb_aat_apply_context_t c (plan, font, buffer);
+  trak.apply (&c);
+}
+
+
+hb_language_t
+_hb_aat_language_get (hb_face_t *face,
+		      unsigned int i)
 {
-#if 0
-  hb_blob_t *blob;
-  const AAT::ankr& ankr = _get_ankr (font->face, &blob);
-  const AAT::kerx& kerx = _get_kerx (font->face, &blob);
-  const AAT::trak& trak = _get_trak (font->face, &blob);
+  return face->table.ltag->get_language (i);
+}
+
+/**