Merge tag '11.3.3'

11.3.3
diff --git a/.github/workflows/crossbuild-mingw.yml b/.github/workflows/crossbuild-mingw.yml
index 978ba13..7d57d15 100644
--- a/.github/workflows/crossbuild-mingw.yml
+++ b/.github/workflows/crossbuild-mingw.yml
@@ -1,4 +1,4 @@
-name: crossbuild
+name: crossbuild-mingw
 
 on:
   push:
@@ -67,7 +67,6 @@
         uses: actions/download-artifact@v4
       - name: Rename artifacts
         run: |
-          ls -R
           for file in harfbuzz-win*.zip; do
             mv -v "$file" "${file%.zip}-${{ github.ref_name }}.zip"
           done
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index cda9bae..a965cef 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -49,6 +49,7 @@
           -Dauto_features=enabled \
           -Dchafa=disabled \
           -Dgraphite=enabled \
+          -Dkbts=enabled \
           -Doptimization=2 \
           -Ddoc_tests=true \
           -Dragel_subproject=true
diff --git a/README.md b/README.md
index 0cbdf00..2072853 100644
--- a/README.md
+++ b/README.md
@@ -119,10 +119,10 @@
 [2]: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6AATIntro.html
 [3]: https://github.com/harfbuzz/harfbuzz/releases
 [4]: https://github.com/harfbuzz/harfbuzz
-[6]: http://behdad.org/text2024
+[6]: https://behdad.org/text2024
 [7]: https://docs.google.com/presentation/d/1x97pfbB1gbD53Yhz6-_yBUozQMVJ_5yMqqR_D-R7b7I/preview
 [8]: https://docs.google.com/presentation/d/1ySTZaXP5XKFg0OpmHZM00v5b17GSr3ojnzJekl4U8qI/preview
-[9]: http://behdad.org/download/Presentations/slippy/harfbuzz_slides.pdf
+[9]: https://behdad.org/doc/harfbuzz2009-slides.pdf
 [10]: https://docs.google.com/document/d/12jfNpQJzeVIAxoUSpk7KziyINAa1msbGliyXqguS86M/preview
 [11]: https://behdad.org/
 [12]: https://docs.google.com/document/d/1_VgObf6Je0J8byMLsi7HCQHnKo2emGnx_ib_sHo-bt4/preview
diff --git a/RELEASING.md b/RELEASING.md
index 9324faf..61391f2 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -36,7 +36,5 @@
 
 - [ ] Push the commit and tag out: `git push --follow-tags`.
 
-- [ ] There should be a GitHub release automatically created,
-      but it takes a few minutes as a bot has to build it.
-      When it does, go to that release page and add description.
-      The description should be the NEWS file additions.
+- [ ] Go to GitHub releases page and create a new release for the pushed tag. Or wait a few minutes and the CI job will create on automatically.
+      Add description to the release, which should be the NEWS file additions.
diff --git a/meson.build b/meson.build
index 18c8a58..79f52bc 100644
--- a/meson.build
+++ b/meson.build
@@ -212,6 +212,11 @@
   harfrust_dep_found = true
 endif
 
+kbts_dep = null_dep
+if get_option('kbts').enabled()
+  kbts_dep = subproject('kbts').get_variable('kbts_dep')
+endif
+
 conf = configuration_data()
 incconfig = include_directories('.')
 
@@ -261,6 +266,10 @@
   conf.set('HAVE_HARFRUST', 1)
 endif
 
+if kbts_dep.found()
+  conf.set('HAVE_KBTS', 1)
+endif
+
 if wasm_dep.found()
   conf.set('HAVE_WASM', 1)
   conf.set('HB_WASM_MODULE_DIR', '"'+get_option('prefix')+'/'+get_option('libdir')+'/harfbuzz/wasm"')
@@ -488,11 +497,12 @@
     {'Graphite2': conf.get('HAVE_GRAPHITE2', 0) == 1,
      'WebAssembly (experimental)': conf.get('HAVE_WASM', 0) == 1,
     },
-  'Platform shapers (not normally needed)':
+  'Platform / other shapers (not normally needed)':
     {'CoreText': conf.get('HAVE_CORETEXT', 0) == 1,
      'DirectWrite': conf.get('HAVE_DIRECTWRITE', 0) == 1,
      'GDI/Uniscribe': (conf.get('HAVE_GDI', 0) == 1) and (conf.get('HAVE_UNISCRIBE', 0) == 1),
      'HarfRust': conf.get('HAVE_HARFRUST', 0) == 1,
+     'kbts': conf.get('HAVE_KBTS', 0) == 1,
     },
   'Other features':
     {'Utilities': not get_option('utilities').disabled(),
diff --git a/meson_options.txt b/meson_options.txt
index b6d0085..a35ab48 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -25,6 +25,8 @@
   description: 'Enable CoreText shaper and font backend on Apple platforms')
 option('harfrust', type: 'feature', value: 'disabled',
   description: 'Enable HarfRust shaper backend')
+option('kbts', type: 'feature', value: 'disabled',
+  description: 'Enable kb_text_shape backend')
 option('wasm', type: 'feature', value: 'disabled',
   description: 'Enable WebAssembly shaper backend (experimental)')
 
diff --git a/perf/benchmark-font.cc b/perf/benchmark-font.cc
index b6a07d3..9236511 100644
--- a/perf/benchmark-font.cc
+++ b/perf/benchmark-font.cc
@@ -1,5 +1,7 @@
 #include "hb-benchmark.hh"
 
+#include <glib.h>
+
 #define SUBSET_FONT_BASE_PATH "test/subset/data/fonts/"
 
 static hb_variation_t default_variations[] = {
@@ -8,6 +10,7 @@
 };
 
 static unsigned max_num_glyphs = 0;
+static const char *text = " ";
 
 static struct test_input_t
 {
@@ -253,7 +256,7 @@
 	}
 
 	hb_buffer_t *buffer = hb_buffer_create ();
-	hb_buffer_add_utf8 (buffer, " ", -1, 0, -1);
+	hb_buffer_add_utf8 (buffer, text, -1, 0, -1);
 	hb_buffer_guess_segment_properties (buffer);
 
 	hb_shape (font, buffer, nullptr, 0);
@@ -306,33 +309,113 @@
   }
 }
 
+static const char *font_file = nullptr;
+static const char *variations_str = nullptr;
+
+static GOptionEntry entries[] =
+{
+  {"font-file", 0, 0, G_OPTION_ARG_STRING, &font_file, "Font file-path to benchmark", "FONTFILE"},
+  {"variations", 0, 0, G_OPTION_ARG_STRING, &variations_str, "Variations to apply", "VAR"},
+  {"max-glyphs", 0, 0, G_OPTION_ARG_INT, &max_num_glyphs, "Maximum number of glyphs to process", "NUM"},
+  {"text", 0, 0, G_OPTION_ARG_STRING, &text, "Text to use for load_face_and_shape test", "TEXT"},
+  {nullptr}
+};
+
+static void print_usage (const char *prgname)
+{
+  g_print ("Usage: %s [OPTIONS] [FONTFILE]\n", prgname);
+}
+
 int main(int argc, char** argv)
 {
-  benchmark::Initialize(&argc, argv);
+  const char *prgname = g_path_get_basename (argv[0]);
 
-  if (argc > 1 && strcmp (argv[1], "--max-glyphs") == 0)
+  GOptionContext *context = g_option_context_new ("");
+  g_option_context_set_summary (context, "Benchmark font operations");
+  g_option_context_set_description (context, "Benchmark Options:");
+  g_option_context_add_main_entries (context, entries, nullptr);
+
+  bool show_help = false;
+  for (int i = 1; i < argc; i++)
   {
-    if (argc < 3)
+    if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
     {
-      fprintf (stderr, "Usage: %s [--max-glyphs <number>] [<font> <variation>...]\n", argv[0]);
-      return 1;
+      argv[i] = (char *) "--help"; // Ensure it is recognized by both google-benchmark
+      show_help = true;
+      break;
     }
-    max_num_glyphs = atoi (argv[2]);
-    argc -= 2;
-    argv += 2;
+  }
+  if (show_help)
+  {
+    print_usage (prgname);
+
+    gchar *help_text = g_option_context_get_help (context, false, nullptr);
+    help_text = strstr (help_text, "\n\n");
+    g_print ("%s", help_text);
+
+    benchmark::Initialize(&argc, argv); // This shows the help for google-benchmark
   }
 
-  if (argc > 1)
+  benchmark::Initialize(&argc, argv);
+  g_option_context_parse (context, &argc, &argv, nullptr);
+  g_option_context_free (context);
+
+  argc--;
+  argv++;
+  if (!font_file && argc)
   {
-    unsigned num_variations = argc - 2;
+    font_file = *argv;
+    argc--;
+    argv++;
+  }
+
+  if (argc)
+  {
+    g_printerr ("Unexpected arguments: ");
+    for (int i = 0; i < argc; i++)
+      g_printerr ("%s ", argv[i]);
+    g_printerr ("\n\n");
+    print_usage (prgname);
+    return 1;
+  }
+
+  if (font_file)
+  {
+    unsigned num_variations = 0;
+    if (variations_str)
+    {
+      const char *p = variations_str;
+      while (p && *p)
+      {
+	num_variations++;
+	p = strchr (p, ',');
+	if (p) p++;
+      }
+    }
     hb_variation_t *variations = num_variations ? (hb_variation_t *) calloc (num_variations, sizeof (hb_variation_t)) : nullptr;
-    for (unsigned i = 0; i < num_variations; i++)
-      hb_variation_from_string (argv[i + 2], -1, &variations[i]);
+    if (variations_str)
+    {
+      const char *p = variations_str;
+      for (unsigned i = 0; i < num_variations; i++)
+      {
+	const char *end = strchr (p, ',');
+	if (end)
+	{
+	  hb_variation_from_string (p, end - p, &variations[i]);
+	  p = end + 1;
+	}
+	else
+	{
+	  hb_variation_from_string (p, -1, &variations[i]);
+	  p = nullptr;
+	}
+      }
+    }
 
     num_tests = 1;
     tests = (test_input_t *) calloc (num_tests, sizeof (test_input_t));
     tests[0].variations = num_variations ? variations : default_variations;
-    tests[0].font_path = argv[1];
+    tests[0].font_path = font_file;
   }
 
 #define TEST_OPERATION(op, time_unit) test_operation (op, #op, time_unit)
@@ -358,3 +441,4 @@
     free (tests);
   }
 }
+
diff --git a/perf/benchmark-shape.cc b/perf/benchmark-shape.cc
index ede3dd6..44cbeac 100644
--- a/perf/benchmark-shape.cc
+++ b/perf/benchmark-shape.cc
@@ -1,5 +1,7 @@
 #include "hb-benchmark.hh"
 
+#include <glib.h>
+
 #define SUBSET_FONT_BASE_PATH "test/subset/data/fonts/"
 
 struct test_input_t
@@ -36,6 +38,31 @@
 const char *variation = nullptr;
 const char *direction = nullptr;
 
+static bool shape (hb_buffer_t *buf,
+		   hb_font_t *font,
+		   const char *text,
+		   unsigned text_length,
+		   const char *shaper)
+{
+  const char *end;
+  while ((end = (const char *) memchr (text, '\n', text_length)))
+  {
+    hb_buffer_clear_contents (buf);
+    hb_buffer_add_utf8 (buf, text, text_length, 0, end - text);
+    hb_buffer_guess_segment_properties (buf);
+    if (direction)
+      hb_buffer_set_direction (buf, hb_direction_from_string (direction, -1));
+    const char *shaper_list[] = {shaper, nullptr};
+    if (!hb_shape_full (font, buf, nullptr, 0, shaper_list))
+      return false;
+
+    unsigned skip = end - text + 1;
+    text_length -= skip;
+    text += skip;
+  }
+  return true;
+}
+
 static void BM_Shape (benchmark::State &state,
 		      const char *shaper,
 		      const test_input_t &input)
@@ -57,31 +84,26 @@
 
   hb_blob_t *text_blob = hb_blob_create_from_file_or_fail (input.text_path);
   assert (text_blob);
-  unsigned orig_text_length;
-  const char *orig_text = hb_blob_get_data (text_blob, &orig_text_length);
+  unsigned text_length;
+  const char *text = hb_blob_get_data (text_blob, &text_length);
 
   hb_buffer_t *buf = hb_buffer_create ();
+
+  // Shape once, to warm up the font and buffer.
+  bool ret = shape (buf, font, text, text_length, shaper);
+  if (!ret)
+  {
+    state.SkipWithMessage ("Shaping failed.");
+    goto done;
+  }
+
   for (auto _ : state)
   {
-    unsigned text_length = orig_text_length;
-    const char *text = orig_text;
-
-    const char *end;
-    while ((end = (const char *) memchr (text, '\n', text_length)))
-    {
-      hb_buffer_clear_contents (buf);
-      hb_buffer_add_utf8 (buf, text, text_length, 0, end - text);
-      hb_buffer_guess_segment_properties (buf);
-      if (direction)
-	hb_buffer_set_direction (buf, hb_direction_from_string (direction, -1));
-      const char *shaper_list[] = {shaper, nullptr};
-      hb_shape_full (font, buf, nullptr, 0, shaper_list);
-
-      unsigned skip = end - text + 1;
-      text_length -= skip;
-      text += skip;
-    }
+    bool ret = shape (buf, font, text, text_length, shaper);
+    assert (ret);
   }
+
+done:
   hb_buffer_destroy (buf);
 
   hb_blob_destroy (text_blob);
@@ -106,22 +128,94 @@
    ->Unit(benchmark::kMillisecond);
 }
 
+static const char *font_file = nullptr;
+static const char *text_file = nullptr;
+
+static GOptionEntry entries[] =
+{
+  {"font-file", 0, 0, G_OPTION_ARG_STRING, &font_file, "Font file-path to benchmark", "FONTFILE"},
+  {"text-file", 0, 0, G_OPTION_ARG_STRING, &text_file, "Text file-path to benchmark", "TEXTFILE"},
+  {"variations", 0, 0, G_OPTION_ARG_STRING, &variation, "Variations to apply during shaping", "VARIATIONS"},
+  {"direction", 0, 0, G_OPTION_ARG_STRING, &direction, "Direction to apply during shaping", "DIRECTION"},
+  {nullptr}
+};
+
+static void print_usage (const char *prgname)
+{
+  g_print ("Usage: %s [OPTIONS] [FONTFILE]\n", prgname);
+}
+
 int main(int argc, char** argv)
 {
+  const char *prgname = g_path_get_basename (argv[0]);
+
+  GOptionContext *context = g_option_context_new ("");
+  g_option_context_set_summary (context, "Benchmark text shaping with different shapers");
+  g_option_context_set_description (context, "Benchmark Options:");
+  g_option_context_add_main_entries (context, entries, nullptr);
+
+  // if --help is provided, we need to write help for both option parsers.
+  bool show_help = false;
+  for (int i = 1; i < argc; i++)
+  {
+    if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
+    {
+      argv[i] = (char *) "--help"; // Ensure it is recognized by both google-benchmark
+      show_help = true;
+      break;
+    }
+  }
+  if (show_help)
+  {
+    print_usage (prgname);
+
+    gchar *help_text = g_option_context_get_help (context, false, nullptr);
+    help_text = strstr (help_text, "\n\n");
+    g_print ("%s", help_text);
+
+    benchmark::Initialize(&argc, argv); // This shows the help for google-benchmark
+  }
+
   benchmark::Initialize(&argc, argv);
+  g_option_context_parse (context, &argc, &argv, nullptr);
+  g_option_context_free (context);
+
+  argc--;
+  argv++;
+  if (!font_file && argc)
+  {
+    font_file = *argv;
+    argv++;
+    argc--;
+  }
+  if (!text_file && argc)
+  {
+    text_file = *argv;
+    argv++;
+    argc--;
+  }
+
+  if (argc)
+  {
+    g_printerr ("Unexpected arguments: ");
+    for (int i = 0; i < argc; i++)
+      g_printerr ("%s ", argv[i]);
+    g_printerr ("\n\n");
+    print_usage (prgname);
+    return 1;
+  }
 
   test_input_t static_test = {};
-  if (argc > 2)
+  if (font_file && text_file)
   {
-    static_test.font_path = argv[1];
-    static_test.text_path = argv[2];
-    tests = &static_test;
-    num_tests = 1;
+    if (font_file && text_file)
+    {
+      static_test.font_path = font_file;
+      static_test.text_path = text_file;
+      tests = &static_test;
+      num_tests = 1;
+    }
   }
-  if (argc > 3)
-    variation = argv[3];
-  if (argc > 4)
-    direction = argv[4];
 
   for (unsigned i = 0; i < num_tests; i++)
   {
diff --git a/perf/hb-benchmark.hh b/perf/hb-benchmark.hh
index c976831..48ab1b0 100644
--- a/perf/hb-benchmark.hh
+++ b/perf/hb-benchmark.hh
@@ -32,17 +32,6 @@
 #include <hb.h>
 #include <hb-subset.h>
 
-#include <hb-ot.h>
-#ifdef HAVE_FREETYPE
-#include <hb-ft.h>
-#endif
-#ifdef HAVE_FONTATIONS
-#include <hb-fontations.h>
-#endif
-#ifdef HAVE_CORETEXT
-#include <hb-coretext.h>
-#endif
-
 #include <benchmark/benchmark.h>
 
 #include <cassert>
diff --git a/perf/meson.build b/perf/meson.build
index 605b8e9..b8bdbb7 100644
--- a/perf/meson.build
+++ b/perf/meson.build
@@ -1,3 +1,8 @@
+if conf.get('HAVE_GLIB', 0) == 0
+  message('You need to have glib support enabled to build perf benchmarks.')
+  subdir_done()
+endif
+
 google_benchmark = subproject('google-benchmark')
 google_benchmark_dep = google_benchmark.get_variable('google_benchmark_dep')
 
@@ -13,11 +18,10 @@
   benchmark_name = source.split('.')[0]
   benchmark(benchmark_name, executable(benchmark_name, source,
     dependencies: [
-      google_benchmark_dep, freetype_dep, coretext_deps,
+      google_benchmark_dep, libharfbuzz_dep
     ],
     cpp_args: [],
     include_directories: [incconfig, incsrc],
-    link_with: [libharfbuzz],
     install: false,
   ), workdir: meson.current_source_dir() / '..', timeout: 100)
 endforeach
@@ -30,11 +34,10 @@
   benchmark_name = source.split('.')[0]
   benchmark(benchmark_name, executable(benchmark_name, source,
     dependencies: [
-      google_benchmark_dep, freetype_dep, coretext_deps,
+      google_benchmark_dep, libharfbuzz_dep, libharfbuzz_subset_dep
     ],
     cpp_args: [],
     include_directories: [incconfig, incsrc],
-    link_with: [libharfbuzz, libharfbuzz_subset],
     install: false,
   ), workdir: meson.current_source_dir() / '..', timeout: 100)
 endforeach
diff --git a/src/OT/Color/COLR/COLR.hh b/src/OT/Color/COLR/COLR.hh
index 896b0fb..7d72a11 100644
--- a/src/OT/Color/COLR/COLR.hh
+++ b/src/OT/Color/COLR/COLR.hh
@@ -178,7 +178,10 @@
   { glyphs->add (glyph_id); }
 
   void add_layer_indices (unsigned first_layer_index, unsigned num_of_layers)
-  { layer_indices->add_range (first_layer_index, first_layer_index + num_of_layers - 1); }
+  {
+    if (num_of_layers == 0) return;
+    layer_indices->add_range (first_layer_index, first_layer_index + num_of_layers - 1);
+  }
 
   void add_palette_index (unsigned palette_index)
   { palette_indices->add (palette_index); }
@@ -650,10 +653,10 @@
     TRACE_SUBSET (this);
     auto *out = c->serializer->embed (this);
     if (unlikely (!out)) return_trace (false);
-    return_trace (c->serializer->check_assign (out->firstLayerIndex, c->plan->colrv1_layers.get (firstLayerIndex),
-                                               HB_SERIALIZE_ERROR_INT_OVERFLOW));
 
-    return_trace (true);
+    uint32_t first_layer_index = numLayers ? c->plan->colrv1_layers.get (firstLayerIndex) : 0;
+    return_trace (c->serializer->check_assign (out->firstLayerIndex, first_layer_index,
+                                               HB_SERIALIZE_ERROR_INT_OVERFLOW));
   }
 
   bool sanitize (hb_sanitize_context_t *c) const
diff --git a/src/OT/Color/CPAL/CPAL.hh b/src/OT/Color/CPAL/CPAL.hh
index 940126a..2b8b69b 100644
--- a/src/OT/Color/CPAL/CPAL.hh
+++ b/src/OT/Color/CPAL/CPAL.hh
@@ -307,6 +307,7 @@
       if (first_color_to_layer_index.has (first_color_record_idx)) continue;
 
       first_color_index_for_layer.push (first_color_record_idx);
+      if (unlikely (!c->serializer->propagate_error (first_color_index_for_layer))) return_trace (false);
       first_color_to_layer_index.set (first_color_record_idx,
                                       first_color_index_for_layer.length - 1);
     }
diff --git a/src/OT/Layout/Common/Coverage.hh b/src/OT/Layout/Common/Coverage.hh
index daa1979..ee976ac 100644
--- a/src/OT/Layout/Common/Coverage.hh
+++ b/src/OT/Layout/Common/Coverage.hh
@@ -97,7 +97,7 @@
     }
   }
   unsigned int get_coverage (hb_codepoint_t glyph_id,
-			     hb_ot_lookup_cache_t *cache) const
+			     hb_ot_layout_mapping_cache_t *cache) const
   {
     unsigned coverage;
     if (cache && cache->get (glyph_id, &coverage)) return coverage;
diff --git a/src/OT/Layout/GDEF/GDEF.hh b/src/OT/Layout/GDEF/GDEF.hh
index 79de21e..ed397b7 100644
--- a/src/OT/Layout/GDEF/GDEF.hh
+++ b/src/OT/Layout/GDEF/GDEF.hh
@@ -977,7 +977,7 @@
       }
 
 #ifndef HB_NO_GDEF_CACHE
-      table->get_mark_glyph_sets ().collect_coverage (mark_glyph_set_digests);
+      table->get_mark_glyph_sets ().collect_coverage (mark_glyph_sets);
 #endif
     }
     ~accelerator_t () { table.destroy (); }
@@ -1006,14 +1006,16 @@
     {
       return
 #ifndef HB_NO_GDEF_CACHE
-	     mark_glyph_set_digests[set_index].may_have (glyph_id) &&
+	     mark_glyph_sets[set_index].may_have (glyph_id)
+#else
+	     table->mark_set_covers (set_index, glyph_id)
 #endif
-	     table->mark_set_covers (set_index, glyph_id);
+      ;
     }
 
     hb_blob_ptr_t<GDEF> table;
 #ifndef HB_NO_GDEF_CACHE
-    hb_vector_t<hb_set_digest_t> mark_glyph_set_digests;
+    hb_vector_t<hb_bit_set_t> mark_glyph_sets;
     mutable hb_cache_t<21, 3> glyph_props_cache;
     static_assert (sizeof (glyph_props_cache) == 512, "");
 #endif
diff --git a/src/OT/Layout/GPOS/AnchorMatrix.hh b/src/OT/Layout/GPOS/AnchorMatrix.hh
index 2557e9a..7e67637 100644
--- a/src/OT/Layout/GPOS/AnchorMatrix.hh
+++ b/src/OT/Layout/GPOS/AnchorMatrix.hh
@@ -77,6 +77,13 @@
 
     return_trace (true);
   }
+
+  bool offset_is_null (unsigned row, unsigned col, unsigned num_cols) const
+  {
+    if (unlikely (row >= rows || col >= num_cols)) return true;
+    auto &offset = matrixZ[row * num_cols + col];
+    return offset.is_null ();
+  }
 };
 
 
diff --git a/src/OT/Layout/GPOS/MarkBasePosFormat1.hh b/src/OT/Layout/GPOS/MarkBasePosFormat1.hh
index ab1378e..c13dc65 100644
--- a/src/OT/Layout/GPOS/MarkBasePosFormat1.hh
+++ b/src/OT/Layout/GPOS/MarkBasePosFormat1.hh
@@ -209,19 +209,22 @@
     ;
 
     new_coverage.reset ();
-    + base_iter
-    | hb_map (hb_first)
-    | hb_map (glyph_map)
-    | hb_sink (new_coverage)
-    ;
-
-    if (!out->baseCoverage.serialize_serialize (c->serializer, new_coverage.iter ()))
-      return_trace (false);
-
     hb_sorted_vector_t<unsigned> base_indexes;
-    for (const unsigned row : + base_iter
-                              | hb_map (hb_second))
+    auto &base_array = (this+baseArray);
+    for (const auto _ : + base_iter)
     {
+      unsigned row = _.second;
+      bool non_empty = + hb_range ((unsigned) classCount)
+                       | hb_filter (klass_mapping)
+                       | hb_map ([&] (const unsigned col) { return !base_array.offset_is_null (row, col, (unsigned) classCount); })
+                       | hb_any
+                       ;
+
+      if (!non_empty) continue;
+      
+      hb_codepoint_t new_g = glyph_map.get ( _.first);
+      new_coverage.push (new_g);
+
       + hb_range ((unsigned) classCount)
       | hb_filter (klass_mapping)
       | hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
@@ -229,8 +232,12 @@
       ;
     }
 
+    if (!new_coverage) return_trace (false);
+    if (!out->baseCoverage.serialize_serialize (c->serializer, new_coverage.iter ()))
+      return_trace (false);
+
     return_trace (out->baseArray.serialize_subset (c, baseArray, this,
-						   base_iter.len (),
+						   new_coverage.length,
 						   base_indexes.iter ()));
   }
 };
diff --git a/src/OT/Layout/GPOS/PairPosFormat1.hh b/src/OT/Layout/GPOS/PairPosFormat1.hh
index a8ea5f9..eb0dbe2 100644
--- a/src/OT/Layout/GPOS/PairPosFormat1.hh
+++ b/src/OT/Layout/GPOS/PairPosFormat1.hh
@@ -107,24 +107,24 @@
   {
     return (this+coverage).cost ();
   }
-  static void * cache_func (void *p, hb_ot_lookup_cache_op_t op)
+  static void * cache_func (void *p, hb_ot_subtable_cache_op_t op)
   {
     switch (op)
     {
-      case hb_ot_lookup_cache_op_t::CREATE:
+      case hb_ot_subtable_cache_op_t::CREATE:
       {
-	hb_ot_lookup_cache_t *cache = (hb_ot_lookup_cache_t *) hb_malloc (sizeof (hb_ot_lookup_cache_t));
+	hb_ot_layout_mapping_cache_t *cache = (hb_ot_layout_mapping_cache_t *) hb_malloc (sizeof (hb_ot_layout_mapping_cache_t));
 	if (likely (cache))
 	  cache->clear ();
 	return cache;
       }
-      case hb_ot_lookup_cache_op_t::ENTER:
+      case hb_ot_subtable_cache_op_t::ENTER:
 	return (void *) true;
-      case hb_ot_lookup_cache_op_t::LEAVE:
+      case hb_ot_subtable_cache_op_t::LEAVE:
 	return nullptr;
-      case hb_ot_lookup_cache_op_t::DESTROY:
+      case hb_ot_subtable_cache_op_t::DESTROY:
       {
-	hb_ot_lookup_cache_t *cache = (hb_ot_lookup_cache_t *) p;
+	hb_ot_layout_mapping_cache_t *cache = (hb_ot_layout_mapping_cache_t *) p;
 	hb_free (cache);
 	return nullptr;
       }
@@ -141,7 +141,7 @@
     hb_buffer_t *buffer = c->buffer;
 
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
-    hb_ot_lookup_cache_t *cache = cached ? (hb_ot_lookup_cache_t *) c->lookup_accel->cache : nullptr;
+    hb_ot_layout_mapping_cache_t *cache = cached ? (hb_ot_layout_mapping_cache_t *) c->lookup_accel->subtable_cache : nullptr;
     unsigned int index = (this+coverage).get_coverage  (buffer->cur().codepoint, cache);
 #else
     unsigned int index = (this+coverage).get_coverage  (buffer->cur().codepoint);
diff --git a/src/OT/Layout/GPOS/PairPosFormat2.hh b/src/OT/Layout/GPOS/PairPosFormat2.hh
index 59c018b..e5023fa 100644
--- a/src/OT/Layout/GPOS/PairPosFormat2.hh
+++ b/src/OT/Layout/GPOS/PairPosFormat2.hh
@@ -125,20 +125,20 @@
 
   struct pair_pos_cache_t
   {
-    hb_ot_lookup_cache_t coverage;
-    hb_ot_lookup_cache_t first;
-    hb_ot_lookup_cache_t second;
+    hb_ot_layout_mapping_cache_t coverage;
+    hb_ot_layout_mapping_cache_t first;
+    hb_ot_layout_mapping_cache_t second;
   };
 
   unsigned cache_cost () const
   {
     return (this+coverage).cost () + (this+classDef1).cost () + (this+classDef2).cost ();
   }
-  static void * cache_func (void *p, hb_ot_lookup_cache_op_t op)
+  static void * cache_func (void *p, hb_ot_subtable_cache_op_t op)
   {
     switch (op)
     {
-      case hb_ot_lookup_cache_op_t::CREATE:
+      case hb_ot_subtable_cache_op_t::CREATE:
       {
 	pair_pos_cache_t *cache = (pair_pos_cache_t *) hb_malloc (sizeof (pair_pos_cache_t));
 	if (likely (cache))
@@ -149,11 +149,11 @@
 	}
 	return cache;
       }
-      case hb_ot_lookup_cache_op_t::ENTER:
+      case hb_ot_subtable_cache_op_t::ENTER:
 	return (void *) true;
-      case hb_ot_lookup_cache_op_t::LEAVE:
+      case hb_ot_subtable_cache_op_t::LEAVE:
 	return nullptr;
-      case hb_ot_lookup_cache_op_t::DESTROY:
+      case hb_ot_subtable_cache_op_t::DESTROY:
 	{
 	  pair_pos_cache_t *cache = (pair_pos_cache_t *) p;
 	  hb_free (cache);
@@ -172,7 +172,7 @@
     hb_buffer_t *buffer = c->buffer;
 
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
-    pair_pos_cache_t *cache = cached ? (pair_pos_cache_t *) c->lookup_accel->cache : nullptr;
+    pair_pos_cache_t *cache = cached ? (pair_pos_cache_t *) c->lookup_accel->subtable_cache : nullptr;
     unsigned int index = (this+coverage).get_coverage  (buffer->cur().codepoint, cache ? &cache->coverage : nullptr);
 #else
     unsigned int index = (this+coverage).get_coverage  (buffer->cur().codepoint);
diff --git a/src/OT/Layout/GSUB/LigatureSubstFormat1.hh b/src/OT/Layout/GSUB/LigatureSubstFormat1.hh
index 2ef4614..26dba65 100644
--- a/src/OT/Layout/GSUB/LigatureSubstFormat1.hh
+++ b/src/OT/Layout/GSUB/LigatureSubstFormat1.hh
@@ -82,24 +82,24 @@
   {
     return (this+coverage).cost ();
   }
-  static void * cache_func (void *p, hb_ot_lookup_cache_op_t op)
+  static void * cache_func (void *p, hb_ot_subtable_cache_op_t op)
   {
     switch (op)
     {
-      case hb_ot_lookup_cache_op_t::CREATE:
+      case hb_ot_subtable_cache_op_t::CREATE:
       {
-	hb_ot_lookup_cache_t *cache = (hb_ot_lookup_cache_t *) hb_malloc (sizeof (hb_ot_lookup_cache_t));
+	hb_ot_layout_mapping_cache_t *cache = (hb_ot_layout_mapping_cache_t *) hb_malloc (sizeof (hb_ot_layout_mapping_cache_t));
 	if (likely (cache))
 	  cache->clear ();
 	return cache;
       }
-      case hb_ot_lookup_cache_op_t::ENTER:
+      case hb_ot_subtable_cache_op_t::ENTER:
 	return (void *) true;
-      case hb_ot_lookup_cache_op_t::LEAVE:
+      case hb_ot_subtable_cache_op_t::LEAVE:
 	return nullptr;
-      case hb_ot_lookup_cache_op_t::DESTROY:
+      case hb_ot_subtable_cache_op_t::DESTROY:
       {
-	hb_ot_lookup_cache_t *cache = (hb_ot_lookup_cache_t *) p;
+	hb_ot_layout_mapping_cache_t *cache = (hb_ot_layout_mapping_cache_t *) p;
 	hb_free (cache);
 	return nullptr;
       }
@@ -115,7 +115,7 @@
     hb_buffer_t *buffer = c->buffer;
 
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
-    hb_ot_lookup_cache_t *cache = cached ? (hb_ot_lookup_cache_t *) c->lookup_accel->cache : nullptr;
+    hb_ot_layout_mapping_cache_t *cache = cached ? (hb_ot_layout_mapping_cache_t *) c->lookup_accel->subtable_cache : nullptr;
     unsigned int index = (this+coverage).get_coverage  (buffer->cur().codepoint, cache);
 #else
     unsigned int index = (this+coverage).get_coverage  (buffer->cur().codepoint);
diff --git a/src/OT/Layout/types.hh b/src/OT/Layout/types.hh
index 527f641..6e412e5 100644
--- a/src/OT/Layout/types.hh
+++ b/src/OT/Layout/types.hh
@@ -29,8 +29,8 @@
 #ifndef OT_LAYOUT_TYPES_HH
 #define OT_LAYOUT_TYPES_HH
 
-using hb_ot_lookup_cache_t = hb_cache_t<15, 8, 7>;
-static_assert (sizeof (hb_ot_lookup_cache_t) == 256, "");
+using hb_ot_layout_mapping_cache_t = hb_cache_t<15, 8, 7>;
+static_assert (sizeof (hb_ot_layout_mapping_cache_t) == 256, "");
 
 namespace OT {
 namespace Layout {
diff --git a/src/OT/Var/VARC/VARC.cc b/src/OT/Var/VARC/VARC.cc
index 9e4fa3c..8c93fcf 100644
--- a/src/OT/Var/VARC/VARC.cc
+++ b/src/OT/Var/VARC/VARC.cc
@@ -11,6 +11,8 @@
 //namespace Var {
 
 
+#ifndef HB_NO_DRAW
+
 struct hb_transforming_pen_context_t
 {
   hb_transform_t<> transform;
@@ -355,6 +357,9 @@
       hb_draw_session_t transformer_session {transformer_funcs, &context};
       hb_draw_session_t &shape_draw_session = transform.is_identity () ? *c.draw_session : transformer_session;
 
+#ifndef HB_NO_VAR_HVF
+      if (c.font->face->table.hvgl->get_path_at (c.font, glyph, shape_draw_session, coords)) return true;
+#endif
       if (c.font->face->table.glyf->get_path_at (c.font, glyph, shape_draw_session, coords, c.scratch.glyf_scratch)) return true;
 #ifndef HB_NO_CFF
       if (c.font->face->table.cff2->get_path_at (c.font, glyph, shape_draw_session, coords)) return true;
@@ -411,6 +416,8 @@
   return true;
 }
 
+#endif
+
 //} // namespace Var
 } // namespace OT
 
diff --git a/src/OT/Var/VARC/VARC.hh b/src/OT/Var/VARC/VARC.hh
index a372701..3f6de0c 100644
--- a/src/OT/Var/VARC/VARC.hh
+++ b/src/OT/Var/VARC/VARC.hh
@@ -3,6 +3,7 @@
 
 #include "../../../hb-decycler.hh"
 #include "../../../hb-geometry.hh"
+#include "../../../hb-aat-var-hvgl-table.hh"
 #include "../../../hb-ot-layout-common.hh"
 #include "../../../hb-ot-glyf-table.hh"
 #include "../../../hb-ot-cff2-table.hh"
@@ -194,6 +195,7 @@
 		 hb_codepoint_t gid,
 		 hb_glyph_extents_t *extents) const
     {
+#ifndef HB_NO_DRAW
       if (!table->has_data ()) return false;
 
       hb_extents_t<> f_extents;
@@ -207,6 +209,9 @@
 	*extents = f_extents.to_glyph_extents (font->x_scale < 0, font->y_scale < 0);
 
       return ret;
+#else
+      return false;
+#endif
     }
 
     private:
diff --git a/src/OT/glyf/Glyph.hh b/src/OT/glyf/Glyph.hh
index f8a396b..a3ec885 100644
--- a/src/OT/glyf/Glyph.hh
+++ b/src/OT/glyf/Glyph.hh
@@ -533,7 +533,11 @@
   bool get_extents_without_var_scaled (hb_font_t *font, const glyf_accelerator_t &glyf_accelerator,
 				       hb_glyph_extents_t *extents) const
   {
-    if (type == EMPTY) return true; /* Empty glyph; zero extents. */
+    if (type == EMPTY)
+    {
+      *extents = {0, 0, 0, 0};
+      return true; /* Empty glyph; zero extents. */
+    }
     return header->get_extents_without_var_scaled (font, glyf_accelerator, gid, extents);
   }
 
diff --git a/src/OT/glyf/glyf.hh b/src/OT/glyf/glyf.hh
index b821dd3..71989d8 100644
--- a/src/OT/glyf/glyf.hh
+++ b/src/OT/glyf/glyf.hh
@@ -445,8 +445,7 @@
     if (coords)
     {
       hb_glyf_scratch_t *scratch = acquire_scratch ();
-      if (unlikely (!scratch))
-        return false;
+      if (unlikely (!scratch)) return false;
       bool ret = get_points (font,
 			     gid,
 			     points_aggregator_t (font, extents, nullptr, true),
@@ -493,8 +492,7 @@
     if (!has_data ()) return false;
 
     hb_glyf_scratch_t *scratch = acquire_scratch ();
-    if (unlikely (!scratch))
-      return true;
+    if (unlikely (!scratch)) return true;
 
     bool ret = get_points (font, gid, glyf_impl::path_builder_t (font, draw_session),
 			   hb_array (font->coords,
@@ -523,6 +521,7 @@
 
   hb_glyf_scratch_t *acquire_scratch () const
   {
+    if (!has_data ()) return nullptr;
     hb_glyf_scratch_t *scratch = cached_scratch.get_acquire ();
     if (!scratch || unlikely (!cached_scratch.cmpexch (scratch, nullptr)))
     {
@@ -534,6 +533,8 @@
   }
   void release_scratch (hb_glyf_scratch_t *scratch) const
   {
+    if (!scratch)
+      return;
     if (!cached_scratch.cmpexch (nullptr, scratch))
     {
       scratch->~hb_glyf_scratch_t ();
diff --git a/src/graph/graph.hh b/src/graph/graph.hh
index 332f271..0b538c3 100644
--- a/src/graph/graph.hh
+++ b/src/graph/graph.hh
@@ -965,9 +965,9 @@
    */
   template<typename O>
   unsigned move_child (unsigned old_parent_idx,
-                   const O* old_offset,
-                   unsigned new_parent_idx,
-                   const O* new_offset)
+                       const O* old_offset,
+                       unsigned new_parent_idx,
+                       const O* new_offset)
   {
     distance_invalid = true;
     positions_invalid = true;
@@ -993,6 +993,50 @@
   }
 
   /*
+   * Moves all outgoing links in old parent that have
+   * a link position between [old_post_start, old_pos_end)
+   * to the new parent. Links are placed serially in the new
+   * parent starting at new_pos_start.
+   */
+  template<typename O>
+  void move_children (unsigned old_parent_idx,
+                      unsigned old_pos_start,
+                      unsigned old_pos_end,
+                      unsigned new_parent_idx,
+                      unsigned new_pos_start)
+  {
+    distance_invalid = true;
+    positions_invalid = true;
+
+    auto& old_v = vertices_[old_parent_idx];
+    auto& new_v = vertices_[new_parent_idx];
+
+    hb_vector_t<hb_serialize_context_t::object_t::link_t> old_links;
+    for (const auto& l : old_v.obj.real_links)
+    {
+      if (l.position < old_pos_start || l.position >= old_pos_end)
+      {
+        old_links.push(l);
+        continue;
+      }
+
+      unsigned array_pos = l.position - old_pos_start;
+
+      unsigned child_id = l.objidx;
+      auto* new_link = new_v.obj.real_links.push ();
+      new_link->width = O::static_size;
+      new_link->objidx = child_id;
+      new_link->position = new_pos_start + array_pos;
+
+      auto& child = vertices_[child_id];
+      child.add_parent (new_parent_idx, false);
+      child.remove_parent (old_parent_idx);
+    }
+
+    old_v.obj.real_links = std::move (old_links);
+  }
+
+  /*
    * duplicates all nodes in the subgraph reachable from node_idx. Does not re-assign
    * links. index_map is updated with mappings from old id to new id. If a duplication has already
    * been performed for a given index, then it will be skipped.
diff --git a/src/graph/ligature-graph.hh b/src/graph/ligature-graph.hh
index 611ab68..fd170bd 100644
--- a/src/graph/ligature-graph.hh
+++ b/src/graph/ligature-graph.hh
@@ -105,6 +105,23 @@
     return result;
   }
 
+  hb_vector_t<unsigned> ligature_index_to_object_id(const graph_t::vertex_and_table_t<LigatureSet>& liga_set) const {
+    hb_vector_t<unsigned> map;
+    map.resize_exact(liga_set.table->ligature.len);
+    if (map.in_error()) return map;
+
+    for (unsigned i = 0; i < map.length; i++) {
+      map[i] = (unsigned) -1;
+    }
+
+    for (const auto& l : liga_set.vertex->obj.real_links) {
+      if (l.position < 2) continue;
+      unsigned array_index = (l.position - 2) / 2;
+      map[array_index] = l.objidx;
+    }
+    return map;
+  }
+
   hb_vector_t<unsigned> compute_split_points(gsubgpos_graph_context_t& c,
                                              unsigned parent_index,
                                              unsigned this_index) const
@@ -128,9 +145,16 @@
         return hb_vector_t<unsigned> {};
       }
 
+      // Finding the object id associated with an array index is O(n)
+      // so to avoid O(n^2), precompute the mapping by scanning through
+      // all links
+      auto index_to_id = ligature_index_to_object_id(liga_set);
+      if (index_to_id.in_error()) return hb_vector_t<unsigned>();
+
       for (unsigned j = 0; j < liga_set.table->ligature.len; j++)
       {
-        const unsigned liga_id = c.graph.index_for_offset (liga_set.index, &liga_set.table->ligature[j]);
+        const unsigned liga_id = index_to_id[j];
+        if (liga_id == (unsigned) -1) continue; // no outgoing link, ignore
         const unsigned liga_size = c.graph.vertices_[liga_id].table_size ();
 
         accumulated += OT::HBUINT16::static_size; // for ligature offset
@@ -154,7 +178,6 @@
     return split_points;
   }
 
-
   struct split_context_t
   {
     gsubgpos_graph_context_t& c;
@@ -323,19 +346,19 @@
       {
         // This liga set partially overlaps [start, end). We'll need to create
         // a new liga set sub table and move the intersecting ligas to it.
-        unsigned liga_count = hb_min(end, current_end) - hb_max(start, current_start);
+        unsigned start_index = hb_max(start, current_start) - count;
+        unsigned end_index = hb_min(end, current_end) - count;
+        unsigned liga_count = end_index - start_index;
         auto result = new_liga_set(c, liga_count);
         liga_set_prime_id = result.first;
-        LigatureSet* prime = result.second;
         if (liga_set_prime_id == (unsigned) -1) return -1;
 
-        unsigned new_index = 0;
-        for (unsigned j = hb_max(start, current_start) - count; j < hb_min(end, current_end) - count; j++) {
-          c.graph.move_child<> (liga_set_index,
-                                &liga_set.table->ligature[j],
-                                liga_set_prime_id,
-                                &prime->ligature[new_index++]);
-        }
+        c.graph.move_children<OT::Offset16>(
+          liga_set_index,
+          2 + start_index * 2,
+          2 + end_index * 2,
+          liga_set_prime_id,
+          2);
 
         liga_set_end = i;
         if (i < liga_set_start) liga_set_start = i;
@@ -392,8 +415,12 @@
       // duplicated. Code later on will re-add the virtual links as needed (via retained_indices).
       clear_virtual_links(c, liga_set.index);
       retained_indices.add(liga_set.index);
-      for (const auto& liga_offset : liga_set.table->ligature) {
-        unsigned liga_index = c.graph.index_for_offset(liga_set.index, &liga_offset);
+
+      auto index_to_id = ligature_index_to_object_id(liga_set);
+      if (index_to_id.in_error()) return false;
+
+      for (unsigned i = 0; i < liga_set.table->ligature.len; i++) {
+        unsigned liga_index = index_to_id[i];
         if (liga_index != (unsigned) -1) {
           clear_virtual_links(c, liga_index);
           retained_indices.add(liga_index);
diff --git a/src/graph/test-classdef-graph.cc b/src/graph/test-classdef-graph.cc
index 2da9348..d3e8d89 100644
--- a/src/graph/test-classdef-graph.cc
+++ b/src/graph/test-classdef-graph.cc
@@ -38,7 +38,7 @@
   hb_serialize_context_t serializer(buffer, 100);
   OT::ClassDef_serialize (&serializer, glyph_and_class);
   serializer.end_serialize ();
-  assert(!serializer.in_error());
+  hb_always_assert(!serializer.in_error());
 
   hb_blob_t* blob = serializer.copy_blob();
   unsigned size = hb_blob_get_length(blob);
@@ -66,7 +66,7 @@
   hb_serialize_context_t serializer(buffer, 100);
   OT::Layout::Common::Coverage_serialize (&serializer, glyphs);
   serializer.end_serialize ();
-  assert(!serializer.in_error());
+  hb_always_assert(!serializer.in_error());
 
   hb_blob_t* blob = serializer.copy_blob();
   unsigned size = hb_blob_get_length(blob);
@@ -101,9 +101,9 @@
   return true;
 }
 
-static bool check_add_class_def_size(graph::class_def_size_estimator_t& estimator,
-                                     const gid_and_class_list_t& map,
-                                     unsigned klass, hb_vector_t<unsigned> klasses)
+static HB_UNUSED bool check_add_class_def_size(graph::class_def_size_estimator_t& estimator,
+					       const gid_and_class_list_t& map,
+					       unsigned klass, hb_vector_t<unsigned> klasses)
 {
   unsigned result = estimator.add_class_def_size(klass);
   unsigned expected = actual_class_def_size(map, klasses);
@@ -115,7 +115,7 @@
   return check_coverage_size(estimator, map, klasses);
 }
 
-static bool check_add_class_def_size (const gid_and_class_list_t& list, unsigned klass)
+static HB_UNUSED bool check_add_class_def_size (const gid_and_class_list_t& list, unsigned klass)
 {
   graph::class_def_size_estimator_t estimator (list.iter ());
 
@@ -149,13 +149,13 @@
 {
   gid_and_class_list_t empty = {
   };
-  assert (check_add_class_def_size (empty, 0));
-  assert (check_add_class_def_size (empty, 1));
+  hb_always_assert (check_add_class_def_size (empty, 0));
+  hb_always_assert (check_add_class_def_size (empty, 1));
 
   gid_and_class_list_t class_zero = {
     {5, 0},
   };
-  assert (check_add_class_def_size (class_zero, 0));
+  hb_always_assert (check_add_class_def_size (class_zero, 0));
 
   gid_and_class_list_t consecutive = {
     {4, 0},
@@ -169,9 +169,9 @@
     {10, 2},
     {11, 2},
   };
-  assert (check_add_class_def_size (consecutive, 0));
-  assert (check_add_class_def_size (consecutive, 1));
-  assert (check_add_class_def_size (consecutive, 2));
+  hb_always_assert (check_add_class_def_size (consecutive, 0));
+  hb_always_assert (check_add_class_def_size (consecutive, 1));
+  hb_always_assert (check_add_class_def_size (consecutive, 2));
 
   gid_and_class_list_t non_consecutive = {
     {4, 0},
@@ -185,9 +185,9 @@
     {11, 2},
     {13, 2},
   };
-  assert (check_add_class_def_size (non_consecutive, 0));
-  assert (check_add_class_def_size (non_consecutive, 1));
-  assert (check_add_class_def_size (non_consecutive, 2));
+  hb_always_assert (check_add_class_def_size (non_consecutive, 0));
+  hb_always_assert (check_add_class_def_size (non_consecutive, 1));
+  hb_always_assert (check_add_class_def_size (non_consecutive, 2));
 
   gid_and_class_list_t multiple_ranges = {
     {4, 0},
@@ -202,8 +202,8 @@
     {12, 1},
     {13, 1},
   };
-  assert (check_add_class_def_size (multiple_ranges, 0));
-  assert (check_add_class_def_size (multiple_ranges, 1));
+  hb_always_assert (check_add_class_def_size (multiple_ranges, 0));
+  hb_always_assert (check_add_class_def_size (multiple_ranges, 1));
 }
 
 static void test_running_class_and_coverage_size_estimates () {
@@ -229,14 +229,14 @@
   };
 
   graph::class_def_size_estimator_t estimator1(consecutive_map.iter());
-  assert(check_add_class_def_size(estimator1, consecutive_map, 1, {1}));
-  assert(check_add_class_def_size(estimator1, consecutive_map, 2, {1, 2}));
-  assert(check_add_class_def_size(estimator1, consecutive_map, 2, {1, 2})); // check that adding the same class again works
-  assert(check_add_class_def_size(estimator1, consecutive_map, 3, {1, 2, 3}));
+  hb_always_assert(check_add_class_def_size(estimator1, consecutive_map, 1, {1}));
+  hb_always_assert(check_add_class_def_size(estimator1, consecutive_map, 2, {1, 2}));
+  hb_always_assert(check_add_class_def_size(estimator1, consecutive_map, 2, {1, 2})); // check that adding the same class again works
+  hb_always_assert(check_add_class_def_size(estimator1, consecutive_map, 3, {1, 2, 3}));
 
   estimator1.reset();
-  assert(check_add_class_def_size(estimator1, consecutive_map, 2, {2}));
-  assert(check_add_class_def_size(estimator1, consecutive_map, 3, {2, 3}));
+  hb_always_assert(check_add_class_def_size(estimator1, consecutive_map, 2, {2}));
+  hb_always_assert(check_add_class_def_size(estimator1, consecutive_map, 3, {2, 3}));
 
   // #### With non-consecutive gids: always uses format 2 ###
   gid_and_class_list_t non_consecutive_map = {
@@ -261,13 +261,13 @@
   };
 
   graph::class_def_size_estimator_t estimator2(non_consecutive_map.iter());
-  assert(check_add_class_def_size(estimator2, non_consecutive_map, 1, {1}));
-  assert(check_add_class_def_size(estimator2, non_consecutive_map, 2, {1, 2}));
-  assert(check_add_class_def_size(estimator2, non_consecutive_map, 3, {1, 2, 3}));
+  hb_always_assert(check_add_class_def_size(estimator2, non_consecutive_map, 1, {1}));
+  hb_always_assert(check_add_class_def_size(estimator2, non_consecutive_map, 2, {1, 2}));
+  hb_always_assert(check_add_class_def_size(estimator2, non_consecutive_map, 3, {1, 2, 3}));
 
   estimator2.reset();
-  assert(check_add_class_def_size(estimator2, non_consecutive_map, 2, {2}));
-  assert(check_add_class_def_size(estimator2, non_consecutive_map, 3, {2, 3}));
+  hb_always_assert(check_add_class_def_size(estimator2, non_consecutive_map, 2, {2}));
+  hb_always_assert(check_add_class_def_size(estimator2, non_consecutive_map, 3, {2, 3}));
 }
 
 static void test_running_class_size_estimates_with_locally_consecutive_glyphs () {
@@ -278,13 +278,13 @@
   };
 
   graph::class_def_size_estimator_t estimator(map.iter());
-  assert(check_add_class_def_size(estimator, map, 1, {1}));
-  assert(check_add_class_def_size(estimator, map, 2, {1, 2}));
-  assert(check_add_class_def_size(estimator, map, 3, {1, 2, 3}));
+  hb_always_assert(check_add_class_def_size(estimator, map, 1, {1}));
+  hb_always_assert(check_add_class_def_size(estimator, map, 2, {1, 2}));
+  hb_always_assert(check_add_class_def_size(estimator, map, 3, {1, 2, 3}));
 
   estimator.reset();
-  assert(check_add_class_def_size(estimator, map, 2, {2}));
-  assert(check_add_class_def_size(estimator, map, 3, {2, 3}));
+  hb_always_assert(check_add_class_def_size(estimator, map, 2, {2}));
+  hb_always_assert(check_add_class_def_size(estimator, map, 3, {2, 3}));
 }
 
 int
diff --git a/src/harfbuzz-subset.cc b/src/harfbuzz-subset.cc
index 79fac9e..010939a 100644
--- a/src/harfbuzz-subset.cc
+++ b/src/harfbuzz-subset.cc
@@ -2,6 +2,7 @@
 #include "graph/gsubgpos-context.cc"
 #include "hb-aat-layout.cc"
 #include "hb-aat-map.cc"
+#include "hb-aat-var-hvgl-table.cc"
 #include "hb-blob.cc"
 #include "hb-buffer-serialize.cc"
 #include "hb-buffer-verify.cc"
@@ -62,6 +63,11 @@
 #include "hb-subset-plan-var.cc"
 #include "hb-subset-plan.cc"
 #include "hb-subset-serialize.cc"
+#include "hb-subset-table-cff.cc"
+#include "hb-subset-table-color.cc"
+#include "hb-subset-table-layout.cc"
+#include "hb-subset-table-other.cc"
+#include "hb-subset-table-var.cc"
 #include "hb-subset.cc"
 #include "hb-ucd.cc"
 #include "hb-unicode.cc"
diff --git a/src/harfbuzz.cc b/src/harfbuzz.cc
index 471942c..d2b487a 100644
--- a/src/harfbuzz.cc
+++ b/src/harfbuzz.cc
@@ -1,6 +1,7 @@
 #include "OT/Var/VARC/VARC.cc"
 #include "hb-aat-layout.cc"
 #include "hb-aat-map.cc"
+#include "hb-aat-var-hvgl-table.cc"
 #include "hb-blob.cc"
 #include "hb-buffer-serialize.cc"
 #include "hb-buffer-verify.cc"
diff --git a/src/hb-aat-var-hvgl-table.cc b/src/hb-aat-var-hvgl-table.cc
new file mode 100644
index 0000000..c02ab4e
--- /dev/null
+++ b/src/hb-aat-var-hvgl-table.cc
@@ -0,0 +1,622 @@
+#include "hb-aat-var-hvgl-table.hh"
+
+#ifdef __APPLE__
+// For endianness
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#if defined(HB_NO_SIMD)
+#define HB_NO_APPLE_SIMD
+#endif
+
+#if !defined(HB_NO_APPLE_SIMD) && !(defined(__APPLE__) && \
+  (!defined(MAC_OS_X_VERSION_MIN_REQUIRED) || MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) \
+)
+#define HB_NO_APPLE_SIMD
+#endif
+
+#ifndef HB_NO_APPLE_SIMD
+#include <simd/simd.h> // Apple SIMD https://developer.apple.com/documentation/accelerate/simd
+#endif
+
+#ifndef HB_NO_SIMD
+#ifdef __AVX2__
+#include <immintrin.h>
+#endif
+#endif
+
+
+#ifndef HB_NO_VAR_HVF
+
+namespace AAT {
+
+namespace hvgl_impl {
+
+
+enum
+segment_point_t
+{
+  SEGMENT_POINT_ON_CURVE_X = 0,
+  SEGMENT_POINT_ON_CURVE_Y = 1,
+  SEGMENT_POINT_OFF_CURVE_X = 2,
+  SEGMENT_POINT_OFF_CURVE_Y = 3,
+};
+
+enum
+blend_type_t
+{
+  BLEND_TYPE_CURVE = 0,
+  BLEND_TYPE_CORNER = 1,
+  BLEND_TYPE_TANGENT = 2,
+  BLEND_TYPE_TANGENT_PAIR_FIRST = 3,
+  BLEND_TYPE_TANGENT_PAIR_SECOND = 4,
+};
+
+using segment_t = double*;
+
+static void
+project_on_curve_to_tangent (const segment_t offcurve1,
+			     segment_t oncurve,
+			     const segment_t offcurve2)
+{
+  double &x = oncurve[SEGMENT_POINT_ON_CURVE_X];
+  double &y = oncurve[SEGMENT_POINT_ON_CURVE_Y];
+
+  double x1 = offcurve1[SEGMENT_POINT_OFF_CURVE_X];
+  double y1 = offcurve1[SEGMENT_POINT_OFF_CURVE_Y];
+  double x2 = offcurve2[SEGMENT_POINT_OFF_CURVE_X];
+  double y2 = offcurve2[SEGMENT_POINT_OFF_CURVE_Y];
+
+  double dx = x2 - x1;
+  double dy = y2 - y1;
+
+  double l2 = dx * dx + dy * dy;
+  double t = l2 ? (dx * (x - x1) + dy * (y - y1)) / l2 : 0;
+  t = hb_clamp (t, 0, 1);
+
+  x = x1 + dx * t;
+  y = y1 + dy * t;
+}
+
+#ifndef HB_NO_SIMD
+#ifdef __AVX2__
+__attribute__((target("avx2")))
+#endif
+#ifdef __FMA__
+__attribute__((target("fma")))
+#endif
+#endif
+void
+PartShape::get_path_at (const hb_hvgl_context_t *c,
+		        hb_array_t<const double> coords,
+			hb_array_t<hb_transform_t<double>> transforms) const
+{
+  hb_transform_t<double> transform = transforms[0];
+
+  const auto &blendTypes = StructAfter<decltype (blendTypesX)> (segmentCountPerPath, pathCount);
+
+  const auto &padding = StructAfter<decltype (paddingX)> (blendTypes, segmentCount);
+  const auto &coordinates = StructAfter<decltype (coordinatesX)> (padding, this);
+
+  auto a = coordinates.get_coords (segmentCount);
+  auto &v = c->scratch.points;
+
+#ifdef __BYTE_ORDER
+  constexpr bool le = __BYTE_ORDER == __LITTLE_ENDIAN;
+#elif defined(__APPLE__)
+  bool le = CFByteOrderGetCurrent () == CFByteOrderLittleEndian;
+#else
+  constexpr bool le = false;
+#endif
+
+  if (le)
+  {
+    // Endianness matches; Faster to memcpy().
+    v.resize (a.length, false);
+    memcpy (v.arrayZ, a.arrayZ, v.length * sizeof (v[0]));
+  }
+  else
+  {
+    v.resize (0);
+    v.extend (a);
+  }
+
+  if (unlikely (v.in_error ()))
+    return;
+
+  coords = coords.sub_array (0, axisCount);
+  // Apply deltas
+  if (coords)
+  {
+    unsigned rows_count = v.length;
+    const auto &deltas = StructAfter<decltype (deltasX)> (coordinates, segmentCount);
+    const auto matrix = deltas.get_matrix (axisCount, segmentCount).arrayZ;
+    unsigned axis_count = coords.length;
+    unsigned axis_index = 0;
+    HB_UNUSED bool src_aligned = (uintptr_t) matrix % 8 == 0;
+
+#ifndef HB_NO_APPLE_SIMD
+    // APPLE SIMD
+
+    // dest is always aligned.
+    if (le && src_aligned)
+    {
+      hb_barrier ();
+      simd_double4 coords4;
+      unsigned column_idx[4];
+      while (axis_index < axis_count)
+      {
+	unsigned j;
+	for (j = 0; j < 4; j++)
+	{
+	  while (axis_index < axis_count && !coords.arrayZ[axis_index])
+	    axis_index++;
+	  if (axis_index >= axis_count)
+	  {
+	    if (!j)
+	      break;
+	    for (; j < 4; j++)
+	    {
+	      coords4[j] = 0.;
+	      column_idx[j] = 0;
+	    }
+	    break;
+	  }
+	  double coord = (double) coords.arrayZ[axis_index];
+	  coords4[j] = coord;
+	  bool pos = coord > 0.;
+	  column_idx[j] = axis_index * 2 + pos;
+	  axis_index++;
+	}
+	if (!j)
+	  break;
+
+	simd_double4 scalar4 = simd_abs (coords4);
+	auto *delta0 = matrix + column_idx[0] * rows_count;
+	auto *delta1 = matrix + column_idx[1] * rows_count;
+	auto *delta2 = matrix + column_idx[2] * rows_count;
+	auto *delta3 = matrix + column_idx[3] * rows_count;
+
+	// Note: Count is always a multiple of 4
+	for (unsigned i = 0; i + 4 <= rows_count; i += 4)
+	{
+	  const auto &src0 = * (const simd_packed_double4 *) (void *) (delta0 + i);
+	  const auto &src1 = * (const simd_packed_double4 *) (void *) (delta1 + i);
+	  const auto &src2 = * (const simd_packed_double4 *) (void *) (delta2 + i);
+	  const auto &src3 = * (const simd_packed_double4 *) (void *) (delta3 + i);
+	  const auto matrix = simd_matrix (src0, src1, src2, src3);
+
+	  * (simd_packed_double4 *) (v.arrayZ + i) += simd_mul (matrix, scalar4);
+	}
+      }
+    }
+#endif
+
+    for (; axis_index < axis_count; axis_index++)
+    {
+      double coord = coords.arrayZ[axis_index];
+      if (!coord) continue;
+      bool pos = coord > 0.;
+      unsigned column_idx = axis_index * 2 + pos;
+      double scalar = fabs(coord);
+
+      const auto *src = matrix + column_idx * rows_count;
+      auto *dest = v.arrayZ;
+      unsigned i = 0;
+
+#ifndef HB_NO_SIMD
+      if (le && src_aligned && rows_count > 4)
+      {
+#ifdef __AVX2__
+	{
+	  __m256d scalar_vec = _mm256_set1_pd(scalar);
+	  for (; i + 4 <= rows_count; i += 4)
+	  {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-align"
+	    __m256d src_vec = _mm256_loadu_pd ((double *) &src[i]);
+#pragma GCC diagnostic pop
+	    __m256d dest_vec = _mm256_loadu_pd (&dest[i]);
+	    __m256d result =
+#ifdef __FMA__
+	      true ? _mm256_fmadd_pd (src_vec, scalar_vec, dest_vec) :
+#endif
+	      _mm256_add_pd (_mm256_mul_pd(src_vec, scalar_vec), dest_vec);
+
+	    _mm256_storeu_pd (&dest[i], result);
+	  }
+	}
+#endif
+      }
+#endif
+
+      // This loop is really hot
+      for (; i + 4 <= rows_count; i += 4)
+      {
+	dest[i] += src[i] * scalar;
+	dest[i + 1] += src[i + 1] * scalar;
+	dest[i + 2] += src[i + 2] * scalar;
+	dest[i + 3] += src[i + 3] * scalar;
+      }
+      // Note: Count is always a multiple of 4
+      // So, the following not needed.
+      if (false)
+	for (; i < rows_count; i++)
+	  dest[i] += src[i] * scalar;
+    }
+  }
+
+  // Resolve blend types, one path at a time, and draw.
+  unsigned start = 0;
+  for (unsigned pathSegmentCount : segmentCountPerPath.as_array (pathCount))
+  {
+    unsigned end = start + pathSegmentCount;
+
+    if (unlikely (end * 4 > v.length))
+      break;
+
+    if (unlikely (start == end))
+      continue;
+
+    // Resolve blend types
+    {
+      segment_t segment = &v.arrayZ[(end - 1) * 4];
+      for (unsigned i = start; i < end; i++)
+      {
+	unsigned blendType = blendTypes.arrayZ[i];
+	const segment_t prev_segment = segment;
+	segment = &v.arrayZ[i * 4];
+
+	switch (blendType)
+	{
+	default:
+	  break;
+
+	case BLEND_TYPE_CURVE:
+	  {
+	    double t = segment[SEGMENT_POINT_ON_CURVE_X];
+	    t = hb_clamp (t, 0, 1);
+
+	    /* Interpolate between the off-curve points */
+	    double x = prev_segment[SEGMENT_POINT_OFF_CURVE_X] + (segment[SEGMENT_POINT_OFF_CURVE_X] - prev_segment[SEGMENT_POINT_OFF_CURVE_X]) * t;
+	    double y = prev_segment[SEGMENT_POINT_OFF_CURVE_Y] + (segment[SEGMENT_POINT_OFF_CURVE_Y] - prev_segment[SEGMENT_POINT_OFF_CURVE_Y]) * t;
+
+	    segment[SEGMENT_POINT_ON_CURVE_X] = x;
+	    segment[SEGMENT_POINT_ON_CURVE_Y] = y;
+	  }
+	  break;
+
+	case BLEND_TYPE_CORNER:
+	  break;
+
+	case BLEND_TYPE_TANGENT:
+	  {
+	    /* Project onto the line between the off-curve point
+	     * of the previous segment and the off-curve point of
+	     * this segment */
+	    project_on_curve_to_tangent (prev_segment, segment, segment);
+	  }
+	  break;
+
+	case BLEND_TYPE_TANGENT_PAIR_FIRST:
+	  {
+	    unsigned next_i = i == end - 1 ? start : i + 1;
+	    segment_t next_segment = &v.arrayZ[next_i * 4];
+
+	    project_on_curve_to_tangent (prev_segment, segment, next_segment);
+	    project_on_curve_to_tangent (prev_segment, next_segment, next_segment);
+	  }
+	  break;
+	}
+      }
+    }
+
+    // Draw
+    {
+      segment_t next_segment = &v.arrayZ[start * 4];
+      double x0 = next_segment[SEGMENT_POINT_ON_CURVE_X];
+      double y0 = next_segment[SEGMENT_POINT_ON_CURVE_Y];
+      transform.transform_point (x0, y0);
+      if (c->draw_session)
+	c->draw_session->move_to ((float) x0, (float) y0);
+      else if (c->extents)
+	c->extents->add_point ((float) x0, (float) y0);
+      for (unsigned i = start; i < end; i++)
+      {
+	segment_t segment = next_segment;
+	unsigned next_i = i == end - 1 ? start : i + 1;
+	next_segment = &v.arrayZ[next_i * 4];
+
+	double x1 = segment[SEGMENT_POINT_OFF_CURVE_X];
+	double y1 = segment[SEGMENT_POINT_OFF_CURVE_Y];
+	double x2 = next_segment[SEGMENT_POINT_ON_CURVE_X];
+	double y2 = next_segment[SEGMENT_POINT_ON_CURVE_Y];
+	transform.transform_point (x1, y1);
+	transform.transform_point (x2, y2);
+	if (c->draw_session)
+	  c->draw_session->quadratic_to ((float) x1, (float) y1, (float) x2, (float) y2);
+	else if (c->extents)
+	{
+	  c->extents->add_point ((float) x1, (float) y1);
+	  c->extents->add_point ((float) x2, (float) y2);
+	}
+      }
+      if (c->draw_session)
+	c->draw_session->close_path ();
+    }
+
+    start = end;
+  }
+}
+
+void
+PartComposite::apply_to_coords (hb_array_t<double> out_coords,
+				hb_array_t<const double> coords) const
+{
+  const auto &ecs = StructAtOffset<ExtremumColumnStarts> (this, extremumColumnStartsOff4 * 4);
+  const auto &extremumColumnStart = ecs.extremumColumnStart;
+  const auto &masterRowIndex = StructAfter<decltype (ecs.masterRowIndexX)> (ecs.extremumColumnStart, 2 * axisCount + 1);
+  const auto &extremumRowIndex = StructAfter<decltype (ecs.extremumRowIndexX)> (masterRowIndex, sparseMasterAxisValueCount);
+
+  const auto &masterAxisValueDeltas = StructAtOffset<MasterAxisValueDeltas> (this, masterAxisValueDeltasOff4 * 4);
+  hb_array_t<const HBFLOAT32LE> master_axis_value_deltas = masterAxisValueDeltas.as_array (sparseMasterAxisValueCount);
+  const auto &extremumAxisValueDeltas = StructAtOffset<ExtremumAxisValueDeltas> (this, extremumAxisValueDeltasOff4 * 4);
+  hb_array_t<const HBFLOAT32LE> extremum_axis_value_deltas = extremumAxisValueDeltas.as_array (sparseExtremumAxisValueCount);
+
+  unsigned count = master_axis_value_deltas.length;
+  for (unsigned i = 0; i < count; i++)
+    out_coords[masterRowIndex.arrayZ[i]] += (double) master_axis_value_deltas.arrayZ[i];
+
+  unsigned axis_count = hb_min (axisCount, coords.length);
+  for (unsigned axis_idx = 0; axis_idx < axis_count; axis_idx++)
+  {
+    double coord = coords.arrayZ[axis_idx];
+    if (!coord) continue;
+    bool pos = coord > 0.;
+    unsigned column_idx = axis_idx * 2 + pos;
+
+    unsigned sparse_row_start = extremumColumnStart.arrayZ[column_idx];
+    unsigned sparse_row_end = extremumColumnStart.arrayZ[column_idx + 1];
+    if (sparse_row_start == sparse_row_end)
+      continue;
+
+    double scalar = fabs (coord);
+    sparse_row_end = hb_min (sparse_row_end, extremum_axis_value_deltas.length);
+    for (unsigned row_idx = sparse_row_start; row_idx < sparse_row_end; row_idx++)
+    {
+      unsigned row = extremumRowIndex.arrayZ[row_idx];
+      double delta = (double) extremum_axis_value_deltas.arrayZ[row_idx];
+      out_coords[row] += delta * scalar;
+    }
+  }
+}
+
+void
+PartComposite::apply_to_transforms (hb_array_t<hb_transform_t<double>> transforms,
+				    hb_array_t<const double> coords) const
+{
+  const auto &allTranslations = StructAtOffset<AllTranslations> (this, allTranslationsOff4 * 4);
+  const auto &masterTranslationDelta = allTranslations.masterTranslationDelta;
+  const auto &extremumTranslationDelta = StructAfter<decltype (allTranslations.extremumTranslationDeltaX)> (masterTranslationDelta, sparseMasterTranslationCount);
+  const auto &extremumTranslationIndex = StructAfter<decltype (allTranslations.extremumTranslationIndexX)> (extremumTranslationDelta, sparseExtremumTranslationCount);
+  const auto &masterTranslationIndex = StructAfter<decltype (allTranslations.masterTranslationIndexX)> (extremumTranslationIndex, sparseExtremumTranslationCount);
+
+  const auto &allRotations = StructAtOffset<AllRotations> (this, allRotationsOff4 * 4);
+  const auto &masterRotationDelta = allRotations.masterRotationDelta;
+  const auto &extremumRotationDelta = StructAfter<decltype (allRotations.extremumRotationDeltaX)> (masterRotationDelta, sparseMasterRotationCount);
+  const auto &extremumRotationIndex = StructAfter<decltype (allRotations.extremumRotationIndexX)> (extremumRotationDelta, sparseExtremumRotationCount);
+  const auto &masterRotationIndex = StructAfter<decltype (allRotations.masterRotationIndexX)> (extremumRotationIndex, sparseExtremumRotationCount);
+
+  /* Note that the spec says walk four iterators together.
+   * But with careful consideration, we have figured out the order
+   * to walk two, then one, then one. This seems to work for all
+   * glyphs in PingFangUI just fine.
+   *
+   * Moreover, for walking the two (extremum ones), if there is
+   * no rotation, we use a separate, faster, loop that just walks
+   * extremum translations.
+   *
+   * See the following commits:
+   *
+   *   [hvgl/transforms] Break up the four-iterator loop again
+   *   [hvgl/transforms] Break up some more
+   *   [hvgl] Fast-path when no extremum rotations are present
+   *
+   */
+
+  auto extremum_translation_indices = extremumTranslationIndex.arrayZ;
+  auto extremum_translation_deltas = extremumTranslationDelta.arrayZ;
+  unsigned extremum_translation_count = sparseExtremumTranslationCount;
+  auto extremum_rotation_indices = extremumRotationIndex.arrayZ;
+  auto extremum_rotation_deltas = extremumRotationDelta.arrayZ;
+  unsigned extremum_rotation_count = sparseExtremumRotationCount;
+
+  if (!extremum_rotation_count)
+  {
+    for (unsigned i = 0; i < extremum_translation_count; i++)
+    {
+      unsigned column = extremum_translation_indices[i].column;
+
+      unsigned axis_idx = column / 2;
+      double coord = coords[axis_idx];
+      if (!coord) continue;
+      bool pos = column & 1;
+      if (pos != (coord > 0)) continue;
+      double scalar = fabs (coord);
+
+      unsigned row = extremum_translation_indices[i].row;
+      if (unlikely (row >= transforms.length)) break;
+
+      transforms.arrayZ[row].translate ((double) extremum_translation_deltas[i].x * scalar,
+					(double) extremum_translation_deltas[i].y * scalar,
+					true);
+    }
+  }
+  else
+  {
+    while (true)
+    {
+      unsigned row = transforms.length;
+      if (extremum_translation_count)
+	row = hb_min (row, extremum_translation_indices->row);
+      if (extremum_rotation_count)
+	row = hb_min (row, extremum_rotation_indices->row);
+      if (row == transforms.length)
+	break;
+
+      hb_transform_t<double> transform;
+      bool is_translate_only = true;
+
+      while (true)
+      {
+	bool has_row_translation = extremum_translation_count &&
+				   extremum_translation_indices->row == row;
+	bool has_row_rotation = extremum_rotation_count &&
+				extremum_rotation_indices->row == row;
+
+	unsigned column = 2 * axisCount;
+	if (has_row_translation)
+	  column = hb_min (column, extremum_translation_indices->column);
+	if (has_row_rotation)
+	  column = hb_min (column, extremum_rotation_indices->column);
+	if (column == 2 * axisCount)
+	  break;
+
+	const auto *extremum_translation_delta = &Null(TranslationDelta);
+	double extremum_rotation_delta = 0.;
+
+	if (has_row_translation &&
+	    extremum_translation_indices->column == column)
+	{
+	  extremum_translation_delta = extremum_translation_deltas;
+	  extremum_translation_count--;
+	  extremum_translation_indices++;
+	  extremum_translation_deltas++;
+	}
+	if (has_row_rotation &&
+	    extremum_rotation_indices->column == column)
+	{
+	  extremum_rotation_delta = (double) *extremum_rotation_deltas;
+	  extremum_rotation_count--;
+	  extremum_rotation_indices++;
+	  extremum_rotation_deltas++;
+	}
+
+	unsigned axis_idx = column / 2;
+	double coord = coords[axis_idx];
+	if (!coord) continue;
+	bool pos = column & 1;
+	if (pos != (coord > 0)) continue;
+	double scalar = fabs (coord);
+
+	if (extremum_rotation_delta)
+	{
+	  double center_x = (double) extremum_translation_delta->x;
+	  double center_y = (double) extremum_translation_delta->y;
+	  double angle = extremum_rotation_delta;
+	  if (center_x || center_y)
+	  {
+	    // The paper has formula for this in terms of complex numbers.
+	    // This is translated to real numbers, partly using ChatGPT.
+	    double s, c;
+	    hb_sincos ((double) angle, s, c);
+	    double _1_minus_c = 1 - c;
+	    if (likely (_1_minus_c))
+	    {
+	      double s_over_1_minus_c = s / _1_minus_c;
+
+	      double new_center_x = (center_x - center_y * s_over_1_minus_c) * .5;
+	      double new_center_y = (center_y + center_x * s_over_1_minus_c) * .5;
+
+	      center_x = new_center_x;
+	      center_y = new_center_y;
+	    }
+	  }
+	  angle *= scalar;
+	  transform.rotate_around_center (angle, center_x, center_y);
+	  is_translate_only = false;
+	}
+	else
+	{
+	  // No rotation, just scale the translate
+	  transform.translate ((double) extremum_translation_delta->x * scalar,
+			       (double) extremum_translation_delta->y * scalar,
+			       is_translate_only);
+	}
+      }
+
+      if (is_translate_only)
+	transforms.arrayZ[row].translate (transform.x0, transform.y0, true);
+      else
+	transforms.arrayZ[row].transform (transform, true);
+    }
+  }
+
+  auto master_rotation_indices = masterRotationIndex.arrayZ;
+  auto master_rotation_deltas = masterRotationDelta.arrayZ;
+  unsigned master_rotation_count = sparseMasterRotationCount;
+  for (unsigned i = 0; i < master_rotation_count; i++)
+  {
+    unsigned row = master_rotation_indices[i];
+    if (unlikely (row >= transforms.length)) break;
+    transforms.arrayZ[row].rotate ((double) master_rotation_deltas[i], true);
+  }
+
+  auto master_translation_indices = masterTranslationIndex.arrayZ;
+  auto master_translation_deltas = masterTranslationDelta.arrayZ;
+  unsigned master_translation_count = sparseMasterTranslationCount;
+  for (unsigned i = 0; i < master_translation_count; i++)
+  {
+    unsigned row = master_translation_indices[i];
+    if (unlikely (row >= transforms.length)) break;
+    transforms.arrayZ[row].translate ((double) master_translation_deltas[i].x,
+				      (double) master_translation_deltas[i].y,
+				      true);
+  }
+}
+
+void
+PartComposite::get_path_at (const hb_hvgl_context_t *c,
+			    hb_array_t<double> coords,
+			    hb_array_t<hb_transform_t<double>> transforms) const
+{
+  const auto &subParts = StructAtOffset<SubParts> (this, subPartsOff4 * 4);
+
+  coords = coords.sub_array (0, totalNumAxes);
+  auto coords_head = coords.sub_array (0, axisCount);
+  auto coords_tail = coords.sub_array (axisCount);
+
+  apply_to_coords (coords_tail, coords_head);
+
+  transforms = transforms.sub_array (0, totalNumParts);
+  auto &transforms_head = transforms[0];
+  auto transforms_tail = transforms.sub_array (1);
+
+  apply_to_transforms (transforms_tail, coords_head);
+
+  for (const auto &subPart : subParts.as_array (subPartCount))
+  {
+    auto &this_transform = transforms_tail[subPart.treeTransformIndex];
+
+    if (likely (this_transform.is_translation ()))
+    {
+      double dx = this_transform.x0;
+      double dy = this_transform.y0;
+      this_transform = transforms_head;
+      this_transform.translate (dx, dy);
+    }
+    else
+      this_transform.transform (transforms_head, true);
+
+    c->hvgl_table.get_part_path_at (c,
+				    subPart.partIndex,
+				    coords_tail.sub_array (subPart.treeAxisIndex),
+				    transforms_tail.sub_array (subPart.treeTransformIndex));
+  }
+}
+
+
+} // namespace hvgl_impl
+} // namespace AAT
+
+#endif
diff --git a/src/hb-aat-var-hvgl-table.hh b/src/hb-aat-var-hvgl-table.hh
new file mode 100644
index 0000000..f4960bf
--- /dev/null
+++ b/src/hb-aat-var-hvgl-table.hh
@@ -0,0 +1,745 @@
+#ifndef HB_AAT_VAR_HVGL_TABLE_HH
+#define HB_AAT_VAR_HVGL_TABLE_HH
+
+#include "hb-bit-vector.hh"
+#include "hb-draw.hh"
+#include "hb-geometry.hh"
+#include "hb-ot-var-common.hh"
+
+/*
+ * `hvgl` table
+ */
+
+#ifndef HB_NO_VAR_HVF
+
+#define HB_AAT_TAG_hvgl HB_TAG('h','v','g','l')
+
+
+namespace AAT {
+
+using namespace OT;
+
+struct hb_hvgl_scratch_t
+{
+  hb_vector_t<double> coords_f;
+  hb_vector_t<hb_transform_t<double>> transforms;
+  hb_vector_t<double> points;
+};
+
+using hb_hvgl_parts_sanitized_t = hb_bit_vector_t<true>;
+
+struct hvgl;
+
+struct hb_hvgl_context_t
+{
+  const hvgl &hvgl_table;
+  hb_draw_session_t *draw_session;
+  hb_extents_t<> *extents;
+  hb_hvgl_scratch_t &scratch;
+  hb_sanitize_context_t &sanitizer;
+  hb_hvgl_parts_sanitized_t &parts_sanitized;
+  mutable signed nodes_left;
+  mutable signed edges_left;
+  mutable signed depth_left;
+};
+
+namespace hvgl_impl {
+
+struct coordinates_t
+{
+  friend struct PartShape;
+
+  public:
+
+  unsigned get_size (unsigned segment_count) const
+  { return coords.get_size (4 * segment_count); }
+
+  hb_array_t<const HBFLOAT64LE>
+  get_coords(unsigned segment_count) const
+  { return coords.as_array (segment_count * 4); }
+
+  bool sanitize (hb_sanitize_context_t *c,
+		 unsigned segment_count) const
+  {
+    TRACE_SANITIZE (this);
+    unsigned count;
+    return_trace (!hb_unsigned_mul_overflows (segment_count, 4, &count) &&
+		  coords.sanitize (c, count));
+  }
+
+  protected:
+  UnsizedArrayOf<HBFLOAT64LE> coords; // length: SegmentCount * 4
+
+  public:
+  DEFINE_SIZE_MIN (0);
+};
+
+struct deltas_t
+{
+  public:
+
+  unsigned get_size (unsigned axis_count, unsigned segment_count) const
+  { return matrix.get_size (2 * 4 * axis_count * segment_count); }
+
+  hb_array_t<const HBFLOAT64LE>
+  get_column(unsigned column_index, unsigned axis_count, unsigned segment_count) const
+  {
+    const unsigned rows = 4 * segment_count;
+    const unsigned start = column_index * rows;
+    return get_matrix (axis_count, segment_count).sub_array (start, rows);
+  }
+
+  hb_array_t<const HBFLOAT64LE>
+  get_matrix(unsigned axis_count, unsigned segment_count) const
+  { return matrix.as_array (2 * 4 * axis_count * segment_count); }
+
+  bool sanitize (hb_sanitize_context_t *c,
+		 unsigned axis_count,
+		 unsigned segment_count) const
+  {
+    TRACE_SANITIZE (this);
+    unsigned count;
+    return_trace (!hb_unsigned_mul_overflows (2 * 4, axis_count, &count) &&
+		  !hb_unsigned_mul_overflows (count, segment_count, &count) &&
+		  matrix.sanitize (c, count));
+  }
+
+  protected:
+  UnsizedArrayOf<HBFLOAT64LE> matrix; // column-major: AxisCount * 2 columns, DeltaSegmentCount * 4 rows
+
+  public:
+  DEFINE_SIZE_MIN (0);
+};
+
+struct PartShape
+{
+  public:
+
+  unsigned get_total_num_axes () const
+  { return axisCount; }
+
+  unsigned get_total_num_parts () const
+  { return 1; }
+
+  HB_INTERNAL void
+  get_path_at (const hb_hvgl_context_t *c,
+	       hb_array_t<const double> coords,
+	       hb_array_t<hb_transform_t<double>> transforms) const;
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    if (unlikely (!c->check_struct (this))) return_trace (false);
+    hb_barrier ();
+
+    const auto &blendTypes = StructAfter<decltype (blendTypesX)> (segmentCountPerPath, pathCount);
+    const auto &padding = StructAfter<decltype (paddingX)> (blendTypes, segmentCount);
+
+    if (unlikely (!(((const char *) this <= (const char *) &padding) &&
+		    c->check_range (this, (unsigned ((const char *) &padding - (const char *) this))))))
+      return_trace (false);
+
+    const auto &coordinates = StructAfter<decltype (coordinatesX)> (padding, this);
+    if (unlikely (!coordinates.sanitize (c, segmentCount))) return_trace (false);
+
+    const auto &deltas = StructAfter<decltype (deltasX)> (coordinates, segmentCount);
+    if (unlikely (!deltas.sanitize (c, axisCount, segmentCount))) return_trace (false);
+
+    return_trace (true);
+  }
+
+  protected:
+  HBUINT16LE flags; // 0x0001 for shape part
+  HBUINT16LE axisCount;
+  HBUINT16LE pathCount;
+  HBUINT16LE segmentCount;
+  UnsizedArrayOf<HBUINT16LE> segmentCountPerPath; // length: PathCount; Sizes of paths
+  UnsizedArrayOf<HBUINT8> blendTypesX; // length: SegmentCount; Blend types for all segments
+  Align<8> paddingX; // Pad to float64le alignment
+  coordinates_t coordinatesX; // Master coordinate vector
+  deltas_t deltasX; // Delta coordinate matrix
+  public:
+  DEFINE_SIZE_MIN (8);
+};
+
+struct SubPart
+{
+  public:
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this));
+  }
+
+  public:
+  HBUINT32LE partIndex; // Index of part that this subpart renders
+  HBUINT16LE treeTransformIndex; // Row index of data in transform vector/matrix
+  HBUINT16LE treeAxisIndex; // Row index of data in axis vector/matrix
+
+  public:
+  DEFINE_SIZE_STATIC (8);
+};
+
+using SubParts = UnsizedArrayOf<SubPart>; // length: subPartCount. Immediate subparts
+
+struct ExtremumColumnStarts
+{
+  friend struct PartComposite;
+
+  public:
+
+  bool sanitize (hb_sanitize_context_t *c,
+		 unsigned axis_count,
+		 unsigned sparse_master_axis_value_count,
+		 unsigned sparse_extremum_axis_value_count) const
+  {
+    TRACE_SANITIZE (this);
+
+    if (unlikely (!extremumColumnStart.sanitize (c, axis_count))) return_trace (false);
+
+    unsigned count;
+    if (unlikely (hb_unsigned_mul_overflows (axis_count, 2, &count))) return_trace (false);
+    if (unlikely (count + 1 < count)) return_trace (false);
+    count++;
+    const auto &masterRowIndex = StructAfter<decltype (masterRowIndexX)> (extremumColumnStart, count);
+    if (unlikely (!masterRowIndex.sanitize (c, sparse_master_axis_value_count))) return_trace (false);
+
+    const auto &extremumRowIndex = StructAfter<decltype (extremumRowIndexX)> (masterRowIndex, sparse_master_axis_value_count);
+    if (unlikely (!extremumRowIndex.sanitize (c, sparse_extremum_axis_value_count))) return_trace (false);
+
+    return_trace (true);
+  }
+
+  protected:
+  UnsizedArrayOf<HBUINT16LE> extremumColumnStart; // length: axisCount; Extremum column starts
+  UnsizedArrayOf<HBUINT16LE> masterRowIndexX; // length: sparseMasterAxisValueCount; Master row indices
+  UnsizedArrayOf<HBUINT16LE> extremumRowIndexX; // length: sparseExtremumAxisValueCount; Extremum row indices
+  Align<4> paddingX; // Pad to uint32le alignment
+
+  public:
+  DEFINE_SIZE_MIN (0);
+};
+
+using MasterAxisValueDeltas = UnsizedArrayOf<HBFLOAT32LE>; // length: sparseMasterAxisValueCount. Master axis value deltas
+
+using ExtremumAxisValueDeltas = UnsizedArrayOf<HBFLOAT32LE>; // length: sparseExtremumAxisValueCount. Extremum axis value deltas
+
+struct TranslationDelta
+{
+  public:
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this));
+  }
+
+  public:
+  HBFLOAT32LE x; // Translation delta X
+  HBFLOAT32LE y; // Translation delta Y
+
+  public:
+  DEFINE_SIZE_STATIC (8);
+};
+
+struct MatrixIndex
+{
+  public:
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (c->check_struct (this));
+  }
+
+  public:
+  HBUINT16LE row; // Row index
+  HBUINT16LE column; // Column index
+
+  public:
+  DEFINE_SIZE_STATIC (4);
+};
+
+struct AllTranslations
+{
+  friend struct PartComposite;
+
+  public:
+
+  bool sanitize (hb_sanitize_context_t *c,
+		 unsigned sparse_master_translation_count,
+		 unsigned sparse_extremum_translation_count) const
+  {
+    TRACE_SANITIZE (this);
+
+    const auto &extremumTranslationDelta = StructAfter<decltype (extremumTranslationDeltaX)> (masterTranslationDelta, sparse_master_translation_count);
+    const auto &extremumTranslationIndex = StructAfter<decltype (extremumTranslationIndexX)> (extremumTranslationDelta, sparse_extremum_translation_count);
+    const auto &masterTranslationIndex = StructAfter<decltype (masterTranslationIndexX)> (extremumTranslationIndex, sparse_extremum_translation_count);
+    const auto &padding = StructAfter<decltype (paddingX)> (masterTranslationIndex, sparse_master_translation_count);
+
+    return_trace (((const char *) this <= (const char *) &padding) &&
+		  c->check_range (this, (unsigned ((const char *) &padding - (const char *) this))));
+  }
+
+  protected:
+  UnsizedArrayOf<TranslationDelta> masterTranslationDelta; // length: sparse_master_translation_count; Master translation deltas
+  UnsizedArrayOf<TranslationDelta> extremumTranslationDeltaX; // length: sparse_extremum_translation_count; Extremum translation deltas
+  UnsizedArrayOf<MatrixIndex> extremumTranslationIndexX; // length: sparse_extremum_translation_count; Extremum translation indices
+  UnsizedArrayOf<HBUINT16LE> masterTranslationIndexX; // length: sparse_master_translation_count; Master translation indices
+  Align<4> paddingX; // Pad to float32le alignment
+
+  public:
+  DEFINE_SIZE_MIN (0);
+};
+
+struct AllRotations
+{
+  friend struct PartComposite;
+
+  public:
+
+  bool sanitize (hb_sanitize_context_t *c,
+		 unsigned sparse_master_rotation_count,
+		 unsigned sparse_extremum_rotation_count) const
+  {
+    TRACE_SANITIZE (this);
+
+    const auto &extremumRotationDelta = StructAfter<decltype (extremumRotationDeltaX)> (masterRotationDelta, sparse_master_rotation_count);
+    const auto &extremumRotationIndex = StructAfter<decltype (extremumRotationIndexX)> (extremumRotationDelta, sparse_extremum_rotation_count);
+    const auto &masterRotationIndex = StructAfter<decltype (masterRotationIndexX)> (extremumRotationIndex, sparse_extremum_rotation_count);
+    const auto &padding = StructAfter<decltype (paddingX)> (masterRotationIndex, sparse_master_rotation_count);
+
+    return_trace (((const char *) this <= (const char *) &padding) &&
+		  c->check_range (this, (unsigned ((const char *) &padding - (const char *) this))));
+  }
+
+  protected:
+  UnsizedArrayOf<HBFLOAT32LE> masterRotationDelta; // length: sparse_master_rotation_count; Master rotation deltas
+  UnsizedArrayOf<HBFLOAT32LE> extremumRotationDeltaX; // length: sparse_extremum_rotation_count; Extremum rotation deltas
+  UnsizedArrayOf<MatrixIndex> extremumRotationIndexX; // length: sparse_extremum_rotation_count; Extremum rotation indices
+  UnsizedArrayOf<HBUINT16LE> masterRotationIndexX; // length: sparse_master_rotation_count; Master rotation indices
+  Align<4> paddingX; // Pad to float32le alignment
+
+  public:
+  DEFINE_SIZE_MIN (0);
+};
+
+using Offset16LEMul4NN = Offset<HBUINT16LE, false>;
+
+struct PartComposite
+{
+  public:
+
+  HB_INTERNAL void apply_to_coords (hb_array_t<double> out_coords,
+				    hb_array_t<const double> coords) const;
+
+  HB_INTERNAL void apply_to_transforms (hb_array_t<hb_transform_t<double>> transforms,
+					hb_array_t<const double> coords) const;
+
+  unsigned get_total_num_axes () const
+  { return totalNumAxes; }
+
+  unsigned get_total_num_parts () const
+  { return totalNumParts; }
+
+  HB_INTERNAL void
+  get_path_at (const hb_hvgl_context_t *c,
+	       hb_array_t<double> coords,
+	       hb_array_t<hb_transform_t<double>> transforms) const;
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+
+    if (unlikely (!c->check_struct (this))) return_trace (false);
+
+    const auto &subParts = StructAtOffset<SubParts> (this, subPartsOff4 * 4);
+    const auto &extremumColumnStarts = StructAtOffset<ExtremumColumnStarts> (this, extremumColumnStartsOff4 * 4);
+    const auto &masterAxisValueDeltas = StructAtOffset<MasterAxisValueDeltas> (this, masterAxisValueDeltasOff4 * 4);
+    const auto &extremumAxisValueDeltas = StructAtOffset<ExtremumAxisValueDeltas> (this, extremumAxisValueDeltasOff4 * 4);
+    const auto &allTranslations = StructAtOffset<AllTranslations> (this, allTranslationsOff4 * 4);
+    const auto &allRotations = StructAtOffset<AllRotations> (this, allRotationsOff4 * 4);
+
+    return_trace (likely (subParts.sanitize (c, subPartCount) &&
+			  extremumColumnStarts.sanitize (c, axisCount, sparseMasterAxisValueCount, sparseExtremumAxisValueCount) &&
+			  masterAxisValueDeltas.sanitize (c, sparseMasterAxisValueCount) &&
+			  extremumAxisValueDeltas.sanitize (c, sparseExtremumAxisValueCount) &&
+			  allTranslations.sanitize (c, sparseMasterTranslationCount, sparseExtremumTranslationCount) &&
+			  allRotations.sanitize (c, sparseMasterRotationCount, sparseExtremumRotationCount)));
+  }
+
+  protected:
+  HBUINT16LE flags; // 0x0001 for composite part
+  HBUINT16LE axisCount; // Number of axes
+  HBUINT16LE subPartCount; // Number of direct subparts
+  HBUINT16LE totalNumParts; // Number of nodes including root
+  HBUINT16LE totalNumAxes; // Sum of axis count for all nodes including root
+  HBUINT16LE maxNumExtremes; // Maximum number of extremes (2*AxisCount) in all nodes
+  HBUINT16LE sparseMasterAxisValueCount; // Count of non-zero axis value deltas for master
+  HBUINT16LE sparseExtremumAxisValueCount; // Count of non-zero axis value deltas for extrema
+  HBUINT16LE sparseMasterTranslationCount; // Count of non-zero translations for master
+  HBUINT16LE sparseMasterRotationCount; // Count of non-zero rotations for master
+  HBUINT16LE sparseExtremumTranslationCount; // Count of non-zero translations for extrema
+  HBUINT16LE sparseExtremumRotationCount; // Count of non-zero rotations for extrema
+  Offset16LEMul4NN/*To<SubParts>*/ subPartsOff4; // Offset to subpart array/4
+  Offset16LEMul4NN/*To<ExtremumColumnStarts>*/ extremumColumnStartsOff4; // Offset to extremum column starts/4
+  Offset16LEMul4NN/*To<MasterAxisValueDeltas>*/ masterAxisValueDeltasOff4; // Offset to master axis value deltas/4
+  Offset16LEMul4NN/*To<ExtremumAxisValueDeltas>*/ extremumAxisValueDeltasOff4; // Offset to extremum axis value deltas/4
+  Offset16LEMul4NN/*To<AllTranslations>*/ allTranslationsOff4; // Offset to all translations/4
+  Offset16LEMul4NN/*To<AllRotations>*/ allRotationsOff4; // Offset to all rotations/4
+
+  public:
+  DEFINE_SIZE_STATIC (36);
+};
+
+struct Part
+{
+  public:
+
+  unsigned get_total_num_axes () const
+  {
+    switch (u.flags & 1) {
+    case 0: hb_barrier(); return u.shape.get_total_num_axes ();
+    case 1: hb_barrier(); return u.composite.get_total_num_axes ();
+    default: return 0;
+    }
+  }
+
+  unsigned get_total_num_parts () const
+  {
+    switch (u.flags & 1) {
+    case 0: hb_barrier(); return u.shape.get_total_num_parts ();
+    case 1: hb_barrier(); return u.composite.get_total_num_parts ();
+    default: return 0;
+    }
+  }
+
+  void
+  get_path_at (const hb_hvgl_context_t *c,
+	       hb_array_t<double> coords,
+	       hb_array_t<hb_transform_t<double>> transforms) const
+  {
+    switch (u.flags & 1) {
+    case 0: hb_barrier(); u.shape.get_path_at (c, coords, transforms); break;
+    case 1: hb_barrier(); u.composite.get_path_at (c, coords, transforms); break;
+    }
+  }
+
+  template <typename context_t, typename ...Ts>
+  typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
+  {
+    if (unlikely (!c->may_dispatch (this, &u.flags))) return c->no_dispatch_return_value ();
+    TRACE_DISPATCH (this, u.flags);
+    switch (u.flags & 1) {
+    case 0: hb_barrier (); return_trace (c->dispatch (u.shape, std::forward<Ts> (ds)...));
+    case 1: hb_barrier (); return_trace (c->dispatch (u.composite, std::forward<Ts> (ds)...));
+    default:return_trace (c->default_return_value ());
+    }
+  }
+
+  protected:
+  union {
+  HBUINT16LE	flags;	/* Flag identifier */
+  PartShape	shape;
+  PartComposite	composite;
+  } u;
+  public:
+  // A null flags will imply PartShape. So, our null size is the size of PartShape::min_size.
+  DEFINE_SIZE_MIN (PartShape::min_size);
+};
+
+struct Index
+{
+  public:
+
+  hb_bytes_t get (unsigned index, unsigned count) const
+  {
+    if (unlikely (index >= count)) return hb_bytes_t ();
+    hb_barrier ();
+    unsigned offset0 = offsets[index];
+    unsigned offset1 = offsets[index + 1];
+    if (unlikely (offset1 < offset0 || offset1 > offsets[count]))
+      return hb_bytes_t ();
+    return hb_bytes_t ((const char *) this + offset0, offset1 - offset0);
+  }
+
+  unsigned int get_size (unsigned count) const { return offsets[count]; }
+
+  bool sanitize (hb_sanitize_context_t *c, unsigned count) const
+  {
+    TRACE_SANITIZE (this);
+    return_trace (likely (count < count + 1u &&
+			  offsets.sanitize (c, count + 1) &&
+			  hb_barrier () &&
+			  offsets[count] >= offsets.get_size (count + 1) &&
+			  c->check_range (this, offsets[count])));
+  }
+
+  protected:
+  UnsizedArrayOf<HBUINT32LE>	offsets;	/* The array of (count + 1) offsets. */
+  public:
+  DEFINE_SIZE_MIN (4);
+};
+
+template <typename Type>
+struct IndexOf : Index
+{
+  public:
+
+  const Type &get (unsigned index, unsigned count,
+		   hb_sanitize_context_t &c, hb_hvgl_parts_sanitized_t &parts_sanitized) const
+  {
+    if (unlikely (index >= count)) return Null(Type);
+
+    hb_bytes_t data = Index::get (index, count);
+    const Type &item = *reinterpret_cast<const Type *> (data.begin ());
+
+    bool sanitized = parts_sanitized.has (index);
+    if (unlikely (!sanitized))
+    {
+      c.start_processing (data.begin (), data.end ());
+      bool sane = c.dispatch (item);
+      c.end_processing ();
+      if (unlikely (!sane)) return Null(Type);
+    }
+    parts_sanitized.add (index);
+
+    return item;
+  }
+
+  bool sanitize (hb_sanitize_context_t *c, unsigned count) const
+  { return Index::sanitize (c, count); }
+};
+
+using PartsIndex = IndexOf<Part>;
+
+} // namespace hvgl_impl
+
+struct hvgl
+{
+  friend struct hvgl_impl::PartComposite;
+
+  static constexpr hb_tag_t tableTag = HB_TAG ('h', 'v', 'g', 'l');
+
+  protected:
+
+  bool
+  get_part_path_at (const hb_hvgl_context_t *c,
+		    hb_codepoint_t part_id,
+		    hb_array_t<double> coords,
+		    hb_array_t<hb_transform_t<double>> transforms) const
+  {
+    if (unlikely (c->edges_left <= 0))
+      return true;
+    c->edges_left--;
+
+    if (unlikely (c->nodes_left <= 0))
+      return true;
+    c->nodes_left--;
+
+    if (unlikely (c->depth_left <= 0))
+      return true;
+    c->depth_left--;
+
+    const auto &parts = StructAtOffset<hvgl_impl::PartsIndex> (this, partsOff);
+    const auto &part = parts.get (part_id, partCount, c->sanitizer, c->parts_sanitized);
+
+    part.get_path_at (c, coords, transforms);
+
+    c->depth_left++;
+
+    return true;
+  }
+
+  public:
+
+  bool
+  get_path_at (hb_font_t *font,
+	       hb_codepoint_t gid,
+	       hb_draw_session_t *draw_session,
+	       hb_extents_t<> *extents,
+	       hb_array_t<const int> coords,
+	       hb_hvgl_scratch_t &scratch,
+	       hb_hvgl_parts_sanitized_t &parts_sanitized) const
+  {
+    if (unlikely (gid >= numGlyphs)) return false;
+
+    hb_sanitize_context_t sanitizer;
+
+    const auto &parts = StructAtOffset<hvgl_impl::PartsIndex> (this, partsOff);
+    const auto &part = parts.get (gid, partCount, sanitizer, parts_sanitized);
+
+    auto &coords_f = scratch.coords_f;
+    coords_f.clear ();
+    coords_f.resize (part.get_total_num_axes ());
+    if (unlikely (coords_f.in_error ())) return true;
+    unsigned count = hb_min (coords.length, coords_f.length);
+    for (unsigned i = 0; i < count; i++)
+      coords_f.arrayZ[i] = double (coords.arrayZ[i]) * (1. / (1 << 14));
+
+    auto &transforms = scratch.transforms;
+    unsigned total_num_parts = part.get_total_num_parts ();
+    transforms.clear ();
+    transforms.resize (total_num_parts);
+    if (unlikely (transforms.in_error ())) return true;
+    transforms[0] = hb_transform_t<double>{(double) font->x_multf, 0, 0, (double) font->y_multf, 0, 0};
+
+    hb_hvgl_context_t c = {*this, draw_session, extents,
+			   scratch,
+			   sanitizer, parts_sanitized,
+			   (int) total_num_parts, HB_MAX_GRAPH_EDGE_COUNT, HB_MAX_NESTING_LEVEL};
+
+    scratch.points.alloc (128);
+
+    return get_part_path_at (&c, gid, coords_f, transforms);
+  }
+
+  bool sanitize (hb_sanitize_context_t *c) const
+  {
+    TRACE_SANITIZE (this);
+    if (unlikely (!c->check_struct (this))) return_trace (false);
+    hb_barrier ();
+
+    if (unlikely (versionMajor != 3 ||
+		  (versionMajor == 3 && versionMinor < 1))) return_trace (false);
+
+
+    const auto &parts = StructAtOffset<hvgl_impl::PartsIndex> (this, partsOff);
+    if (unlikely (!parts.sanitize (c, (unsigned) partCount))) return_trace (false);
+
+    return_trace (true);
+  }
+
+  struct accelerator_t
+  {
+    accelerator_t (hb_face_t *face)
+      : table (hb_sanitize_context_t ().reference_table<hvgl> (face)),
+        parts_sanitized (0, table->partCount) {}
+    ~accelerator_t ()
+    {
+      auto *scratch = cached_scratch.get_relaxed ();
+      if (scratch)
+      {
+	scratch->~hb_hvgl_scratch_t ();
+	hb_free (scratch);
+      }
+
+      table.destroy ();
+    }
+
+    bool
+    get_path_at (hb_font_t *font,
+		 hb_codepoint_t gid,
+		 hb_draw_session_t &draw_session,
+		 hb_array_t<const int> coords) const
+    {
+      if (!table->has_data ()) return false;
+
+      hb_hvgl_scratch_t *scratch = acquire_scratch ();
+      if (unlikely (!scratch)) return true;
+      bool ret = table->get_path_at (font, gid,
+				     &draw_session, nullptr,
+				     coords,
+				     *scratch,
+				     parts_sanitized);
+      release_scratch (scratch);
+
+      return ret;
+    }
+
+    bool
+    get_path (hb_font_t *font,
+	      hb_codepoint_t gid,
+	      hb_draw_session_t &draw_session) const
+    {
+      return get_path_at (font, gid, draw_session, hb_array (font->coords, font->num_coords));
+    }
+
+    bool
+    get_extents_at (hb_font_t *font,
+		    hb_codepoint_t gid,
+		    hb_glyph_extents_t *extents,
+		    hb_array_t<const int> coords) const
+    {
+      if (!table->has_data ()) return false;
+
+      hb_extents_t<> f_extents;
+
+      auto *scratch = acquire_scratch ();
+      if (unlikely (!scratch)) return true;
+      bool ret = table->get_path_at (font, gid,
+				     nullptr, &f_extents,
+				     coords,
+				     *scratch,
+				     parts_sanitized);
+      release_scratch (scratch);
+
+      if (ret)
+	*extents = f_extents.to_glyph_extents (font->x_scale < 0, font->y_scale < 0);
+
+      return ret;
+    }
+
+    bool
+    get_extents (hb_font_t *font,
+		 hb_codepoint_t gid,
+		 hb_glyph_extents_t *extents) const
+    {
+      return get_extents_at (font, gid, extents,
+			     hb_array (font->coords, font->num_coords));
+    }
+
+    private:
+
+    hb_hvgl_scratch_t *acquire_scratch () const
+    {
+      hb_hvgl_scratch_t *scratch = cached_scratch.get_acquire ();
+
+      if (!scratch || unlikely (!cached_scratch.cmpexch (scratch, nullptr)))
+      {
+	scratch = (hb_hvgl_scratch_t *) hb_calloc (1, sizeof (hb_hvgl_scratch_t));
+	if (unlikely (!scratch))
+	  return nullptr;
+      }
+
+      return scratch;
+    }
+    void release_scratch (hb_hvgl_scratch_t *scratch) const
+    {
+      if (!cached_scratch.cmpexch (nullptr, scratch))
+      {
+	scratch->~hb_hvgl_scratch_t ();
+	hb_free (scratch);
+      }
+    }
+
+    private:
+    hb_blob_ptr_t<hvgl> table;
+    mutable hb_atomic_t<hb_hvgl_scratch_t *> cached_scratch;
+    mutable hb_hvgl_parts_sanitized_t parts_sanitized;
+  };
+
+  bool has_data () const { return versionMajor != 0; }
+
+  protected:
+  HBUINT16LE	versionMajor;	/* Major version of the hvgl table, currently 3 */
+  HBUINT16LE	versionMinor;	/* Minor version of the hvgl table, currently 1 */
+  HBUINT32LE	flags;		/* Flags; currently all zero */
+  HBUINT32LE	partCount;	/* Number of all shapes and composites */
+  Offset<HBUINT32LE>  partsOff;	/* To: Index. length: partCount. Parts */
+  HBUINT32LE	numGlyphs;	/* Number of externally visible parts */
+  HBUINT32LE	reserved;	/* Reserved; currently zero */
+  public:
+  DEFINE_SIZE_STATIC (24);
+};
+
+struct hvgl_accelerator_t : hvgl::accelerator_t {
+  hvgl_accelerator_t (hb_face_t *face) : hvgl::accelerator_t (face) {}
+};
+
+} // namespace AAT
+
+#endif // HB_NO_VAR_HVF
+
+#endif  /* HB_AAT_VAR_HVGL_TABLE_HH */
diff --git a/src/hb-bit-page.hh b/src/hb-bit-page.hh
index 1941009..e845579 100644
--- a/src/hb-bit-page.hh
+++ b/src/hb-bit-page.hh
@@ -290,7 +290,7 @@
     unsigned int j = m & ELT_MASK;
 
     const elt_t vv = v[i] & ~((elt_t (1) << j) - 1);
-    for (const elt_t *p = &vv; i < len (); p = &v[++i])
+    for (const elt_t *p = &vv; i < len (); p = ((const elt_t *) &v[0]) + (++i))
       if (*p)
       {
 	*codepoint = i * ELT_BITS + elt_get_min (*p);
diff --git a/src/hb-buffer-deserialize-text-unicode.hh b/src/hb-buffer-deserialize-text-unicode.hh
index 26ec0b4..0bb00c4 100644
--- a/src/hb-buffer-deserialize-text-unicode.hh
+++ b/src/hb-buffer-deserialize-text-unicode.hh
@@ -32,7 +32,7 @@
 #include "hb.hh"
 
 
-#line 36 "hb-buffer-deserialize-text-unicode.hh"
+#line 33 "hb-buffer-deserialize-text-unicode.hh"
 static const unsigned char _deserialize_text_unicode_trans_keys[] = {
 	0u, 0u, 43u, 102u, 48u, 102u, 48u, 124u, 48u, 57u, 62u, 124u, 48u, 124u, 60u, 117u, 
 	85u, 117u, 85u, 117u, 0
@@ -150,12 +150,12 @@
   hb_glyph_info_t info = {0};
   const hb_glyph_position_t pos = {0};
   
-#line 154 "hb-buffer-deserialize-text-unicode.hh"
+#line 147 "hb-buffer-deserialize-text-unicode.hh"
 	{
 	cs = deserialize_text_unicode_start;
 	}
 
-#line 159 "hb-buffer-deserialize-text-unicode.hh"
+#line 150 "hb-buffer-deserialize-text-unicode.hh"
 	{
 	int _slen;
 	int _trans;
@@ -215,7 +215,7 @@
 	hb_memset (&info, 0, sizeof (info));
 }
 	break;
-#line 219 "hb-buffer-deserialize-text-unicode.hh"
+#line 203 "hb-buffer-deserialize-text-unicode.hh"
 	}
 
 _again:
@@ -238,7 +238,7 @@
 	*end_ptr = p;
 }
 	break;
-#line 242 "hb-buffer-deserialize-text-unicode.hh"
+#line 224 "hb-buffer-deserialize-text-unicode.hh"
 	}
 	}
 
diff --git a/src/hb-cff2-interp-cs.hh b/src/hb-cff2-interp-cs.hh
index 22871e3..27a0c8c 100644
--- a/src/hb-cff2-interp-cs.hh
+++ b/src/hb-cff2-interp-cs.hh
@@ -72,7 +72,7 @@
   cff2_cs_interp_env_t (const hb_ubytes_t &str, ACC &acc, unsigned int fd,
 			const int *coords_=nullptr, unsigned int num_coords_=0)
     : SUPER (str, acc.globalSubrs, acc.privateDicts[fd].localSubrs),
-      cached_scalars_vector (&acc.cached_scalars_vector)
+      region_count (0), cached_scalars_vector (&acc.cached_scalars_vector)
   {
     coords = coords_;
     num_coords = num_coords_;
diff --git a/src/hb-config.hh b/src/hb-config.hh
index 3956690..d6b952d 100644
--- a/src/hb-config.hh
+++ b/src/hb-config.hh
@@ -38,7 +38,6 @@
 #ifndef HB_EXPERIMENTAL_API
 #define HB_NO_BEYOND_64K
 #define HB_NO_CUBIC_GLYF
-#define HB_NO_VAR_COMPOSITES
 #endif
 
 #ifdef HB_TINY
@@ -91,7 +90,10 @@
 #ifdef HB_MINI
 #define HB_NO_AAT
 #define HB_NO_LEGACY
-#define HB_NO_BORING_EXPANSION
+#define HB_NO_BEYOND_64K
+#define HB_NO_CUBIC_GLYF
+#define HB_NO_VAR_COMPOSITES
+#define HB_NO_VAR_HVF
 #endif
 
 #ifdef __OPTIMIZE_SIZE__
@@ -109,14 +111,13 @@
 
 /* Closure of options. */
 
-#ifdef HB_NO_BORING_EXPANSION
-#define HB_NO_BEYOND_64K
-#define HB_NO_CUBIC_GLYF
-#define HB_NO_VAR_COMPOSITES
+#ifdef HB_NO_SIMD
+#define HB_NO_APPLE_SIMD
 #endif
 
 #ifdef HB_NO_VAR
 #define HB_NO_VAR_COMPOSITES
+#define HB_NO_VAR_HVF
 #endif
 
 #ifdef HB_DISABLE_DEPRECATED
@@ -186,6 +187,7 @@
 #endif
 
 #ifdef HB_OPTIMIZE_SIZE_MORE
+#define HB_NO_SIMD
 #define HB_NO_OT_RULESETS_FAST_PATH
 #endif
 
diff --git a/src/hb-debug.hh b/src/hb-debug.hh
index f79b42b..1f7a7f9 100644
--- a/src/hb-debug.hh
+++ b/src/hb-debug.hh
@@ -394,6 +394,10 @@
 #define HB_DEBUG_WASM (HB_DEBUG+0)
 #endif
 
+#ifndef HB_DEBUG_KBTS
+#define HB_DEBUG_KBTS (HB_DEBUG+0)
+#endif
+
 /*
  * With tracing.
  */
diff --git a/src/hb-iter.hh b/src/hb-iter.hh
index 04d0994..a954159 100644
--- a/src/hb-iter.hh
+++ b/src/hb-iter.hh
@@ -972,7 +972,7 @@
 		    Proj&& f = hb_identity) const
   {
     for (auto it = hb_iter (c); it; ++it)
-      if (!hb_match (std::forward<Pred> (p), hb_get (std::forward<Proj> (f), *it)))
+      if (!hb_match (p, hb_get (f, *it)))
 	return false;
     return true;
   }
@@ -989,7 +989,7 @@
 		    Proj&& f = hb_identity) const
   {
     for (auto it = hb_iter (c); it; ++it)
-      if (hb_match (std::forward<Pred> (p), hb_get (std::forward<Proj> (f), *it)))
+      if (hb_match (p, hb_get (f, *it)))
 	return true;
     return false;
   }
@@ -1006,7 +1006,7 @@
 		    Proj&& f = hb_identity) const
   {
     for (auto it = hb_iter (c); it; ++it)
-      if (hb_match (std::forward<Pred> (p), hb_get (std::forward<Proj> (f), *it)))
+      if (hb_match (p, hb_get (f, *it)))
 	return false;
     return true;
   }
diff --git a/src/hb-kbts.cc b/src/hb-kbts.cc
new file mode 100644
index 0000000..eb3bfc2
--- /dev/null
+++ b/src/hb-kbts.cc
@@ -0,0 +1,259 @@
+/*
+ *  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.
+ *
+ * Author(s): Khaled Hosny
+ */
+
+#include "hb.hh"
+
+#if HAVE_KBTS
+
+#include "hb-shaper-impl.hh"
+
+#define KB_TEXT_SHAPE_IMPLEMENTATION
+#define KB_TEXT_SHAPE_STATIC
+#define KB_TEXT_SHAPE_NO_CRT
+#define KBTS_MEMSET hb_memset
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-align"
+#pragma GCC diagnostic ignored "-Wunused-function"
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#include "kb_text_shape.h"
+#pragma GCC diagnostic pop
+
+
+hb_kbts_face_data_t *
+_hb_kbts_shaper_face_data_create (hb_face_t *face)
+{
+  hb_blob_t *blob = hb_face_reference_blob (face);
+
+  unsigned int blob_length;
+  const char *blob_data = hb_blob_get_data (blob, &blob_length);
+  if (unlikely (!blob_length))
+  {
+    DEBUG_MSG (KBTS, blob, "Empty blob");
+    hb_blob_destroy (blob);
+    return nullptr;
+  }
+
+  void *data = hb_malloc (blob_length);
+  if (likely (data))
+    hb_memcpy (data, blob_data, blob_length);
+
+  hb_blob_destroy (blob);
+  blob = nullptr;
+
+  if (unlikely (!data))
+  {
+    DEBUG_MSG (KBTS, face, "Failed to allocate memory for font data");
+    return nullptr;
+  }
+
+  kbts_font *kb_font = (kbts_font *) hb_calloc (1, sizeof (kbts_font));
+  if (unlikely (!kb_font))
+  {
+    hb_free (data);
+    return nullptr;
+  }
+
+  size_t memory_size;
+  {
+    unsigned scratch_size = kbts_ReadFontHeader (kb_font, data, blob_length);
+    void *scratch = hb_malloc (scratch_size);
+    memory_size = kbts_ReadFontData (kb_font, scratch, scratch_size);
+    hb_free (scratch);
+  }
+
+  void *memory = hb_malloc (memory_size);
+  if (unlikely (!kbts_PostReadFontInitialize (kb_font, memory, memory_size)))
+  {
+    DEBUG_MSG (KBTS, face, "kbts_PostReadFontInitialize failed");
+    hb_free (memory);
+    hb_free (data);
+    hb_free (kb_font);
+    return nullptr;
+  }
+
+  return (hb_kbts_face_data_t *) kb_font;
+}
+
+void
+_hb_kbts_shaper_face_data_destroy (hb_kbts_face_data_t *data)
+{
+  kbts_font *font = (kbts_font *) data;
+
+  assert (kbts_FontIsValid (font));
+
+  hb_free (font->FileBase);
+  hb_free (font->GlyphLookupMatrix);
+  hb_free (font);
+}
+
+hb_kbts_font_data_t *
+_hb_kbts_shaper_font_data_create (hb_font_t *font)
+{
+  return (hb_kbts_font_data_t *) HB_SHAPER_DATA_SUCCEEDED;
+}
+
+void
+_hb_kbts_shaper_font_data_destroy (hb_kbts_font_data_t *data)
+{
+}
+
+hb_bool_t
+_hb_kbts_shape (hb_shape_plan_t    *shape_plan,
+		hb_font_t          *font,
+		hb_buffer_t        *buffer,
+		const hb_feature_t *features,
+		unsigned int        num_features)
+{
+  hb_face_t *face = font->face;
+  kbts_font *kb_font = (kbts_font *) (const void *) face->data.kbts;
+
+  kbts_direction kb_direction;
+  switch (buffer->props.direction)
+  {
+    case HB_DIRECTION_LTR: kb_direction = KBTS_DIRECTION_LTR; break;
+    case HB_DIRECTION_RTL: kb_direction = KBTS_DIRECTION_RTL; break;
+    case HB_DIRECTION_TTB:
+    case HB_DIRECTION_BTT:
+      DEBUG_MSG (KBTS, face, "Vertical direction is not supported");
+      return false;
+    default:
+    case HB_DIRECTION_INVALID:
+      DEBUG_MSG (KBTS, face, "Invalid direction");
+      return false;
+  }
+
+  kbts_script kb_script = KBTS_SCRIPT_DONT_KNOW;
+  kbts_language kb_language = KBTS_LANGUAGE_DEFAULT;
+  {
+    hb_tag_t scripts[HB_OT_MAX_TAGS_PER_SCRIPT];
+    hb_tag_t language;
+    unsigned int script_count = ARRAY_LENGTH (scripts);
+    unsigned int language_count = 1;
+
+    hb_ot_tags_from_script_and_language (buffer->props.script, buffer->props.language,
+					 &script_count, scripts,
+					 &language_count, &language);
+
+    for (unsigned int i = 0; i < script_count && scripts[i] != HB_TAG_NONE; ++i)
+    {
+      kb_script = kbts_ScriptTagToScript (hb_uint32_swap (scripts[i]));
+      if (kb_script != KBTS_SCRIPT_DONT_KNOW)
+        break;
+    }
+
+    if (language_count)
+      kb_language = (kbts_language) hb_uint32_swap (language);
+  }
+
+  hb_vector_t<kbts_glyph> kb_glyphs;
+  if (unlikely (!kb_glyphs.resize_exact (buffer->len, false)))
+    return false;
+
+  for (size_t i = 0; i < buffer->len; ++i)
+    kb_glyphs.arrayZ[i] = kbts_CodepointToGlyph (kb_font, buffer->info[i].codepoint);
+
+  if (num_features)
+  {
+    for (unsigned int i = 0; i < num_features; ++i)
+    {
+      hb_feature_t feature = features[i];
+      for (unsigned int j = 0; j < buffer->len; ++j)
+      {
+	kbts_glyph *kb_glyph = &kb_glyphs.arrayZ[j];
+	if (hb_in_range (j, feature.start, feature.end))
+	{
+	  if (!kb_glyph->Config)
+	    kb_glyph->Config = (kbts_glyph_config *) hb_calloc (1, sizeof (kbts_glyph_config));
+	  kbts_glyph_config *config = kb_glyph->Config;
+	  while (!kbts_GlyphConfigOverrideFeatureFromTag (config, hb_uint32_swap (feature.tag),
+	                                                  feature.value > 1, feature.value))
+	  {
+	    config->FeatureOverrides = (kbts_feature_override *) hb_realloc (config->FeatureOverrides,
+									     config->RequiredFeatureOverrideCapacity);
+	    config->FeatureOverrideCapacity += 1;
+	  }
+	}
+      }
+    }
+  }
+
+  kbts_shape_state *kb_shape_state;
+  {
+    size_t kb_shape_state_size = kbts_SizeOfShapeState (kb_font);
+    void *kb_shape_state_buffer = hb_malloc (kb_shape_state_size);
+    if (unlikely (!kb_shape_state_buffer))
+    {
+      DEBUG_MSG (KBTS, face, "Failed to allocate memory for shape state");
+      return false;
+    }
+    kb_shape_state = kbts_PlaceShapeState (kb_shape_state_buffer, kb_shape_state_size);
+  }
+  kbts_shape_config kb_shape_config = kbts_ShapeConfig (kb_font, kb_script, kb_language);
+  uint32_t glyph_count = buffer->len;
+  uint32_t glyph_capacity = kb_glyphs.length;
+  while (kbts_Shape (kb_shape_state, &kb_shape_config, KBTS_DIRECTION_LTR, kb_direction,
+		     kb_glyphs.arrayZ, &glyph_count, glyph_capacity))
+  {
+    glyph_capacity = kb_shape_state->RequiredGlyphCapacity;
+    /* kb increases capacity by a fixed number only. We increase it by 50% to
+     * avoid O(n^2) behavior in case of expanding text.
+     *
+     * https://github.com/JimmyLefevre/kb/issues/32
+     */
+    glyph_capacity += glyph_capacity / 2;
+    if (unlikely (!kb_glyphs.resize_exact (glyph_capacity, false)))
+      return false;
+  }
+
+  hb_buffer_set_content_type (buffer, HB_BUFFER_CONTENT_TYPE_GLYPHS);
+  hb_buffer_set_length (buffer, glyph_count);
+
+  hb_glyph_info_t *info = buffer->info;
+  hb_glyph_position_t *pos = buffer->pos;
+
+  buffer->clear_positions ();
+  for (size_t i = 0; i < glyph_count; ++i)
+  {
+    kbts_glyph kb_glyph = kb_glyphs.arrayZ[i];
+    info[i].codepoint = kb_glyph.Id;
+    info[i].cluster = 0; // FIXME
+    pos[i].x_advance = font->em_scalef_x (kb_glyph.AdvanceX);
+    pos[i].y_advance = font->em_scalef_y (kb_glyph.AdvanceY);
+    pos[i].x_offset = font->em_scalef_x (kb_glyph.OffsetX);
+    pos[i].y_offset = font->em_scalef_y (kb_glyph.OffsetY);
+
+    if (kb_glyph.Config)
+      hb_free (kb_glyph.Config->FeatureOverrides);
+  }
+
+  hb_free (kb_shape_state);
+
+  buffer->clear_glyph_flags ();
+  buffer->unsafe_to_break ();
+
+  return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/hb-number-parser.hh b/src/hb-number-parser.hh
index 1a9dbba..ec68c3a 100644
--- a/src/hb-number-parser.hh
+++ b/src/hb-number-parser.hh
@@ -31,7 +31,7 @@
 #include "hb.hh"
 
 
-#line 35 "hb-number-parser.hh"
+#line 32 "hb-number-parser.hh"
 static const unsigned char _double_parser_trans_keys[] = {
 	0u, 0u, 43u, 57u, 46u, 57u, 48u, 57u, 43u, 57u, 48u, 57u, 48u, 101u, 48u, 57u, 
 	46u, 101u, 0
@@ -135,12 +135,12 @@
 
   int cs;
   
-#line 139 "hb-number-parser.hh"
+#line 132 "hb-number-parser.hh"
 	{
 	cs = double_parser_start;
 	}
 
-#line 144 "hb-number-parser.hh"
+#line 135 "hb-number-parser.hh"
 	{
 	int _slen;
 	int _trans;
@@ -198,7 +198,7 @@
 	  exp_overflow = true;
 }
 	break;
-#line 202 "hb-number-parser.hh"
+#line 187 "hb-number-parser.hh"
 	}
 
 _again:
diff --git a/src/hb-ot-cff-common.hh b/src/hb-ot-cff-common.hh
index b49c0be..4308a73 100644
--- a/src/hb-ot-cff-common.hh
+++ b/src/hb-ot-cff-common.hh
@@ -79,7 +79,7 @@
   {
     TRACE_SERIALIZE (this);
     for (unsigned int i = 0; i < dictval.get_count (); i++)
-      if (unlikely (!opszr.serialize (c, dictval[i], std::forward<Ts> (ds)...)))
+      if (unlikely (!opszr.serialize (c, dictval[i], ds...)))
 	return_trace (false);
 
     return_trace (true);
diff --git a/src/hb-ot-face-table-list.hh b/src/hb-ot-face-table-list.hh
index afc8aa4..ee05cd0 100644
--- a/src/hb-ot-face-table-list.hh
+++ b/src/hb-ot-face-table-list.hh
@@ -133,6 +133,9 @@
 HB_OT_TABLE (AAT, feat)
 // HB_OT_TABLE (AAT, opbd)
 #endif
+#ifndef HB_NO_VAR_HVF
+HB_OT_ACCELERATOR (AAT, hvgl)
+#endif
 
 /* OpenType color fonts. */
 #ifndef HB_NO_COLOR
diff --git a/src/hb-ot-font.cc b/src/hb-ot-font.cc
index 0f19b55..c17ca5d 100644
--- a/src/hb-ot-font.cc
+++ b/src/hb-ot-font.cc
@@ -35,6 +35,9 @@
 #include "hb-machinery.hh"
 #include "hb-ot-face.hh"
 
+#ifndef HB_NO_VAR_HVF
+#include "hb-aat-var-hvgl-table.hh"
+#endif
 #include "hb-ot-cmap-table.hh"
 #include "hb-ot-glyf-table.hh"
 #include "hb-ot-var-gvar-table.hh"
@@ -466,6 +469,11 @@
   {
     const auto &glyf = *ot_face->glyf;
     auto *scratch = glyf.acquire_scratch ();
+    if (unlikely (!scratch))
+    {
+      ot_font->h.release_advance_cache (advance_cache);
+      goto fallback;
+    }
     OT::hb_scalar_cache_t *gvar_cache = ot_font->draw.acquire_gvar_cache (gvar);
 
     for (unsigned int i = 0; i < count; i++)
@@ -586,6 +594,11 @@
   {
     const auto &glyf = *ot_face->glyf;
     auto *scratch = glyf.acquire_scratch ();
+    if (unlikely (!scratch))
+    {
+      ot_font->v.release_advance_cache (advance_cache);
+      goto fallback;
+    }
     OT::hb_scalar_cache_t *gvar_cache = ot_font->draw.acquire_gvar_cache (gvar);
 
     for (unsigned int i = 0; i < count; i++)
@@ -664,7 +677,7 @@
 	hb_position_t origin;
 	unsigned cv;
 	if (origin_cache->get (*first_glyph, &cv))
-	  origin = font->y_scale < 0 ? -cv : cv;
+	  origin = font->y_scale < 0 ? -static_cast<hb_position_t>(cv) : static_cast<hb_position_t>(cv);
 	else
 	{
 	  origin = font->em_scalef_y (VORG.get_y_origin (*first_glyph));
@@ -688,7 +701,7 @@
 	hb_position_t origin;
 	unsigned cv;
 	if (origin_cache->get (*first_glyph, &cv))
-	  origin = font->y_scale < 0 ? -cv : cv;
+	  origin = font->y_scale < 0 ? -static_cast<hb_position_t>(cv) : static_cast<hb_position_t>(cv);
 	else
 	{
 	  origin = font->em_scalef_y (VORG.get_y_origin (*first_glyph) +
@@ -717,6 +730,11 @@
   if (origin_cache && vmtx.has_data() && glyf.has_data ())
   {
     auto *scratch = glyf.acquire_scratch ();
+    if (unlikely (!scratch))
+    {
+      ot_font->v_origin.release_origin_cache (origin_cache);
+      return false;
+    }
     OT::hb_scalar_cache_t *gvar_cache = font->has_nonzero_coords ?
 					ot_font->draw.acquire_gvar_cache (*ot_face->gvar) :
 					nullptr;
@@ -726,7 +744,7 @@
       hb_position_t origin;
       unsigned cv;
       if (origin_cache->get (*first_glyph, &cv))
-	origin = font->y_scale < 0 ? -cv : cv;
+	origin = font->y_scale < 0 ? -static_cast<hb_position_t>(cv) : static_cast<hb_position_t>(cv);
       else
       {
 	origin = font->em_scalef_y (glyf.get_v_origin_with_var_unscaled (*first_glyph, font, *scratch, gvar_cache));
@@ -760,7 +778,7 @@
       unsigned cv;
 
       if (origin_cache->get (*first_glyph, &cv))
-	origin = font->y_scale < 0 ? -cv : cv;
+	origin = font->y_scale < 0 ? -static_cast<hb_position_t>(cv) : static_cast<hb_position_t>(cv);
       else
       {
 	hb_glyph_extents_t extents = {0};
@@ -804,6 +822,9 @@
 #ifndef HB_NO_VAR_COMPOSITES
   if (ot_face->VARC->get_extents (font, glyph, extents)) return true;
 #endif
+#ifndef HB_NO_VAR_HVF
+  if (ot_face->hvgl->get_extents (font, glyph, extents)) return true;
+#endif
   if (ot_face->glyf->get_extents (font, glyph, extents)) return true;
 #ifndef HB_NO_OT_FONT_CFF
   if (ot_face->cff2->get_extents (font, glyph, extents)) return true;
@@ -895,6 +916,9 @@
   if (font->face->table.VARC->get_path (font, glyph, draw_session)) { ret = true; goto done; }
 #endif
   // Keep the following in synch with VARC::get_path_at()
+#ifndef HB_NO_VAR_HVF
+  if (font->face->table.hvgl->get_path (font, glyph, draw_session)) { ret = true; goto done; }
+#endif
   if (font->face->table.glyf->get_path (font, glyph, draw_session, gvar_cache)) { ret = true; goto done; }
 
 #ifndef HB_NO_CFF
diff --git a/src/hb-ot-layout-common.hh b/src/hb-ot-layout-common.hh
index 06c3c0e..53c186f 100644
--- a/src/hb-ot-layout-common.hh
+++ b/src/hb-ot-layout-common.hh
@@ -1337,7 +1337,7 @@
     TRACE_DISPATCH (this, lookup_type);
     unsigned int count = get_subtable_count ();
     for (unsigned int i = 0; i < count; i++) {
-      typename context_t::return_t r = get_subtable<TSubTable> (i).dispatch (c, lookup_type, std::forward<Ts> (ds)...);
+      typename context_t::return_t r = get_subtable<TSubTable> (i).dispatch (c, lookup_type, ds...);
       if (c->stop_sublookup_iteration (r))
 	return_trace (r);
     }
@@ -2078,7 +2078,7 @@
     }
   }
   unsigned int get_class (hb_codepoint_t glyph_id,
-			  hb_ot_lookup_cache_t *cache) const
+			  hb_ot_layout_mapping_cache_t *cache) const
   {
     unsigned klass;
     if (cache && cache->get (glyph_id, &klass)) return klass;
@@ -2575,7 +2575,7 @@
       return scratch_cache;
     }
 
-    auto *cache = (hb_scalar_cache_t *) hb_malloc (sizeof (hb_scalar_cache_t) - sizeof (values) + sizeof (values[0]) * count);
+    auto *cache = (hb_scalar_cache_t *) hb_malloc (sizeof (hb_scalar_cache_t) - sizeof (static_values) + sizeof (static_values[0]) * count);
     if (unlikely (!cache)) return (hb_scalar_cache_t *) &Null(hb_scalar_cache_t);
 
     cache->length = count;
@@ -2593,6 +2593,7 @@
 
   void clear ()
   {
+    auto *values = &static_values[0];
     for (unsigned i = 0; i < length; i++)
       values[i] = INVALID;
   }
@@ -2605,6 +2606,7 @@
       *value = 0.f;
       return true;
     }
+    auto *values = &static_values[0];
     auto *cached_value = &values[i];
     if (*cached_value != INVALID)
     {
@@ -2618,13 +2620,14 @@
   void set (unsigned i, float value)
   {
     if (unlikely (i >= length)) return;
+    auto *values = &static_values[0];
     auto *cached_value = &values[i];
     *cached_value = roundf(value * MULTIPLIER);
   }
 
   private:
   unsigned length;
-  mutable hb_atomic_t<int> values[STATIC_LENGTH];
+  mutable hb_atomic_t<int> static_values[STATIC_LENGTH];
 };
 
 struct VarRegionList
@@ -3439,7 +3442,7 @@
     for (unsigned i = 0; i < count; i++)
     {
       hb_inc_bimap_t *map = inner_maps.push ();
-      if (!c->propagate_error(inner_maps))
+      if (unlikely (!c->propagate_error(inner_maps)))
         return_trace(nullptr);
       auto &data = this+dataSets[i];
 
diff --git a/src/hb-ot-layout-gsubgpos.hh b/src/hb-ot-layout-gsubgpos.hh
index 8332554..b7c7436 100644
--- a/src/hb-ot-layout-gsubgpos.hh
+++ b/src/hb-ot-layout-gsubgpos.hh
@@ -455,7 +455,7 @@
   HB_ALWAYS_INLINE
 #endif
   may_skip_t may_skip (const context_t *c,
-		       const hb_glyph_info_t       &info) const
+		       const hb_glyph_info_t &info) const
   {
     if (!c->check_glyph_property (&info, lookup_props))
       return SKIP_YES;
@@ -470,7 +470,7 @@
   }
 
   public:
-  unsigned int lookup_props = 0;
+  unsigned int lookup_props = (unsigned) -1;
   hb_mask_t mask = -1;
   bool ignore_zwnj = false;
   bool ignore_zwj = false;
@@ -706,7 +706,8 @@
   hb_direction_t direction;
   hb_mask_t lookup_mask = 1;
   unsigned int lookup_index = (unsigned) -1;
-  unsigned int lookup_props = 0;
+  unsigned int lookup_props = (unsigned) -1;
+  unsigned int cached_props = (unsigned) -1; /* Cached glyph properties for the current lookup. */
   unsigned int nesting_level_left = HB_MAX_NESTING_LEVEL;
 
   bool has_glyph_classes;
@@ -772,15 +773,22 @@
     return buffer->random_state;
   }
 
-  bool match_properties_mark (hb_codepoint_t  glyph,
+  HB_ALWAYS_INLINE
+  HB_HOT
+  bool match_properties_mark (const hb_glyph_info_t *info,
 			      unsigned int    glyph_props,
-			      unsigned int    match_props) const
+			      unsigned int    match_props,
+			      bool            cached) const
   {
     /* If using mark filtering sets, the high short of
      * match_props has the set index.
      */
     if (match_props & LookupFlag::UseMarkFilteringSet)
-      return gdef_accel.mark_set_covers (match_props >> 16, glyph);
+    {
+      if (cached && match_props == cached_props)
+	return _hb_glyph_info_matches (info);
+      return gdef_accel.mark_set_covers (match_props >> 16, info->codepoint);
+    }
 
     /* The second byte of match_props has the meaning
      * "ignore marks of attachment type different than
@@ -796,7 +804,8 @@
   HB_ALWAYS_INLINE
 #endif
   bool check_glyph_property (const hb_glyph_info_t *info,
-			     unsigned int  match_props) const
+			     unsigned match_props,
+			     bool cached = true) const
   {
     unsigned int glyph_props = _hb_glyph_info_get_glyph_props (info);
 
@@ -807,7 +816,7 @@
       return false;
 
     if (unlikely (glyph_props & HB_OT_LAYOUT_GLYPH_PROPS_MARK))
-      return match_properties_mark (info->codepoint, glyph_props, match_props);
+      return match_properties_mark (info, glyph_props, match_props, cached);
 
     return true;
   }
@@ -849,6 +858,10 @@
     }
     else
       _hb_glyph_info_set_glyph_props (&buffer->cur(), props);
+
+    if (cached_props != (unsigned) -1)
+      _hb_glyph_info_set_match (&buffer->cur(),
+				check_glyph_property (&buffer->cur(), cached_props, false));
   }
 
   void replace_glyph (hb_codepoint_t glyph_index)
@@ -875,7 +888,7 @@
   }
 };
 
-enum class hb_ot_lookup_cache_op_t
+enum class hb_ot_subtable_cache_op_t
 {
   CREATE,
   ENTER,
@@ -907,22 +920,22 @@
 
   template <typename T>
   static inline auto cache_func_ (void *p,
-				  hb_ot_lookup_cache_op_t op,
+				  hb_ot_subtable_cache_op_t op,
 				  hb_priority<1>) HB_RETURN (void *, T::cache_func (p, op) )
   template <typename T=void>
   static inline void * cache_func_ (void *p,
-				    hb_ot_lookup_cache_op_t op HB_UNUSED,
+				    hb_ot_subtable_cache_op_t op HB_UNUSED,
 				    hb_priority<0>) { return (void *) false; }
   template <typename Type>
   static inline void * cache_func_to (void *p,
-				      hb_ot_lookup_cache_op_t op)
+				      hb_ot_subtable_cache_op_t op)
   {
     return cache_func_<Type> (p, op, hb_prioritize);
   }
 #endif
 
   typedef bool (*hb_apply_func_t) (const void *obj, hb_ot_apply_context_t *c);
-  typedef void * (*hb_cache_func_t) (void *p, hb_ot_lookup_cache_op_t op);
+  typedef void * (*hb_cache_func_t) (void *p, hb_ot_subtable_cache_op_t op);
 
   struct hb_applicable_t
   {
@@ -959,11 +972,11 @@
     }
     bool cache_enter (hb_ot_apply_context_t *c) const
     {
-      return (bool) cache_func (c, hb_ot_lookup_cache_op_t::ENTER);
+      return (bool) cache_func (c, hb_ot_subtable_cache_op_t::ENTER);
     }
     void cache_leave (hb_ot_apply_context_t *c) const
     {
-      cache_func (c, hb_ot_lookup_cache_op_t::LEAVE);
+      cache_func (c, hb_ot_subtable_cache_op_t::LEAVE);
     }
 #endif
 
@@ -1008,10 +1021,10 @@
      * and we allocate the cache opportunity to the costliest subtable.
      */
     unsigned cost = cache_cost (obj, hb_prioritize);
-    if (cost > cache_user_cost)
+    if (cost > subtable_cache_user_cost)
     {
-      cache_user_idx = i - 1;
-      cache_user_cost = cost;
+      subtable_cache_user_idx = i - 1;
+      subtable_cache_user_cost = cost;
     }
 #endif
 
@@ -1026,8 +1039,8 @@
   unsigned i = 0;
 
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
-  unsigned cache_user_idx = (unsigned) -1;
-  unsigned cache_user_cost = 0;
+  unsigned subtable_cache_user_idx = (unsigned) -1;
+  unsigned subtable_cache_user_cost = 0;
 #endif
 };
 
@@ -1321,7 +1334,7 @@
 	if (ligbase == LIGBASE_NOT_CHECKED)
 	{
 	  bool found = false;
-	  const auto *out = buffer->out_info;
+	  auto *out = buffer->out_info;
 	  unsigned int j = buffer->out_len;
 	  while (j && _hb_glyph_info_get_lig_id (&out[j - 1]) == first_lig_id)
 	  {
@@ -2610,13 +2623,13 @@
     unsigned c = (this+classDef).cost () * ruleSet.len;
     return c >= 4 ? c : 0;
   }
-  static void * cache_func (void *p, hb_ot_lookup_cache_op_t op)
+  static void * cache_func (void *p, hb_ot_subtable_cache_op_t op)
   {
     switch (op)
     {
-      case hb_ot_lookup_cache_op_t::CREATE:
+      case hb_ot_subtable_cache_op_t::CREATE:
 	return (void *) true;
-      case hb_ot_lookup_cache_op_t::ENTER:
+      case hb_ot_subtable_cache_op_t::ENTER:
       {
 	hb_ot_apply_context_t *c = (hb_ot_apply_context_t *) p;
 	if (!HB_BUFFER_TRY_ALLOCATE_VAR (c->buffer, syllable))
@@ -2628,14 +2641,14 @@
 	c->new_syllables = 255;
 	return (void *) true;
       }
-      case hb_ot_lookup_cache_op_t::LEAVE:
+      case hb_ot_subtable_cache_op_t::LEAVE:
       {
 	hb_ot_apply_context_t *c = (hb_ot_apply_context_t *) p;
 	c->new_syllables = (unsigned) -1;
 	HB_BUFFER_DEALLOCATE_VAR (c->buffer, syllable);
 	return nullptr;
       }
-      case hb_ot_lookup_cache_op_t::DESTROY:
+      case hb_ot_subtable_cache_op_t::DESTROY:
         return nullptr;
     }
     return nullptr;
@@ -3860,13 +3873,13 @@
   {
     return (this+lookaheadClassDef).cost () * ruleSet.len;
   }
-  static void * cache_func (void *p, hb_ot_lookup_cache_op_t op)
+  static void * cache_func (void *p, hb_ot_subtable_cache_op_t op)
   {
     switch (op)
     {
-      case hb_ot_lookup_cache_op_t::CREATE:
+      case hb_ot_subtable_cache_op_t::CREATE:
 	return (void *) true;
-      case hb_ot_lookup_cache_op_t::ENTER:
+      case hb_ot_subtable_cache_op_t::ENTER:
       {
 	hb_ot_apply_context_t *c = (hb_ot_apply_context_t *) p;
 	if (!HB_BUFFER_TRY_ALLOCATE_VAR (c->buffer, syllable))
@@ -3878,14 +3891,14 @@
 	c->new_syllables = 255;
 	return (void *) true;
       }
-      case hb_ot_lookup_cache_op_t::LEAVE:
+      case hb_ot_subtable_cache_op_t::LEAVE:
       {
 	hb_ot_apply_context_t *c = (hb_ot_apply_context_t *) p;
 	c->new_syllables = (unsigned) -1;
 	HB_BUFFER_DEALLOCATE_VAR (c->buffer, syllable);
 	return nullptr;
       }
-      case hb_ot_lookup_cache_op_t::DESTROY:
+      case hb_ot_subtable_cache_op_t::DESTROY:
         return nullptr;
     }
     return nullptr;
@@ -4414,20 +4427,20 @@
       thiz->digest.union_ (subtable.digest);
 
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
-    if (c_accelerate_subtables.cache_user_cost < 4)
-      c_accelerate_subtables.cache_user_idx = (unsigned) -1;
+    if (c_accelerate_subtables.subtable_cache_user_cost < 4)
+      c_accelerate_subtables.subtable_cache_user_idx = (unsigned) -1;
 
-    thiz->cache_user_idx = c_accelerate_subtables.cache_user_idx;
+    thiz->subtable_cache_user_idx = c_accelerate_subtables.subtable_cache_user_idx;
 
-    if (thiz->cache_user_idx != (unsigned) -1)
+    if (thiz->subtable_cache_user_idx != (unsigned) -1)
     {
-      thiz->cache = thiz->subtables[thiz->cache_user_idx].cache_func (nullptr, hb_ot_lookup_cache_op_t::CREATE);
-      if (!thiz->cache)
-	thiz->cache_user_idx = (unsigned) -1;
+      thiz->subtable_cache = thiz->subtables[thiz->subtable_cache_user_idx].cache_func (nullptr, hb_ot_subtable_cache_op_t::CREATE);
+      if (!thiz->subtable_cache)
+	thiz->subtable_cache_user_idx = (unsigned) -1;
     }
 
     for (unsigned i = 0; i < count; i++)
-      if (i != thiz->cache_user_idx)
+      if (i != thiz->subtable_cache_user_idx)
 	thiz->subtables[i].apply_cached_func = thiz->subtables[i].apply_func;
 #endif
 
@@ -4437,10 +4450,10 @@
   void fini ()
   {
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
-    if (cache)
+    if (subtable_cache)
     {
-      assert (cache_user_idx != (unsigned) -1);
-      subtables[cache_user_idx].cache_func (cache, hb_ot_lookup_cache_op_t::DESTROY);
+      assert (subtable_cache_user_idx != (unsigned) -1);
+      subtables[subtable_cache_user_idx].cache_func (subtable_cache, hb_ot_subtable_cache_op_t::DESTROY);
     }
 #endif
   }
@@ -4478,8 +4491,8 @@
   bool cache_enter (hb_ot_apply_context_t *c) const
   {
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
-    return cache_user_idx != (unsigned) -1 &&
-	   subtables[cache_user_idx].cache_enter (c);
+    return subtable_cache_user_idx != (unsigned) -1 &&
+	   subtables[subtable_cache_user_idx].cache_enter (c);
 #else
     return false;
 #endif
@@ -4487,7 +4500,7 @@
   void cache_leave (hb_ot_apply_context_t *c) const
   {
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
-    subtables[cache_user_idx].cache_leave (c);
+    subtables[subtable_cache_user_idx].cache_leave (c);
 #endif
   }
 
@@ -4495,9 +4508,9 @@
   hb_set_digest_t digest;
 #ifndef HB_NO_OT_LAYOUT_LOOKUP_CACHE
   public:
-  void *cache = nullptr;
+  void *subtable_cache = nullptr;
   private:
-  unsigned cache_user_idx = (unsigned) -1;
+  unsigned subtable_cache_user_idx = (unsigned) -1;
 #endif
   private:
   hb_accelerate_subtables_context_t::hb_applicable_t subtables[HB_VAR_ARRAY];
diff --git a/src/hb-ot-layout.cc b/src/hb-ot-layout.cc
index 51572ce..a8863d8 100644
--- a/src/hb-ot-layout.cc
+++ b/src/hb-ot-layout.cc
@@ -1982,7 +1982,22 @@
 
   bool ret = false;
 
-  c->set_lookup_props (lookup.get_props ());
+  unsigned lookup_props = lookup.get_props ();
+  if (lookup_props != c->cached_props)
+  {
+    bool cache_it = subtable_count > 1 && (lookup_props & OT::LookupFlag::UseMarkFilteringSet);
+    if (cache_it)
+    {
+      auto &info = buffer->info;
+      for (unsigned int i = 0; i < buffer->len; i++)
+	_hb_glyph_info_set_match (&info[i],
+				  c->check_glyph_property (&info[i], lookup_props, false));
+      c->cached_props = lookup_props;
+    }
+    else
+      c->cached_props = (unsigned) -1;
+  }
+  c->set_lookup_props (lookup_props);
 
   if (likely (!lookup.is_reverse ()))
   {
diff --git a/src/hb-ot-layout.hh b/src/hb-ot-layout.hh
index a42212f..72de36b 100644
--- a/src/hb-ot-layout.hh
+++ b/src/hb-ot-layout.hh
@@ -81,9 +81,10 @@
   HB_OT_LAYOUT_GLYPH_PROPS_MARK		= 0x08u,
 
   /* The following are used internally; not derived from GDEF. */
-  HB_OT_LAYOUT_GLYPH_PROPS_SUBSTITUTED	= 0x10u,
-  HB_OT_LAYOUT_GLYPH_PROPS_LIGATED	= 0x20u,
-  HB_OT_LAYOUT_GLYPH_PROPS_MULTIPLIED	= 0x40u,
+  HB_OT_LAYOUT_GLYPH_PROPS_MATCHES      = 0x10u,
+  HB_OT_LAYOUT_GLYPH_PROPS_SUBSTITUTED	= 0x20u,
+  HB_OT_LAYOUT_GLYPH_PROPS_LIGATED	= 0x40u,
+  HB_OT_LAYOUT_GLYPH_PROPS_MULTIPLIED	= 0x80u,
 
   HB_OT_LAYOUT_GLYPH_PROPS_PRESERVE     = HB_OT_LAYOUT_GLYPH_PROPS_SUBSTITUTED |
 					  HB_OT_LAYOUT_GLYPH_PROPS_LIGATED |
@@ -610,6 +611,20 @@
   return false;
 }
 
+static inline bool
+_hb_glyph_info_matches (const hb_glyph_info_t *info)
+{
+  return info->glyph_props() & HB_OT_LAYOUT_GLYPH_PROPS_MATCHES;
+}
+static inline void
+_hb_glyph_info_set_match (hb_glyph_info_t *info, bool match)
+{
+  if (match)
+    info->glyph_props() |= HB_OT_LAYOUT_GLYPH_PROPS_MATCHES;
+  else
+    info->glyph_props() &= ~HB_OT_LAYOUT_GLYPH_PROPS_MATCHES;
+}
+
 
 /* Allocation / deallocation. */
 
diff --git a/src/hb-ot-shape.cc b/src/hb-ot-shape.cc
index 6ee0e2b..f97af72 100644
--- a/src/hb-ot-shape.cc
+++ b/src/hb-ot-shape.cc
@@ -614,14 +614,14 @@
     for (unsigned i = 0; i < count; i++)
     {
       auto gc = _hb_glyph_info_get_general_category (&info[i]);
-      if (gc == HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER)
-	found_number = true;
-      else if (HB_UNICODE_GENERAL_CATEGORY_IS_LETTER (gc))
+      if (HB_UNICODE_GENERAL_CATEGORY_IS_LETTER (gc))
       {
 	found_letter = true;
 	break;
       }
-      else if (_hb_codepoint_is_regional_indicator (info[i].codepoint))
+      else if (gc == HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER)
+	found_number = true;
+      else if (unlikely (_hb_codepoint_is_regional_indicator (info[i].codepoint)))
 	found_ri = true;
     }
     if ((found_number || found_ri) && !found_letter)
diff --git a/src/hb-ot-shaper-indic-machine.hh b/src/hb-ot-shaper-indic-machine.hh
index 6ff65c3..92fb64a 100644
--- a/src/hb-ot-shaper-indic-machine.hh
+++ b/src/hb-ot-shaper-indic-machine.hh
@@ -53,7 +53,7 @@
 };
 
 
-#line 57 "hb-ot-shaper-indic-machine.hh"
+#line 54 "hb-ot-shaper-indic-machine.hh"
 #define indic_syllable_machine_ex_A 9u
 #define indic_syllable_machine_ex_C 1u
 #define indic_syllable_machine_ex_CM 16u
@@ -77,7 +77,7 @@
 #define indic_syllable_machine_ex_ZWNJ 5u
 
 
-#line 81 "hb-ot-shaper-indic-machine.hh"
+#line 76 "hb-ot-shaper-indic-machine.hh"
 static const unsigned char _indic_syllable_machine_trans_keys[] = {
 	8u, 57u, 4u, 57u, 5u, 57u, 5u, 57u, 13u, 13u, 4u, 57u, 4u, 57u, 4u, 57u, 
 	8u, 57u, 5u, 57u, 5u, 57u, 13u, 13u, 4u, 57u, 4u, 57u, 4u, 57u, 4u, 57u, 
@@ -1126,7 +1126,7 @@
   int cs;
   hb_glyph_info_t *info = buffer->info;
   
-#line 1130 "hb-ot-shaper-indic-machine.hh"
+#line 1119 "hb-ot-shaper-indic-machine.hh"
 	{
 	cs = indic_syllable_machine_start;
 	ts = 0;
@@ -1142,7 +1142,7 @@
 
   unsigned int syllable_serial = 1;
   
-#line 1146 "hb-ot-shaper-indic-machine.hh"
+#line 1131 "hb-ot-shaper-indic-machine.hh"
 	{
 	int _slen;
 	int _trans;
@@ -1156,7 +1156,7 @@
 #line 1 "NONE"
 	{ts = p;}
 	break;
-#line 1160 "hb-ot-shaper-indic-machine.hh"
+#line 1143 "hb-ot-shaper-indic-machine.hh"
 	}
 
 	_keys = _indic_syllable_machine_trans_keys + (cs<<1);
@@ -1268,7 +1268,7 @@
 #line 117 "hb-ot-shaper-indic-machine.rl"
 	{act = 7;}
 	break;
-#line 1272 "hb-ot-shaper-indic-machine.hh"
+#line 1232 "hb-ot-shaper-indic-machine.hh"
 	}
 
 _again:
@@ -1277,7 +1277,7 @@
 #line 1 "NONE"
 	{ts = 0;}
 	break;
-#line 1281 "hb-ot-shaper-indic-machine.hh"
+#line 1239 "hb-ot-shaper-indic-machine.hh"
 	}
 
 	if ( ++p != pe )
diff --git a/src/hb-ot-shaper-khmer-machine.hh b/src/hb-ot-shaper-khmer-machine.hh
index f1e7a91..848ed23 100644
--- a/src/hb-ot-shaper-khmer-machine.hh
+++ b/src/hb-ot-shaper-khmer-machine.hh
@@ -48,7 +48,7 @@
 };
 
 
-#line 52 "hb-ot-shaper-khmer-machine.hh"
+#line 49 "hb-ot-shaper-khmer-machine.hh"
 #define khmer_syllable_machine_ex_C 1u
 #define khmer_syllable_machine_ex_DOTTEDCIRCLE 11u
 #define khmer_syllable_machine_ex_H 4u
@@ -66,7 +66,7 @@
 #define khmer_syllable_machine_ex_ZWNJ 5u
 
 
-#line 70 "hb-ot-shaper-khmer-machine.hh"
+#line 65 "hb-ot-shaper-khmer-machine.hh"
 static const unsigned char _khmer_syllable_machine_trans_keys[] = {
 	5u, 26u, 5u, 26u, 1u, 15u, 5u, 26u, 5u, 26u, 5u, 26u, 5u, 26u, 5u, 26u, 
 	5u, 26u, 5u, 26u, 5u, 26u, 5u, 26u, 5u, 26u, 1u, 15u, 5u, 26u, 5u, 26u, 
@@ -294,7 +294,7 @@
   int cs;
   hb_glyph_info_t *info = buffer->info;
   
-#line 298 "hb-ot-shaper-khmer-machine.hh"
+#line 287 "hb-ot-shaper-khmer-machine.hh"
 	{
 	cs = khmer_syllable_machine_start;
 	ts = 0;
@@ -310,7 +310,7 @@
 
   unsigned int syllable_serial = 1;
   
-#line 314 "hb-ot-shaper-khmer-machine.hh"
+#line 299 "hb-ot-shaper-khmer-machine.hh"
 	{
 	int _slen;
 	int _trans;
@@ -324,7 +324,7 @@
 #line 1 "NONE"
 	{ts = p;}
 	break;
-#line 328 "hb-ot-shaper-khmer-machine.hh"
+#line 311 "hb-ot-shaper-khmer-machine.hh"
 	}
 
 	_keys = _khmer_syllable_machine_trans_keys + (cs<<1);
@@ -394,7 +394,7 @@
 #line 98 "hb-ot-shaper-khmer-machine.rl"
 	{act = 3;}
 	break;
-#line 398 "hb-ot-shaper-khmer-machine.hh"
+#line 368 "hb-ot-shaper-khmer-machine.hh"
 	}
 
 _again:
@@ -403,7 +403,7 @@
 #line 1 "NONE"
 	{ts = 0;}
 	break;
-#line 407 "hb-ot-shaper-khmer-machine.hh"
+#line 375 "hb-ot-shaper-khmer-machine.hh"
 	}
 
 	if ( ++p != pe )
diff --git a/src/hb-ot-shaper-myanmar-machine.hh b/src/hb-ot-shaper-myanmar-machine.hh
index 4b8da58..292bc9f 100644
--- a/src/hb-ot-shaper-myanmar-machine.hh
+++ b/src/hb-ot-shaper-myanmar-machine.hh
@@ -50,7 +50,7 @@
 };
 
 
-#line 54 "hb-ot-shaper-myanmar-machine.hh"
+#line 51 "hb-ot-shaper-myanmar-machine.hh"
 #define myanmar_syllable_machine_ex_A 9u
 #define myanmar_syllable_machine_ex_As 32u
 #define myanmar_syllable_machine_ex_C 1u
@@ -78,7 +78,7 @@
 #define myanmar_syllable_machine_ex_ZWNJ 5u
 
 
-#line 82 "hb-ot-shaper-myanmar-machine.hh"
+#line 77 "hb-ot-shaper-myanmar-machine.hh"
 static const unsigned char _myanmar_syllable_machine_trans_keys[] = {
 	1u, 57u, 3u, 57u, 5u, 57u, 5u, 57u, 3u, 57u, 5u, 57u, 3u, 57u, 3u, 57u, 
 	3u, 57u, 3u, 57u, 3u, 57u, 5u, 57u, 1u, 15u, 3u, 57u, 3u, 57u, 3u, 57u, 
@@ -549,7 +549,7 @@
   int cs;
   hb_glyph_info_t *info = buffer->info;
   
-#line 553 "hb-ot-shaper-myanmar-machine.hh"
+#line 542 "hb-ot-shaper-myanmar-machine.hh"
 	{
 	cs = myanmar_syllable_machine_start;
 	ts = 0;
@@ -565,7 +565,7 @@
 
   unsigned int syllable_serial = 1;
   
-#line 569 "hb-ot-shaper-myanmar-machine.hh"
+#line 554 "hb-ot-shaper-myanmar-machine.hh"
 	{
 	int _slen;
 	int _trans;
@@ -579,7 +579,7 @@
 #line 1 "NONE"
 	{ts = p;}
 	break;
-#line 583 "hb-ot-shaper-myanmar-machine.hh"
+#line 566 "hb-ot-shaper-myanmar-machine.hh"
 	}
 
 	_keys = _myanmar_syllable_machine_trans_keys + (cs<<1);
@@ -649,7 +649,7 @@
 #line 113 "hb-ot-shaper-myanmar-machine.rl"
 	{act = 3;}
 	break;
-#line 653 "hb-ot-shaper-myanmar-machine.hh"
+#line 623 "hb-ot-shaper-myanmar-machine.hh"
 	}
 
 _again:
@@ -658,7 +658,7 @@
 #line 1 "NONE"
 	{ts = 0;}
 	break;
-#line 662 "hb-ot-shaper-myanmar-machine.hh"
+#line 630 "hb-ot-shaper-myanmar-machine.hh"
 	}
 
 	if ( ++p != pe )
diff --git a/src/hb-ot-shaper-thai.cc b/src/hb-ot-shaper-thai.cc
index 6d293b5..90ef8e3 100644
--- a/src/hb-ot-shaper-thai.cc
+++ b/src/hb-ot-shaper-thai.cc
@@ -191,7 +191,7 @@
 };
 
 
-static enum thai_below_state_t
+static const enum thai_below_state_t
 {
   B0, /* No descender */
   B1, /* Removable descender */
diff --git a/src/hb-ot-shaper-use-machine.hh b/src/hb-ot-shaper-use-machine.hh
index 65b6adc..5072e4d 100644
--- a/src/hb-ot-shaper-use-machine.hh
+++ b/src/hb-ot-shaper-use-machine.hh
@@ -53,7 +53,7 @@
 };
 
 
-#line 57 "hb-ot-shaper-use-machine.hh"
+#line 54 "hb-ot-shaper-use-machine.hh"
 #define use_syllable_machine_ex_B 1u
 #define use_syllable_machine_ex_CGJ 6u
 #define use_syllable_machine_ex_CMAbv 31u
@@ -100,7 +100,7 @@
 #define use_syllable_machine_ex_ZWNJ 14u
 
 
-#line 104 "hb-ot-shaper-use-machine.hh"
+#line 99 "hb-ot-shaper-use-machine.hh"
 static const unsigned char _use_syllable_machine_trans_keys[] = {
 	49u, 51u, 0u, 56u, 11u, 56u, 11u, 56u, 1u, 53u, 14u, 48u, 14u, 47u, 14u, 47u, 
 	14u, 47u, 14u, 46u, 14u, 46u, 14u, 14u, 14u, 48u, 14u, 48u, 14u, 48u, 1u, 14u, 
@@ -929,7 +929,7 @@
   unsigned int act HB_UNUSED;
   int cs;
   
-#line 933 "hb-ot-shaper-use-machine.hh"
+#line 922 "hb-ot-shaper-use-machine.hh"
 	{
 	cs = use_syllable_machine_start;
 	ts = 0;
@@ -942,7 +942,7 @@
 
   unsigned int syllable_serial = 1;
   
-#line 946 "hb-ot-shaper-use-machine.hh"
+#line 931 "hb-ot-shaper-use-machine.hh"
 	{
 	int _slen;
 	int _trans;
@@ -956,7 +956,7 @@
 #line 1 "NONE"
 	{ts = p;}
 	break;
-#line 960 "hb-ot-shaper-use-machine.hh"
+#line 943 "hb-ot-shaper-use-machine.hh"
 	}
 
 	_keys = _use_syllable_machine_trans_keys + (cs<<1);
@@ -1078,7 +1078,7 @@
 #line 181 "hb-ot-shaper-use-machine.rl"
 	{act = 9;}
 	break;
-#line 1082 "hb-ot-shaper-use-machine.hh"
+#line 1039 "hb-ot-shaper-use-machine.hh"
 	}
 
 _again:
@@ -1087,7 +1087,7 @@
 #line 1 "NONE"
 	{ts = 0;}
 	break;
-#line 1091 "hb-ot-shaper-use-machine.hh"
+#line 1046 "hb-ot-shaper-use-machine.hh"
 	}
 
 	if ( ++p != pe )
diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh
index c6dbf8d..cfc4092 100644
--- a/src/hb-serialize.hh
+++ b/src/hb-serialize.hh
@@ -724,7 +724,7 @@
 	   hb_requires (hb_is_iterator (Iterator)),
 	   typename ...Ts>
   void copy_all (Iterator it, Ts&&... ds)
-  { for (decltype (*it) _ : it) copy (_, std::forward<Ts> (ds)...); }
+  { for (decltype (*it) _ : it) copy (_, ds...); }
 
   template <typename Type>
   hb_serialize_context_t& operator << (const Type &obj) & { embed (obj); return *this; }
diff --git a/src/hb-shape.cc b/src/hb-shape.cc
index c62f8c6..db46e11 100644
--- a/src/hb-shape.cc
+++ b/src/hb-shape.cc
@@ -199,6 +199,7 @@
 
 
 #ifdef HB_EXPERIMENTAL_API
+#ifndef HB_NO_VAR
 
 static float
 buffer_advance (hb_buffer_t *buffer)
@@ -440,7 +441,7 @@
 
   return true;
 }
-
+#endif
 #endif
 
 
diff --git a/src/hb-shaper-list.hh b/src/hb-shaper-list.hh
index 41673a3..cb386f2 100644
--- a/src/hb-shaper-list.hh
+++ b/src/hb-shaper-list.hh
@@ -61,6 +61,10 @@
 HB_SHAPER_IMPLEMENT (harfrust)
 #endif
 
+#ifdef HAVE_KBTS
+HB_SHAPER_IMPLEMENT (kbts)
+#endif
+
 #ifndef HB_NO_FALLBACK_SHAPE
 HB_SHAPER_IMPLEMENT (fallback) /* <--- This should be last. */
 #endif
diff --git a/src/hb-subset-plan.hh b/src/hb-subset-plan.hh
index 335b750..30e8e63 100644
--- a/src/hb-subset-plan.hh
+++ b/src/hb-subset-plan.hh
@@ -300,6 +300,7 @@
 // compile times more reasonable:
 // - hb-subset-plan.cc
 // - hb-subset-plan-layout.cc
+// - hb-subset-plan-var.cc
 //
 // The functions below are those needed to connect the split files
 // above together.
diff --git a/src/hb-subset-table-cff.cc b/src/hb-subset-table-cff.cc
new file mode 100644
index 0000000..3984cfb
--- /dev/null
+++ b/src/hb-subset-table-cff.cc
@@ -0,0 +1,143 @@
+#include "hb-subset-table.hh"
+
+#include "hb-ot-cff1-table.hh"
+#include "hb-ot-cff2-table.hh"
+#include "hb-ot-vorg-table.hh"
+
+
+#ifndef HB_NO_SUBSET_CFF
+template<>
+struct hb_subset_plan_t::source_table_loader<const OT::cff1>
+{
+  auto operator () (hb_subset_plan_t *plan)
+  HB_AUTO_RETURN (plan->accelerator ? plan->accelerator->cff1_accel :
+		  plan->inprogress_accelerator ? plan->inprogress_accelerator->cff1_accel :
+		  plan->cff1_accel)
+};
+template<>
+struct hb_subset_plan_t::source_table_loader<const OT::cff2>
+{
+  auto operator () (hb_subset_plan_t *plan)
+  HB_AUTO_RETURN (plan->accelerator ? plan->accelerator->cff2_accel :
+		  plan->inprogress_accelerator ? plan->inprogress_accelerator->cff2_accel :
+		  plan->cff2_accel)
+};
+#endif
+
+
+bool _hb_subset_table_cff		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success)
+{
+#ifndef HB_NO_SUBSET_CFF
+  switch (tag)
+  {
+  case HB_TAG('C','F','F',' '): *success = _hb_subset_table<const OT::cff1> (plan, buf); return true;
+  case HB_TAG('C','F','F','2'): *success = _hb_subset_table<const OT::cff2> (plan, buf); return true;
+  case HB_TAG('V','O','R','G'): *success = _hb_subset_table<const OT::VORG> (plan, buf); return true;
+  }
+#endif
+  return false;
+}
+
+
+#ifdef HB_EXPERIMENTAL_API
+#ifndef HB_NO_CFF
+
+template<typename accel_t>
+static hb_blob_t* get_charstrings_data(accel_t& accel, hb_codepoint_t glyph_index) {
+  if (!accel.is_valid()) {
+    return hb_blob_get_empty ();
+  }
+
+  hb_ubytes_t bytes = (*accel.charStrings)[glyph_index];
+  if (!bytes) {
+    return hb_blob_get_empty ();
+  }
+
+  hb_blob_t* cff_blob = accel.get_blob();
+  uint32_t length;
+  const char* cff_data = hb_blob_get_data(cff_blob, &length) ;
+
+  long int offset = (const char*) bytes.arrayZ - cff_data;
+  if (offset < 0 || offset > INT32_MAX) {
+    return hb_blob_get_empty ();
+  }
+
+  return hb_blob_create_sub_blob(cff_blob, (uint32_t) offset, bytes.length);
+}
+
+template<typename accel_t>
+static hb_blob_t* get_charstrings_index(accel_t& accel) {
+  if (!accel.is_valid()) {
+    return hb_blob_get_empty ();
+  }
+
+  const char* charstrings_start = (const char*) accel.charStrings;
+  unsigned charstrings_length = accel.charStrings->get_size();
+
+  hb_blob_t* cff_blob = accel.get_blob();
+  uint32_t length;
+  const char* cff_data = hb_blob_get_data(cff_blob, &length) ;
+
+  long int offset = charstrings_start - cff_data;
+  if (offset < 0 || offset > INT32_MAX) {
+    return hb_blob_get_empty ();
+  }
+
+  return hb_blob_create_sub_blob(cff_blob, (uint32_t) offset, charstrings_length);
+}
+
+/**
+ * hb_subset_cff_get_charstring_data:
+ * @face: A face object
+ * @glyph_index: Glyph index to get data for.
+ *
+ * Returns the raw outline data from the CFF/CFF2 table associated with the given glyph index.
+ *
+ * XSince: EXPERIMENTAL
+ **/
+HB_EXTERN hb_blob_t*
+hb_subset_cff_get_charstring_data(hb_face_t* face, hb_codepoint_t glyph_index) {
+  return get_charstrings_data(*face->table.cff1, glyph_index);
+}
+
+/**
+ * hb_subset_cff_get_charstrings_index:
+ * @face: A face object
+ *
+ * Returns the raw CFF CharStrings INDEX from the CFF table.
+ *
+ * XSince: EXPERIMENTAL
+ **/
+HB_EXTERN hb_blob_t*
+hb_subset_cff_get_charstrings_index (hb_face_t* face) {
+  return get_charstrings_index (*face->table.cff1);
+}
+
+/**
+ * hb_subset_cff2_get_charstring_data:
+ * @face: A face object
+ * @glyph_index: Glyph index to get data for.
+ *
+ * Returns the raw outline data from the CFF/CFF2 table associated with the given glyph index.
+ *
+ * XSince: EXPERIMENTAL
+ **/
+HB_EXTERN hb_blob_t*
+hb_subset_cff2_get_charstring_data(hb_face_t* face, hb_codepoint_t glyph_index) {
+  return get_charstrings_data(*face->table.cff2, glyph_index);
+}
+
+/**
+ * hb_subset_cff2_get_charstrings_index:
+ * @face: A face object
+ *
+ * Returns the raw CFF2 CharStrings INDEX from the CFF2 table.
+ *
+ * XSince: EXPERIMENTAL
+ **/
+HB_EXTERN hb_blob_t*
+hb_subset_cff2_get_charstrings_index (hb_face_t* face) {
+  return get_charstrings_index (*face->table.cff2);
+}
+#endif
+#endif
diff --git a/src/hb-subset-table-color.cc b/src/hb-subset-table-color.cc
new file mode 100644
index 0000000..e44305e
--- /dev/null
+++ b/src/hb-subset-table-color.cc
@@ -0,0 +1,21 @@
+#include "hb-subset-table.hh"
+
+#include "OT/Color/sbix/sbix.hh"
+#include "OT/Color/CPAL/CPAL.hh"
+#include "OT/Color/COLR/COLR.hh"
+#include "OT/Color/CBDT/CBDT.hh"
+
+bool _hb_subset_table_color		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success)
+{
+#ifndef HB_NO_COLOR
+  switch (tag)
+  {
+  case HB_TAG('s','b','i','x'): *success = _hb_subset_table<const OT::sbix> (plan, buf); return true;
+  case HB_TAG('C','O','L','R'): *success = _hb_subset_table<const OT::COLR> (plan, buf); return true;
+  case HB_TAG('C','P','A','L'): *success = _hb_subset_table<const OT::CPAL> (plan, buf); return true;
+  case HB_TAG('C','B','L','C'): *success = _hb_subset_table<const OT::CBLC> (plan, buf); return true;
+  case HB_TAG('C','B','D','T'): *success = true; return true; /* skip CBDT, handled by CBLC */
+  }
+#endif
+  return false;
+}
diff --git a/src/hb-subset-table-layout.cc b/src/hb-subset-table-layout.cc
new file mode 100644
index 0000000..765422e
--- /dev/null
+++ b/src/hb-subset-table-layout.cc
@@ -0,0 +1,22 @@
+#include "hb-subset-table.hh"
+
+#include "hb-ot-layout-gdef-table.hh"
+#include "hb-ot-layout-gsub-table.hh"
+#include "hb-ot-layout-gpos-table.hh"
+#include "hb-ot-layout-base-table.hh"
+#include "hb-ot-math-table.hh"
+
+bool _hb_subset_table_layout		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success)
+{
+#ifndef HB_NO_SUBSET_LAYOUT
+  switch (tag)
+  {
+  case HB_TAG('G','D','E','F'): *success = _hb_subset_table<const OT::GDEF> (plan, buf); return true;
+  case HB_TAG('G','S','U','B'): *success = _hb_subset_table<const OT::Layout::GSUB> (plan, buf); return true;
+  case HB_TAG('G','P','O','S'): *success = _hb_subset_table<const OT::Layout::GPOS> (plan, buf); return true;
+  case HB_TAG('B','A','S','E'): *success = _hb_subset_table<const OT::BASE> (plan, buf); return true;
+  case HB_TAG('M','A','T','H'): *success = _hb_subset_table<const OT::MATH> (plan, buf); return true;
+  }
+#endif
+  return false;
+}
diff --git a/src/hb-subset-table-other.cc b/src/hb-subset-table-other.cc
new file mode 100644
index 0000000..3b7ff4b
--- /dev/null
+++ b/src/hb-subset-table-other.cc
@@ -0,0 +1,31 @@
+#include "hb-subset-table.hh"
+
+#include "hb-ot-cmap-table.hh"
+#include "hb-ot-glyf-table.hh"
+#include "hb-ot-hdmx-table.hh"
+#include "hb-ot-hhea-table.hh"
+#include "hb-ot-hmtx-table.hh"
+#include "hb-ot-maxp-table.hh"
+#include "hb-ot-os2-table.hh"
+#include "hb-ot-name-table.hh"
+#include "hb-ot-post-table.hh"
+
+bool _hb_subset_table_other		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success)
+{
+  switch (tag)
+  {
+  case HB_TAG('g','l','y','f'): *success = _hb_subset_table<const OT::glyf> (plan, buf); return true;
+  case HB_TAG('h','d','m','x'): *success = _hb_subset_table<const OT::hdmx> (plan, buf); return true;
+  case HB_TAG('n','a','m','e'): *success = _hb_subset_table<const OT::name> (plan, buf); return true;
+  case HB_TAG('h','h','e','a'): *success = true; return true; /* skip hhea, handled by hmtx */
+  case HB_TAG('h','m','t','x'): *success = _hb_subset_table<const OT::hmtx> (plan, buf); return true;
+  case HB_TAG('v','h','e','a'): *success = true; return true; /* skip vhea, handled by vmtx */
+  case HB_TAG('v','m','t','x'): *success = _hb_subset_table<const OT::vmtx> (plan, buf); return true;
+  case HB_TAG('m','a','x','p'): *success = _hb_subset_table<const OT::maxp> (plan, buf); return true;
+  case HB_TAG('l','o','c','a'): *success = true; return true; /* skip loca, handled by glyf */
+  case HB_TAG('c','m','a','p'): *success = _hb_subset_table<const OT::cmap> (plan, buf); return true;
+  case HB_TAG('O','S','/','2'): *success = _hb_subset_table<const OT::OS2 > (plan, buf); return true;
+  case HB_TAG('p','o','s','t'): *success = _hb_subset_table<const OT::post> (plan, buf); return true;
+  }
+  return false;
+}
diff --git a/src/hb-subset-table-var.cc b/src/hb-subset-table-var.cc
new file mode 100644
index 0000000..b569b08
--- /dev/null
+++ b/src/hb-subset-table-var.cc
@@ -0,0 +1,45 @@
+#include "hb-subset-table.hh"
+
+#include "hb-ot-var-hvar-table.hh"
+#include "hb-ot-var-gvar-table.hh"
+#include "hb-ot-var-fvar-table.hh"
+#include "hb-ot-var-avar-table.hh"
+#include "hb-ot-var-cvar-table.hh"
+#include "hb-ot-var-mvar-table.hh"
+
+bool _hb_subset_table_var		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success)
+{
+#ifndef HB_NO_VAR
+  switch (tag)
+  {
+  case HB_TAG('H','V','A','R'): *success = _hb_subset_table<const OT::HVAR> (plan, buf); return true;
+  case HB_TAG('V','V','A','R'): *success = _hb_subset_table<const OT::VVAR> (plan, buf); return true;
+  case HB_TAG('g','v','a','r'): *success = _hb_subset_table<const OT::gvar> (plan, buf); return true;
+  case HB_TAG('f','v','a','r'):
+    if (plan->user_axes_location.is_empty ())
+      *success = _hb_subset_table_passthrough (plan, tag);
+    else
+      *success = _hb_subset_table<const OT::fvar> (plan, buf);
+    return true;
+  case HB_TAG('a','v','a','r'):
+    if (plan->user_axes_location.is_empty ())
+      *success = _hb_subset_table_passthrough (plan, tag);
+    else
+      *success = _hb_subset_table<const OT::avar> (plan, buf);
+    return true;
+  case HB_TAG('c','v','a','r'):
+    if (plan->user_axes_location.is_empty ())
+      *success = _hb_subset_table_passthrough (plan, tag);
+    else
+      *success = _hb_subset_table<const OT::cvar> (plan, buf);
+    return true;
+  case HB_TAG('M','V','A','R'):
+    if (plan->user_axes_location.is_empty ())
+      *success = _hb_subset_table_passthrough (plan, tag);
+    else
+      *success = _hb_subset_table<const OT::MVAR> (plan, buf);
+    return true;
+  }
+#endif
+  return false;
+}
diff --git a/src/hb-subset-table.hh b/src/hb-subset-table.hh
new file mode 100644
index 0000000..66588ec
--- /dev/null
+++ b/src/hb-subset-table.hh
@@ -0,0 +1,217 @@
+/*
+ * Copyright © 2018  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Garret Rieger, Roderick Sheeter
+ */
+
+#ifndef HB_SUBSET_TABLE_HH
+#define HB_SUBSET_TABLE_HH
+
+
+#include "hb.hh"
+
+#include "hb-subset.hh"
+#include "hb-repacker.hh"
+
+
+template<typename TableType>
+static bool
+_hb_subset_table_try (const TableType *table,
+		      hb_vector_t<char>* buf,
+		      hb_subset_context_t* c /* OUT */)
+{
+  c->serializer->start_serialize ();
+  if (c->serializer->in_error ()) return false;
+
+  bool needed = table->subset (c);
+  if (!c->serializer->ran_out_of_room ())
+  {
+    c->serializer->end_serialize ();
+    return needed;
+  }
+
+  unsigned buf_size = buf->allocated;
+  buf_size = buf_size * 2 + 16;
+
+
+
+
+  DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c ran out of room; reallocating to %u bytes.",
+             HB_UNTAG (c->table_tag), buf_size);
+
+  if (unlikely (buf_size > c->source_blob->length * 256 ||
+		!buf->alloc_exact (buf_size)))
+  {
+    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c failed to reallocate %u bytes.",
+               HB_UNTAG (c->table_tag), buf_size);
+    return needed;
+  }
+
+  c->serializer->reset (buf->arrayZ, buf->allocated);
+  return _hb_subset_table_try (table, buf, c);
+}
+
+static HB_UNUSED unsigned
+_hb_subset_estimate_table_size (hb_subset_plan_t *plan,
+				unsigned table_len,
+				hb_tag_t table_tag)
+{
+  unsigned src_glyphs = plan->source->get_num_glyphs ();
+  unsigned dst_glyphs = plan->glyphset ()->get_population ();
+
+  unsigned bulk = 8192;
+  /* Tables that we want to allocate same space as the source table. For GSUB/GPOS it's
+   * because those are expensive to subset, so giving them more room is fine. */
+  bool same_size = table_tag == HB_TAG('G','S','U','B') ||
+		   table_tag == HB_TAG('G','P','O','S') ||
+		   table_tag == HB_TAG('G','D','E','F') ||
+		   table_tag == HB_TAG('n','a','m','e');
+
+  if (plan->flags & HB_SUBSET_FLAGS_RETAIN_GIDS)
+  {
+    if (table_tag == HB_TAG('C','F','F',' '))
+    {
+      /* Add some extra room for the CFF charset. */
+      bulk += src_glyphs * 16;
+    }
+    else if (table_tag == HB_TAG('C','F','F','2'))
+    {
+      /* Just extra CharString offsets. */
+      bulk += src_glyphs * 4;
+    }
+  }
+
+  if (unlikely (!src_glyphs) || same_size)
+    return bulk + table_len;
+
+  return bulk + (unsigned) (table_len * sqrt ((double) dst_glyphs / src_glyphs));
+}
+
+/*
+ * Repack the serialization buffer if any offset overflows exist.
+ */
+static HB_UNUSED hb_blob_t*
+_hb_subset_repack (hb_tag_t tag, const hb_serialize_context_t& c)
+{
+  if (!c.offset_overflow ())
+    return c.copy_blob ();
+
+  hb_blob_t* result = hb_resolve_overflows (c.object_graph (), tag);
+
+  if (unlikely (!result))
+  {
+    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c offset overflow resolution failed.",
+               HB_UNTAG (tag));
+    return nullptr;
+  }
+
+  return result;
+}
+
+template <typename T>
+static HB_UNUSED auto _hb_do_destroy (T &t, hb_priority<1>) HB_RETURN (void, t.destroy ())
+
+template <typename T>
+static HB_UNUSED void _hb_do_destroy (T &t, hb_priority<0>) {}
+
+template<typename TableType>
+static bool
+_hb_subset_table (hb_subset_plan_t *plan, hb_vector_t<char> &buf)
+{
+  auto &&source_blob = plan->source_table<TableType> ();
+  auto *table = source_blob.get ();
+
+  hb_tag_t tag = TableType::tableTag;
+  hb_blob_t *blob = source_blob.get_blob();
+  if (unlikely (!blob || !blob->data))
+  {
+    DEBUG_MSG (SUBSET, nullptr,
+               "OT::%c%c%c%c::subset sanitize failed on source table.", HB_UNTAG (tag));
+    _hb_do_destroy (source_blob, hb_prioritize);
+    return false;
+  }
+
+  unsigned buf_size = _hb_subset_estimate_table_size (plan, blob->length, TableType::tableTag);
+  DEBUG_MSG (SUBSET, nullptr,
+             "OT::%c%c%c%c initial estimated table size: %u bytes.", HB_UNTAG (tag), buf_size);
+  if (unlikely (!buf.alloc (buf_size)))
+  {
+    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c failed to allocate %u bytes.", HB_UNTAG (tag), buf_size);
+    _hb_do_destroy (source_blob, hb_prioritize);
+    return false;
+  }
+
+  bool needed = false;
+  hb_serialize_context_t serializer (buf.arrayZ, buf.allocated);
+  {
+    hb_subset_context_t c (blob, plan, &serializer, tag);
+    needed = _hb_subset_table_try (table, &buf, &c);
+  }
+  _hb_do_destroy (source_blob, hb_prioritize);
+
+  if (serializer.in_error () && !serializer.only_offset_overflow ())
+  {
+    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset FAILED!", HB_UNTAG (tag));
+    return false;
+  }
+
+  if (!needed)
+  {
+    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset table subsetted to empty.", HB_UNTAG (tag));
+    return true;
+  }
+
+  bool result = false;
+  hb_blob_t *dest_blob = _hb_subset_repack (tag, serializer);
+  if (dest_blob)
+  {
+    DEBUG_MSG (SUBSET, nullptr,
+               "OT::%c%c%c%c final subset table size: %u bytes.",
+               HB_UNTAG (tag), dest_blob->length);
+    result = plan->add_table (tag, dest_blob);
+    hb_blob_destroy (dest_blob);
+  }
+
+  DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset %s",
+             HB_UNTAG (tag), result ? "success" : "FAILED!");
+  return result;
+}
+
+static HB_UNUSED bool
+_hb_subset_table_passthrough (hb_subset_plan_t *plan, hb_tag_t tag)
+{
+  hb_blob_t *source_table = hb_face_reference_table (plan->source, tag);
+  bool result = plan->add_table (tag, source_table);
+  hb_blob_destroy (source_table);
+  return result;
+}
+
+
+HB_INTERNAL bool _hb_subset_table_layout	(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success);
+HB_INTERNAL bool _hb_subset_table_var		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success);
+HB_INTERNAL bool _hb_subset_table_cff		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success);
+HB_INTERNAL bool _hb_subset_table_color		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success);
+HB_INTERNAL bool _hb_subset_table_other		(hb_subset_plan_t *plan, hb_vector_t<char> &buf, hb_tag_t tag, bool *success);
+
+
+#endif /* HB_SUBSET_TABLE_HH */
diff --git a/src/hb-subset.cc b/src/hb-subset.cc
index 7f9dc34..d1ca60e 100644
--- a/src/hb-subset.cc
+++ b/src/hb-subset.cc
@@ -25,65 +25,19 @@
  */
 
 #include "hb.hh"
+
 #include "hb-open-type.hh"
+#include "hb-open-file.hh"
 
 #include "hb-subset.hh"
-
-#include "hb-open-file.hh"
-#include "hb-ot-cmap-table.hh"
-#include "hb-ot-glyf-table.hh"
-#include "hb-ot-hdmx-table.hh"
-#include "hb-ot-head-table.hh"
-#include "hb-ot-hhea-table.hh"
-#include "hb-ot-hmtx-table.hh"
-#include "hb-ot-maxp-table.hh"
-#include "OT/Color/CBDT/CBDT.hh"
-#include "OT/Color/COLR/COLR.hh"
-#include "OT/Color/CPAL/CPAL.hh"
-#include "OT/Color/sbix/sbix.hh"
-#include "hb-ot-os2-table.hh"
-#include "hb-ot-post-table.hh"
-#include "hb-ot-post-table-v2subset.hh"
-#include "hb-ot-cff1-table.hh"
-#include "hb-ot-cff2-table.hh"
-#include "hb-ot-vorg-table.hh"
-#include "hb-ot-name-table.hh"
-#include "hb-ot-layout-base-table.hh"
-#include "hb-ot-layout-gsub-table.hh"
-#include "hb-ot-layout-gpos-table.hh"
-#include "hb-ot-var-avar-table.hh"
-#include "hb-ot-var-cvar-table.hh"
-#include "hb-ot-var-fvar-table.hh"
-#include "hb-ot-var-gvar-table.hh"
-#include "hb-ot-var-hvar-table.hh"
-#include "hb-ot-var-mvar-table.hh"
-#include "hb-ot-math-table.hh"
-#include "hb-ot-stat-table.hh"
-#include "hb-repacker.hh"
+#include "hb-subset-table.hh"
 #include "hb-subset-accelerator.hh"
 
-using OT::Layout::GSUB;
-using OT::Layout::GPOS;
-
-
-#ifndef HB_NO_SUBSET_CFF
-template<>
-struct hb_subset_plan_t::source_table_loader<const OT::cff1>
-{
-  auto operator () (hb_subset_plan_t *plan)
-  HB_AUTO_RETURN (plan->accelerator ? plan->accelerator->cff1_accel :
-		  plan->inprogress_accelerator ? plan->inprogress_accelerator->cff1_accel :
-		  plan->cff1_accel)
-};
-template<>
-struct hb_subset_plan_t::source_table_loader<const OT::cff2>
-{
-  auto operator () (hb_subset_plan_t *plan)
-  HB_AUTO_RETURN (plan->accelerator ? plan->accelerator->cff2_accel :
-		  plan->inprogress_accelerator ? plan->inprogress_accelerator->cff2_accel :
-		  plan->cff2_accel)
-};
-#endif
+#include "hb-ot-cmap-table.hh"
+#include "hb-ot-var-cvar-table.hh"
+#include "hb-ot-head-table.hh"
+#include "hb-ot-stat-table.hh"
+#include "hb-ot-post-table-v2subset.hh"
 
 
 /**
@@ -116,56 +70,56 @@
  * if we are unable to list the tables in a face.
  */
 static hb_tag_t known_tables[] {
-  HB_TAG ('a', 'v', 'a', 'r'),
-  HB_OT_TAG_BASE,
-  HB_OT_TAG_CBDT,
-  HB_OT_TAG_CBLC,
-  HB_OT_TAG_CFF1,
-  HB_OT_TAG_CFF2,
-  HB_OT_TAG_cmap,
-  HB_OT_TAG_COLR,
-  HB_OT_TAG_CPAL,
-  HB_TAG ('c', 'v', 'a', 'r'),
-  HB_TAG ('c', 'v', 't', ' '),
-  HB_TAG ('D', 'S', 'I', 'G'),
-  HB_TAG ('E', 'B', 'D', 'T'),
-  HB_TAG ('E', 'B', 'L', 'C'),
-  HB_TAG ('E', 'B', 'S', 'C'),
-  HB_TAG ('f', 'p', 'g', 'm'),
-  HB_TAG ('f', 'v', 'a', 'r'),
-  HB_TAG ('g', 'a', 's', 'p'),
-  HB_OT_TAG_GDEF,
-  HB_OT_TAG_glyf,
-  HB_OT_TAG_GPOS,
-  HB_OT_TAG_GSUB,
-  HB_OT_TAG_gvar,
-  HB_OT_TAG_hdmx,
-  HB_OT_TAG_head,
-  HB_OT_TAG_hhea,
-  HB_OT_TAG_hmtx,
-  HB_OT_TAG_HVAR,
-  HB_OT_TAG_JSTF,
-  HB_TAG ('k', 'e', 'r', 'n'),
-  HB_OT_TAG_loca,
-  HB_TAG ('L', 'T', 'S', 'H'),
-  HB_OT_TAG_MATH,
-  HB_OT_TAG_maxp,
-  HB_TAG ('M', 'E', 'R', 'G'),
-  HB_TAG ('m', 'e', 't', 'a'),
-  HB_TAG ('M', 'V', 'A', 'R'),
-  HB_TAG ('P', 'C', 'L', 'T'),
-  HB_OT_TAG_post,
-  HB_TAG ('p', 'r', 'e', 'p'),
-  HB_OT_TAG_sbix,
-  HB_TAG ('S', 'T', 'A', 'T'),
-  HB_TAG ('S', 'V', 'G', ' '),
-  HB_TAG ('V', 'D', 'M', 'X'),
-  HB_OT_TAG_vhea,
-  HB_OT_TAG_vmtx,
-  HB_OT_TAG_VORG,
-  HB_OT_TAG_VVAR,
-  HB_OT_TAG_name,
-  HB_OT_TAG_OS2
+  HB_TAG('a','v','a','r'),
+  HB_TAG('B','A','S','E'),
+  HB_TAG('C','B','D','T'),
+  HB_TAG('C','B','L','C'),
+  HB_TAG('C','F','F',' '),
+  HB_TAG('C','F','F','2'),
+  HB_TAG('c','m','a','p'),
+  HB_TAG('C','O','L','R'),
+  HB_TAG('C','P','A','L'),
+  HB_TAG('c','v','a','r'),
+  HB_TAG('c','v','t',' '),
+  HB_TAG('D','S','I','G'),
+  HB_TAG('E','B','D','T'),
+  HB_TAG('E','B','L','C'),
+  HB_TAG('E','B','S','C'),
+  HB_TAG('f','p','g','m'),
+  HB_TAG('f','v','a','r'),
+  HB_TAG('g','a','s','p'),
+  HB_TAG('G','D','E','F'),
+  HB_TAG('g','l','y','f'),
+  HB_TAG('G','P','O','S'),
+  HB_TAG('G','S','U','B'),
+  HB_TAG('g','v','a','r'),
+  HB_TAG('h','d','m','x'),
+  HB_TAG('h','e','a','d'),
+  HB_TAG('h','h','e','a'),
+  HB_TAG('h','m','t','x'),
+  HB_TAG('H','V','A','R'),
+  HB_TAG('J','S','T','F'),
+  HB_TAG('k','e','r','n'),
+  HB_TAG('l','o','c','a'),
+  HB_TAG('L','T','S','H'),
+  HB_TAG('M','A','T','H'),
+  HB_TAG('m','a','x','p'),
+  HB_TAG('M','E','R','G'),
+  HB_TAG('m','e','t','a'),
+  HB_TAG('M','V','A','R'),
+  HB_TAG('P','C','L','T'),
+  HB_TAG('p','o','s','t'),
+  HB_TAG('p','r','e','p'),
+  HB_TAG('s','b','i','x'),
+  HB_TAG('S','T','A','T'),
+  HB_TAG('S','V','G',' '),
+  HB_TAG('V','D','M','X'),
+  HB_TAG('v','h','e','a'),
+  HB_TAG('v','m','t','x'),
+  HB_TAG('V','O','R','G'),
+  HB_TAG('V','V','A','R'),
+  HB_TAG('n','a','m','e'),
+  HB_TAG('O','S','/','2')
 };
 
 static bool _table_is_empty (const hb_face_t *face, hb_tag_t tag)
@@ -213,169 +167,6 @@
 }
 
 
-static unsigned
-_plan_estimate_subset_table_size (hb_subset_plan_t *plan,
-				  unsigned table_len,
-				  hb_tag_t table_tag)
-{
-  unsigned src_glyphs = plan->source->get_num_glyphs ();
-  unsigned dst_glyphs = plan->glyphset ()->get_population ();
-
-  unsigned bulk = 8192;
-  /* Tables that we want to allocate same space as the source table. For GSUB/GPOS it's
-   * because those are expensive to subset, so giving them more room is fine. */
-  bool same_size = table_tag == HB_OT_TAG_GSUB ||
-		   table_tag == HB_OT_TAG_GPOS ||
-		   table_tag == HB_OT_TAG_name;
-
-  if (plan->flags & HB_SUBSET_FLAGS_RETAIN_GIDS)
-  {
-    if (table_tag == HB_OT_TAG_CFF1)
-    {
-      /* Add some extra room for the CFF charset. */
-      bulk += src_glyphs * 16;
-    }
-    else if (table_tag == HB_OT_TAG_CFF2)
-    {
-      /* Just extra CharString offsets. */
-      bulk += src_glyphs * 4;
-    }
-  }
-
-  if (unlikely (!src_glyphs) || same_size)
-    return bulk + table_len;
-
-  return bulk + (unsigned) (table_len * sqrt ((double) dst_glyphs / src_glyphs));
-}
-
-/*
- * Repack the serialization buffer if any offset overflows exist.
- */
-static hb_blob_t*
-_repack (hb_tag_t tag, const hb_serialize_context_t& c)
-{
-  if (!c.offset_overflow ())
-    return c.copy_blob ();
-
-  hb_blob_t* result = hb_resolve_overflows (c.object_graph (), tag);
-
-  if (unlikely (!result))
-  {
-    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c offset overflow resolution failed.",
-               HB_UNTAG (tag));
-    return nullptr;
-  }
-
-  return result;
-}
-
-template<typename TableType>
-static
-bool
-_try_subset (const TableType *table,
-             hb_vector_t<char>* buf,
-             hb_subset_context_t* c /* OUT */)
-{
-  c->serializer->start_serialize ();
-  if (c->serializer->in_error ()) return false;
-
-  bool needed = table->subset (c);
-  if (!c->serializer->ran_out_of_room ())
-  {
-    c->serializer->end_serialize ();
-    return needed;
-  }
-
-  unsigned buf_size = buf->allocated;
-  buf_size = buf_size * 2 + 16;
-
-
-
-
-  DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c ran out of room; reallocating to %u bytes.",
-             HB_UNTAG (c->table_tag), buf_size);
-
-  if (unlikely (buf_size > c->source_blob->length * 256 ||
-		!buf->alloc_exact (buf_size)))
-  {
-    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c failed to reallocate %u bytes.",
-               HB_UNTAG (c->table_tag), buf_size);
-    return needed;
-  }
-
-  c->serializer->reset (buf->arrayZ, buf->allocated);
-  return _try_subset (table, buf, c);
-}
-
-template <typename T>
-static auto _do_destroy (T &t, hb_priority<1>) HB_RETURN (void, t.destroy ())
-
-template <typename T>
-static void _do_destroy (T &t, hb_priority<0>) {}
-
-template<typename TableType>
-static bool
-_subset (hb_subset_plan_t *plan, hb_vector_t<char> &buf)
-{
-  auto &&source_blob = plan->source_table<TableType> ();
-  auto *table = source_blob.get ();
-
-  hb_tag_t tag = TableType::tableTag;
-  hb_blob_t *blob = source_blob.get_blob();
-  if (unlikely (!blob || !blob->data))
-  {
-    DEBUG_MSG (SUBSET, nullptr,
-               "OT::%c%c%c%c::subset sanitize failed on source table.", HB_UNTAG (tag));
-    _do_destroy (source_blob, hb_prioritize);
-    return false;
-  }
-
-  unsigned buf_size = _plan_estimate_subset_table_size (plan, blob->length, TableType::tableTag);
-  DEBUG_MSG (SUBSET, nullptr,
-             "OT::%c%c%c%c initial estimated table size: %u bytes.", HB_UNTAG (tag), buf_size);
-  if (unlikely (!buf.alloc (buf_size)))
-  {
-    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c failed to allocate %u bytes.", HB_UNTAG (tag), buf_size);
-    _do_destroy (source_blob, hb_prioritize);
-    return false;
-  }
-
-  bool needed = false;
-  hb_serialize_context_t serializer (buf.arrayZ, buf.allocated);
-  {
-    hb_subset_context_t c (blob, plan, &serializer, tag);
-    needed = _try_subset (table, &buf, &c);
-  }
-  _do_destroy (source_blob, hb_prioritize);
-
-  if (serializer.in_error () && !serializer.only_offset_overflow ())
-  {
-    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset FAILED!", HB_UNTAG (tag));
-    return false;
-  }
-
-  if (!needed)
-  {
-    DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset table subsetted to empty.", HB_UNTAG (tag));
-    return true;
-  }
-
-  bool result = false;
-  hb_blob_t *dest_blob = _repack (tag, serializer);
-  if (dest_blob)
-  {
-    DEBUG_MSG (SUBSET, nullptr,
-               "OT::%c%c%c%c final subset table size: %u bytes.",
-               HB_UNTAG (tag), dest_blob->length);
-    result = plan->add_table (tag, dest_blob);
-    hb_blob_destroy (dest_blob);
-  }
-
-  DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset %s",
-             HB_UNTAG (tag), result ? "success" : "FAILED!");
-  return result;
-}
-
 static bool
 _is_table_present (hb_face_t *source, hb_tag_t tag)
 {
@@ -407,34 +198,34 @@
 
   switch (tag)
   {
-  case HB_TAG ('c','v','a','r'): /* hint table, fallthrough */
+  case HB_TAG('c','v','a','r'): /* hint table, fallthrough */
     return plan->all_axes_pinned || (plan->flags & HB_SUBSET_FLAGS_NO_HINTING);
 
-  case HB_TAG ('c','v','t',' '): /* hint table, fallthrough */
-  case HB_TAG ('f','p','g','m'): /* hint table, fallthrough */
-  case HB_TAG ('p','r','e','p'): /* hint table, fallthrough */
-  case HB_TAG ('h','d','m','x'): /* hint table, fallthrough */
-  case HB_TAG ('V','D','M','X'): /* hint table, fallthrough */
+  case HB_TAG('c','v','t',' '): /* hint table, fallthrough */
+  case HB_TAG('f','p','g','m'): /* hint table, fallthrough */
+  case HB_TAG('p','r','e','p'): /* hint table, fallthrough */
+  case HB_TAG('h','d','m','x'): /* hint table, fallthrough */
+  case HB_TAG('V','D','M','X'): /* hint table, fallthrough */
     return plan->flags & HB_SUBSET_FLAGS_NO_HINTING;
 
 #ifdef HB_NO_SUBSET_LAYOUT
     // Drop Layout Tables if requested.
-  case HB_OT_TAG_GDEF:
-  case HB_OT_TAG_GPOS:
-  case HB_OT_TAG_GSUB:
-  case HB_TAG ('m','o','r','x'):
-  case HB_TAG ('m','o','r','t'):
-  case HB_TAG ('k','e','r','x'):
-  case HB_TAG ('k','e','r','n'):
+  case HB_TAG('G','D','E','F'):
+  case HB_TAG('G','P','O','S'):
+  case HB_TAG('G','S','U','B'):
+  case HB_TAG('m','o','r','x'):
+  case HB_TAG('m','o','r','t'):
+  case HB_TAG('k','e','r','x'):
+  case HB_TAG('k','e','r','n'):
     return true;
 #endif
 
-  case HB_TAG ('a','v','a','r'):
-  case HB_TAG ('f','v','a','r'):
-  case HB_TAG ('g','v','a','r'):
-  case HB_OT_TAG_HVAR:
-  case HB_OT_TAG_VVAR:
-  case HB_TAG ('M','V','A','R'):
+  case HB_TAG('a','v','a','r'):
+  case HB_TAG('f','v','a','r'):
+  case HB_TAG('g','v','a','r'):
+  case HB_TAG('H','V','A','R'):
+  case HB_TAG('V','V','A','R'):
+  case HB_TAG('M','V','A','R'):
     return plan->all_axes_pinned;
 
   default:
@@ -443,28 +234,19 @@
 }
 
 static bool
-_passthrough (hb_subset_plan_t *plan, hb_tag_t tag)
-{
-  hb_blob_t *source_table = hb_face_reference_table (plan->source, tag);
-  bool result = plan->add_table (tag, source_table);
-  hb_blob_destroy (source_table);
-  return result;
-}
-
-static bool
 _dependencies_satisfied (hb_subset_plan_t *plan, hb_tag_t tag,
                          const hb_set_t &subsetted_tags,
                          const hb_set_t &pending_subset_tags)
 {
   switch (tag)
   {
-  case HB_OT_TAG_hmtx:
-  case HB_OT_TAG_vmtx:
-  case HB_OT_TAG_maxp:
-  case HB_OT_TAG_OS2:
-    return !plan->normalized_coords || !pending_subset_tags.has (HB_OT_TAG_glyf);
-  case HB_OT_TAG_GPOS:
-    return plan->all_axes_pinned || !pending_subset_tags.has (HB_OT_TAG_GDEF);
+  case HB_TAG('h','m','t','x'):
+  case HB_TAG('v','m','t','x'):
+  case HB_TAG('m','a','x','p'):
+  case HB_TAG('O','S','/','2'):
+    return !plan->normalized_coords || !pending_subset_tags.has (HB_TAG('g','l','y','f'));
+  case HB_TAG('G','P','O','S'):
+    return plan->all_axes_pinned || !pending_subset_tags.has (HB_TAG('G','D','E','F'));
   default:
     return true;
   }
@@ -476,88 +258,48 @@
 	       hb_tag_t tag)
 {
   if (plan->no_subset_tables.has (tag)) {
-    return _passthrough (plan, tag);
+    return _hb_subset_table_passthrough (plan, tag);
   }
 
   DEBUG_MSG (SUBSET, nullptr, "subset %c%c%c%c", HB_UNTAG (tag));
+
+  bool success;
+  if (_hb_subset_table_layout (plan, buf, tag, &success) ||
+      _hb_subset_table_var (plan, buf, tag, &success) ||
+      _hb_subset_table_cff (plan, buf, tag, &success) ||
+      _hb_subset_table_color (plan, buf, tag, &success) ||
+      _hb_subset_table_other (plan, buf, tag, &success))
+    return success;
+
+
   switch (tag)
   {
-  case HB_OT_TAG_glyf: return _subset<const OT::glyf> (plan, buf);
-  case HB_OT_TAG_hdmx: return _subset<const OT::hdmx> (plan, buf);
-  case HB_OT_TAG_name: return _subset<const OT::name> (plan, buf);
-  case HB_OT_TAG_head:
-    if (_is_table_present (plan->source, HB_OT_TAG_glyf) && !_should_drop_table (plan, HB_OT_TAG_glyf))
+  case HB_TAG('h','e','a','d'):
+    if (_is_table_present (plan->source, HB_TAG('g','l','y','f')) && !_should_drop_table (plan, HB_TAG('g','l','y','f')))
       return true; /* skip head, handled by glyf */
-    return _subset<const OT::head> (plan, buf);
-  case HB_OT_TAG_hhea: return true; /* skip hhea, handled by hmtx */
-  case HB_OT_TAG_hmtx: return _subset<const OT::hmtx> (plan, buf);
-  case HB_OT_TAG_vhea: return true; /* skip vhea, handled by vmtx */
-  case HB_OT_TAG_vmtx: return _subset<const OT::vmtx> (plan, buf);
-  case HB_OT_TAG_maxp: return _subset<const OT::maxp> (plan, buf);
-  case HB_OT_TAG_sbix: return _subset<const OT::sbix> (plan, buf);
-  case HB_OT_TAG_loca: return true; /* skip loca, handled by glyf */
-  case HB_OT_TAG_cmap: return _subset<const OT::cmap> (plan, buf);
-  case HB_OT_TAG_OS2 : return _subset<const OT::OS2 > (plan, buf);
-  case HB_OT_TAG_post: return _subset<const OT::post> (plan, buf);
-  case HB_OT_TAG_COLR: return _subset<const OT::COLR> (plan, buf);
-  case HB_OT_TAG_CPAL: return _subset<const OT::CPAL> (plan, buf);
-  case HB_OT_TAG_CBLC: return _subset<const OT::CBLC> (plan, buf);
-  case HB_OT_TAG_CBDT: return true; /* skip CBDT, handled by CBLC */
-  case HB_OT_TAG_MATH: return _subset<const OT::MATH> (plan, buf);
-  case HB_OT_TAG_BASE: return _subset<const OT::BASE> (plan, buf);
+    return _hb_subset_table<const OT::head> (plan, buf);
 
-#ifndef HB_NO_SUBSET_CFF
-  case HB_OT_TAG_CFF1: return _subset<const OT::cff1> (plan, buf);
-  case HB_OT_TAG_CFF2: return _subset<const OT::cff2> (plan, buf);
-  case HB_OT_TAG_VORG: return _subset<const OT::VORG> (plan, buf);
-#endif
+  case HB_TAG('S','T','A','T'):
+    if (!plan->user_axes_location.is_empty ()) return _hb_subset_table<const OT::STAT> (plan, buf);
+    else return _hb_subset_table_passthrough (plan, tag);
 
-#ifndef HB_NO_SUBSET_LAYOUT
-  case HB_OT_TAG_GDEF: return _subset<const OT::GDEF> (plan, buf);
-  case HB_OT_TAG_GSUB: return _subset<const GSUB> (plan, buf);
-  case HB_OT_TAG_GPOS: return _subset<const GPOS> (plan, buf);
-  case HB_OT_TAG_gvar: return _subset<const OT::gvar> (plan, buf);
-  case HB_OT_TAG_HVAR: return _subset<const OT::HVAR> (plan, buf);
-  case HB_OT_TAG_VVAR: return _subset<const OT::VVAR> (plan, buf);
-#endif
-
+  case HB_TAG('c','v','t',' '):
 #ifndef HB_NO_VAR
-  case HB_OT_TAG_fvar:
-    if (plan->user_axes_location.is_empty ()) return _passthrough (plan, tag);
-    return _subset<const OT::fvar> (plan, buf);
-  case HB_OT_TAG_avar:
-    if (plan->user_axes_location.is_empty ()) return _passthrough (plan, tag);
-    return _subset<const OT::avar> (plan, buf);
-  case HB_OT_TAG_cvar:
-    if (plan->user_axes_location.is_empty ()) return _passthrough (plan, tag);
-    return _subset<const OT::cvar> (plan, buf);
-  case HB_OT_TAG_MVAR:
-    if (plan->user_axes_location.is_empty ()) return _passthrough (plan, tag);
-    return _subset<const OT::MVAR> (plan, buf);
-#endif
-
-  case HB_OT_TAG_STAT:
-    if (!plan->user_axes_location.is_empty ()) return _subset<const OT::STAT> (plan, buf);
-    else return _passthrough (plan, tag);
-
-  case HB_TAG ('c', 'v', 't', ' '):
-#ifndef HB_NO_VAR
-    if (_is_table_present (plan->source, HB_OT_TAG_cvar) &&
+    if (_is_table_present (plan->source, HB_TAG('c','v','a','r')) &&
         plan->normalized_coords && !plan->pinned_at_default)
     {
       auto &cvar = *plan->source->table.cvar;
       return OT::cvar::add_cvt_and_apply_deltas (plan, cvar.get_tuple_var_data (), &cvar);
     }
 #endif
-    return _passthrough (plan, tag);
-
-  default:
-    if (plan->flags & HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED)
-      return _passthrough (plan, tag);
-
-    // Drop table
-    return true;
+    return _hb_subset_table_passthrough (plan, tag);
   }
+
+  if (plan->flags & HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED)
+    return _hb_subset_table_passthrough (plan, tag);
+
+  // Drop table
+  return true;
 }
 
 static void _attach_accelerator_data (hb_subset_plan_t* plan,
@@ -707,108 +449,4 @@
 
 end:
   return success ? hb_face_reference (plan->dest) : nullptr;
-}
-
-
-#ifdef HB_EXPERIMENTAL_API
-
-#include "hb-ot-cff1-table.hh"
-
-template<typename accel_t>
-static hb_blob_t* get_charstrings_data(accel_t& accel, hb_codepoint_t glyph_index) {
-  if (!accel.is_valid()) {
-    return hb_blob_get_empty ();
-  }
-
-  hb_ubytes_t bytes = (*accel.charStrings)[glyph_index];
-  if (!bytes) {
-    return hb_blob_get_empty ();
-  }
-
-  hb_blob_t* cff_blob = accel.get_blob();
-  uint32_t length;
-  const char* cff_data = hb_blob_get_data(cff_blob, &length) ;
-
-  long int offset = (const char*) bytes.arrayZ - cff_data;
-  if (offset < 0 || offset > INT32_MAX) {
-    return hb_blob_get_empty ();
-  }
-
-  return hb_blob_create_sub_blob(cff_blob, (uint32_t) offset, bytes.length);
-}
-
-template<typename accel_t>
-static hb_blob_t* get_charstrings_index(accel_t& accel) {
-  if (!accel.is_valid()) {
-    return hb_blob_get_empty ();
-  }
-
-  const char* charstrings_start = (const char*) accel.charStrings;
-  unsigned charstrings_length = accel.charStrings->get_size();
-
-  hb_blob_t* cff_blob = accel.get_blob();
-  uint32_t length;
-  const char* cff_data = hb_blob_get_data(cff_blob, &length) ;
-
-  long int offset = charstrings_start - cff_data;
-  if (offset < 0 || offset > INT32_MAX) {
-    return hb_blob_get_empty ();
-  }
-
-  return hb_blob_create_sub_blob(cff_blob, (uint32_t) offset, charstrings_length);
-}
-
-/**
- * hb_subset_cff_get_charstring_data:
- * @face: A face object
- * @glyph_index: Glyph index to get data for.
- *
- * Returns the raw outline data from the CFF/CFF2 table associated with the given glyph index.
- *
- * XSince: EXPERIMENTAL
- **/
-HB_EXTERN hb_blob_t*
-hb_subset_cff_get_charstring_data(hb_face_t* face, hb_codepoint_t glyph_index) {
-  return get_charstrings_data(*face->table.cff1, glyph_index);
-}
-
-/**
- * hb_subset_cff_get_charstrings_index:
- * @face: A face object
- *
- * Returns the raw CFF CharStrings INDEX from the CFF table.
- *
- * XSince: EXPERIMENTAL
- **/
-HB_EXTERN hb_blob_t*
-hb_subset_cff_get_charstrings_index (hb_face_t* face) {
-  return get_charstrings_index (*face->table.cff1);
-}
- 
-/**
- * hb_subset_cff2_get_charstring_data:
- * @face: A face object
- * @glyph_index: Glyph index to get data for.
- *
- * Returns the raw outline data from the CFF/CFF2 table associated with the given glyph index.
- *
- * XSince: EXPERIMENTAL
- **/
-HB_EXTERN hb_blob_t*
-hb_subset_cff2_get_charstring_data(hb_face_t* face, hb_codepoint_t glyph_index) {
-  return get_charstrings_data(*face->table.cff2, glyph_index);
-}
-
-/**
- * hb_subset_cff2_get_charstrings_index:
- * @face: A face object
- *
- * Returns the raw CFF2 CharStrings INDEX from the CFF2 table.
- *
- * XSince: EXPERIMENTAL
- **/
-HB_EXTERN hb_blob_t*
-hb_subset_cff2_get_charstrings_index (hb_face_t* face) {
-  return get_charstrings_index (*face->table.cff2);
-}
-#endif
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/hb-subset.hh b/src/hb-subset.hh
index 4f192aa..fca378d 100644
--- a/src/hb-subset.hh
+++ b/src/hb-subset.hh
@@ -70,5 +70,4 @@
 			table_tag (table_tag_) {}
 };
 
-
 #endif /* HB_SUBSET_HH */
diff --git a/src/hb.hh b/src/hb.hh
index 47b710e..128f2fc 100644
--- a/src/hb.hh
+++ b/src/hb.hh
@@ -239,6 +239,8 @@
 // clang defines it so no need.
 #ifdef __has_builtin
 #define hb_has_builtin __has_builtin
+#elif defined(_MSC_VER)
+#define hb_has_builtin(x) 0
 #else
 #define hb_has_builtin(x) ((defined(__GNUC__) && __GNUC__ >= 5))
 #endif
@@ -557,4 +559,13 @@
 #include "hb-vector.hh"	// Requires: hb-array hb-null
 #include "hb-object.hh"	// Requires: hb-atomic hb-mutex hb-vector
 
+
+/* Our src/test-*.cc use hb_assert(), such that it's not compiled out under NDEBUG.
+ * https://github.com/harfbuzz/harfbuzz/issues/5418 */
+#define hb_always_assert(x) \
+	HB_STMT_START { \
+	  if (!(x)) { fprintf(stderr, "Assertion failed: %s, at %s:%d\n", #x, __FILE__, __LINE__); abort(); } \
+	} HB_STMT_END
+
+
 #endif /* HB_HH */
diff --git a/src/meson.build b/src/meson.build
index 7476716..531f151 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -23,6 +23,8 @@
   'hb-aat-ltag-table.hh',
   'hb-aat-map.cc',
   'hb-aat-map.hh',
+  'hb-aat-var-hvgl-table.cc',
+  'hb-aat-var-hvgl-table.hh',
   'hb-algs.hh',
   'hb-array.hh',
   'hb-atomic.hh',
@@ -341,6 +343,9 @@
 hb_harfrust_sources = files('hb-harfrust.cc')
 hb_harfrust_headers = files()
 
+hb_kbts_sources = files('hb-kbts.cc')
+hb_kbts_headers = files()
+
 hb_glib_sources = files('hb-glib.cc')
 hb_glib_headers = files('hb-glib.h')
 
@@ -403,6 +408,12 @@
   'hb-subset-plan.hh',
   'hb-subset-plan-member-list.hh',
   'hb-subset-serialize.cc',
+  'hb-subset-table.hh',
+  'hb-subset-table-layout.cc',
+  'hb-subset-table-var.cc',
+  'hb-subset-table-cff.cc',
+  'hb-subset-table-color.cc',
+  'hb-subset-table-other.cc',
   'graph/gsubgpos-context.cc',
   'graph/gsubgpos-context.hh',
   'graph/gsubgpos-graph.hh',
@@ -549,6 +560,12 @@
   hb_headers += hb_harfrust_headers
 endif
 
+if conf.get('HAVE_KBTS', 0) == 1
+  hb_sources += hb_kbts_sources
+  hb_headers += hb_kbts_headers
+  harfbuzz_deps += [kbts_dep]
+endif
+
 have_icu = conf.get('HAVE_ICU', 0) == 1
 have_icu_builtin = conf.get('HAVE_ICU_BUILTIN', 0) == 1
 if have_icu and have_icu_builtin
@@ -750,7 +767,6 @@
     foreach name, source : noinst_programs
       executable(name, source,
         include_directories: incconfig,
-        cpp_args: cpp_args + ['-UNDEBUG'],
         dependencies: libharfbuzz_dep,
         install: false,
       )
diff --git a/src/rust/.gitignore b/src/rust/.gitignore
new file mode 100644
index 0000000..03314f7
--- /dev/null
+++ b/src/rust/.gitignore
@@ -0,0 +1 @@
+Cargo.lock
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
deleted file mode 100644
index 9f92845..0000000
--- a/src/rust/Cargo.lock
+++ /dev/null
@@ -1,135 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "bitflags"
-version = "2.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
-
-[[package]]
-name = "bytemuck"
-version = "1.23.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
-dependencies = [
- "bytemuck_derive",
-]
-
-[[package]]
-name = "bytemuck_derive"
-version = "1.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "core_maths"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
-dependencies = [
- "libm",
-]
-
-[[package]]
-name = "font-types"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02a596f5713680923a2080d86de50fe472fb290693cf0f701187a1c8b36996b7"
-dependencies = [
- "bytemuck",
-]
-
-[[package]]
-name = "harfbuzz_rust"
-version = "0.0.0"
-dependencies = [
- "harfrust",
- "skrifa",
-]
-
-[[package]]
-name = "harfrust"
-version = "0.1.0"
-source = "git+https://github.com/harfbuzz/harfrust#77fb262c8e99a2556b84b8c6ddead5980d2b3ccd"
-dependencies = [
- "bitflags",
- "bytemuck",
- "core_maths",
- "read-fonts",
- "smallvec",
-]
-
-[[package]]
-name = "libm"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.95"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "read-fonts"
-version = "0.30.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "192735ef611aac958468e670cb98432c925426f3cb71521fda202130f7388d91"
-dependencies = [
- "bytemuck",
- "core_maths",
- "font-types",
-]
-
-[[package]]
-name = "skrifa"
-version = "0.32.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6d632b5a73f566303dbeabd344dc3e716fd4ddc9a70d6fc8ea8e6f06617da97"
-dependencies = [
- "bytemuck",
- "read-fonts",
-]
-
-[[package]]
-name = "smallvec"
-version = "1.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-
-[[package]]
-name = "syn"
-version = "2.0.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
diff --git a/src/rust/meson.build b/src/rust/meson.build
index 5374105..b086dd9 100644
--- a/src/rust/meson.build
+++ b/src/rust/meson.build
@@ -52,7 +52,7 @@
 
 harfbuzz_rust = custom_target(
   'harfbuzz_rust',
-  input: sources + ['Cargo.toml', 'Cargo.lock'],
+  input: sources + ['Cargo.toml'],
   output: ['libharfbuzz_rust.a'],
   depends: [hb_rs],
   env: ['OUT_DIR=' + meson.current_build_dir(),
diff --git a/src/test-algs.cc b/src/test-algs.cc
index 450a7c4..0f25420 100644
--- a/src/test-algs.cc
+++ b/src/test-algs.cc
@@ -47,24 +47,24 @@
   auto p = hb_pair (1, i);
 
   p.second = 2;
-  assert (i == 2);
+  hb_always_assert (i == 2);
 
   const int c = 3;
   auto pc = hb_pair (1, c);
-  assert (pc.second == 3);
+  hb_always_assert (pc.second == 3);
 
   auto q = p;
-  assert (&q != &p);
+  hb_always_assert (&q != &p);
   q.second = 4;
-  assert (i == 4);
+  hb_always_assert (i == 4);
 
   hb_invoke (test_func, 0, nullptr);
 
   A a;
   hb_invoke (&A::a, a);
 
-  assert (1 == hb_min (8, 1));
-  assert (8 == hb_max (8, 1));
+  hb_always_assert (1 == hb_min (8, 1));
+  hb_always_assert (8 == hb_max (8, 1));
 
   int x = 1, y = 2;
   hb_min (x, 3);
@@ -72,39 +72,39 @@
   hb_min (x, 4 + 3);
   int &z = hb_min (x, y);
   z = 3;
-  assert (x == 3);
+  hb_always_assert (x == 3);
 
   hb_pair_t<const int*, int> xp = hb_pair_t<int *, long> (nullptr, 0);
   xp = hb_pair_t<int *, double> (nullptr, 1);
   xp = hb_pair_t<const int*, int> (nullptr, 1);
 
-  assert (3 == hb_partial (hb_min, 3) (4));
-  assert (3 == hb_partial<1> (hb_min, 4) (3));
+  hb_always_assert (3 == hb_partial (hb_min, 3) (4));
+  hb_always_assert (3 == hb_partial<1> (hb_min, 4) (3));
 
   auto M0 = hb_partial<2> (hb_max, 0);
-  assert (M0 (-2) == 0);
-  assert (M0 (+2) == 2);
+  hb_always_assert (M0 (-2) == 0);
+  hb_always_assert (M0 (+2) == 2);
 
-  assert (hb_add (2) (5) == 7);
-  assert (hb_add (5) (2) == 7);
+  hb_always_assert (hb_add (2) (5) == 7);
+  hb_always_assert (hb_add (5) (2) == 7);
 
   x = 1;
-  assert (++hb_inc (x) == 3);
-  assert (x == 3);
+  hb_always_assert (++hb_inc (x) == 3);
+  hb_always_assert (x == 3);
 
   hb_set_t set1 {1};
   hb_set_t set2 {2};
 
-  assert (hb_hash (set1) != hb_hash (set2));
-  assert (hb_hash (set1) == hb_hash (hb_set_t {1}));
-  assert (hb_hash (set1) != hb_hash (hb_set_t {}));
-  assert (hb_hash (set1) != hb_hash (hb_set_t {2}));
-  assert (hb_hash (set2) == hb_hash (hb_set_t {2}));
+  hb_always_assert (hb_hash (set1) != hb_hash (set2));
+  hb_always_assert (hb_hash (set1) == hb_hash (hb_set_t {1}));
+  hb_always_assert (hb_hash (set1) != hb_hash (hb_set_t {}));
+  hb_always_assert (hb_hash (set1) != hb_hash (hb_set_t {2}));
+  hb_always_assert (hb_hash (set2) == hb_hash (hb_set_t {2}));
 
   /* hb_hash, unlike std::hash, dereferences pointers. */
-  assert (hb_hash (set1) == hb_hash (&set1));
-  assert (hb_hash (set1) == hb_hash (hb::shared_ptr<hb_set_t> {hb_set_reference (&set1)}));
-  assert (hb_hash (set1) == hb_hash (hb::unique_ptr<hb_set_t> {hb_set_reference (&set1)}));
+  hb_always_assert (hb_hash (set1) == hb_hash (&set1));
+  hb_always_assert (hb_hash (set1) == hb_hash (hb::shared_ptr<hb_set_t> {hb_set_reference (&set1)}));
+  hb_always_assert (hb_hash (set1) == hb_hash (hb::unique_ptr<hb_set_t> {hb_set_reference (&set1)}));
 
   return 0;
 }
diff --git a/src/test-array.cc b/src/test-array.cc
index 28cd023..2e27147 100644
--- a/src/test-array.cc
+++ b/src/test-array.cc
@@ -36,7 +36,7 @@
 
   int expected_values[] = {9, 8, 7, 6, 5, 4, 3, 2, 1};
   hb_array_t<int> expected (expected_values, 9);
-  assert (a == expected);
+  hb_always_assert (a == expected);
 }
 
 static void
@@ -48,7 +48,7 @@
 
   int expected_values[] = {1, 2, 6, 5, 4, 3, 7, 8, 9};
   hb_array_t<int> expected (expected_values, 9);
-  assert (a == expected);
+  hb_always_assert (a == expected);
 }
 
 static void
@@ -64,14 +64,14 @@
 
   int expected_values[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
   hb_array_t<int> expected (expected_values, 9);
-  assert (a == expected);
+  hb_always_assert (a == expected);
 }
 
 int
 main (int argc, char **argv)
 {
   /* The following fails on MSVC. */
-  // assert (sizeof (hb_array_t<int>) == sizeof (hb_sorted_array_t<int>));
+  // hb_always_assert (sizeof (hb_array_t<int>) == sizeof (hb_sorted_array_t<int>));
 
   test_reverse ();
   test_reverse_range ();
diff --git a/src/test-bimap.cc b/src/test-bimap.cc
index 1253d0c..a80f44b 100644
--- a/src/test-bimap.cc
+++ b/src/test-bimap.cc
@@ -32,45 +32,45 @@
 {
   hb_bimap_t	bm;
 
-  assert (bm.is_empty () == true);
+  hb_always_assert (bm.is_empty () == true);
   bm.set (1, 4);
   bm.set (2, 5);
   bm.set (3, 6);
-  assert (bm.get_population () == 3);
-  assert (bm.has (1) == true);
-  assert (bm.has (4) == false);
-  assert (bm[2] == 5);
-  assert (bm.backward (6) == 3);
+  hb_always_assert (bm.get_population () == 3);
+  hb_always_assert (bm.has (1) == true);
+  hb_always_assert (bm.has (4) == false);
+  hb_always_assert (bm[2] == 5);
+  hb_always_assert (bm.backward (6) == 3);
   bm.del (1);
-  assert (bm.has (1) == false);
-  assert (bm.has (3) == true);
+  hb_always_assert (bm.has (1) == false);
+  hb_always_assert (bm.has (3) == true);
   bm.clear ();
-  assert (bm.get_population () == 0);
+  hb_always_assert (bm.get_population () == 0);
 
   hb_inc_bimap_t  ibm;
 
-  assert (ibm.add (13) == 0);
-  assert (ibm.add (8) == 1);
-  assert (ibm.add (10) == 2);
-  assert (ibm.add (8) == 1);
-  assert (ibm.add (7) == 3);
-  assert (ibm.get_population () == 4);
-  assert (ibm[7] == 3);
+  hb_always_assert (ibm.add (13) == 0);
+  hb_always_assert (ibm.add (8) == 1);
+  hb_always_assert (ibm.add (10) == 2);
+  hb_always_assert (ibm.add (8) == 1);
+  hb_always_assert (ibm.add (7) == 3);
+  hb_always_assert (ibm.get_population () == 4);
+  hb_always_assert (ibm[7] == 3);
 
   ibm.sort ();
-  assert (ibm.get_population () == 4);
-  assert (ibm[7] == 0);
-  assert (ibm[13] == 3);
+  hb_always_assert (ibm.get_population () == 4);
+  hb_always_assert (ibm[7] == 0);
+  hb_always_assert (ibm[13] == 3);
 
   ibm.identity (3);
-  assert (ibm.get_population () == 3);
-  assert (ibm[0] == 0);
-  assert (ibm[1] == 1);
-  assert (ibm[2] == 2);
-  assert (ibm.backward (0) == 0);
-  assert (ibm.backward (1) == 1);
-  assert (ibm.backward (2) == 2);
-  assert (ibm.has (4) == false);
+  hb_always_assert (ibm.get_population () == 3);
+  hb_always_assert (ibm[0] == 0);
+  hb_always_assert (ibm[1] == 1);
+  hb_always_assert (ibm[2] == 2);
+  hb_always_assert (ibm.backward (0) == 0);
+  hb_always_assert (ibm.backward (1) == 1);
+  hb_always_assert (ibm.backward (2) == 2);
+  hb_always_assert (ibm.has (4) == false);
 
   return 0;
 }
diff --git a/src/test-buffer-serialize.cc b/src/test-buffer-serialize.cc
index 421e5da..87d364b 100644
--- a/src/test-buffer-serialize.cc
+++ b/src/test-buffer-serialize.cc
@@ -64,7 +64,7 @@
     argv[1] = (char *) "/dev/null";
 
   hb_blob_t *blob = hb_blob_create_from_file_or_fail (argv[1]);
-  assert (blob);
+  hb_always_assert (blob);
   hb_face_t *face = hb_face_create (blob, 0 /* first face */);
   hb_blob_destroy (blob);
   blob = nullptr;
diff --git a/src/test-cff.cc b/src/test-cff.cc
index 38ddba3..6127cfb 100644
--- a/src/test-cff.cc
+++ b/src/test-cff.cc
@@ -64,9 +64,9 @@
       struct num_tp_test num_tp_test = num_tp_tests[t];
       number.set_real (num_tp_test.input);
       encoder.encode_num_tp (number);
-      assert (buff.length == num_tp_test.length);
+      hb_always_assert (buff.length == num_tp_test.length);
       for (unsigned i = 0; i < buff.length; i++)
-	assert (buff[i] == num_tp_test.output[i]);
+	hb_always_assert (buff[i] == num_tp_test.output[i]);
       encoder.reset ();
     }
   }
diff --git a/src/test-decycler.cc b/src/test-decycler.cc
index dbe0f24..c1afcb8 100644
--- a/src/test-decycler.cc
+++ b/src/test-decycler.cc
@@ -38,7 +38,7 @@
   hb_decycler_node_t node (decycler);
 
   bool ret = node.visit (value);
-  assert (ret);
+  hb_always_assert (ret);
 
   tree_recurse_binary (value * 2 + 1, max_value, decycler);
   tree_recurse_binary (value * 2 + 2, max_value, decycler);
@@ -63,7 +63,7 @@
   for (unsigned i = 1; i <= 3; i++)
   {
     bool ret = node.visit (value + i);
-    assert (ret);
+    hb_always_assert (ret);
 
     tree_recurse_tertiary (value + i, max_value, decycler);
   }
@@ -82,7 +82,7 @@
 	       signed cycle_length,
 	       hb_decycler_t &decycler)
 {
-  assert (cycle_length > 0);
+  hb_always_assert (cycle_length > 0);
 
   hb_decycler_node_t node (decycler);
 
diff --git a/src/test-gpos-size-params.cc b/src/test-gpos-size-params.cc
index b96381d..87dc54a 100644
--- a/src/test-gpos-size-params.cc
+++ b/src/test-gpos-size-params.cc
@@ -43,7 +43,7 @@
 
   /* Create the face */
   hb_blob_t *blob = hb_blob_create_from_file_or_fail (argv[1]);
-  assert (blob);
+  hb_always_assert (blob);
   hb_face_t *face = hb_face_create (blob, 0 /* first face */);
   hb_blob_destroy (blob);
   blob = nullptr;
diff --git a/src/test-gsub-would-substitute.cc b/src/test-gsub-would-substitute.cc
index 8712303..b3e67fd 100644
--- a/src/test-gsub-would-substitute.cc
+++ b/src/test-gsub-would-substitute.cc
@@ -47,7 +47,7 @@
 
   /* Create the face */
   hb_blob_t *blob = hb_blob_create_from_file_or_fail (argv[1]);
-  assert (blob);
+  hb_always_assert (blob);
   hb_face_t *face = hb_face_create (blob, 0 /* first face */);
   hb_blob_destroy (blob);
   blob = nullptr;
diff --git a/src/test-item-varstore.cc b/src/test-item-varstore.cc
index bf0a100..3e2cae7 100644
--- a/src/test-item-varstore.cc
+++ b/src/test-item-varstore.cc
@@ -43,7 +43,7 @@
   const OT::ItemVariationStore& src_var_store = hvar_table+(hvar_table->varStore);
   bool result = item_vars.create_from_item_varstore (src_var_store, axis_idx_tag_map);
       
-  assert (result);
+  hb_always_assert (result);
 
   /* partial instancing wght=300:800 */
   hb_hashmap_t<hb_tag_t, Triple> normalized_axes_location;
@@ -53,10 +53,10 @@
   axes_triple_distances.set (axis_tag, TripleDistances (200.0, 500.0));
 
   result = item_vars.instantiate_tuple_vars (normalized_axes_location, axes_triple_distances);
-  assert (result);
+  hb_always_assert (result);
   result = item_vars.as_item_varstore (false);
-  assert (result);
-  assert (item_vars.get_region_list().length == 8);
+  hb_always_assert (result);
+  hb_always_assert (item_vars.get_region_list().length == 8);
 }
 
 int
diff --git a/src/test-iter.cc b/src/test-iter.cc
index bb966d4..be50ad9 100644
--- a/src/test-iter.cc
+++ b/src/test-iter.cc
@@ -84,7 +84,7 @@
   it = it + 10;
   it = 10 + it;
 
-  assert (*it == it[0]);
+  hb_always_assert (*it == it[0]);
 
   static_assert (true || it.is_random_access_iterator, "");
   static_assert (true || it.is_sorted_iterator, "");
@@ -96,7 +96,7 @@
 test_iterator (Iter it)
 {
   Iter default_constructed;
-  assert (!default_constructed);
+  hb_always_assert (!default_constructed);
 
   test_iterator_non_default_constructable (it);
 }
@@ -118,7 +118,7 @@
 {
   int i = 1;
   for (int v : +it) {
-    assert (v == i++);
+    hb_always_assert (v == i++);
   }
 }
 
@@ -131,16 +131,16 @@
   hb_vector_t<int> d = {1, 2, 3, 4, 5};
 
   auto it1 = hb_concat (a, b);
-  assert (it1.len () == 5);
-  assert (it1.is_random_access_iterator);
+  hb_always_assert (it1.len () == 5);
+  hb_always_assert (it1.is_random_access_iterator);
   auto it2 = hb_concat (c, d);
-  assert (it2.len () == 5);
+  hb_always_assert (it2.len () == 5);
   auto it3 = hb_concat (d, c);
-  assert (it3.len () == 5);
+  hb_always_assert (it3.len () == 5);
   for (int i = 0; i < 5; i++) {
-    assert(it1[i] == i + 1);
-    assert(it2[i] == i + 1);
-    assert(it3[i] == i + 1);
+    hb_always_assert(it1[i] == i + 1);
+    hb_always_assert(it2[i] == i + 1);
+    hb_always_assert(it3[i] == i + 1);
   }
 
   check_sequential (it1);
@@ -149,40 +149,40 @@
 
   auto it4 = +it1;
   it4 += 0;
-  assert (*it4 == 1);
+  hb_always_assert (*it4 == 1);
 
   it4 += 2;
-  assert (*it4 == 3);
-  assert (it4);
-  assert (it4.len () == 3);
+  hb_always_assert (*it4 == 3);
+  hb_always_assert (it4);
+  hb_always_assert (it4.len () == 3);
 
   it4 += 2;
-  assert (*it4 == 5);
-  assert (it4);
-  assert (it4.len () == 1);
+  hb_always_assert (*it4 == 5);
+  hb_always_assert (it4);
+  hb_always_assert (it4.len () == 1);
 
   it4++;
-  assert (!it4);
-  assert (it4.len () == 0);
+  hb_always_assert (!it4);
+  hb_always_assert (it4.len () == 0);
 
   auto it5 = +it1;
   it5 += 3;
-  assert (*it5 == 4);
+  hb_always_assert (*it5 == 4);
 
   hb_set_t s_a = {1, 2, 3};
   hb_set_t s_b = {4, 5};
   auto it6 = hb_concat (s_a, s_b);
-  assert (!it6.is_random_access_iterator);
+  hb_always_assert (!it6.is_random_access_iterator);
   check_sequential (it6);
-  assert (it6.len () == 5);
+  hb_always_assert (it6.len () == 5);
 
   it6 += 0;
-  assert (*it6 == 1);
+  hb_always_assert (*it6 == 1);
 
   it6 += 3;
-  assert (*it6 == 4);
-  assert (it6);
-  assert (it6.len () == 2);
+  hb_always_assert (*it6 == 4);
+  hb_always_assert (it6);
+  hb_always_assert (it6.len () == 2);
 }
 
 int
@@ -235,16 +235,16 @@
   test_iterator_non_default_constructable (hb_iter (st) | hb_filter ());
   test_iterator_non_default_constructable (hb_iter (st) | hb_map (hb_lidentity));
 
-  assert (true == hb_all (st));
-  assert (false == hb_all (st, 42u));
-  assert (true == hb_any (st));
-  assert (false == hb_any (st, 14u));
-  assert (true == hb_any (st, 14u, [] (unsigned _) { return _ - 1u; }));
-  assert (true == hb_any (st, [] (unsigned _) { return _ == 15u; }));
-  assert (true == hb_any (st, 15u));
-  assert (false == hb_none (st));
-  assert (false == hb_none (st, 15u));
-  assert (true == hb_none (st, 17u));
+  hb_always_assert (true == hb_all (st));
+  hb_always_assert (false == hb_all (st, 42u));
+  hb_always_assert (true == hb_any (st));
+  hb_always_assert (false == hb_any (st, 14u));
+  hb_always_assert (true == hb_any (st, 14u, [] (unsigned _) { return _ - 1u; }));
+  hb_always_assert (true == hb_any (st, [] (unsigned _) { return _ == 15u; }));
+  hb_always_assert (true == hb_any (st, 15u));
+  hb_always_assert (false == hb_none (st));
+  hb_always_assert (false == hb_none (st, 15u));
+  hb_always_assert (true == hb_none (st, 17u));
 
   hb_array_t<hb_vector_t<int>> pa;
   pa->as_array ();
@@ -318,7 +318,7 @@
 	       }, hb_map_create ())
   ;
   /* The result should be something like 0->10, 1->11, ..., 9->19 */
-  assert (hb_map_get (result, 9) == 19);
+  hb_always_assert (hb_map_get (result, 9) == 19);
   hb_map_destroy (result);
 
   /* Like above, but passing hb_set_t instead of hb_set_t * */
@@ -341,7 +341,7 @@
 	       }, hb_map_create ())
   ;
   /* The result should be something like 0->10, 1->11, ..., 9->19 */
-  assert (hb_map_get (result, 9) == 19);
+  hb_always_assert (hb_map_get (result, 9) == 19);
   hb_map_destroy (result);
 
   unsigned int temp3 = 0;
@@ -361,20 +361,20 @@
   hb_iota ();
   hb_iota (3);
   hb_iota (3, 2);
-  assert ((&vl) + 1 == *++hb_iota (&vl, hb_inc));
+  hb_always_assert ((&vl) + 1 == *++hb_iota (&vl, hb_inc));
   hb_range ();
   hb_repeat (7u);
   hb_repeat (nullptr);
   hb_repeat (vl) | hb_chop (3);
-  assert (hb_len (hb_range (10) | hb_take (3)) == 3);
-  assert (hb_range (9).len () == 9);
-  assert (hb_range (2, 9).len () == 7);
-  assert (hb_range (2, 9, 3).len () == 3);
-  assert (hb_range (2, 8, 3).len () == 2);
-  assert (hb_range (2, 7, 3).len () == 2);
-  assert (hb_range (-2, -9, -3).len () == 3);
-  assert (hb_range (-2, -8, -3).len () == 2);
-  assert (hb_range (-2, -7, -3).len () == 2);
+  hb_always_assert (hb_len (hb_range (10) | hb_take (3)) == 3);
+  hb_always_assert (hb_range (9).len () == 9);
+  hb_always_assert (hb_range (2, 9).len () == 7);
+  hb_always_assert (hb_range (2, 9, 3).len () == 3);
+  hb_always_assert (hb_range (2, 8, 3).len () == 2);
+  hb_always_assert (hb_range (2, 7, 3).len () == 2);
+  hb_always_assert (hb_range (-2, -9, -3).len () == 3);
+  hb_always_assert (hb_range (-2, -8, -3).len () == 2);
+  hb_always_assert (hb_range (-2, -7, -3).len () == 2);
 
   test_concat ();
 
diff --git a/src/test-map.cc b/src/test-map.cc
index cb722eb..a68da8c 100644
--- a/src/test-map.cc
+++ b/src/test-map.cc
@@ -36,10 +36,10 @@
     hb_map_t v1;
     v1.set (1, 2);
     hb_map_t v2 {v1};
-    assert (v1.get_population () == 1);
-    assert (v2.get_population () == 1);
-    assert (v1[1] == 2);
-    assert (v2[1] == 2);
+    hb_always_assert (v1.get_population () == 1);
+    hb_always_assert (v2.get_population () == 1);
+    hb_always_assert (v1[1] == 2);
+    hb_always_assert (v2[1] == 2);
   }
 
   /* Test copy assignment. */
@@ -47,10 +47,10 @@
     hb_map_t v1;
     v1.set (1, 2);
     hb_map_t v2 = v1;
-    assert (v1.get_population () == 1);
-    assert (v2.get_population () == 1);
-    assert (v1[1] == 2);
-    assert (v2[1] == 2);
+    hb_always_assert (v1.get_population () == 1);
+    hb_always_assert (v2.get_population () == 1);
+    hb_always_assert (v1[1] == 2);
+    hb_always_assert (v2[1] == 2);
   }
 
   /* Test move constructor. */
@@ -58,8 +58,8 @@
     hb_map_t s {};
     s.set (1, 2);
     hb_map_t v (std::move (s));
-    assert (s.get_population () == 0);
-    assert (v.get_population () == 1);
+    hb_always_assert (s.get_population () == 0);
+    hb_always_assert (v.get_population () == 1);
   }
 
   /* Test move assignment. */
@@ -68,8 +68,8 @@
     s.set (1, 2);
     hb_map_t v;
     v = std::move (s);
-    assert (s.get_population () == 0);
-    assert (v.get_population () == 1);
+    hb_always_assert (s.get_population () == 0);
+    hb_always_assert (v.get_population () == 1);
   }
 
   /* Test initializing from iterable. */
@@ -84,10 +84,10 @@
     hb_map_t v1 (s);
     hb_map_t v2 (std::move (s));
 
-    assert (s.get_population () == 0);
-    assert (v0.get_population () == 2);
-    assert (v1.get_population () == 2);
-    assert (v2.get_population () == 2);
+    hb_always_assert (s.get_population () == 0);
+    hb_always_assert (v0.get_population () == 2);
+    hb_always_assert (v1.get_population () == 2);
+    hb_always_assert (v2.get_population () == 2);
   }
 
   /* Test call fini() twice. */
@@ -107,7 +107,7 @@
 
     hb_map_t v (hb_iter (s));
 
-    assert (v.get_population () == 2);
+    hb_always_assert (v.get_population () == 2);
   }
 
   /* Test initializing from initializer list and swapping. */
@@ -116,8 +116,8 @@
     hb_map_t v1 {pair_t{1,2}, pair_t{4,5}};
     hb_map_t v2 {pair_t{3,4}};
     hb_swap (v1, v2);
-    assert (v1.get_population () == 1);
-    assert (v2.get_population () == 2);
+    hb_always_assert (v1.get_population () == 1);
+    hb_always_assert (v2.get_population () == 2);
   }
 
   /* Test class key / value types. */
@@ -125,9 +125,9 @@
     hb_hashmap_t<hb_bytes_t, int> m1;
     hb_hashmap_t<int, hb_bytes_t> m2;
     hb_hashmap_t<hb_bytes_t, hb_bytes_t> m3;
-    assert (m1.get_population () == 0);
-    assert (m2.get_population () == 0);
-    assert (m3.get_population () == 0);
+    hb_always_assert (m1.get_population () == 0);
+    hb_always_assert (m2.get_population () == 0);
+    hb_always_assert (m3.get_population () == 0);
   }
 
   {
@@ -157,8 +157,8 @@
     m1.set (hb_map_t (), hb_map_t {pair (1u, 2u)});
     m1.set (hb_map_t {pair (1u, 2u)}, hb_map_t {pair (2u, 3u)});
 
-    assert (m1.get (hb_map_t ()) == hb_map_t {pair (1u, 2u)});
-    assert (m1.get (hb_map_t {pair (1u, 2u)}) == hb_map_t {pair (2u, 3u)});
+    hb_always_assert (m1.get (hb_map_t ()) == hb_map_t {pair (1u, 2u)});
+    hb_always_assert (m1.get (hb_map_t {pair (1u, 2u)}) == hb_map_t {pair (2u, 3u)});
   }
 
   /* Test hashing sets. */
@@ -169,8 +169,8 @@
     m1.set (hb_set_t (), hb_set_t {1});
     m1.set (hb_set_t {1, 1000}, hb_set_t {2});
 
-    assert (m1.get (hb_set_t ()) == hb_set_t {1});
-    assert (m1.get (hb_set_t {1000, 1}) == hb_set_t {2});
+    hb_always_assert (m1.get (hb_set_t ()) == hb_set_t {1});
+    hb_always_assert (m1.get (hb_set_t {1000, 1}) == hb_set_t {2});
   }
 
   /* Test hashing vectors. */
@@ -184,8 +184,8 @@
 
     m1 << hb_pair_t<vector_t, vector_t> {vector_t {2}, vector_t ()};
 
-    assert (m1.get (vector_t ()) == vector_t {1});
-    assert (m1.get (vector_t {1}) == vector_t {2});
+    hb_always_assert (m1.get (vector_t ()) == vector_t {1});
+    hb_always_assert (m1.get (vector_t {1}) == vector_t {2});
   }
 
   /* Test moving values */
@@ -194,20 +194,20 @@
 
     hb_hashmap_t<vector_t, vector_t> m1;
     vector_t v {3};
-    assert (v.length == 1);
+    hb_always_assert (v.length == 1);
     m1 << hb_pair_t<vector_t, vector_t> {vector_t {3}, v};
-    assert (v.length == 1);
+    hb_always_assert (v.length == 1);
     m1 << hb_pair_t<vector_t, vector_t&&> {vector_t {4}, std::move (v)};
-    assert (v.length == 0);
+    hb_always_assert (v.length == 0);
     m1 << hb_pair_t<vector_t&&, vector_t> {vector_t {4}, vector_t {5}};
     m1 << hb_pair_t<vector_t&&, vector_t&&> {vector_t {4}, vector_t {5}};
 
     hb_hashmap_t<vector_t, vector_t> m2;
     vector_t v2 {3};
     m2.set (vector_t {4}, v2);
-    assert (v2.length == 1);
+    hb_always_assert (v2.length == 1);
     m2.set (vector_t {5}, std::move (v2));
-    assert (v2.length == 0);
+    hb_always_assert (v2.length == 0);
   }
 
   /* Test hb::shared_ptr. */
@@ -250,10 +250,10 @@
     hb::shared_ptr<hb_map_t> p2 {m2};
     m.set (p1,1);
 
-    assert (m.has (p2));
+    hb_always_assert (m.has (p2));
 
     m1->set (2,4);
-    assert (!m.has (p2));
+    hb_always_assert (!m.has (p2));
   }
   /* Test value type with hb_bytes_t. */
   {
@@ -262,8 +262,8 @@
     hb_bytes_t bytes (c_str);
 
     m.set (1, bytes);
-    assert (m.has (1));
-    assert (m.get (1) == hb_bytes_t {"Test"});
+    hb_always_assert (m.has (1));
+    hb_always_assert (m.get (1) == hb_bytes_t {"Test"});
   }
   /* Test operators. */
   {
@@ -275,16 +275,16 @@
     m3.set (1, 3);
     m3.set (3, 5);
 
-    assert (m1 == m2);
-    assert (m1 != m3);
-    assert (!(m2 == m3));
+    hb_always_assert (m1 == m2);
+    hb_always_assert (m1 != m3);
+    hb_always_assert (!(m2 == m3));
 
     m2 = m3;
-    assert (m2.has (1));
-    assert (!m2.has (2));
-    assert (m2.has (3));
+    hb_always_assert (m2.has (1));
+    hb_always_assert (!m2.has (2));
+    hb_always_assert (m2.has (3));
 
-    assert (m3.has (3));
+    hb_always_assert (m3.has (3));
   }
   /* Test reset. */
   {
@@ -309,15 +309,15 @@
 	 m.next (&i, &k, &v);)
     {
       pop++;
-           if (k == 1) assert (v == 1);
-      else if (k == 2) assert (v == 1);
-      else if (k == 3) assert (v == 2);
-      else if (k == 4) assert (v == 3);
-      else if (k == 5) assert (v == 5);
-      else if (k == 6) assert (v == 8);
-      else assert (false);
+           if (k == 1) hb_always_assert (v == 1);
+      else if (k == 2) hb_always_assert (v == 1);
+      else if (k == 3) hb_always_assert (v == 2);
+      else if (k == 4) hb_always_assert (v == 3);
+      else if (k == 5) hb_always_assert (v == 5);
+      else if (k == 6) hb_always_assert (v == 8);
+      else hb_always_assert (false);
     }
-    assert (pop == m.get_population ());
+    hb_always_assert (pop == m.get_population ());
   }
   /* Test update */
   {
@@ -327,9 +327,9 @@
     m2.set (1, 3);
 
     m1.update (m2);
-    assert (m1.get_population () == 2);
-    assert (m1[1] == 3);
-    assert (m1[2] == 4);
+    hb_always_assert (m1.get_population () == 2);
+    hb_always_assert (m1[1] == 3);
+    hb_always_assert (m1[2] == 4);
   }
   /* Test keys / values */
   {
@@ -347,11 +347,11 @@
     hb_copy (m.keys (), keys);
     hb_copy (m.values (), values);
 
-    assert (keys.is_equal (hb_set_t ({1, 2, 3, 4, 5, 6})));
-    assert (values.is_equal (hb_set_t ({1, 1, 2, 3, 5, 8})));
+    hb_always_assert (keys.is_equal (hb_set_t ({1, 2, 3, 4, 5, 6})));
+    hb_always_assert (values.is_equal (hb_set_t ({1, 1, 2, 3, 5, 8})));
 
-    assert (keys.is_equal (hb_set_t (m.keys ())));
-    assert (values.is_equal (hb_set_t (m.values ())));
+    hb_always_assert (keys.is_equal (hb_set_t (m.keys ())));
+    hb_always_assert (values.is_equal (hb_set_t (m.values ())));
   }
 
   return 0;
diff --git a/src/test-multimap.cc b/src/test-multimap.cc
index 8cd8f52..d3b8436 100644
--- a/src/test-multimap.cc
+++ b/src/test-multimap.cc
@@ -30,30 +30,30 @@
 {
   hb_multimap_t m;
 
-  assert (m.get (10).length == 0);
+  hb_always_assert (m.get (10).length == 0);
 
   m.add (10, 11);
-  assert (m.get (10).length == 1);
+  hb_always_assert (m.get (10).length == 1);
 
   m.add (10, 12);
-  assert (m.get (10).length == 2);
+  hb_always_assert (m.get (10).length == 2);
 
   m.add (10, 13);
-  assert (m.get (10).length == 3);
-  assert (m.get (10)[0] == 11);
-  assert (m.get (10)[1] == 12);
-  assert (m.get (10)[2] == 13);
+  hb_always_assert (m.get (10).length == 3);
+  hb_always_assert (m.get (10)[0] == 11);
+  hb_always_assert (m.get (10)[1] == 12);
+  hb_always_assert (m.get (10)[2] == 13);
 
-  assert (m.get (11).length == 0);
+  hb_always_assert (m.get (11).length == 0);
   m.add (11, 14);
-  assert (m.get (10).length == 3);
-  assert (m.get (11).length == 1);
-  assert (m.get (12).length == 0);
-  assert (m.get (10)[0] == 11);
-  assert (m.get (10)[1] == 12);
-  assert (m.get (10)[2] == 13);
-  assert (m.get (11)[0] == 14);
-  assert (m.get (12)[0] == 0); // Array fallback value
+  hb_always_assert (m.get (10).length == 3);
+  hb_always_assert (m.get (11).length == 1);
+  hb_always_assert (m.get (12).length == 0);
+  hb_always_assert (m.get (10)[0] == 11);
+  hb_always_assert (m.get (10)[1] == 12);
+  hb_always_assert (m.get (10)[2] == 13);
+  hb_always_assert (m.get (11)[0] == 14);
+  hb_always_assert (m.get (12)[0] == 0); // Array fallback value
 
   return 0;
 }
diff --git a/src/test-number.cc b/src/test-number.cc
index 5783528..8a114c9 100644
--- a/src/test-number.cc
+++ b/src/test-number.cc
@@ -36,11 +36,11 @@
     const char *end = str + 3;
 
     int pv;
-    assert (hb_parse_int (&pp, end, &pv));
-    assert (pv == 123);
-    assert (pp - str == 3);
-    assert (end - pp == 0);
-    assert (!*end);
+    hb_always_assert (hb_parse_int (&pp, end, &pv));
+    hb_always_assert (pv == 123);
+    hb_always_assert (pp - str == 3);
+    hb_always_assert (end - pp == 0);
+    hb_always_assert (!*end);
   }
 
   {
@@ -49,11 +49,11 @@
     const char *end = str + strlen (str);
 
     unsigned int pv;
-    assert (hb_parse_uint (&pp, end, &pv));
-    assert (pv == 123);
-    assert (pp - str == 3);
-    assert (end - pp == 0);
-    assert (!*end);
+    hb_always_assert (hb_parse_uint (&pp, end, &pv));
+    hb_always_assert (pv == 123);
+    hb_always_assert (pp - str == 3);
+    hb_always_assert (end - pp == 0);
+    hb_always_assert (!*end);
   }
 
   {
@@ -62,11 +62,11 @@
     const char *end = str + 3;
 
     unsigned int pv;
-    assert (hb_parse_uint (&pp, end, &pv, true, 16));
-    assert (pv == 0x12F);
-    assert (pp - str == 3);
-    assert (end - pp == 0);
-    assert (!*end);
+    hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16));
+    hb_always_assert (pv == 0x12F);
+    hb_always_assert (pp - str == 3);
+    hb_always_assert (end - pp == 0);
+    hb_always_assert (!*end);
   }
 
   {
@@ -75,12 +75,12 @@
     const char *end = str + 4;
 
     unsigned int pv;
-    assert (!hb_parse_uint (&pp, end, &pv, true, 16));
-    assert (hb_parse_uint (&pp, end, &pv, false, 16));
-    assert (pv == 0x12F);
-    assert (pp - str == 3);
-    assert (end - pp == 1);
-    assert (!*end);
+    hb_always_assert (!hb_parse_uint (&pp, end, &pv, true, 16));
+    hb_always_assert (hb_parse_uint (&pp, end, &pv, false, 16));
+    hb_always_assert (pv == 0x12F);
+    hb_always_assert (pp - str == 3);
+    hb_always_assert (end - pp == 1);
+    hb_always_assert (!*end);
   }
 
   {
@@ -89,50 +89,50 @@
     const char *end = str + 4;
 
     int pv;
-    assert (hb_parse_int (&pp, end, &pv));
-    assert (pv == -123);
-    assert (pp - str == 4);
-    assert (end - pp == 0);
-    assert (!*end);
+    hb_always_assert (hb_parse_int (&pp, end, &pv));
+    hb_always_assert (pv == -123);
+    hb_always_assert (pp - str == 4);
+    hb_always_assert (end - pp == 0);
+    hb_always_assert (!*end);
   }
 
   {
     const char str[] = "123";
     const char *pp = str;
-    assert (ARRAY_LENGTH (str) == 4);
+    hb_always_assert (ARRAY_LENGTH (str) == 4);
     const char *end = str + ARRAY_LENGTH (str);
 
     unsigned int pv;
-    assert (hb_parse_uint (&pp, end, &pv));
-    assert (pv == 123);
-    assert (pp - str == 3);
-    assert (end - pp == 1);
+    hb_always_assert (hb_parse_uint (&pp, end, &pv));
+    hb_always_assert (pv == 123);
+    hb_always_assert (pp - str == 3);
+    hb_always_assert (end - pp == 1);
   }
 
   {
     const char str[] = "123\0";
     const char *pp = str;
-    assert (ARRAY_LENGTH (str) == 5);
+    hb_always_assert (ARRAY_LENGTH (str) == 5);
     const char *end = str + ARRAY_LENGTH (str);
 
     unsigned int pv;
-    assert (hb_parse_uint (&pp, end, &pv));
-    assert (pv == 123);
-    assert (pp - str == 3);
-    assert (end - pp == 2);
+    hb_always_assert (hb_parse_uint (&pp, end, &pv));
+    hb_always_assert (pv == 123);
+    hb_always_assert (pp - str == 3);
+    hb_always_assert (end - pp == 2);
   }
 
   {
     const char str[] = "123V";
     const char *pp = str;
-    assert (ARRAY_LENGTH (str) == 5);
+    hb_always_assert (ARRAY_LENGTH (str) == 5);
     const char *end = str + ARRAY_LENGTH (str);
 
     unsigned int pv;
-    assert (hb_parse_uint (&pp, end, &pv));
-    assert (pv == 123);
-    assert (pp - str == 3);
-    assert (end - pp == 2);
+    hb_always_assert (hb_parse_uint (&pp, end, &pv));
+    hb_always_assert (pv == 123);
+    hb_always_assert (pp - str == 3);
+    hb_always_assert (end - pp == 2);
   }
 
   {
@@ -141,10 +141,10 @@
     const char *end = str + ARRAY_LENGTH (str);
 
     double pv;
-    assert (hb_parse_double (&pp, end, &pv));
-    assert ((int) roundf (pv * 1000.) == 123);
-    assert (pp - str == 4);
-    assert (end - pp == 1);
+    hb_always_assert (hb_parse_double (&pp, end, &pv));
+    hb_always_assert ((int) roundf (pv * 1000.) == 123);
+    hb_always_assert (pp - str == 4);
+    hb_always_assert (end - pp == 1);
   }
 
   {
@@ -153,10 +153,10 @@
     const char *end = str + ARRAY_LENGTH (str) - 1;
 
     double pv;
-    assert (hb_parse_double (&pp, end, &pv));
-    assert ((int) roundf (pv * 1000.) == 123);
-    assert (pp - str == 5);
-    assert (end - pp == 0);
+    hb_always_assert (hb_parse_double (&pp, end, &pv));
+    hb_always_assert ((int) roundf (pv * 1000.) == 123);
+    hb_always_assert (pp - str == 5);
+    hb_always_assert (end - pp == 0);
   }
 
   {
@@ -165,10 +165,10 @@
     const char *end = str + ARRAY_LENGTH (str) - 1;
 
     double pv;
-    assert (hb_parse_double (&pp, end, &pv));
-    assert ((int) roundf (pv * 1000.) == 123);
-    assert (pp - str == 7);
-    assert (end - pp == 0);
+    hb_always_assert (hb_parse_double (&pp, end, &pv));
+    hb_always_assert ((int) roundf (pv * 1000.) == 123);
+    hb_always_assert (pp - str == 7);
+    hb_always_assert (end - pp == 0);
   }
 
   {
@@ -177,10 +177,10 @@
     const char *end = str + ARRAY_LENGTH (str) - 1;
 
     double pv;
-    assert (hb_parse_double (&pp, end, &pv));
-    assert ((int) roundf (pv * 1000.) == 123);
-    assert (pp - str == 6);
-    assert (end - pp == 0);
+    hb_always_assert (hb_parse_double (&pp, end, &pv));
+    hb_always_assert ((int) roundf (pv * 1000.) == 123);
+    hb_always_assert (pp - str == 6);
+    hb_always_assert (end - pp == 0);
   }
 
   {
@@ -189,10 +189,10 @@
     const char *end = str + ARRAY_LENGTH (str) - 1;
 
     double pv;
-    assert (hb_parse_double (&pp, end, &pv));
-    assert ((int) roundf (pv * 1000.) == 123);
-    assert (pp - str == 10);
-    assert (end - pp == 0);
+    hb_always_assert (hb_parse_double (&pp, end, &pv));
+    hb_always_assert ((int) roundf (pv * 1000.) == 123);
+    hb_always_assert (pp - str == 10);
+    hb_always_assert (end - pp == 0);
   }
 
   {
@@ -201,10 +201,10 @@
     const char *end = str + ARRAY_LENGTH (str) - 1;
 
     double pv;
-    assert (hb_parse_double (&pp, end, &pv));
-    assert ((int) roundf (pv * 1000.) == -123);
-    assert (pp - str == 13);
-    assert (end - pp == 0);
+    hb_always_assert (hb_parse_double (&pp, end, &pv));
+    hb_always_assert ((int) roundf (pv * 1000.) == -123);
+    hb_always_assert (pp - str == 13);
+    hb_always_assert (end - pp == 0);
 
   }
 
@@ -214,10 +214,10 @@
     const char *end = str + ARRAY_LENGTH (str) - 1;
 
     double pv;
-    assert (hb_parse_double (&pp, end, &pv));
-    assert ((int) roundf (pv * 1000.) == -123);
-    assert (pp - str == 8);
-    assert (end - pp == 0);
+    hb_always_assert (hb_parse_double (&pp, end, &pv));
+    hb_always_assert ((int) roundf (pv * 1000.) == -123);
+    hb_always_assert (pp - str == 8);
+    hb_always_assert (end - pp == 0);
   }
 
   return 0;
diff --git a/src/test-ot-glyphname.cc b/src/test-ot-glyphname.cc
index ec6e149..2656568 100644
--- a/src/test-ot-glyphname.cc
+++ b/src/test-ot-glyphname.cc
@@ -40,7 +40,7 @@
   }
 
   hb_blob_t *blob = hb_blob_create_from_file_or_fail (argv[1]);
-  assert (blob);
+  hb_always_assert (blob);
   hb_face_t *face = hb_face_create (blob, 0 /* first face */);
   hb_font_t *font = hb_font_create (face);
   hb_blob_destroy (blob);
diff --git a/src/test-ot-meta.cc b/src/test-ot-meta.cc
index 6daacc4..f538fec 100644
--- a/src/test-ot-meta.cc
+++ b/src/test-ot-meta.cc
@@ -38,7 +38,7 @@
   }
 
   hb_blob_t *blob = hb_blob_create_from_file_or_fail (argv[1]);
-  assert (blob);
+  hb_always_assert (blob);
   hb_face_t *face = hb_face_create (blob, 0 /* first face */);
   hb_blob_destroy (blob);
   blob = nullptr;
diff --git a/src/test-ot-name.cc b/src/test-ot-name.cc
index bfa654a..50f38aa 100644
--- a/src/test-ot-name.cc
+++ b/src/test-ot-name.cc
@@ -40,7 +40,7 @@
   }
 
   hb_blob_t *blob = hb_blob_create_from_file_or_fail (argv[1]);
-  assert (blob);
+  hb_always_assert (blob);
   hb_face_t *face = hb_face_create (blob, 0 /* first face */);
   hb_blob_destroy (blob);
   blob = nullptr;
diff --git a/src/test-priority-queue.cc b/src/test-priority-queue.cc
index 67ee5ee..f4fd5e9 100644
--- a/src/test-priority-queue.cc
+++ b/src/test-priority-queue.cc
@@ -31,23 +31,23 @@
 test_insert ()
 {
   hb_priority_queue_t<int64_t> queue;
-  assert (queue.is_empty ());
+  hb_always_assert (queue.is_empty ());
 
   queue.insert (10, 0);
-  assert (!queue.is_empty ());
-  assert (queue.minimum () == hb_pair (10, 0));
+  hb_always_assert (!queue.is_empty ());
+  hb_always_assert (queue.minimum () == hb_pair (10, 0));
 
   queue.insert (20, 1);
-  assert (queue.minimum () == hb_pair (10, 0));
+  hb_always_assert (queue.minimum () == hb_pair (10, 0));
 
   queue.insert (5, 2);
-  assert (queue.minimum () == hb_pair (5, 2));
+  hb_always_assert (queue.minimum () == hb_pair (5, 2));
 
   queue.insert (15, 3);
-  assert (queue.minimum () == hb_pair (5, 2));
+  hb_always_assert (queue.minimum () == hb_pair (5, 2));
 
   queue.insert (1, 4);
-  assert (queue.minimum () == hb_pair (1, 4));
+  hb_always_assert (queue.minimum () == hb_pair (1, 4));
 }
 
 static void
@@ -65,12 +65,12 @@
 
   for (int i = 0; i < 8; i++)
   {
-    assert (!queue.is_empty ());
-    assert (queue.minimum () == hb_pair (i * 10, i));
-    assert (queue.pop_minimum () == hb_pair (i * 10, i));
+    hb_always_assert (!queue.is_empty ());
+    hb_always_assert (queue.minimum () == hb_pair (i * 10, i));
+    hb_always_assert (queue.pop_minimum () == hb_pair (i * 10, i));
   }
 
-  assert (queue.is_empty ());
+  hb_always_assert (queue.is_empty ());
 }
 
 int
diff --git a/src/test-repacker.cc b/src/test-repacker.cc
index 51256b9..e157d4e 100644
--- a/src/test-repacker.cc
+++ b/src/test-repacker.cc
@@ -488,24 +488,24 @@
   {
     if (check_binary_equivalence) {
       printf("when binary equivalence checking is enabled, the expected graph cannot overflow.");
-      assert(!check_binary_equivalence);
+      hb_always_assert(!check_binary_equivalence);
     }
     expected_graph.assign_spaces ();
     expected_graph.sort_shortest_distance ();
   }
 
   // Check that overflow resolution succeeds
-  assert (overflowing.offset_overflow ());
-  assert (hb_resolve_graph_overflows (tag,
+  hb_always_assert (overflowing.offset_overflow ());
+  hb_always_assert (hb_resolve_graph_overflows (tag,
                                       num_iterations,
                                       recalculate_extensions,
                                       graph));
 
   // Check the graphs can be serialized.
   hb_blob_t* out1 = graph::serialize (graph);
-  assert (out1);
+  hb_always_assert (out1);
   hb_blob_t* out2 = graph::serialize (expected_graph);
-  assert (out2);
+  hb_always_assert (out2);
   if (check_binary_equivalence) {
     unsigned l1, l2;
     const char* d1 = hb_blob_get_data(out1, &l1);
@@ -517,7 +517,7 @@
       graph.print();
       printf("## Expected:\n");
       expected_graph.print();
-      assert(match);
+      hb_always_assert(match);
     }
   }
 
@@ -533,7 +533,7 @@
     printf("## Result:\n");
     graph.print();
   }
-  assert (graph == expected_graph);
+  hb_always_assert (graph == expected_graph);
 }
 
 static void add_virtual_offset (unsigned id,
@@ -888,7 +888,7 @@
   unsigned obj_d = c->pop_pack(false);
 
   add_offset(obj_d, c);
-  assert(c->last_added_child_index() == obj_d);
+  hb_always_assert(c->last_added_child_index() == obj_d);
 
   if (!with_overflow) {
     obj_e_1 = add_object("a", 1, c);
@@ -1804,27 +1804,27 @@
 
   graph_t graph (c.object_graph ());
   graph.sort_shortest_distance ();
-  assert (!graph.in_error ());
+  hb_always_assert (!graph.in_error ());
 
-  assert(strncmp (graph.object (4).head, "abc", 3) == 0);
-  assert(graph.object (4).real_links.length == 3);
-  assert(graph.object (4).real_links[0].objidx == 2);
-  assert(graph.object (4).real_links[1].objidx == 0);
-  assert(graph.object (4).real_links[2].objidx == 3);
+  hb_always_assert(strncmp (graph.object (4).head, "abc", 3) == 0);
+  hb_always_assert(graph.object (4).real_links.length == 3);
+  hb_always_assert(graph.object (4).real_links[0].objidx == 2);
+  hb_always_assert(graph.object (4).real_links[1].objidx == 0);
+  hb_always_assert(graph.object (4).real_links[2].objidx == 3);
 
-  assert(strncmp (graph.object (3).head, "mn", 2) == 0);
-  assert(graph.object (3).real_links.length == 0);
+  hb_always_assert(strncmp (graph.object (3).head, "mn", 2) == 0);
+  hb_always_assert(graph.object (3).real_links.length == 0);
 
-  assert(strncmp (graph.object (2).head, "def", 3) == 0);
-  assert(graph.object (2).real_links.length == 1);
-  assert(graph.object (2).real_links[0].objidx == 1);
+  hb_always_assert(strncmp (graph.object (2).head, "def", 3) == 0);
+  hb_always_assert(graph.object (2).real_links.length == 1);
+  hb_always_assert(graph.object (2).real_links[0].objidx == 1);
 
-  assert(strncmp (graph.object (1).head, "ghi", 3) == 0);
-  assert(graph.object (1).real_links.length == 1);
-  assert(graph.object (1).real_links[0].objidx == 0);
+  hb_always_assert(strncmp (graph.object (1).head, "ghi", 3) == 0);
+  hb_always_assert(graph.object (1).real_links.length == 1);
+  hb_always_assert(graph.object (1).real_links[0].objidx == 0);
 
-  assert(strncmp (graph.object (0).head, "jkl", 3) == 0);
-  assert(graph.object (0).real_links.length == 0);
+  hb_always_assert(strncmp (graph.object (0).head, "jkl", 3) == 0);
+  hb_always_assert(graph.object (0).real_links.length == 0);
 
   free (buffer);
 }
@@ -1839,28 +1839,28 @@
   graph_t graph (c.object_graph ());
   graph.duplicate (4, 1);
 
-  assert(strncmp (graph.object (5).head, "abc", 3) == 0);
-  assert(graph.object (5).real_links.length == 3);
-  assert(graph.object (5).real_links[0].objidx == 3);
-  assert(graph.object (5).real_links[1].objidx == 4);
-  assert(graph.object (5).real_links[2].objidx == 0);
+  hb_always_assert(strncmp (graph.object (5).head, "abc", 3) == 0);
+  hb_always_assert(graph.object (5).real_links.length == 3);
+  hb_always_assert(graph.object (5).real_links[0].objidx == 3);
+  hb_always_assert(graph.object (5).real_links[1].objidx == 4);
+  hb_always_assert(graph.object (5).real_links[2].objidx == 0);
 
-  assert(strncmp (graph.object (4).head, "jkl", 3) == 0);
-  assert(graph.object (4).real_links.length == 0);
+  hb_always_assert(strncmp (graph.object (4).head, "jkl", 3) == 0);
+  hb_always_assert(graph.object (4).real_links.length == 0);
 
-  assert(strncmp (graph.object (3).head, "def", 3) == 0);
-  assert(graph.object (3).real_links.length == 1);
-  assert(graph.object (3).real_links[0].objidx == 2);
+  hb_always_assert(strncmp (graph.object (3).head, "def", 3) == 0);
+  hb_always_assert(graph.object (3).real_links.length == 1);
+  hb_always_assert(graph.object (3).real_links[0].objidx == 2);
 
-  assert(strncmp (graph.object (2).head, "ghi", 3) == 0);
-  assert(graph.object (2).real_links.length == 1);
-  assert(graph.object (2).real_links[0].objidx == 1);
+  hb_always_assert(strncmp (graph.object (2).head, "ghi", 3) == 0);
+  hb_always_assert(graph.object (2).real_links.length == 1);
+  hb_always_assert(graph.object (2).real_links[0].objidx == 1);
 
-  assert(strncmp (graph.object (1).head, "jkl", 3) == 0);
-  assert(graph.object (1).real_links.length == 0);
+  hb_always_assert(strncmp (graph.object (1).head, "jkl", 3) == 0);
+  hb_always_assert(graph.object (1).real_links.length == 0);
 
-  assert(strncmp (graph.object (0).head, "mn", 2) == 0);
-  assert(graph.object (0).real_links.length == 0);
+  hb_always_assert(strncmp (graph.object (0).head, "mn", 2) == 0);
+  hb_always_assert(graph.object (0).real_links.length == 0);
 
   free (buffer);
 }
@@ -1875,33 +1875,33 @@
   graph_t graph (c.object_graph ());
   graph.duplicate (3, 2);
 
-  assert(strncmp (graph.object (6).head, "abc", 3) == 0);
-  assert(graph.object (6).real_links.length == 3);
-  assert(graph.object (6).real_links[0].objidx == 4);
-  assert(graph.object (6).real_links[1].objidx == 2);
-  assert(graph.object (6).real_links[2].objidx == 1);
+  hb_always_assert(strncmp (graph.object (6).head, "abc", 3) == 0);
+  hb_always_assert(graph.object (6).real_links.length == 3);
+  hb_always_assert(graph.object (6).real_links[0].objidx == 4);
+  hb_always_assert(graph.object (6).real_links[1].objidx == 2);
+  hb_always_assert(graph.object (6).real_links[2].objidx == 1);
 
-  assert(strncmp (graph.object (5).head, "jkl", 3) == 0);
-  assert(graph.object (5).real_links.length == 1);
-  assert(graph.object (5).real_links[0].objidx == 0);
+  hb_always_assert(strncmp (graph.object (5).head, "jkl", 3) == 0);
+  hb_always_assert(graph.object (5).real_links.length == 1);
+  hb_always_assert(graph.object (5).real_links[0].objidx == 0);
 
-  assert(strncmp (graph.object (4).head, "def", 3) == 0);
-  assert(graph.object (4).real_links.length == 1);
-  assert(graph.object (4).real_links[0].objidx == 3);
+  hb_always_assert(strncmp (graph.object (4).head, "def", 3) == 0);
+  hb_always_assert(graph.object (4).real_links.length == 1);
+  hb_always_assert(graph.object (4).real_links[0].objidx == 3);
 
-  assert(strncmp (graph.object (3).head, "ghi", 3) == 0);
-  assert(graph.object (3).real_links.length == 1);
-  assert(graph.object (3).real_links[0].objidx == 5);
+  hb_always_assert(strncmp (graph.object (3).head, "ghi", 3) == 0);
+  hb_always_assert(graph.object (3).real_links.length == 1);
+  hb_always_assert(graph.object (3).real_links[0].objidx == 5);
 
-  assert(strncmp (graph.object (2).head, "jkl", 3) == 0);
-  assert(graph.object (2).real_links.length == 1);
-  assert(graph.object (2).real_links[0].objidx == 0);
+  hb_always_assert(strncmp (graph.object (2).head, "jkl", 3) == 0);
+  hb_always_assert(graph.object (2).real_links.length == 1);
+  hb_always_assert(graph.object (2).real_links[0].objidx == 0);
 
-  assert(strncmp (graph.object (1).head, "mn", 2) == 0);
-  assert(graph.object (1).real_links.length == 0);
+  hb_always_assert(strncmp (graph.object (1).head, "mn", 2) == 0);
+  hb_always_assert(graph.object (1).real_links.length == 0);
 
-  assert(strncmp (graph.object (0).head, "opqrst", 6) == 0);
-  assert(graph.object (0).real_links.length == 0);
+  hb_always_assert(strncmp (graph.object (0).head, "opqrst", 6) == 0);
+  hb_always_assert(graph.object (0).real_links.length == 0);
 
   free (buffer);
 }
@@ -1920,7 +1920,7 @@
   free (buffer_1);
 
   hb_bytes_t actual = out->as_bytes ();
-  assert (actual == expected);
+  hb_always_assert (actual == expected);
   expected.fini ();
   hb_blob_destroy (out);
 }
@@ -1933,7 +1933,7 @@
   populate_serializer_complex_2 (&c);
   graph_t graph (c.object_graph ());
 
-  assert (!graph::will_overflow (graph, nullptr));
+  hb_always_assert (!graph::will_overflow (graph, nullptr));
 
   free (buffer);
 }
@@ -1946,7 +1946,7 @@
   populate_serializer_with_overflow (&c);
   graph_t graph (c.object_graph ());
 
-  assert (graph::will_overflow (graph, nullptr));
+  hb_always_assert (graph::will_overflow (graph, nullptr));
 
   free (buffer);
 }
@@ -1959,7 +1959,7 @@
   populate_serializer_with_dedup_overflow (&c);
   graph_t graph (c.object_graph ());
 
-  assert (graph::will_overflow (graph, nullptr));
+  hb_always_assert (graph::will_overflow (graph, nullptr));
 
   free (buffer);
 }
@@ -1973,9 +1973,9 @@
   graph_t graph (c.object_graph ());
 
   hb_blob_t* out = hb_resolve_overflows (c.object_graph (), HB_TAG_NONE);
-  assert (out);
+  hb_always_assert (out);
   hb_bytes_t result = out->as_bytes ();
-  assert (result.length == (80000 + 3 + 3 * 2));
+  hb_always_assert (result.length == (80000 + 3 + 3 * 2));
 
   free (buffer);
   hb_blob_destroy (out);
@@ -1990,9 +1990,9 @@
   graph_t graph (c.object_graph ());
 
   hb_blob_t* out = hb_resolve_overflows (c.object_graph (), HB_TAG_NONE);
-  assert (out);
+  hb_always_assert (out);
   hb_bytes_t result = out->as_bytes ();
-  assert (result.length == (10000 + 2 * 2 + 60000 + 2 + 3 * 2));
+  hb_always_assert (result.length == (10000 + 2 * 2 + 60000 + 2 + 3 * 2));
 
   free (buffer);
   hb_blob_destroy (out);
@@ -2007,7 +2007,7 @@
   graph_t graph (c.object_graph ());
 
   hb_blob_t* out = hb_resolve_overflows (c.object_graph (), HB_TAG_NONE, 5);
-  assert (out);
+  hb_always_assert (out);
 
   free (buffer);
   hb_blob_destroy (out);
@@ -2040,11 +2040,11 @@
   populate_serializer_with_isolation_overflow (&c);
   graph_t graph (c.object_graph ());
 
-  assert (c.offset_overflow ());
+  hb_always_assert (c.offset_overflow ());
   hb_blob_t* out = hb_resolve_overflows (c.object_graph (), HB_TAG ('G', 'S', 'U', 'B'), 0);
-  assert (out);
+  hb_always_assert (out);
   hb_bytes_t result = out->as_bytes ();
-  assert (result.length == (1 + 10000 + 60000 + 1 + 1
+  hb_always_assert (result.length == (1 + 10000 + 60000 + 1 + 1
                             + 4 + 3 * 2));
 
   free (buffer);
@@ -2115,14 +2115,14 @@
   populate_serializer_with_isolation_overflow_spaces (&c);
   graph_t graph (c.object_graph ());
 
-  assert (c.offset_overflow ());
+  hb_always_assert (c.offset_overflow ());
   hb_blob_t* out = hb_resolve_overflows (c.object_graph (), HB_TAG ('G', 'S', 'U', 'B'), 0);
-  assert (out);
+  hb_always_assert (out);
   hb_bytes_t result = out->as_bytes ();
 
   unsigned expected_length = 3 + 2 * 60000; // objects
   expected_length += 2 * 4 + 2 * 2; // links
-  assert (result.length == expected_length);
+  hb_always_assert (result.length == expected_length);
 
   free (buffer);
   hb_blob_destroy (out);
@@ -2136,9 +2136,9 @@
   populate_serializer_with_24_and_32_bit_offsets (&c);
   graph_t graph (c.object_graph ());
 
-  assert (c.offset_overflow ());
+  hb_always_assert (c.offset_overflow ());
   hb_blob_t* out = hb_resolve_overflows (c.object_graph (), HB_TAG ('G', 'S', 'U', 'B'), 0);
-  assert (out);
+  hb_always_assert (out);
   hb_bytes_t result = out->as_bytes ();
 
   unsigned expected_length =
@@ -2152,7 +2152,7 @@
       4 * 3 +  // 24
       4 * 2;   // 16
 
-  assert (result.length == expected_length);
+  hb_always_assert (result.length == expected_length);
 
   free (buffer);
   hb_blob_destroy (out);
@@ -2162,12 +2162,12 @@
 {
   size_t buffer_size = 200000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_extension_promotion (&c);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_extension_promotion (&e, 3);
 
@@ -2184,12 +2184,12 @@
 {
   size_t buffer_size = 200000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_extension_promotion (&c, 0, true);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_extension_promotion (&e, 3, true);
 
@@ -2206,12 +2206,12 @@
 {
   size_t buffer_size = 200000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_pair_pos_1 <1, 4>(&c);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_pair_pos_1 <2, 2>(&e, true);
 
@@ -2229,12 +2229,12 @@
 {
   size_t buffer_size = 200000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_pair_pos_1 <1, 4>(&c, true);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_pair_pos_1 <2, 2>(&e, true);
 
@@ -2252,12 +2252,12 @@
 {
   size_t buffer_size = 300000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_pair_pos_2 <1, 4, 3000>(&c);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_pair_pos_2 <2, 2, 3000>(&e, true);
 
@@ -2275,12 +2275,12 @@
 {
   size_t buffer_size = 300000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_pair_pos_2 <1, 1636, 10>(&c, true, false, false);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_pair_pos_2 <2, 818, 10>(&e, true, false, false);
 
@@ -2298,12 +2298,12 @@
 {
   size_t buffer_size = 300000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_pair_pos_2 <1, 4, 2000>(&c, false, true);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_pair_pos_2 <2, 2, 2000>(&e, true, true);
 
@@ -2321,12 +2321,12 @@
 {
   size_t buffer_size = 200000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_mark_base_pos_1 <40, 10, 110, 1>(&c);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_mark_base_pos_1 <40, 10, 110, 2>(&e);
 
@@ -2344,12 +2344,12 @@
 {
   size_t buffer_size = 200000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_liga<1, 1, 2, 40000>(&c, false);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_liga<2, 1, 1, 40000>(&e, false);
 
@@ -2367,12 +2367,12 @@
 {
   size_t buffer_size = 400000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_liga<1, 6, 2, 16000>(&c, true);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_liga<3, 2, 2, 16000>(&e, true);
 
@@ -2390,12 +2390,12 @@
 {
   size_t buffer_size = 400000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_large_liga<1, 2, 3, 30000>(&c, true);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_large_liga_overlapping_clone_result(&e);
 
@@ -2477,15 +2477,15 @@
   populate_serializer_virtual_link (&c);
 
   hb_blob_t* out = hb_resolve_overflows (c.object_graph (), HB_TAG_NONE);
-  assert (out);
+  hb_always_assert (out);
 
   hb_bytes_t result = out->as_bytes ();
-  assert (result.length == 5 + 4 * 2);
-  assert (result[0]  == 'a');
-  assert (result[5]  == 'c');
-  assert (result[8]  == 'e');
-  assert (result[9]  == 'b');
-  assert (result[12] == 'd');
+  hb_always_assert (result.length == 5 + 4 * 2);
+  hb_always_assert (result[0]  == 'a');
+  hb_always_assert (result[5]  == 'c');
+  hb_always_assert (result[8]  == 'e');
+  hb_always_assert (result[9]  == 'b');
+  hb_always_assert (result[12] == 'd');
 
   free (buffer);
   hb_blob_destroy (out);
@@ -2511,7 +2511,7 @@
   add_virtual_offset (obj_c, &c);
   unsigned obj_d_2 = c.pop_pack ();
 
-  assert (obj_d_1 == obj_d_2);
+  hb_always_assert (obj_d_1 == obj_d_2);
 
   start_object ("a", 1, &c);
   add_offset (obj_b, &c);
@@ -2521,9 +2521,9 @@
   c.pop_pack ();
   c.end_serialize ();
 
-  assert(c.object_graph() [obj_d_1]->virtual_links.length == 2);
-  assert(c.object_graph() [obj_d_1]->virtual_links[0].objidx == obj_b);
-  assert(c.object_graph() [obj_d_1]->virtual_links[1].objidx == obj_c);
+  hb_always_assert(c.object_graph() [obj_d_1]->virtual_links.length == 2);
+  hb_always_assert(c.object_graph() [obj_d_1]->virtual_links[0].objidx == obj_b);
+  hb_always_assert(c.object_graph() [obj_d_1]->virtual_links[1].objidx == obj_c);
   free(buffer);
 }
 
@@ -2532,12 +2532,12 @@
 {
   size_t buffer_size = 200000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_with_repack_last (&c, true);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_with_repack_last (&e, false);
 
@@ -2558,12 +2558,12 @@
 {
   size_t buffer_size = 200000;
   void* buffer = malloc (buffer_size);
-  assert (buffer);
+  hb_always_assert (buffer);
   hb_serialize_context_t c (buffer, buffer_size);
   populate_serializer_virtual (&c, true);
 
   void* expected_buffer = malloc (buffer_size);
-  assert (expected_buffer);
+  hb_always_assert (expected_buffer);
   hb_serialize_context_t e (expected_buffer, buffer_size);
   populate_serializer_virtual (&e, false);
 
diff --git a/src/test-serialize.cc b/src/test-serialize.cc
index 4c90abb..d148660 100644
--- a/src/test-serialize.cc
+++ b/src/test-serialize.cc
@@ -45,7 +45,7 @@
   s.end_serialize ();
 
   hb_bytes_t bytes = s.copy_bytes ();
-  assert (bytes.length == 10);
+  hb_always_assert (bytes.length == 10);
   bytes.fini ();
 
   return 0;
diff --git a/src/test-set.cc b/src/test-set.cc
index 853aed4..70d3f67 100644
--- a/src/test-set.cc
+++ b/src/test-set.cc
@@ -33,9 +33,9 @@
   {
     hb_set_t v1 {1, 2};
     hb_set_t v2 {v1};
-    assert (v1.get_population () == 2);
-    assert (hb_len (hb_iter (v1)) == 2);
-    assert (v2.get_population () == 2);
+    hb_always_assert (v1.get_population () == 2);
+    hb_always_assert (hb_len (hb_iter (v1)) == 2);
+    hb_always_assert (v2.get_population () == 2);
   }
 
   /* Test copy assignment. */
@@ -43,17 +43,17 @@
     hb_set_t v1 {1, 2};
     hb_set_t v2;
     v2 = v1;
-    assert (v1.get_population () == 2);
-    assert (v2.get_population () == 2);
+    hb_always_assert (v1.get_population () == 2);
+    hb_always_assert (v2.get_population () == 2);
   }
 
   /* Test move constructor. */
   {
     hb_set_t s {1, 2};
     hb_set_t v (std::move (s));
-    assert (s.get_population () == 0);
-    assert (hb_len (hb_iter (s)) == 0);
-    assert (v.get_population () == 2);
+    hb_always_assert (s.get_population () == 0);
+    hb_always_assert (hb_len (hb_iter (s)) == 0);
+    hb_always_assert (v.get_population () == 2);
   }
 
   /* Test move assignment. */
@@ -61,8 +61,8 @@
     hb_set_t s = hb_set_t {1, 2};
     hb_set_t v;
     v = std::move (s);
-    assert (s.get_population () == 0);
-    assert (v.get_population () == 2);
+    hb_always_assert (s.get_population () == 0);
+    hb_always_assert (v.get_population () == 2);
   }
 
   /* Test initializing from iterable. */
@@ -77,10 +77,10 @@
     hb_set_t v1 (s);
     hb_set_t v2 (std::move (s));
 
-    assert (s.get_population () == 0);
-    assert (v0.get_population () == 2);
-    assert (v1.get_population () == 2);
-    assert (v2.get_population () == 2);
+    hb_always_assert (s.get_population () == 0);
+    hb_always_assert (v0.get_population () == 2);
+    hb_always_assert (v1.get_population () == 2);
+    hb_always_assert (v2.get_population () == 2);
   }
 
   /* Test initializing from iterator. */
@@ -95,7 +95,7 @@
 
     hb_set_t v (hb_iter (s));
 
-    assert (v.get_population () == 5);
+    hb_always_assert (v.get_population () == 5);
   }
 
   /* Test initializing from initializer list and swapping. */
@@ -103,8 +103,8 @@
     hb_set_t v1 {1, 2, 3};
     hb_set_t v2 {4, 5};
     hb_swap (v1, v2);
-    assert (v1.get_population () == 2);
-    assert (v2.get_population () == 3);
+    hb_always_assert (v1.get_population () == 2);
+    hb_always_assert (v2.get_population () == 3);
   }
 
   /* Test inverted sets. */
@@ -114,27 +114,27 @@
     s.del (5);
 
     hb_codepoint_t start = HB_SET_VALUE_INVALID, last = HB_SET_VALUE_INVALID;
-    assert (s.next_range (&start, &last));
-    assert (start == 0);
-    assert (last == 4);
-    assert (s.next_range (&start, &last));
-    assert (start == 6);
-    assert (last == HB_SET_VALUE_INVALID - 1);
-    assert (!s.next_range (&start, &last));
+    hb_always_assert (s.next_range (&start, &last));
+    hb_always_assert (start == 0);
+    hb_always_assert (last == 4);
+    hb_always_assert (s.next_range (&start, &last));
+    hb_always_assert (start == 6);
+    hb_always_assert (last == HB_SET_VALUE_INVALID - 1);
+    hb_always_assert (!s.next_range (&start, &last));
 
     start = HB_SET_VALUE_INVALID;
     last = HB_SET_VALUE_INVALID;
-    assert (s.previous_range (&start, &last));
-    assert (start == 6);
-    assert (last == HB_SET_VALUE_INVALID - 1);
-    assert (s.previous_range (&start, &last));
-    assert (start == 0);
-    assert (last == 4);
-    assert (!s.previous_range (&start, &last));
+    hb_always_assert (s.previous_range (&start, &last));
+    hb_always_assert (start == 6);
+    hb_always_assert (last == HB_SET_VALUE_INVALID - 1);
+    hb_always_assert (s.previous_range (&start, &last));
+    hb_always_assert (start == 0);
+    hb_always_assert (last == 4);
+    hb_always_assert (!s.previous_range (&start, &last));
 
-    assert (s.is_inverted ());
+    hb_always_assert (s.is_inverted ());
     /* Inverted set returns true for invalid value; oh well. */
-    assert (s.has (HB_SET_VALUE_INVALID));
+    hb_always_assert (s.has (HB_SET_VALUE_INVALID));
   }
 
   /* Adding HB_SET_VALUE_INVALID */
@@ -142,23 +142,23 @@
     hb_set_t s;
 
     s.add(HB_SET_VALUE_INVALID);
-    assert(!s.has(HB_SET_VALUE_INVALID));
+    hb_always_assert(!s.has(HB_SET_VALUE_INVALID));
 
     s.clear();
-    assert(!s.add_range(HB_SET_VALUE_INVALID - 2, HB_SET_VALUE_INVALID));
-    assert(!s.has(HB_SET_VALUE_INVALID));
+    hb_always_assert(!s.add_range(HB_SET_VALUE_INVALID - 2, HB_SET_VALUE_INVALID));
+    hb_always_assert(!s.has(HB_SET_VALUE_INVALID));
 
     hb_codepoint_t array[] = {(unsigned) HB_SET_VALUE_INVALID, 0, 2};
     s.clear();
     s.add_array(array, 3);
-    assert(!s.has(HB_SET_VALUE_INVALID));
-    assert(s.has(2));
+    hb_always_assert(!s.has(HB_SET_VALUE_INVALID));
+    hb_always_assert(s.has(2));
 
     hb_codepoint_t sorted_array[] = {0, 2, (unsigned) HB_SET_VALUE_INVALID};
     s.clear();
     s.add_sorted_array(sorted_array, 3);
-    assert(!s.has(HB_SET_VALUE_INVALID));
-    assert(s.has(2));
+    hb_always_assert(!s.has(HB_SET_VALUE_INVALID));
+    hb_always_assert(s.has(2));
   }
 
   return 0;
diff --git a/src/test-subset-instancer-solver.cc b/src/test-subset-instancer-solver.cc
index 06b71bd..eba83ff 100644
--- a/src/test-subset-instancer-solver.cc
+++ b/src/test-subset-instancer-solver.cc
@@ -49,7 +49,7 @@
     Triple tent (0.0, 1.0, 1.0);
     Triple axis_range (0.0, 0.0, 0.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 0);
+    hb_always_assert (out.length == 0);
   }
 
   {
@@ -57,9 +57,9 @@
     Triple tent (0.0, 1.0, 1.0);
     Triple axis_range (0.5, 0.5, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 0.5);
-    assert (out[0].second == Triple ());
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 0.5);
+    hb_always_assert (out[0].second == Triple ());
   }
 
   {
@@ -67,7 +67,7 @@
     Triple tent (0.3, 0.5, 0.8);
     Triple axis_range (0.1, 0.2, 0.3);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 0);
+    hb_always_assert (out.length == 0);
   }
 
   /* Case 2 */
@@ -75,9 +75,9 @@
     Triple tent (0.0, 1.0, 1.0);
     Triple axis_range (-1.0, 0.0, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 0.5);
-    assert (out[0].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 0.5);
+    hb_always_assert (out[0].second == Triple (0.0, 1.0, 1.0));
   }
 
   /* Case 2 */
@@ -85,9 +85,9 @@
     Triple tent (0.0, 1.0, 1.0);
     Triple axis_range (-1.0, 0.0, 0.75);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 0.75);
-    assert (out[0].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 0.75);
+    hb_always_assert (out[0].second == Triple (0.0, 1.0, 1.0));
   }
 
   /* Without gain: */
@@ -96,11 +96,11 @@
     Triple tent (0.0, 0.2, 1.0);
     Triple axis_range (-1.0, 0.0, 0.8);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 2);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (0.0, 0.25, 1.0));
-    assert (approx (out[1].first, 0.250));
-    assert (out[1].second == Triple (0.25, 1.0, 1.0));
+    hb_always_assert (out.length == 2);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (0.0, 0.25, 1.0));
+    hb_always_assert (approx (out[1].first, 0.250));
+    hb_always_assert (out[1].second == Triple (0.25, 1.0, 1.0));
   }
 
   /* Case 3 boundary */
@@ -108,11 +108,11 @@
     Triple tent (0.0, 0.4, 1.0);
     Triple axis_range (-1.0, 0.0, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 2);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (0.0, 0.8, 1.0));
-    assert (approx (out[1].first, 2.5/3));
-    assert (out[1].second == Triple (0.8, 1.0, 1.0));
+    hb_always_assert (out.length == 2);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (0.0, 0.8, 1.0));
+    hb_always_assert (approx (out[1].first, 2.5/3));
+    hb_always_assert (out[1].second == Triple (0.8, 1.0, 1.0));
   }
 
   /* Case 4 */
@@ -120,11 +120,11 @@
     Triple tent (0.0, 0.25, 1.0);
     Triple axis_range (-1.0, 0.0, 0.4);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 2);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (0.0, 0.625, 1.0));
-    assert (approx (out[1].first, 0.80));
-    assert (out[1].second == Triple (0.625, 1.0, 1.0));
+    hb_always_assert (out.length == 2);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (0.0, 0.625, 1.0));
+    hb_always_assert (approx (out[1].first, 0.80));
+    hb_always_assert (out[1].second == Triple (0.625, 1.0, 1.0));
   }
 
   /* Case 4 */
@@ -132,11 +132,11 @@
     Triple tent (0.25, 0.3, 1.05);
     Triple axis_range (0.0, 0.2, 0.4);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 2);
-    assert (out[0].first == 1.0);
-    assert (approx (out[0].second, Triple (0.25, 0.5, 1.0)));
-    assert (approx (out[1].first, 2.6 / 3));
-    assert (approx (out[1].second, Triple (0.5, 1.0, 1.0)));
+    hb_always_assert (out.length == 2);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (approx (out[0].second, Triple (0.25, 0.5, 1.0)));
+    hb_always_assert (approx (out[1].first, 2.6 / 3));
+    hb_always_assert (approx (out[1].second, Triple (0.5, 1.0, 1.0)));
   }
 
   /* Case 4 boundary */
@@ -144,9 +144,9 @@
     Triple tent (0.25, 0.5, 1.0);
     Triple axis_range (0.0, 0.25, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (0.0, 1.0, 1.0));
   }
 
   /* With gain */
@@ -155,41 +155,41 @@
     Triple tent (0.0, 0.5, 1.0);
     Triple axis_range (0.0, 0.5, 1.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 3);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == -1.0);
-    assert (out[1].second == Triple (0.0, 1.0, 1.0));
-    assert (out[2].first == -1.0);
-    assert (out[2].second == Triple (-1.0, -1.0, 0.0));
+    hb_always_assert (out.length == 3);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == -1.0);
+    hb_always_assert (out[1].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (out[2].first == -1.0);
+    hb_always_assert (out[2].second == Triple (-1.0, -1.0, 0.0));
   }
 
   {
     Triple tent (0.0, 0.5, 1.0);
     Triple axis_range (0.0, 0.5, 0.75);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 3);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == -0.5);
-    assert (out[1].second == Triple (0.0, 1.0, 1.0));
-    assert (out[2].first == -1.0);
-    assert (out[2].second == Triple (-1.0, -1.0, 0.0));
+    hb_always_assert (out.length == 3);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == -0.5);
+    hb_always_assert (out[1].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (out[2].first == -1.0);
+    hb_always_assert (out[2].second == Triple (-1.0, -1.0, 0.0));
   }
 
   {
     Triple tent (0.0, 0.50, 1.0);
     Triple axis_range (0.0, 0.25, 0.8);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 4);
-    assert (out[0].first == 0.5);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == 0.5);
-    assert (approx (out[1].second, Triple (0.0, 0.454545, 0.909091)));
-    assert (approx (out[2].first, -0.1));
-    assert (approx (out[2].second, Triple (0.909091, 1.0, 1.0)));
-    assert (out[3].first == -0.5);
-    assert (out[3].second == Triple (-1.0, -1.0, 0.0));
+    hb_always_assert (out.length == 4);
+    hb_always_assert (out[0].first == 0.5);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == 0.5);
+    hb_always_assert (approx (out[1].second, Triple (0.0, 0.454545, 0.909091)));
+    hb_always_assert (approx (out[2].first, -0.1));
+    hb_always_assert (approx (out[2].second, Triple (0.909091, 1.0, 1.0)));
+    hb_always_assert (out[3].first == -0.5);
+    hb_always_assert (out[3].second == Triple (-1.0, -1.0, 0.0));
   }
 
   /* Case 3a/1neg */
@@ -197,13 +197,13 @@
     Triple tent (0.0, 0.5, 2.0);
     Triple axis_range (0.2, 0.5, 0.8);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 3);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
-    assert (approx (out[1].first, -0.2));
-    assert (out[1].second == Triple (0.0, 1.0, 1.0));
-    assert (approx (out[2].first, -0.6));
-    assert (out[2].second == Triple (-1.0, -1.0, 0.0));
+    hb_always_assert (out.length == 3);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (approx (out[1].first, -0.2));
+    hb_always_assert (out[1].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (approx (out[2].first, -0.6));
+    hb_always_assert (out[2].second == Triple (-1.0, -1.0, 0.0));
   }
 
   /* Case 3a/1neg */
@@ -211,13 +211,13 @@
     Triple tent (0.0, 0.5, 2.0);
     Triple axis_range (0.2, 0.5, 1.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 3);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
-    assert (approx (out[1].first, -1.0/3));
-    assert (out[1].second == Triple (0.0, 1.0, 1.0));
-    assert (approx (out[2].first, -0.6));
-    assert (out[2].second == Triple (-1.0, -1.0, 0.0));
+    hb_always_assert (out.length == 3);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (approx (out[1].first, -1.0/3));
+    hb_always_assert (out[1].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (approx (out[2].first, -0.6));
+    hb_always_assert (out[2].second == Triple (-1.0, -1.0, 0.0));
   }
 
   /* Case 3 */
@@ -225,11 +225,11 @@
     Triple tent (0.0, 0.5, 1.0);
     Triple axis_range (0.25, 0.25, 0.75);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 2);
-    assert (out[0].first == 0.5);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == 0.5);
-    assert (out[1].second == Triple (0.0, 0.5, 1.0));
+    hb_always_assert (out.length == 2);
+    hb_always_assert (out[0].first == 0.5);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == 0.5);
+    hb_always_assert (out[1].second == Triple (0.0, 0.5, 1.0));
   }
 
   /* Case 1neg */
@@ -237,13 +237,13 @@
     Triple tent (0.0, 0.5, 1.0);
     Triple axis_range (0.0, 0.25, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 3);
-    assert (out[0].first == 0.5);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == 0.5);
-    assert (out[1].second == Triple (0.0, 1.0, 1.0));
-    assert (out[2].first == -0.5);
-    assert (out[2].second == Triple (-1.0, -1.0, 0.0));
+    hb_always_assert (out.length == 3);
+    hb_always_assert (out[0].first == 0.5);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == 0.5);
+    hb_always_assert (out[1].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (out[2].first == -0.5);
+    hb_always_assert (out[2].second == Triple (-1.0, -1.0, 0.0));
   }
 
   /* Case 2neg */
@@ -251,15 +251,15 @@
     Triple tent (0.05, 0.55, 1.0);
     Triple axis_range (0.0, 0.25, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 4);
-    assert (approx (out[0].first, 0.4));
-    assert (out[0].second == Triple ());
-    assert (approx (out[1].first, 0.5));
-    assert (out[1].second == Triple (0.0, 1.0, 1.0));
-    assert (approx (out[2].first, -0.4));
-    assert (out[2].second == Triple (-1.0, -0.8, 0.0));
-    assert (approx (out[3].first, -0.4));
-    assert (out[3].second == Triple (-1.0, -1.0, -0.8));
+    hb_always_assert (out.length == 4);
+    hb_always_assert (approx (out[0].first, 0.4));
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (approx (out[1].first, 0.5));
+    hb_always_assert (out[1].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (approx (out[2].first, -0.4));
+    hb_always_assert (out[2].second == Triple (-1.0, -0.8, 0.0));
+    hb_always_assert (approx (out[3].first, -0.4));
+    hb_always_assert (out[3].second == Triple (-1.0, -1.0, -0.8));
   }
 
   /* Case 2neg, other side */
@@ -267,15 +267,15 @@
     Triple tent (-1.0, -0.55, -0.05);
     Triple axis_range (-0.5, -0.25, 0.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 4);
-    assert (approx (out[0].first, 0.4));
-    assert (out[0].second == Triple ());
-    assert (approx (out[1].first, 0.5));
-    assert (out[1].second == Triple (-1.0, -1.0, 0.0));
-    assert (approx (out[2].first, -0.4));
-    assert (out[2].second == Triple (0.0, 0.8, 1.0));
-    assert (approx (out[3].first, -0.4));
-    assert (out[3].second == Triple (0.8, 1.0, 1.0));
+    hb_always_assert (out.length == 4);
+    hb_always_assert (approx (out[0].first, 0.4));
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (approx (out[1].first, 0.5));
+    hb_always_assert (out[1].second == Triple (-1.0, -1.0, 0.0));
+    hb_always_assert (approx (out[2].first, -0.4));
+    hb_always_assert (out[2].second == Triple (0.0, 0.8, 1.0));
+    hb_always_assert (approx (out[3].first, -0.4));
+    hb_always_assert (out[3].second == Triple (0.8, 1.0, 1.0));
   }
 
   /* Misc corner cases */
@@ -283,86 +283,86 @@
     Triple tent (0.5, 0.5, 0.5);
     Triple axis_range (0.5, 0.5, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
   }
 
   {
     Triple tent (0.3, 0.5, 0.7);
     Triple axis_range (0.1, 0.5, 0.9);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 5);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == -1.0);
-    assert (approx(out[1].second, Triple (0.0, 0.5, 1.0)));
-    assert (out[2].first == -1.0);
-    assert (approx(out[2].second, Triple (0.5, 1.0, 1.0)));
-    assert (out[3].first == -1.0);
-    assert (approx (out[3].second, Triple (-1.0, -0.5, 0.0)));
-    assert (out[4].first == -1.0);
-    assert (approx (out[4].second, Triple (-1.0, -1.0, -0.5)));
+    hb_always_assert (out.length == 5);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == -1.0);
+    hb_always_assert (approx(out[1].second, Triple (0.0, 0.5, 1.0)));
+    hb_always_assert (out[2].first == -1.0);
+    hb_always_assert (approx(out[2].second, Triple (0.5, 1.0, 1.0)));
+    hb_always_assert (out[3].first == -1.0);
+    hb_always_assert (approx (out[3].second, Triple (-1.0, -0.5, 0.0)));
+    hb_always_assert (out[4].first == -1.0);
+    hb_always_assert (approx (out[4].second, Triple (-1.0, -1.0, -0.5)));
   }
 
   {
     Triple tent (0.5, 0.5, 0.5);
     Triple axis_range (0.25, 0.25, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (1.0, 1.0, 1.0));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (1.0, 1.0, 1.0));
   }
 
   {
     Triple tent (0.5, 0.5, 0.5);
     Triple axis_range (0.25, 0.35, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (1.0, 1.0, 1.0));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (1.0, 1.0, 1.0));
   }
 
   {
     Triple tent (0.5, 0.5, 0.55);
     Triple axis_range (0.25, 0.35, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (1.0, 1.0, 1.0));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (1.0, 1.0, 1.0));
   }
 
   {
     Triple tent (0.5, 0.5, 1.0);
     Triple axis_range (0.5, 0.5, 1.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 2);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == -1.0);
-    assert (out[1].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (out.length == 2);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == -1.0);
+    hb_always_assert (out[1].second == Triple (0.0, 1.0, 1.0));
   }
 
   {
     Triple tent (0.25, 0.5, 1.0);
     Triple axis_range (0.5, 0.5, 1.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 2);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == -1.0);
-    assert (out[1].second == Triple (0.0, 1.0, 1.0));
+    hb_always_assert (out.length == 2);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == -1.0);
+    hb_always_assert (out[1].second == Triple (0.0, 1.0, 1.0));
   }
 
   {
     Triple tent (0.0, 0.2, 1.0);
     Triple axis_range (0.0, 0.0, 0.5);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 2);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (0.0, 0.4, 1.0));
-    assert (out[1].first == 0.625);
-    assert (out[1].second == Triple (0.4, 1.0, 1.0));
+    hb_always_assert (out.length == 2);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (0.0, 0.4, 1.0));
+    hb_always_assert (out[1].first == 0.625);
+    hb_always_assert (out[1].second == Triple (0.4, 1.0, 1.0));
   }
 
 
@@ -370,43 +370,43 @@
     Triple tent (0.0, 0.5, 1.0);
     Triple axis_range (-1.0, 0.25, 1.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 5);
-    assert (out[0].first == 0.5);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == 0.5);
-    assert (out[1].second == Triple (0.0, 1.0/3, 2.0/3));
-    assert (out[2].first == -0.5);
-    assert (out[2].second == Triple (2.0/3, 1.0, 1.0));
-    assert (out[3].first == -0.5);
-    assert (out[3].second == Triple (-1.0, -0.2, 0.0));
-    assert (out[4].first == -0.5);
-    assert (out[4].second == Triple (-1.0, -1.0, -0.2));
+    hb_always_assert (out.length == 5);
+    hb_always_assert (out[0].first == 0.5);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == 0.5);
+    hb_always_assert (out[1].second == Triple (0.0, 1.0/3, 2.0/3));
+    hb_always_assert (out[2].first == -0.5);
+    hb_always_assert (out[2].second == Triple (2.0/3, 1.0, 1.0));
+    hb_always_assert (out[3].first == -0.5);
+    hb_always_assert (out[3].second == Triple (-1.0, -0.2, 0.0));
+    hb_always_assert (out[4].first == -0.5);
+    hb_always_assert (out[4].second == Triple (-1.0, -1.0, -0.2));
   }
 
   {
     Triple tent (0.5, 0.5, 0.5);
     Triple axis_range (0.0, 0.5, 1.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 5);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple ());
-    assert (out[1].first == -1.0);
-    assert (out[1].second == Triple (0.0, 2/(double) (1 << 14), 1.0));
-    assert (out[2].first == -1.0);
-    assert (out[2].second == Triple (2/(double) (1 << 14), 1.0, 1.0));
-    assert (out[3].first == -1.0);
-    assert (out[3].second == Triple (-1.0, -2/(double) (1 << 14), 0.0));
-    assert (out[4].first == -1.0);
-    assert (out[4].second == Triple (-1.0, -1.0, -2/(double) (1 << 14)));
+    hb_always_assert (out.length == 5);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple ());
+    hb_always_assert (out[1].first == -1.0);
+    hb_always_assert (out[1].second == Triple (0.0, 2/(double) (1 << 14), 1.0));
+    hb_always_assert (out[2].first == -1.0);
+    hb_always_assert (out[2].second == Triple (2/(double) (1 << 14), 1.0, 1.0));
+    hb_always_assert (out[3].first == -1.0);
+    hb_always_assert (out[3].second == Triple (-1.0, -2/(double) (1 << 14), 0.0));
+    hb_always_assert (out[4].first == -1.0);
+    hb_always_assert (out[4].second == Triple (-1.0, -1.0, -2/(double) (1 << 14)));
   }
 
   {
     Triple tent (0.0, 1.0, 1.0);
     Triple axis_range (-1.0, -0.5, 1.0);
     rebase_tent_result_t out = rebase_tent (tent, axis_range, default_axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (1.0/3, 1.0, 1.0));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (1.0/3, 1.0, 1.0));
   }
 
   {
@@ -414,9 +414,9 @@
     Triple axis_range (-1.0, -0.5, 1.0);
     TripleDistances axis_distances{2.0, 1.0};
     rebase_tent_result_t out = rebase_tent (tent, axis_range, axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 1.0);
-    assert (out[0].second == Triple (0.5, 1.0, 1.0));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (out[0].second == Triple (0.5, 1.0, 1.0));
   }
 
   {
@@ -424,8 +424,8 @@
     Triple axis_range (-1.0, 0.2, 1.0);
     TripleDistances axis_distances{1.0, 1.0};
     rebase_tent_result_t out = rebase_tent (tent, axis_range, axis_distances);
-    assert (out.length == 1);
-    assert (out[0].first == 1.0);
-    assert (approx (out[0].second, Triple (0.5, 0.625, 0.75)));
+    hb_always_assert (out.length == 1);
+    hb_always_assert (out[0].first == 1.0);
+    hb_always_assert (approx (out[0].second, Triple (0.5, 0.625, 0.75)));
   }
 }
diff --git a/src/test-tuple-varstore.cc b/src/test-tuple-varstore.cc
index 9fdbaca..cef04ab 100644
--- a/src/test-tuple-varstore.cc
+++ b/src/test-tuple-varstore.cc
@@ -48,33 +48,33 @@
   hb_bytes_t var_data_bytes{(const char* ) cvar_data + 4, len - 4};
   bool result = OT::TupleVariationData<>::get_tuple_iterator (var_data_bytes, axis_count, cvar_table,
                                                             shared_indices, &iterator);
-  assert (result);
+  hb_always_assert (result);
 
   result = tuple_var_data->decompile_tuple_variations (point_count, false, iterator, &axis_idx_tag_map,
                                                        shared_indices, hb_array<const OT::F2DOT14> (),
                                                        tuple_variations);
 
-  assert (result);
-  assert (tuple_variations.tuple_vars.length == 2);
+  hb_always_assert (result);
+  hb_always_assert (tuple_variations.tuple_vars.length == 2);
   for (unsigned i = 0; i < 2; i++)
   {
-    assert (tuple_variations.tuple_vars[i].axis_tuples.get_population () == 1);
-    assert (!tuple_variations.tuple_vars[i].deltas_y);
-    assert (tuple_variations.tuple_vars[i].indices.length == 65);
-    assert (tuple_variations.tuple_vars[i].indices.length == tuple_variations.tuple_vars[i].deltas_x.length);
+    hb_always_assert (tuple_variations.tuple_vars[i].axis_tuples.get_population () == 1);
+    hb_always_assert (!tuple_variations.tuple_vars[i].deltas_y);
+    hb_always_assert (tuple_variations.tuple_vars[i].indices.length == 65);
+    hb_always_assert (tuple_variations.tuple_vars[i].indices.length == tuple_variations.tuple_vars[i].deltas_x.length);
   }
-  assert (tuple_variations.tuple_vars[0].axis_tuples.get (axis_tag) == Triple (-1.0, -1.0, 0.0));
-  assert (tuple_variations.tuple_vars[1].axis_tuples.get (axis_tag) == Triple (0.0, 1.0, 1.0));
+  hb_always_assert (tuple_variations.tuple_vars[0].axis_tuples.get (axis_tag) == Triple (-1.0, -1.0, 0.0));
+  hb_always_assert (tuple_variations.tuple_vars[1].axis_tuples.get (axis_tag) == Triple (0.0, 1.0, 1.0));
 
   hb_vector_t<float> deltas_1 {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -3.0, 1.0, 0.0, -1.0, 0.0, -3.0, 1.0, 0.0, -37.0, -37.0, -26.0, -26.0, 0.0, 0.0, 0.0, -3.0, 0.0, 0.0, 0.0, 0.0, 0.0, -3.0, 0.0, 2.0, -29.0, -29.0, -20.0, -20.0, 0.0, 0.0, 0.0, 1.0, -29.0, -29.0, -20.0, -20.0, 0.0, 0.0, 0.0, 1.0};
   for (unsigned i = 0; i < 65; i++)
   {
     if (i < 23)
-      assert (tuple_variations.tuple_vars[0].indices[i] == 0);
+      hb_always_assert (tuple_variations.tuple_vars[0].indices[i] == 0);
     else
     {
-      assert (tuple_variations.tuple_vars[0].indices[i] == 1);
-      assert (tuple_variations.tuple_vars[0].deltas_x[i] == deltas_1[i]);
+      hb_always_assert (tuple_variations.tuple_vars[0].indices[i] == 1);
+      hb_always_assert (tuple_variations.tuple_vars[0].deltas_x[i] == deltas_1[i]);
     }
   }
 
@@ -82,11 +82,11 @@
   for (unsigned i = 0 ; i < 65; i++)
   {
     if (i < 23)
-      assert (tuple_variations.tuple_vars[1].indices[i] == 0);
+      hb_always_assert (tuple_variations.tuple_vars[1].indices[i] == 0);
     else
     {
-      assert (tuple_variations.tuple_vars[1].indices[i] == 1);
-      assert (tuple_variations.tuple_vars[1].deltas_x[i] == deltas_2[i]);
+      hb_always_assert (tuple_variations.tuple_vars[1].indices[i] == 1);
+      hb_always_assert (tuple_variations.tuple_vars[1].deltas_x[i] == deltas_2[i]);
     }
   }
 
@@ -99,12 +99,12 @@
 
   tuple_variations.instantiate (normalized_axes_location, axes_triple_distances);
 
-  assert (tuple_variations.tuple_vars[0].indices.length == 65);
-  assert (tuple_variations.tuple_vars[1].indices.length == 65);
-  assert (!tuple_variations.tuple_vars[0].deltas_y);
-  assert (!tuple_variations.tuple_vars[1].deltas_y);
-  assert (tuple_variations.tuple_vars[0].axis_tuples.get (axis_tag) == Triple (-1.0, -1.0, 0.0));
-  assert (tuple_variations.tuple_vars[1].axis_tuples.get (axis_tag) == Triple (0.0, 1.0, 1.0));
+  hb_always_assert (tuple_variations.tuple_vars[0].indices.length == 65);
+  hb_always_assert (tuple_variations.tuple_vars[1].indices.length == 65);
+  hb_always_assert (!tuple_variations.tuple_vars[0].deltas_y);
+  hb_always_assert (!tuple_variations.tuple_vars[1].deltas_y);
+  hb_always_assert (tuple_variations.tuple_vars[0].axis_tuples.get (axis_tag) == Triple (-1.0, -1.0, 0.0));
+  hb_always_assert (tuple_variations.tuple_vars[1].axis_tuples.get (axis_tag) == Triple (0.0, 1.0, 1.0));
 
   hb_vector_t<float> rounded_deltas_1 {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1, 0.0, -2, 1, 0.0, -1, 0.0, -2, 1, 0.0, -19, -19, -13, -13, 0.0, 0.0, 0.0, -2, 0.0, 0.0, 0.0, 0.0, 0.0, -2, 0.0, 1, -15, -15, -10.0, -10.0, 0.0, 0.0, 0.0, 1, -15, -15, -10.0, -10.0, 0.0, 0.0, 0.0, 1};
 
@@ -114,41 +114,41 @@
   {
     if (i < 23)
     {
-      assert (tuple_variations.tuple_vars[0].indices[i] == 0);
-      assert (tuple_variations.tuple_vars[1].indices[i] == 0);
+      hb_always_assert (tuple_variations.tuple_vars[0].indices[i] == 0);
+      hb_always_assert (tuple_variations.tuple_vars[1].indices[i] == 0);
     }
     else
     {
-      assert (tuple_variations.tuple_vars[0].indices[i] == 1);
-      assert (tuple_variations.tuple_vars[1].indices[i] == 1);
-      assert (roundf (tuple_variations.tuple_vars[0].deltas_x[i]) == rounded_deltas_1[i]);
-      assert (roundf (tuple_variations.tuple_vars[1].deltas_x[i]) == rounded_deltas_2[i]);
+      hb_always_assert (tuple_variations.tuple_vars[0].indices[i] == 1);
+      hb_always_assert (tuple_variations.tuple_vars[1].indices[i] == 1);
+      hb_always_assert (roundf (tuple_variations.tuple_vars[0].deltas_x[i]) == rounded_deltas_1[i]);
+      hb_always_assert (roundf (tuple_variations.tuple_vars[1].deltas_x[i]) == rounded_deltas_2[i]);
     }
   }
 
   hb_map_t axes_index_map;
   axes_index_map.set (0, 0);
   bool res = tuple_variations.compile_bytes (axes_index_map, axis_idx_tag_map, false);
-  assert (res);
-  assert (tuple_variations.tuple_vars[0].compiled_tuple_header.length == 6);
+  hb_always_assert (res);
+  hb_always_assert (tuple_variations.tuple_vars[0].compiled_tuple_header.length == 6);
   const unsigned char tuple_var_header_1[] = "\x0\x51\xa0\x0\xc0\x0";
   for (unsigned i = 0; i < 6; i++)
-    assert(tuple_variations.tuple_vars[0].compiled_tuple_header.arrayZ[i] == tuple_var_header_1[i]);
+    hb_always_assert(tuple_variations.tuple_vars[0].compiled_tuple_header.arrayZ[i] == tuple_var_header_1[i]);
 
-  assert (tuple_variations.tuple_vars[1].compiled_tuple_header.length == 6);
+  hb_always_assert (tuple_variations.tuple_vars[1].compiled_tuple_header.length == 6);
   const unsigned char tuple_var_header_2[] = "\x0\x54\xa0\x0\x40\x0";
   for (unsigned i = 0; i < 6; i++)
-    assert(tuple_variations.tuple_vars[1].compiled_tuple_header.arrayZ[i] == tuple_var_header_2[i]);
+    hb_always_assert(tuple_variations.tuple_vars[1].compiled_tuple_header.arrayZ[i] == tuple_var_header_2[i]);
 
-  assert (tuple_variations.tuple_vars[0].compiled_deltas.length == 37);
-  assert (tuple_variations.tuple_vars[1].compiled_deltas.length == 40);
+  hb_always_assert (tuple_variations.tuple_vars[0].compiled_deltas.length == 37);
+  hb_always_assert (tuple_variations.tuple_vars[1].compiled_deltas.length == 40);
   const unsigned char compiled_deltas_1[] = "\x0d\xff\x00\xfe\x01\x00\xff\x00\xfe\x01\x00\xed\xed\xf3\xf3\x82\x00\xfe\x84\x06\xfe\x00\x01\xf1\xf1\xf6\xf6\x82\x04\x01\xf1\xf1\xf6\xf6\x82\x00\x01";
   for (unsigned i = 0; i < 37; i++)
-    assert (tuple_variations.tuple_vars[0].compiled_deltas.arrayZ[i] == compiled_deltas_1[i]);
+    hb_always_assert (tuple_variations.tuple_vars[0].compiled_deltas.arrayZ[i] == compiled_deltas_1[i]);
 
   const unsigned char compiled_deltas_2[] = "\x0d\x01\x00\x04\xfe\x00\x01\x00\x04\xfe\x00\x44\x44\x30\x30\x82\x00\x04\x81\x09\x01\xff\x01\x05\xff\xfc\x33\x33\x25\x25\x82\x04\xff\x33\x33\x25\x25\x82\x00\xff";
   for (unsigned i = 0; i < 40; i++)
-    assert (tuple_variations.tuple_vars[1].compiled_deltas.arrayZ[i] == compiled_deltas_2[i]);
+    hb_always_assert (tuple_variations.tuple_vars[1].compiled_deltas.arrayZ[i] == compiled_deltas_2[i]);
 }
 
 int
diff --git a/src/test-vector.cc b/src/test-vector.cc
index cc8ceab..1f6dc82 100644
--- a/src/test-vector.cc
+++ b/src/test-vector.cc
@@ -33,19 +33,19 @@
 int
 main (int argc, char **argv)
 {
-  assert (sizeof (hb_vector_t<int>) == sizeof (hb_sorted_vector_t<int>));
+  hb_always_assert (sizeof (hb_vector_t<int>) == sizeof (hb_sorted_vector_t<int>));
 
   /* Test copy constructor. */
   {
     hb_vector_t<int> v1 {1, 2};
     hb_vector_t<int> v2 {v1};
     hb_vector_t<int> V2 {v1};
-    assert (v1.length == 2);
-    assert (v1[0] == 1);
-    assert (v1[1] == 2);
-    assert (v2.length == 2);
-    assert (v2[0] == 1);
-    assert (v2[1] == 2);
+    hb_always_assert (v1.length == 2);
+    hb_always_assert (v1[0] == 1);
+    hb_always_assert (v1[1] == 2);
+    hb_always_assert (v2.length == 2);
+    hb_always_assert (v2[0] == 1);
+    hb_always_assert (v2[1] == 2);
   }
 
   /* Test copy assignment. */
@@ -53,12 +53,12 @@
     hb_vector_t<int> v1 {1, 2};
     hb_vector_t<int> v2 = v1;
     hb_vector_t<int> V2 = v1;
-    assert (v1.length == 2);
-    assert (v1[0] == 1);
-    assert (v1[1] == 2);
-    assert (v2.length == 2);
-    assert (v2[0] == 1);
-    assert (v2[1] == 2);
+    hb_always_assert (v1.length == 2);
+    hb_always_assert (v1[0] == 1);
+    hb_always_assert (v1[1] == 2);
+    hb_always_assert (v2.length == 2);
+    hb_always_assert (v2[0] == 1);
+    hb_always_assert (v2[1] == 2);
   }
 
   /* Test move constructor. */
@@ -67,11 +67,11 @@
     hb_sorted_vector_t<int> S {1, 2};
     hb_vector_t<int> v (std::move (s));
     hb_sorted_vector_t<int> V (std::move (S));
-    assert (s.length == 0);
-    assert (S.length == 0);
-    assert (v.length == 2);
-    assert (v[0] == 1);
-    assert (v[1] == 2);
+    hb_always_assert (s.length == 0);
+    hb_always_assert (S.length == 0);
+    hb_always_assert (v.length == 2);
+    hb_always_assert (v[0] == 1);
+    hb_always_assert (v[1] == 2);
   }
 
   /* Test move assignment. */
@@ -82,12 +82,12 @@
     hb_sorted_vector_t<int> V;
     v = std::move (s);
     V = std::move (S);
-    assert (s.length == 0);
-    assert (S.length == 0);
-    assert (v.length == 2);
-    assert (V.length == 2);
-    assert (v[0] == 1);
-    assert (v[1] == 2);
+    hb_always_assert (s.length == 0);
+    hb_always_assert (S.length == 0);
+    hb_always_assert (v.length == 2);
+    hb_always_assert (V.length == 2);
+    hb_always_assert (v[0] == 1);
+    hb_always_assert (v[1] == 2);
   }
 
   /* Test initializing from iterable. */
@@ -100,12 +100,12 @@
     hb_vector_t<int> v (s);
     hb_sorted_vector_t<int> V (s);
 
-    assert (v.length == 2);
-    assert (V.length == 2);
-    assert (v[0] == 12);
-    assert (V[0] == 12);
-    assert (v[1] == 18);
-    assert (V[1] == 18);
+    hb_always_assert (v.length == 2);
+    hb_always_assert (V.length == 2);
+    hb_always_assert (v[0] == 12);
+    hb_always_assert (V[0] == 12);
+    hb_always_assert (v[1] == 18);
+    hb_always_assert (V[1] == 18);
   }
 
   /* Test initializing from iterator. */
@@ -118,12 +118,12 @@
     hb_vector_t<int> v (hb_iter (s));
     hb_vector_t<int> V (hb_iter (s));
 
-    assert (v.length == 2);
-    assert (V.length == 2);
-    assert (v[0] == 12);
-    assert (V[0] == 12);
-    assert (v[1] == 18);
-    assert (V[1] == 18);
+    hb_always_assert (v.length == 2);
+    hb_always_assert (V.length == 2);
+    hb_always_assert (v[0] == 12);
+    hb_always_assert (V[0] == 12);
+    hb_always_assert (v[1] == 18);
+    hb_always_assert (V[1] == 18);
   }
 
   /* Test initializing from initializer list and swapping. */
@@ -131,10 +131,10 @@
     hb_vector_t<int> v1 {1, 2, 3};
     hb_vector_t<int> v2 {4, 5};
     hb_swap (v1, v2);
-    assert (v1.length == 2);
-    assert (v1[0] == 4);
-    assert (v2.length == 3);
-    assert (v2[2] == 3);
+    hb_always_assert (v1.length == 2);
+    hb_always_assert (v1[0] == 4);
+    hb_always_assert (v2.length == 3);
+    hb_always_assert (v2[2] == 3);
   }
 
   /* Test initializing sorted-vector from initializer list and swapping. */
@@ -142,10 +142,10 @@
     hb_sorted_vector_t<int> v1 {1, 2, 3};
     hb_sorted_vector_t<int> v2 {4, 5};
     hb_swap (v1, v2);
-    assert (v1.length == 2);
-    assert (v1[0] == 4);
-    assert (v2.length == 3);
-    assert (v2[2] == 3);
+    hb_always_assert (v1.length == 2);
+    hb_always_assert (v1[0] == 4);
+    hb_always_assert (v2.length == 3);
+    hb_always_assert (v2[2] == 3);
   }
 
   {
@@ -171,9 +171,9 @@
     hb_set_t s {1, 5, 7};
     v.push (s);
     v << s;
-    assert (s.get_population () == 3);
+    hb_always_assert (s.get_population () == 3);
     v << std::move (s);
-    assert (s.get_population () == 0);
+    hb_always_assert (s.get_population () == 0);
   }
 
   {
diff --git a/subprojects/.gitignore b/subprojects/.gitignore
index c845751..4b58b0a 100644
--- a/subprojects/.gitignore
+++ b/subprojects/.gitignore
@@ -7,3 +7,4 @@
 /benchmark-*
 /cairo
 /ragel-6.10
+/kbts
diff --git a/subprojects/kbts.wrap b/subprojects/kbts.wrap
new file mode 100644
index 0000000..0730ea6
--- /dev/null
+++ b/subprojects/kbts.wrap
@@ -0,0 +1,5 @@
+[wrap-git]
+url = https://github.com/JimmyLefevre/kb.git
+revision = 880ebea2d4d9ee9b2478eecd1ba060751adc5d45
+depth = 1
+patch_directory = kbts
\ No newline at end of file
diff --git a/subprojects/packagefiles/kbts/meson.build b/subprojects/packagefiles/kbts/meson.build
new file mode 100644
index 0000000..1d78552
--- /dev/null
+++ b/subprojects/packagefiles/kbts/meson.build
@@ -0,0 +1,3 @@
+project('kbts', 'c')
+
+kbts_dep = declare_dependency(include_directories : include_directories('.'))
\ No newline at end of file
diff --git a/test/fuzzing/fonts/clusterfuzz-testcase-minimized-hb-subset-fuzzer-5291661985120256 b/test/fuzzing/fonts/clusterfuzz-testcase-minimized-hb-subset-fuzzer-5291661985120256
new file mode 100644
index 0000000..97b67eb
--- /dev/null
+++ b/test/fuzzing/fonts/clusterfuzz-testcase-minimized-hb-subset-fuzzer-5291661985120256
Binary files differ
diff --git a/test/shape/data/in-house/meson.build b/test/shape/data/in-house/meson.build
index 145dcc5..ef2729b 100644
--- a/test/shape/data/in-house/meson.build
+++ b/test/shape/data/in-house/meson.build
@@ -39,6 +39,7 @@
   'indic-syllable.tests',
   'indic-vowel-letter-spoofing.tests',
   'item-context.tests',
+  'kbts.tests',
   'kern-format2.tests',
   'khmer-mark-order.tests',
   'khmer-misc.tests',
diff --git a/test/shape/data/in-house/tests/kbts.tests b/test/shape/data/in-house/tests/kbts.tests
new file mode 100644
index 0000000..eb2a9cc
--- /dev/null
+++ b/test/shape/data/in-house/tests/kbts.tests
@@ -0,0 +1,8 @@
+@shapers=kbts
+../fonts/872d2955d326bd6676a06f66b8238ebbaabc212f.ttf;;U+0628,U+0628,U+0628;[uni0628.fina=0+883|uni0628.medi_High=0+244|uni0628.init_High=0+233]
+../fonts/872d2955d326bd6676a06f66b8238ebbaabc212f.ttf;--features=-calt[0];U+0628,U+0628,U+0628,U+0628,U+0628;[uni0628.fina=0+883|uni0628.medi=0+244|uni0628.medi_High=0+244|uni0628.medi=0+244|uni0628.init=0+190]
+../fonts/872d2955d326bd6676a06f66b8238ebbaabc212f.ttf;--features=-calt;U+0628,U+0628,U+0628,U+0628,U+0628;[uni0628.fina=0+883|uni0628.medi=0+244|uni0628.medi=0+244|uni0628.medi=0+244|uni0628.init=0+190]
+../fonts/7bbd3175734d5d291e1c15271ec0cbb97b626ebf.ttf;;U+0066,U+0066,U+0069;[f_f_i=0+795]
+../fonts/7bbd3175734d5d291e1c15271ec0cbb97b626ebf.ttf;--features=-liga;U+0066,U+0066,U+0069;[f=0+300|f=0+300|i=0+263]
+../fonts/7bbd3175734d5d291e1c15271ec0cbb97b626ebf.ttf;;U+0648,U+0628,U+0627;[alef-ar.fina=0+229|beh-ar.init=0+190|waw-ar=0@83,0+483]
+#../fonts/7bbd3175734d5d291e1c15271ec0cbb97b626ebf.ttf;--features=ss01;U+0648,U+0628,U+0627;[alef-ar.fina=0+229|beh-ar.init.LD=0+190|waw-ar=0+400]
diff --git a/test/subset/data/expected/layout.empty_ligature_offset/AnekBangla-latin-subset.default.all.ttf b/test/subset/data/expected/layout.empty_ligature_offset/AnekBangla-latin-subset.default.all.ttf
index 9521893..1347ba0 100644
--- a/test/subset/data/expected/layout.empty_ligature_offset/AnekBangla-latin-subset.default.all.ttf
+++ b/test/subset/data/expected/layout.empty_ligature_offset/AnekBangla-latin-subset.default.all.ttf
Binary files differ
diff --git a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.627,644,62D,628.ttf b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.627,644,62D,628.ttf
index 15d86cc..230e1b0 100644
--- a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.627,644,62D,628.ttf
+++ b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.627,644,62D,628.ttf
Binary files differ
diff --git a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.633,6D2.ttf b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.633,6D2.ttf
index 953fa21..372efb6 100644
--- a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.633,6D2.ttf
+++ b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.633,6D2.ttf
Binary files differ
diff --git a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.63A,64A,631.ttf b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.63A,64A,631.ttf
index 412f534..b8edb15 100644
--- a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.63A,64A,631.ttf
+++ b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.default.63A,64A,631.ttf
Binary files differ
diff --git a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.627,644,62D,628.ttf b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.627,644,62D,628.ttf
index f278d34..a94bfca 100644
--- a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.627,644,62D,628.ttf
+++ b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.627,644,62D,628.ttf
Binary files differ
diff --git a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.633,6D2.ttf b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.633,6D2.ttf
index 89a186a..6f65c09 100644
--- a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.633,6D2.ttf
+++ b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.633,6D2.ttf
Binary files differ
diff --git a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.63A,64A,631.ttf b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.63A,64A,631.ttf
index cba2f55..6f6f69b 100644
--- a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.63A,64A,631.ttf
+++ b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Regular.retain-gids.63A,64A,631.ttf
Binary files differ