[HarfBuzz] harfbuzz-ng: Branch 'master' - 20 commits

Behdad Esfahbod behdad at kemper.freedesktop.org
Thu Apr 28 16:45:12 PDT 2011


 TODO                             |    4 
 src/hb-blob.cc                   |   59 ++-
 src/hb-blob.h                    |   12 
 src/hb-buffer-private.hh         |    2 
 src/hb-buffer.cc                 |   82 +++--
 src/hb-buffer.h                  |   10 
 src/hb-common.h                  |   11 
 src/hb-font.cc                   |   84 ++++-
 src/hb-font.h                    |   47 ++-
 src/hb-object-private.hh         |  140 +++++++--
 src/hb-open-type-private.hh      |    2 
 src/hb-ot-layout-gsub-private.hh |    2 
 src/hb-private.hh                |  143 ++++++++-
 src/hb-unicode.cc                |   25 +
 src/hb-unicode.h                 |   12 
 test/Makefile.am                 |    1 
 test/hb-test.h                   |  155 +++++++++-
 test/test-buffer.c               |  593 ++++++++++++++++++++++++++++++++++++---
 test/test-common.c               |   14 
 test/test-unicode.c              |   32 +-
 20 files changed, 1240 insertions(+), 190 deletions(-)

New commits:
commit 13db3d40bfc09c68f9761a71435b1840b9d34099
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 19:44:45 2011 -0400

    [test/buffer] Add UTF-16 tests

diff --git a/test/test-buffer.c b/test/test-buffer.c
index ba7f15e..f9dabe6 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -633,6 +633,46 @@ test_buffer_utf8_validity (gconstpointer user_data)
 }
 
 
+typedef struct {
+  const uint16_t utf16[8];
+  const uint32_t codepoints[8];
+} utf16_test_t;
+
+/* note: we skip the first and last item from utf16 when adding to buffer */
+static const utf16_test_t utf16_tests[] = {
+  {{0x41, 0x004D, 0x0430, 0x4E8C, 0xD800, 0xDF02, 0x61} , {0x004D, 0x0430, 0x4E8C, 0x10302}},
+  {{0x41, 0xD800, 0xDF02, 0x61}, {0x10302}},
+  {{0x41, 0xD800, 0xDF02}, {-1}},
+  {{0x41, 0x61, 0xD800, 0xDF02}, {0x61, -1}},
+  {{0x41, 0xD800, 0x61, 0xDF02}, {-1, 0x61}},
+  {{0x41, 0x61}, {}}
+};
+
+static void
+test_buffer_utf16 (gconstpointer user_data)
+{
+  const utf16_test_t *test = user_data;
+  hb_buffer_t *b;
+  hb_glyph_info_t *glyphs;
+  unsigned int u_len, chars, i, len;
+
+  for (u_len = 0; test->utf16[u_len]; u_len++)
+    ;
+  for (chars = 0; test->codepoints[chars]; chars++)
+    ;
+
+  b = hb_buffer_create (0);
+  hb_buffer_add_utf16 (b, test->utf16, u_len,  1, u_len - 2);
+
+  glyphs = hb_buffer_get_glyph_infos (b, &len);
+  g_assert_cmpint (len, ==, chars);
+  for (i = 0; i < chars; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, test->codepoints[i]);
+
+  hb_buffer_destroy (b);
+}
+
+
 int
 main (int argc, char **argv)
 {
@@ -658,7 +698,6 @@ main (int argc, char **argv)
     hb_test_add_data_flavor (&utf8_tests[i], flavor, test_buffer_utf8);
     g_free (flavor);
   }
-
   for (i = 0; i < G_N_ELEMENTS (utf8_validity_tests); i++)
   {
     char *flavor = g_strdup_printf ("%d", i);
@@ -666,7 +705,12 @@ main (int argc, char **argv)
     g_free (flavor);
   }
 
-  /* XXX test invalid UTF-16 text input */
+  for (i = 0; i < G_N_ELEMENTS (utf16_tests); i++)
+  {
+    char *flavor = g_strdup_printf ("%d", i);
+    hb_test_add_data_flavor (&utf16_tests[i], flavor, test_buffer_utf16);
+    g_free (flavor);
+  }
 
   return hb_test_run();
 }
commit 243673d601588a6f704ceafbff5dd5cdf66c47b7
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 19:37:51 2011 -0400

    [test/buffer] Add more extensive UTF-8 test data from glib

diff --git a/src/hb-buffer.cc b/src/hb-buffer.cc
index c3e9be0..1e49c7a 100644
--- a/src/hb-buffer.cc
+++ b/src/hb-buffer.cc
@@ -624,7 +624,7 @@ hb_utf8_next (const uint8_t *text,
   uint8_t c = *text;
   unsigned int mask, len;
 
-  /* TODO check for overlong sequences?  also: optimize? */
+  /* TODO check for overlong sequences? */
 
   UTF8_COMPUTE (c, mask, len);
   if (unlikely (!len || (unsigned int) (end - text) < len)) {
diff --git a/test/test-buffer.c b/test/test-buffer.c
index e0a582c..ba7f15e 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -33,20 +33,6 @@ static const char utf8[10] = "ab\360\240\200\200defg";
 static const uint16_t utf16[8] = {'a', 'b', 0xD840, 0xDC00, 'd', 'e', 'f', 'g'};
 static const uint32_t utf32[7] = {'a', 'b', 0x20000, 'd', 'e', 'f', 'g'};
 
-typedef struct {
-  const char utf8[8];
-  const uint32_t codepoints[8];
-} utf8_test_t;
-
-
-/* note: we skip the first and last byte when adding to buffer */
-static const utf8_test_t utf8_tests[] = {
-  {"a\303\207", {-1}},
-  {"a\303\207b", {0xC7}},
-  {"ab\303cd", {'b', -1, 'c'}}
-};
-
-
 
 typedef enum {
   BUFFER_EMPTY,
@@ -330,6 +316,20 @@ test_buffer_allocation (fixture_t *fixture, gconstpointer user_data)
   g_assert (hb_buffer_allocation_successful (fixture->b));
 }
 
+
+typedef struct {
+  const char utf8[8];
+  const uint32_t codepoints[8];
+} utf8_test_t;
+
+/* note: we skip the first and last byte when adding to buffer */
+static const utf8_test_t utf8_tests[] = {
+  {"a\303\207", {-1}},
+  {"a\303\207b", {0xC7}},
+  {"ab\303cd", {'b', -1, 'c'}},
+  {"ab\303\302\301cd", {'b', -1, -1, -1, 'c'}}
+};
+
 static void
 test_buffer_utf8 (gconstpointer user_data)
 {
@@ -354,6 +354,285 @@ test_buffer_utf8 (gconstpointer user_data)
 }
 
 
+
+/* Following test table is adapted from glib/glib/tests/utf8-validate.c
+ * with relicensing permission from Matthias Clasen. */
+
+typedef struct {
+  const char *text;
+  int max_len;
+  unsigned int offset;
+  gboolean valid;
+} utf8_validity_test_t;
+
+static const utf8_validity_test_t utf8_validity_tests[] = {
+  /* some tests to check max_len handling */
+  /* length 1 */
+  { "abcde", -1, 5, TRUE },
+  { "abcde", 3, 3, TRUE },
+  { "abcde", 5, 5, TRUE },
+  /* length 2 */
+  { "\xc2\xa9\xc2\xa9\xc2\xa9", -1, 6, TRUE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  1, 0, FALSE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  2, 2, TRUE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  3, 2, FALSE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  4, 4, TRUE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  5, 4, FALSE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  6, 6, TRUE },
+  /* length 3 */
+  { "\xe2\x89\xa0\xe2\x89\xa0", -1, 6, TRUE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  1, 0, FALSE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  2, 0, FALSE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  3, 3, TRUE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  4, 3, FALSE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  5, 3, FALSE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  6, 6, TRUE },
+
+  /* examples from http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt */
+  /* greek 'kosme' */
+  { "\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5", -1, 11, TRUE },
+  /* first sequence of each length */
+  { "\x00", -1, 0, TRUE },
+  { "\xc2\x80", -1, 2, TRUE },
+  { "\xe0\xa0\x80", -1, 3, TRUE },
+  { "\xf0\x90\x80\x80", -1, 4, TRUE },
+  { "\xf8\x88\x80\x80\x80", -1, 0, FALSE },
+  { "\xfc\x84\x80\x80\x80\x80", -1, 0, FALSE },
+  /* last sequence of each length */
+  { "\x7f", -1, 1, TRUE },
+  { "\xdf\xbf", -1, 2, TRUE },
+  { "\xef\xbf\xbf", -1, 0, TRUE },
+  { "\xf7\xbf\xbf\xbf", -1, 0, TRUE },
+  { "\xfb\xbf\xbf\xbf\xbf", -1, 0, FALSE },
+  { "\xfd\xbf\xbf\xbf\xbf\xbf", -1, 0, FALSE },
+  /* other boundary conditions */
+  { "\xed\x9f\xbf", -1, 3, TRUE },
+  { "\xee\x80\x80", -1, 3, TRUE },
+  { "\xef\xbf\xbd", -1, 3, TRUE },
+  { "\xf4\x8f\xbf\xbf", -1, 0, TRUE },
+  /* malformed sequences */
+  /* continuation bytes */
+  { "\x80", -1, 0, FALSE },
+  { "\xbf", -1, 0, FALSE },
+  { "\x80\xbf", -1, 0, FALSE },
+  { "\x80\xbf\x80", -1, 0, FALSE },
+  { "\x80\xbf\x80\xbf", -1, 0, FALSE },
+  { "\x80\xbf\x80\xbf\x80", -1, 0, FALSE },
+  { "\x80\xbf\x80\xbf\x80\xbf", -1, 0, FALSE },
+  { "\x80\xbf\x80\xbf\x80\xbf\x80", -1, 0, FALSE },
+
+  /* all possible continuation byte */
+  { "\x80", -1, 0, FALSE },
+  { "\x81", -1, 0, FALSE },
+  { "\x82", -1, 0, FALSE },
+  { "\x83", -1, 0, FALSE },
+  { "\x84", -1, 0, FALSE },
+  { "\x85", -1, 0, FALSE },
+  { "\x86", -1, 0, FALSE },
+  { "\x87", -1, 0, FALSE },
+  { "\x88", -1, 0, FALSE },
+  { "\x89", -1, 0, FALSE },
+  { "\x8a", -1, 0, FALSE },
+  { "\x8b", -1, 0, FALSE },
+  { "\x8c", -1, 0, FALSE },
+  { "\x8d", -1, 0, FALSE },
+  { "\x8e", -1, 0, FALSE },
+  { "\x8f", -1, 0, FALSE },
+  { "\x90", -1, 0, FALSE },
+  { "\x91", -1, 0, FALSE },
+  { "\x92", -1, 0, FALSE },
+  { "\x93", -1, 0, FALSE },
+  { "\x94", -1, 0, FALSE },
+  { "\x95", -1, 0, FALSE },
+  { "\x96", -1, 0, FALSE },
+  { "\x97", -1, 0, FALSE },
+  { "\x98", -1, 0, FALSE },
+  { "\x99", -1, 0, FALSE },
+  { "\x9a", -1, 0, FALSE },
+  { "\x9b", -1, 0, FALSE },
+  { "\x9c", -1, 0, FALSE },
+  { "\x9d", -1, 0, FALSE },
+  { "\x9e", -1, 0, FALSE },
+  { "\x9f", -1, 0, FALSE },
+  { "\xa0", -1, 0, FALSE },
+  { "\xa1", -1, 0, FALSE },
+  { "\xa2", -1, 0, FALSE },
+  { "\xa3", -1, 0, FALSE },
+  { "\xa4", -1, 0, FALSE },
+  { "\xa5", -1, 0, FALSE },
+  { "\xa6", -1, 0, FALSE },
+  { "\xa7", -1, 0, FALSE },
+  { "\xa8", -1, 0, FALSE },
+  { "\xa9", -1, 0, FALSE },
+  { "\xaa", -1, 0, FALSE },
+  { "\xab", -1, 0, FALSE },
+  { "\xac", -1, 0, FALSE },
+  { "\xad", -1, 0, FALSE },
+  { "\xae", -1, 0, FALSE },
+  { "\xaf", -1, 0, FALSE },
+  { "\xb0", -1, 0, FALSE },
+  { "\xb1", -1, 0, FALSE },
+  { "\xb2", -1, 0, FALSE },
+  { "\xb3", -1, 0, FALSE },
+  { "\xb4", -1, 0, FALSE },
+  { "\xb5", -1, 0, FALSE },
+  { "\xb6", -1, 0, FALSE },
+  { "\xb7", -1, 0, FALSE },
+  { "\xb8", -1, 0, FALSE },
+  { "\xb9", -1, 0, FALSE },
+  { "\xba", -1, 0, FALSE },
+  { "\xbb", -1, 0, FALSE },
+  { "\xbc", -1, 0, FALSE },
+  { "\xbd", -1, 0, FALSE },
+  { "\xbe", -1, 0, FALSE },
+  { "\xbf", -1, 0, FALSE },
+  /* lone start characters */
+  { "\xc0\x20", -1, 0, FALSE },
+  { "\xc1\x20", -1, 0, FALSE },
+  { "\xc2\x20", -1, 0, FALSE },
+  { "\xc3\x20", -1, 0, FALSE },
+  { "\xc4\x20", -1, 0, FALSE },
+  { "\xc5\x20", -1, 0, FALSE },
+  { "\xc6\x20", -1, 0, FALSE },
+  { "\xc7\x20", -1, 0, FALSE },
+  { "\xc8\x20", -1, 0, FALSE },
+  { "\xc9\x20", -1, 0, FALSE },
+  { "\xca\x20", -1, 0, FALSE },
+  { "\xcb\x20", -1, 0, FALSE },
+  { "\xcc\x20", -1, 0, FALSE },
+  { "\xcd\x20", -1, 0, FALSE },
+  { "\xce\x20", -1, 0, FALSE },
+  { "\xcf\x20", -1, 0, FALSE },
+  { "\xd0\x20", -1, 0, FALSE },
+  { "\xd1\x20", -1, 0, FALSE },
+  { "\xd2\x20", -1, 0, FALSE },
+  { "\xd3\x20", -1, 0, FALSE },
+  { "\xd4\x20", -1, 0, FALSE },
+  { "\xd5\x20", -1, 0, FALSE },
+  { "\xd6\x20", -1, 0, FALSE },
+  { "\xd7\x20", -1, 0, FALSE },
+  { "\xd8\x20", -1, 0, FALSE },
+  { "\xd9\x20", -1, 0, FALSE },
+  { "\xda\x20", -1, 0, FALSE },
+  { "\xdb\x20", -1, 0, FALSE },
+  { "\xdc\x20", -1, 0, FALSE },
+  { "\xdd\x20", -1, 0, FALSE },
+  { "\xde\x20", -1, 0, FALSE },
+  { "\xdf\x20", -1, 0, FALSE },
+  { "\xe0\x20", -1, 0, FALSE },
+  { "\xe1\x20", -1, 0, FALSE },
+  { "\xe2\x20", -1, 0, FALSE },
+  { "\xe3\x20", -1, 0, FALSE },
+  { "\xe4\x20", -1, 0, FALSE },
+  { "\xe5\x20", -1, 0, FALSE },
+  { "\xe6\x20", -1, 0, FALSE },
+  { "\xe7\x20", -1, 0, FALSE },
+  { "\xe8\x20", -1, 0, FALSE },
+  { "\xe9\x20", -1, 0, FALSE },
+  { "\xea\x20", -1, 0, FALSE },
+  { "\xeb\x20", -1, 0, FALSE },
+  { "\xec\x20", -1, 0, FALSE },
+  { "\xed\x20", -1, 0, FALSE },
+  { "\xee\x20", -1, 0, FALSE },
+  { "\xef\x20", -1, 0, FALSE },
+  { "\xf0\x20", -1, 0, FALSE },
+  { "\xf1\x20", -1, 0, FALSE },
+  { "\xf2\x20", -1, 0, FALSE },
+  { "\xf3\x20", -1, 0, FALSE },
+  { "\xf4\x20", -1, 0, FALSE },
+  { "\xf5\x20", -1, 0, FALSE },
+  { "\xf6\x20", -1, 0, FALSE },
+  { "\xf7\x20", -1, 0, FALSE },
+  { "\xf8\x20", -1, 0, FALSE },
+  { "\xf9\x20", -1, 0, FALSE },
+  { "\xfa\x20", -1, 0, FALSE },
+  { "\xfb\x20", -1, 0, FALSE },
+  { "\xfc\x20", -1, 0, FALSE },
+  { "\xfd\x20", -1, 0, FALSE },
+  /* missing continuation bytes */
+  { "\x20\xc0", -1, 1, FALSE },
+  { "\x20\xe0\x80", -1, 1, FALSE },
+  { "\x20\xf0\x80\x80", -1, 1, FALSE },
+  { "\x20\xf8\x80\x80\x80", -1, 1, FALSE },
+  { "\x20\xfc\x80\x80\x80\x80", -1, 1, FALSE },
+  { "\x20\xdf", -1, 1, FALSE },
+  { "\x20\xef\xbf", -1, 1, FALSE },
+  { "\x20\xf7\xbf\xbf", -1, 1, FALSE },
+  { "\x20\xfb\xbf\xbf\xbf", -1, 1, FALSE },
+  { "\x20\xfd\xbf\xbf\xbf\xbf", -1, 1, FALSE },
+  /* impossible bytes */
+  { "\x20\xfe\x20", -1, 1, FALSE },
+  { "\x20\xff\x20", -1, 1, FALSE },
+#if 0
+  /* XXX fix these, or document that we don't detect them? */
+  /* overlong sequences */
+  { "\x20\xc0\xaf\x20", -1, 1, FALSE },
+  { "\x20\xe0\x80\xaf\x20", -1, 1, FALSE },
+  { "\x20\xf0\x80\x80\xaf\x20", -1, 1, FALSE },
+  { "\x20\xf8\x80\x80\x80\xaf\x20", -1, 1, FALSE },
+  { "\x20\xfc\x80\x80\x80\x80\xaf\x20", -1, 1, FALSE },
+  { "\x20\xc1\xbf\x20", -1, 1, FALSE },
+  { "\x20\xe0\x9f\xbf\x20", -1, 1, FALSE },
+  { "\x20\xf0\x8f\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xf8\x87\xbf\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xfc\x83\xbf\xbf\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xc0\x80\x20", -1, 1, FALSE },
+  { "\x20\xe0\x80\x80\x20", -1, 1, FALSE },
+  { "\x20\xf0\x80\x80\x80\x20", -1, 1, FALSE },
+  { "\x20\xf8\x80\x80\x80\x80\x20", -1, 1, FALSE },
+  { "\x20\xfc\x80\x80\x80\x80\x80\x20", -1, 1, FALSE },
+  /* illegal code positions */
+  { "\x20\xed\xa0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xad\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xae\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xaf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xbe\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xa0\x80\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xa0\x80\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xad\xbf\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xad\xbf\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xae\x80\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xae\x80\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xaf\xbf\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xaf\xbf\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xef\xbf\xbe\x20", -1, 1, FALSE },
+  { "\x20\xef\xbf\xbf\x20", -1, 1, FALSE },
+#endif
+  { "", -1, 0, TRUE }
+};
+
+static void
+test_buffer_utf8_validity (gconstpointer user_data)
+{
+  const utf8_validity_test_t *test = user_data;
+  hb_buffer_t *b;
+  hb_glyph_info_t *glyphs;
+  unsigned int text_bytes, segment_bytes, i, len;
+
+  text_bytes = strlen (test->text);
+  if (test->max_len == -1)
+    segment_bytes = text_bytes;
+  else
+    segment_bytes = test->max_len;
+
+  b = hb_buffer_create (0);
+  hb_buffer_add_utf8 (b, test->text, text_bytes,  0, segment_bytes);
+
+  glyphs = hb_buffer_get_glyph_infos (b, &len);
+  for (i = 0; i < len; i++)
+    if (glyphs[i].codepoint == (hb_codepoint_t) -1)
+      break;
+
+  g_assert (test->valid ? i == len : i < len);
+  if (!test->valid)
+    g_assert (glyphs[i].cluster == test->offset);
+
+  hb_buffer_destroy (b);
+}
+
+
 int
 main (int argc, char **argv)
 {
@@ -379,7 +658,15 @@ main (int argc, char **argv)
     hb_test_add_data_flavor (&utf8_tests[i], flavor, test_buffer_utf8);
     g_free (flavor);
   }
-  /* XXX test invalid UTF-8 / UTF-16 text input (also overlong sequences) */
+
+  for (i = 0; i < G_N_ELEMENTS (utf8_validity_tests); i++)
+  {
+    char *flavor = g_strdup_printf ("%d", i);
+    hb_test_add_data_flavor (&utf8_validity_tests[i], flavor, test_buffer_utf8_validity);
+    g_free (flavor);
+  }
+
+  /* XXX test invalid UTF-16 text input */
 
   return hb_test_run();
 }
commit dfec67f958482e5c3bb01e06b08694cd4ded6f66
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 18:34:42 2011 -0400

    [test/buffer] Add initial utf-8 tests

diff --git a/test/hb-test.h b/test/hb-test.h
index 44c9405..81aba66 100644
--- a/test/hb-test.h
+++ b/test/hb-test.h
@@ -140,7 +140,30 @@ hb_test_add_func_flavor (const char *test_path,
   hb_test_add_func (path, test_func);
   g_free (path);
 }
-#define hb_test_add_flavor(Func, Flavor) hb_test_add_func (#Func, Flavor, Func)
+#define hb_test_add_flavor(Flavor, Func) hb_test_add_func (#Func, Flavor, Func)
+
+static inline void
+hb_test_add_data_func (const char    *test_path,
+		       gconstpointer  test_data,
+		       GTestDataFunc  test_func)
+{
+  char *normal_path = hb_test_normalize_path (test_path);
+  g_test_add_data_func (normal_path, test_data, test_func);
+  g_free (normal_path);
+}
+#define hb_test_add_data(Func, UserData) hb_test_add_data_func (#Func, UserData, Func)
+
+static inline void
+hb_test_add_data_func_flavor (const char    *test_path,
+			      const char    *flavor,
+			      gconstpointer  test_data,
+			      GTestDataFunc  test_func)
+{
+  char *path = g_strdup_printf ("%s/%s", test_path, flavor);
+  hb_test_add_data_func (path, test_data, test_func);
+  g_free (path);
+}
+#define hb_test_add_data_flavor(UserData, Flavor, Func) hb_test_add_data_func_flavor (#Func, Flavor, UserData, Func)
 
 
 static inline void
diff --git a/test/test-buffer.c b/test/test-buffer.c
index 1499550..e0a582c 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -33,6 +33,21 @@ static const char utf8[10] = "ab\360\240\200\200defg";
 static const uint16_t utf16[8] = {'a', 'b', 0xD840, 0xDC00, 'd', 'e', 'f', 'g'};
 static const uint32_t utf32[7] = {'a', 'b', 0x20000, 'd', 'e', 'f', 'g'};
 
+typedef struct {
+  const char utf8[8];
+  const uint32_t codepoints[8];
+} utf8_test_t;
+
+
+/* note: we skip the first and last byte when adding to buffer */
+static const utf8_test_t utf8_tests[] = {
+  {"a\303\207", {-1}},
+  {"a\303\207b", {0xC7}},
+  {"ab\303cd", {'b', -1, 'c'}}
+};
+
+
+
 typedef enum {
   BUFFER_EMPTY,
   BUFFER_ONE_BY_ONE,
@@ -315,11 +330,34 @@ test_buffer_allocation (fixture_t *fixture, gconstpointer user_data)
   g_assert (hb_buffer_allocation_successful (fixture->b));
 }
 
+static void
+test_buffer_utf8 (gconstpointer user_data)
+{
+  const utf8_test_t *test = user_data;
+  hb_buffer_t *b;
+  hb_glyph_info_t *glyphs;
+  unsigned int bytes, chars, i, len;
+
+  bytes = strlen (test->utf8);
+  for (chars = 0; test->codepoints[chars]; chars++)
+    ;
+
+  b = hb_buffer_create (0);
+  hb_buffer_add_utf8 (b, test->utf8, bytes,  1, bytes - 2);
+
+  glyphs = hb_buffer_get_glyph_infos (b, &len);
+  g_assert_cmpint (len, ==, chars);
+  for (i = 0; i < chars; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, test->codepoints[i]);
+
+  hb_buffer_destroy (b);
+}
+
 
 int
 main (int argc, char **argv)
 {
-  int i;
+  unsigned int i;
 
   hb_test_init (&argc, &argv);
 
@@ -335,6 +373,12 @@ main (int argc, char **argv)
 
   hb_test_add_fixture (fixture, GINT_TO_POINTER (BUFFER_EMPTY), test_buffer_allocation);
 
+  for (i = 0; i < G_N_ELEMENTS (utf8_tests); i++)
+  {
+    char *flavor = g_strdup_printf ("%d", i);
+    hb_test_add_data_flavor (&utf8_tests[i], flavor, test_buffer_utf8);
+    g_free (flavor);
+  }
   /* XXX test invalid UTF-8 / UTF-16 text input (also overlong sequences) */
 
   return hb_test_run();
commit aafe395ab550d3ba2fabc69155662e87d45e74a8
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 17:10:44 2011 -0400

    Add test suite infrastructure
    
    Wraps around glib for convenience and ease of use.

diff --git a/test/Makefile.am b/test/Makefile.am
index e4fdb20..e48708b 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -7,6 +7,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/ $(GLIB_CFLAGS)
 LDADD = $(top_builddir)/src/libharfbuzz.la $(GLIB_LIBS)
 
 check_PROGRAMS = $(TEST_PROGS)
+noinst_PROGRAMS = $(TEST_PROGS)
 
 TEST_PROGS += \
 	test-buffer \
diff --git a/test/hb-test.h b/test/hb-test.h
index 2c04642..44c9405 100644
--- a/test/hb-test.h
+++ b/test/hb-test.h
@@ -27,12 +27,10 @@
 #ifndef HB_TEST_H
 #define HB_TEST_H
 
-#include <hb.h>
 #include <hb-glib.h>
 
-#include <glib.h>
-
 #include <stdlib.h>
+#include <string.h>
 
 HB_BEGIN_DECLS
 
@@ -40,6 +38,30 @@ HB_BEGIN_DECLS
 #undef G_DISABLE_ASSERT
 
 
+/* Misc */
+
+/* This is too ugly to be public API, but quite handy. */
+#define HB_TAG_CHAR4(s)   (HB_TAG(((const char *) s)[0], \
+				  ((const char *) s)[1], \
+				  ((const char *) s)[2], \
+				  ((const char *) s)[3]))
+
+
+/* Helpers */
+
+static inline void
+hb_test_init (int *argc, char ***argv)
+{
+  g_test_init (argc, argv, NULL);
+}
+
+static inline int
+hb_test_run (void)
+{
+  return g_test_run ();
+}
+
+
 /* Bugzilla helpers */
 
 static inline void
@@ -78,13 +100,105 @@ hb_test_bug_redhat (unsigned int number)
 }
 
 
-/* Misc */
+/* Wrap glib test functions to simplify.  Should have been in glib already. */
+
+/* Drops the "test_" prefix and converts '_' to '/'.
+ * Essentially builds test path from function name. */
+static inline char *
+hb_test_normalize_path (const char *path)
+{
+  char *s, *p;
+
+  g_assert (0 == strncmp (path, "test_", 5));
+  path += 4;
+
+  s = g_strdup (path);
+  for (p = s; *p; p++)
+    if (*p == '_')
+      *p = '/';
+
+  return s;
+}
+
+
+static inline void
+hb_test_add_func (const char *test_path,
+		  GTestFunc   test_func)
+{
+  char *normal_path = hb_test_normalize_path (test_path);
+  g_test_add_func (normal_path, test_func);
+  g_free (normal_path);
+}
+#define hb_test_add(Func) hb_test_add_func (#Func, Func)
+
+static inline void
+hb_test_add_func_flavor (const char *test_path,
+			 const char *flavor,
+			 GTestFunc   test_func)
+{
+  char *path = g_strdup_printf ("%s/%s", test_path, flavor);
+  hb_test_add_func (path, test_func);
+  g_free (path);
+}
+#define hb_test_add_flavor(Func, Flavor) hb_test_add_func (#Func, Flavor, Func)
+
+
+static inline void
+hb_test_add_vtable (const char       *test_path,
+		    gsize             data_size,
+		    gconstpointer     test_data,
+		    GTestFixtureFunc  data_setup,
+		    GTestFixtureFunc  data_test,
+		    GTestFixtureFunc  data_teardown)
+{
+  char *normal_path = hb_test_normalize_path (test_path);
+  g_test_add_vtable (normal_path, data_size, test_data, data_setup, data_test, data_teardown);
+  g_free (normal_path);
+}
+#define hb_test_add_fixture(FixturePrefix, UserData, Func) \
+G_STMT_START { \
+  typedef G_PASTE (FixturePrefix, _t) Fixture; \
+  void (*add_vtable) (const char*, gsize, gconstpointer, \
+		      void (*) (Fixture*, gconstpointer), \
+		      void (*) (Fixture*, gconstpointer), \
+		      void (*) (Fixture*, gconstpointer)) \
+	= (void (*) (const gchar *, gsize, gconstpointer, \
+		     void (*) (Fixture*, gconstpointer), \
+		     void (*) (Fixture*, gconstpointer), \
+		     void (*) (Fixture*, gconstpointer))) hb_test_add_vtable; \
+  add_vtable (#Func, sizeof (G_PASTE (FixturePrefix, _t)), UserData, \
+	      G_PASTE (FixturePrefix, _init), Func, G_PASTE (FixturePrefix, _finish)); \
+} G_STMT_END
+
+static inline void
+hb_test_add_vtable_flavor (const char       *test_path,
+			   const char       *flavor,
+			   gsize             data_size,
+			   gconstpointer     test_data,
+			   GTestFixtureFunc  data_setup,
+			   GTestFixtureFunc  data_test,
+			   GTestFixtureFunc  data_teardown)
+{
+  char *path = g_strdup_printf ("%s/%s", test_path, flavor);
+  hb_test_add_vtable (path, data_size, test_data, data_setup, data_test, data_teardown);
+  g_free (path);
+}
+#define hb_test_add_fixture_flavor(FixturePrefix, UserData, Flavor, Func) \
+G_STMT_START { \
+  typedef G_PASTE (FixturePrefix, _t) Fixture; \
+  void (*add_vtable) (const char*, const char *, gsize, gconstpointer, \
+		      void (*) (Fixture*, gconstpointer), \
+		      void (*) (Fixture*, gconstpointer), \
+		      void (*) (Fixture*, gconstpointer)) \
+	= (void (*) (const gchar *, const char *, gsize, gconstpointer, \
+		     void (*) (Fixture*, gconstpointer), \
+		     void (*) (Fixture*, gconstpointer), \
+		     void (*) (Fixture*, gconstpointer))) hb_test_add_vtable_flavor; \
+  add_vtable (#Func, Flavor, sizeof (G_PASTE (FixturePrefix, _t)), UserData, \
+	      G_PASTE (FixturePrefix, _init), Func, G_PASTE (FixturePrefix, _finish)); \
+} G_STMT_END
+
 
-/* This is too ugly to be public API, but quite handy. */
-#define HB_TAG_CHAR4(s)   (HB_TAG(((const char *) s)[0], \
-				  ((const char *) s)[1], \
-				  ((const char *) s)[2], \
-				  ((const char *) s)[3]))
 HB_END_DECLS
 
 #endif /* HB_TEST_H */
diff --git a/test/test-buffer.c b/test/test-buffer.c
index 7da4c92..1499550 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -89,7 +89,7 @@ fixture_init (fixture_t *fixture, gconstpointer user_data)
 }
 
 static void
-fixture_fini (fixture_t *fixture, gconstpointer user_data)
+fixture_finish (fixture_t *fixture, gconstpointer user_data)
 {
   hb_buffer_destroy (fixture->b);
 }
@@ -321,24 +321,21 @@ main (int argc, char **argv)
 {
   int i;
 
-  g_test_init (&argc, &argv, NULL);
-
-  for (i = 0; i < BUFFER_NUM_TYPES; i++) {
-#define TEST_ADD(path, func) \
-    G_STMT_START { \
-      char *s = g_strdup_printf ("%s/%s", path, buffer_names[i]); \
-      g_test_add (s, fixture_t, GINT_TO_POINTER (i), fixture_init, func, fixture_fini); \
-      g_free (s); \
-    } G_STMT_END
-    TEST_ADD ("/buffer/properties", test_buffer_properties);
-    TEST_ADD ("/buffer/contents", test_buffer_contents);
-    TEST_ADD ("/buffer/positions", test_buffer_positions);
-#undef TEST_ADD
+  hb_test_init (&argc, &argv);
+
+  for (i = 0; i < BUFFER_NUM_TYPES; i++)
+  {
+    const void *buffer_type = GINT_TO_POINTER (i);
+    const char *buffer_name = buffer_names[i];
+
+    hb_test_add_fixture_flavor (fixture, buffer_type, buffer_name, test_buffer_properties);
+    hb_test_add_fixture_flavor (fixture, buffer_type, buffer_name, test_buffer_contents);
+    hb_test_add_fixture_flavor (fixture, buffer_type, buffer_name, test_buffer_positions);
   }
 
-  g_test_add ("/buffer/allocation", fixture_t, GINT_TO_POINTER (BUFFER_EMPTY), fixture_init, test_buffer_allocation, fixture_fini);
+  hb_test_add_fixture (fixture, GINT_TO_POINTER (BUFFER_EMPTY), test_buffer_allocation);
 
   /* XXX test invalid UTF-8 / UTF-16 text input (also overlong sequences) */
 
-  return g_test_run();
+  return hb_test_run();
 }
diff --git a/test/test-common.c b/test/test-common.c
index d232740..31661f0 100644
--- a/test/test-common.c
+++ b/test/test-common.c
@@ -173,13 +173,13 @@ test_types_language (void)
 int
 main (int argc, char **argv)
 {
-  g_test_init (&argc, &argv, NULL);
+  hb_test_init (&argc, &argv);
 
-  g_test_add_func ("/types/int", test_types_int);
-  g_test_add_func ("/types/direction", test_types_direction);
-  g_test_add_func ("/types/tag", test_types_tag);
-  g_test_add_func ("/types/script", test_types_script);
-  g_test_add_func ("/types/language", test_types_language);
+  hb_test_add (test_types_int);
+  hb_test_add (test_types_direction);
+  hb_test_add (test_types_tag);
+  hb_test_add (test_types_script);
+  hb_test_add (test_types_language);
 
-  return g_test_run();
+  return hb_test_run();
 }
diff --git a/test/test-unicode.c b/test/test-unicode.c
index c696341..30a89be 100644
--- a/test/test-unicode.c
+++ b/test/test-unicode.c
@@ -29,7 +29,7 @@
 /* This file tests the unicode virtual functions interface */
 
 static void
-test_nil (void)
+test_unicode_nil (void)
 {
   hb_unicode_funcs_t *uf = hb_unicode_funcs_create (NULL);
 
@@ -39,7 +39,7 @@ test_nil (void)
 }
 
 static void
-test_glib (void)
+test_unicode_glib (void)
 {
   hb_unicode_funcs_t *uf = hb_glib_get_unicode_funcs ();
 
@@ -47,7 +47,7 @@ test_glib (void)
 }
 
 static void
-test_default (void)
+test_unicode_default (void)
 {
   hb_unicode_funcs_t *uf = hb_unicode_funcs_get_default ();
 
@@ -85,7 +85,7 @@ simple_get_script (hb_unicode_funcs_t *ufuncs,
 }
 
 static void
-test_custom (void)
+test_unicode_custom (void)
 {
   hb_unicode_funcs_t *uf = hb_unicode_funcs_create (NULL);
 
@@ -120,7 +120,7 @@ a_is_for_arabic_get_script (hb_unicode_funcs_t *ufuncs,
 }
 
 static void
-test_subclassing_nil (void)
+test_unicode_subclassing_nil (void)
 {
   hb_unicode_funcs_t *uf, *aa;
 
@@ -143,7 +143,7 @@ test_subclassing_nil (void)
 }
 
 static void
-test_subclassing_glib (void)
+test_unicode_subclassing_glib (void)
 {
   hb_unicode_funcs_t *uf, *aa;
 
@@ -163,7 +163,7 @@ test_subclassing_glib (void)
 }
 
 static void
-test_subclassing_deep (void)
+test_unicode_subclassing_deep (void)
 {
   hb_unicode_funcs_t *uf, *aa;
 
@@ -195,18 +195,18 @@ test_subclassing_deep (void)
 int
 main (int argc, char **argv)
 {
-  g_test_init (&argc, &argv, NULL);
+  hb_test_init (&argc, &argv);
 
-  g_test_add_func ("/unicode/nil", test_nil);
-  g_test_add_func ("/unicode/glib", test_glib);
-  g_test_add_func ("/unicode/default", test_default);
-  g_test_add_func ("/unicode/custom", test_custom);
-  g_test_add_func ("/unicode/subclassing/nil", test_subclassing_nil);
-  g_test_add_func ("/unicode/subclassing/glib", test_subclassing_glib);
-  g_test_add_func ("/unicode/subclassing/deep", test_subclassing_deep);
+  hb_test_add (test_unicode_nil);
+  hb_test_add (test_unicode_glib);
+  hb_test_add (test_unicode_default);
+  hb_test_add (test_unicode_custom);
+  hb_test_add (test_unicode_subclassing_nil);
+  hb_test_add (test_unicode_subclassing_glib);
+  hb_test_add (test_unicode_subclassing_deep);
 
   /* XXX test all methods for their defaults and various (glib, icu, default) implementations. */
   /* XXX test glib & icu two-way script conversion */
 
-  return g_test_run ();
+  return hb_test_run ();
 }
commit c7ffe2ad5f6e97e26d14e2cc0d4098af8f5f36d0
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 16:03:29 2011 -0400

    [API Remove hb_font_funcs_copy()
    
    Will be adding font_funcs subclassing instead.

diff --git a/src/hb-font.cc b/src/hb-font.cc
index b067901..5e4d379 100644
--- a/src/hb-font.cc
+++ b/src/hb-font.cc
@@ -141,19 +141,6 @@ hb_font_funcs_get_user_data (hb_font_funcs_t    *ffuncs,
 }
 
 
-hb_font_funcs_t *
-hb_font_funcs_copy (hb_font_funcs_t *other_ffuncs)
-{
-  hb_font_funcs_t *ffuncs;
-
-  if (!(ffuncs = hb_object_create<hb_font_funcs_t> ()))
-    return &_hb_font_funcs_nil;
-
-  ffuncs->v = other_ffuncs->v;
-
-  return ffuncs;
-}
-
 void
 hb_font_funcs_make_immutable (hb_font_funcs_t *ffuncs)
 {
diff --git a/src/hb-font.h b/src/hb-font.h
index 9c69470..c26ab01 100644
--- a/src/hb-font.h
+++ b/src/hb-font.h
@@ -105,9 +105,6 @@ hb_font_funcs_get_user_data (hb_font_funcs_t    *ffuncs,
 			     hb_user_data_key_t *key);
 
 
-hb_font_funcs_t *
-hb_font_funcs_copy (hb_font_funcs_t *ffuncs);
-
 void
 hb_font_funcs_make_immutable (hb_font_funcs_t *ffuncs);
 
commit 30f34d08d445722320db711c3ddf41e66225752c
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 16:02:40 2011 -0400

    [TODO] Remove finished items

diff --git a/TODO b/TODO
index d8d4d56..e95dd4f 100644
--- a/TODO
+++ b/TODO
@@ -14,8 +14,6 @@ API issues to fix before 1.0:
 
 - Figure out how many .so objects, how to link, etc
 
-- User-data support ala cairo
-
 - Real subclassing support for vfunc vectors
 
 - Add hb-cairo glue
diff --git a/src/hb-font.h b/src/hb-font.h
index f018a8b..9c69470 100644
--- a/src/hb-font.h
+++ b/src/hb-font.h
@@ -70,16 +70,6 @@ hb_face_get_user_data (hb_face_t          *face,
 		       hb_user_data_key_t *key);
 
 
-/* XXX
- *
- * I have two major concerns about this API as it is right now:
- *
- *   - Jonathan Kew convinced me to make it return NULL if table not found (280af1bd),
- *     however, that is WRONG IMO.  The API should not differentiate between a non-existing
- *     table vs a zero-length table vs a very short table.  It only leads to implementations
- *     that check for non-NULL and assume that they've got a usable table going on...  This
- *     actually happened with Firefox.
- */
 hb_blob_t *
 hb_face_reference_table (hb_face_t *face,
 			 hb_tag_t   tag);
commit 080a0eb7d82d7195be72c16ece6e0a3ffed636b6
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 16:01:01 2011 -0400

    Add _hb_unsigned_int_mul_overflows

diff --git a/src/hb-buffer.cc b/src/hb-buffer.cc
index 13d0040..c3e9be0 100644
--- a/src/hb-buffer.cc
+++ b/src/hb-buffer.cc
@@ -77,19 +77,16 @@ _hb_buffer_enlarge (hb_buffer_t *buffer, unsigned int size)
   unsigned int new_allocated = buffer->allocated;
   hb_glyph_position_t *new_pos = NULL;
   hb_glyph_info_t *new_info = NULL;
-  bool overflows = FALSE;
   bool separate_out = buffer->out_info != buffer->info;
 
-  overflows = size >= ((unsigned int) -1) / sizeof (buffer->info[0]);
-  if (unlikely (overflows))
+  if (unlikely (_hb_unsigned_int_mul_overflows (size, sizeof (buffer->info[0]))))
     goto done;
 
   while (size > new_allocated)
     new_allocated += (new_allocated >> 1) + 32;
 
   ASSERT_STATIC (sizeof (buffer->info[0]) == sizeof (buffer->pos[0]));
-  overflows = new_allocated >= ((unsigned int) -1) / sizeof (buffer->info[0]);
-  if (unlikely (overflows))
+  if (unlikely (_hb_unsigned_int_mul_overflows (new_allocated, sizeof (buffer->info[0]))))
     goto done;
 
   new_pos = (hb_glyph_position_t *) realloc (buffer->pos, new_allocated * sizeof (buffer->pos[0]));
diff --git a/src/hb-open-type-private.hh b/src/hb-open-type-private.hh
index 5810cc3..af8274d 100644
--- a/src/hb-open-type-private.hh
+++ b/src/hb-open-type-private.hh
@@ -231,7 +231,7 @@ struct hb_sanitize_context_t
   inline bool check_array (const void *base, unsigned int record_size, unsigned int len) const
   {
     const char *p = (const char *) base;
-    bool overflows = record_size > 0 && len >= ((unsigned int) -1) / record_size;
+    bool overflows = _hb_unsigned_int_mul_overflows (len, record_size);
 
     (void) (HB_DEBUG_SANITIZE && (int) this->debug_depth < (int) HB_DEBUG_SANITIZE &&
       fprintf (stderr, "SANITIZE(%p) %-*d-> array [%p..%p] (%d*%d=%ld bytes) in [%p..%p] -> %s\n",
diff --git a/src/hb-ot-layout-gsub-private.hh b/src/hb-ot-layout-gsub-private.hh
index 4bf4441..1bd5984 100644
--- a/src/hb-ot-layout-gsub-private.hh
+++ b/src/hb-ot-layout-gsub-private.hh
@@ -409,7 +409,7 @@ struct Ligature
 
   inline uint16_t allocate_lig_id (hb_buffer_t *buffer) const {
     uint16_t lig_id = buffer->next_serial ();
-    if (unlikely (!lig_id)) lig_id = buffer->next_serial (); /* in case of overflows */
+    if (unlikely (!lig_id)) lig_id = buffer->next_serial (); /* in case of overflow */
     return lig_id;
   }
 
diff --git a/src/hb-private.hh b/src/hb-private.hh
index ca37084..b91595d 100644
--- a/src/hb-private.hh
+++ b/src/hb-private.hh
@@ -210,6 +210,13 @@ _hb_ctz (unsigned int number)
 #endif
 }
 
+static inline bool
+_hb_unsigned_int_mul_overflows (unsigned int count, unsigned int size)
+{
+  return (size > 0) && (count >= ((unsigned int) -1) / size);
+}
+
+
 /* Type of bsearch() / qsort() compare function */
 typedef int (*hb_compare_func_t) (const void *, const void *);
 
@@ -297,7 +304,7 @@ struct hb_static_array_t {
 	array = new_array;
       }
     } else {
-      bool overflows = (new_allocated < allocated) || (new_allocated >= ((unsigned int) -1) / sizeof (Type));
+      bool overflows = (new_allocated < allocated) || _hb_unsigned_int_mul_overflows (new_allocated, sizeof (Type));
       if (unlikely (overflows))
         new_array = NULL;
       else
commit 1d39d6e42b3d7628512d675a84a831a0f58624eb
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 15:54:16 2011 -0400

    Desable possibly lethal test on 64-bit machines

diff --git a/test/test-buffer.c b/test/test-buffer.c
index 61a1b48..7da4c92 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -304,9 +304,12 @@ test_buffer_allocation (fixture_t *fixture, gconstpointer user_data)
   g_assert (hb_buffer_allocation_successful (fixture->b));
 
   /* technically, this one can actually pass on 64bit machines, but
-   * I'm doubtful that any malloc allows 4GB allocations at a time. */
-  g_assert (!hb_buffer_pre_allocate (fixture->b, ((unsigned int) -1) / 20 - 1));
-  g_assert (!hb_buffer_allocation_successful (fixture->b));
+   * I'm doubtful that any malloc allows 4GB allocations at a time.
+   * But let's only enable it on a 32-bit machine. */
+  if (sizeof (long) == 4) {
+    g_assert (!hb_buffer_pre_allocate (fixture->b, ((unsigned int) -1) / 20 - 1));
+    g_assert (!hb_buffer_allocation_successful (fixture->b));
+  }
 
   hb_buffer_reset (fixture->b);
   g_assert (hb_buffer_allocation_successful (fixture->b));
commit 3264042873fd639f3ef8ff0acfad777a0a9f3355
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 14:24:16 2011 -0400

    [test/buffer] Test pre_allocate() and allocation_successful()

diff --git a/src/hb-buffer.cc b/src/hb-buffer.cc
index 7dfe83a..13d0040 100644
--- a/src/hb-buffer.cc
+++ b/src/hb-buffer.cc
@@ -80,7 +80,8 @@ _hb_buffer_enlarge (hb_buffer_t *buffer, unsigned int size)
   bool overflows = FALSE;
   bool separate_out = buffer->out_info != buffer->info;
 
-  if (unlikely (size > ((unsigned int)-1) / 2))
+  overflows = size >= ((unsigned int) -1) / sizeof (buffer->info[0]);
+  if (unlikely (overflows))
     goto done;
 
   while (size > new_allocated)
diff --git a/test/test-buffer.c b/test/test-buffer.c
index b79f955..61a1b48 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -281,10 +281,35 @@ test_buffer_allocation (fixture_t *fixture, gconstpointer user_data)
 
   g_assert (hb_buffer_pre_allocate (fixture->b, 100));
   g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
+  g_assert (hb_buffer_allocation_successful (fixture->b));
 
   /* lets try a huge allocation, make sure it fails */
   g_assert (!hb_buffer_pre_allocate (fixture->b, (unsigned int) -1));
   g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
+  g_assert (!hb_buffer_allocation_successful (fixture->b));
+
+  /* small one again */
+  g_assert (hb_buffer_pre_allocate (fixture->b, 50));
+  g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
+  g_assert (!hb_buffer_allocation_successful (fixture->b));
+
+  hb_buffer_reset (fixture->b);
+  g_assert (hb_buffer_allocation_successful (fixture->b));
+
+  /* all allocation and size  */
+  g_assert (!hb_buffer_pre_allocate (fixture->b, ((unsigned int) -1) / 20 + 1));
+  g_assert (!hb_buffer_allocation_successful (fixture->b));
+
+  hb_buffer_reset (fixture->b);
+  g_assert (hb_buffer_allocation_successful (fixture->b));
+
+  /* technically, this one can actually pass on 64bit machines, but
+   * I'm doubtful that any malloc allows 4GB allocations at a time. */
+  g_assert (!hb_buffer_pre_allocate (fixture->b, ((unsigned int) -1) / 20 - 1));
+  g_assert (!hb_buffer_allocation_successful (fixture->b));
+
+  hb_buffer_reset (fixture->b);
+  g_assert (hb_buffer_allocation_successful (fixture->b));
 }
 
 
@@ -311,7 +336,6 @@ main (int argc, char **argv)
   g_test_add ("/buffer/allocation", fixture_t, GINT_TO_POINTER (BUFFER_EMPTY), fixture_init, test_buffer_allocation, fixture_fini);
 
   /* XXX test invalid UTF-8 / UTF-16 text input (also overlong sequences) */
-  /* XXX test pre_allocate(), allocation_successful(), and memory management */
 
   return g_test_run();
 }
commit 123aa04f7b3241d6e43de2d472c4a1cbdb250ac7
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 12:58:28 2011 -0400

    Fix possible but improbable overflow in hb_array_t

diff --git a/src/hb-private.hh b/src/hb-private.hh
index ce09d18..ca37084 100644
--- a/src/hb-private.hh
+++ b/src/hb-private.hh
@@ -297,7 +297,7 @@ struct hb_static_array_t {
 	array = new_array;
       }
     } else {
-      bool overflows = new_allocated >= ((unsigned int) -1) / sizeof (Type);
+      bool overflows = (new_allocated < allocated) || (new_allocated >= ((unsigned int) -1) / sizeof (Type));
       if (unlikely (overflows))
         new_array = NULL;
       else
commit e0db4b868f9fdd8e680890f87dd4e13a1c27b7a1
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 12:56:49 2011 -0400

    [buffer] More error handling
    
    Should be all set now.

diff --git a/src/hb-buffer-private.hh b/src/hb-buffer-private.hh
index 6954f96..7c298b0 100644
--- a/src/hb-buffer-private.hh
+++ b/src/hb-buffer-private.hh
@@ -95,9 +95,9 @@ struct _hb_buffer_t {
 
   /* Buffer contents */
 
+  bool in_error; /* Allocation failed */
   bool have_output; /* Whether we have an output buffer going on */
   bool have_positions; /* Whether we have positions */
-  bool in_error; /* Allocation failed */
 
   unsigned int i; /* Cursor into ->info and ->pos arrays */
   unsigned int len; /* Length of ->info and ->pos arrays */
diff --git a/src/hb-buffer.cc b/src/hb-buffer.cc
index 98ddd96..7dfe83a 100644
--- a/src/hb-buffer.cc
+++ b/src/hb-buffer.cc
@@ -43,6 +43,10 @@ static hb_buffer_t _hb_buffer_nil = {
     HB_SCRIPT_INVALID,
     NULL,
   },
+
+  TRUE, /* in_error */
+  TRUE, /* have_output */
+  TRUE  /* have_positions */
 };
 
 /* Here is how the buffer works internally:
@@ -71,26 +75,26 @@ _hb_buffer_enlarge (hb_buffer_t *buffer, unsigned int size)
     return FALSE;
 
   unsigned int new_allocated = buffer->allocated;
-  hb_glyph_position_t *new_pos;
-  hb_glyph_info_t *new_info;
-  bool separate_out;
+  hb_glyph_position_t *new_pos = NULL;
+  hb_glyph_info_t *new_info = NULL;
+  bool overflows = FALSE;
+  bool separate_out = buffer->out_info != buffer->info;
 
-  separate_out = buffer->out_info != buffer->info;
+  if (unlikely (size > ((unsigned int)-1) / 2))
+    goto done;
 
   while (size > new_allocated)
     new_allocated += (new_allocated >> 1) + 32;
 
   ASSERT_STATIC (sizeof (buffer->info[0]) == sizeof (buffer->pos[0]));
-  bool overflows = new_allocated >= ((unsigned int) -1) / sizeof (buffer->info[0]);
+  overflows = new_allocated >= ((unsigned int) -1) / sizeof (buffer->info[0]);
+  if (unlikely (overflows))
+    goto done;
 
-  if (unlikely (overflows)) {
-    new_pos = NULL;
-    new_info = NULL;
-  } else {
-    new_pos = (hb_glyph_position_t *) realloc (buffer->pos, new_allocated * sizeof (buffer->pos[0]));
-    new_info = (hb_glyph_info_t *) realloc (buffer->info, new_allocated * sizeof (buffer->info[0]));
-  }
+  new_pos = (hb_glyph_position_t *) realloc (buffer->pos, new_allocated * sizeof (buffer->pos[0]));
+  new_info = (hb_glyph_info_t *) realloc (buffer->info, new_allocated * sizeof (buffer->info[0]));
 
+done:
   if (unlikely (!new_pos || !new_info))
     buffer->in_error = TRUE;
 
@@ -116,7 +120,7 @@ _hb_buffer_ensure (hb_buffer_t *buffer, unsigned int size)
 static inline hb_bool_t
 _hb_buffer_ensure_separate (hb_buffer_t *buffer, unsigned int size)
 {
-  if (unlikely (!_hb_buffer_ensure (buffer, size))) return FALSE;
+  if (unlikely (!size || !_hb_buffer_ensure (buffer, size))) return FALSE;
 
   if (buffer->out_info == buffer->info)
   {
@@ -188,8 +192,11 @@ void
 hb_buffer_set_unicode_funcs (hb_buffer_t        *buffer,
 			     hb_unicode_funcs_t *unicode)
 {
+  if (unlikely (hb_object_is_inert (buffer)))
+    return;
+
   if (!unicode)
-    unicode = &_hb_unicode_funcs_default;
+    unicode = _hb_buffer_nil.unicode;
 
   hb_unicode_funcs_reference (unicode);
   hb_unicode_funcs_destroy (buffer->unicode);
@@ -207,6 +214,9 @@ hb_buffer_set_direction (hb_buffer_t    *buffer,
 			 hb_direction_t  direction)
 
 {
+  if (unlikely (hb_object_is_inert (buffer)))
+    return;
+
   buffer->props.direction = direction;
 }
 
@@ -220,6 +230,9 @@ void
 hb_buffer_set_script (hb_buffer_t *buffer,
 		      hb_script_t  script)
 {
+  if (unlikely (hb_object_is_inert (buffer)))
+    return;
+
   buffer->props.script = script;
 }
 
@@ -233,6 +246,9 @@ void
 hb_buffer_set_language (hb_buffer_t   *buffer,
 			hb_language_t  language)
 {
+  if (unlikely (hb_object_is_inert (buffer)))
+    return;
+
   buffer->props.language = language;
 }
 
@@ -246,14 +262,17 @@ hb_buffer_get_language (hb_buffer_t *buffer)
 void
 hb_buffer_reset (hb_buffer_t *buffer)
 {
+  if (unlikely (hb_object_is_inert (buffer)))
+    return;
+
   hb_unicode_funcs_destroy (buffer->unicode);
   buffer->unicode = _hb_buffer_nil.unicode;
 
   buffer->props = _hb_buffer_nil.props;
 
+  buffer->in_error = FALSE;
   buffer->have_output = FALSE;
   buffer->have_positions = FALSE;
-  buffer->in_error = FALSE;
 
   buffer->i = 0;
   buffer->len = 0;
@@ -301,6 +320,9 @@ hb_buffer_add (hb_buffer_t    *buffer,
 void
 _hb_buffer_clear_output (hb_buffer_t *buffer)
 {
+  if (unlikely (hb_object_is_inert (buffer)))
+    return;
+
   buffer->have_output = TRUE;
   buffer->have_positions = FALSE;
 
@@ -311,6 +333,9 @@ _hb_buffer_clear_output (hb_buffer_t *buffer)
 void
 _hb_buffer_clear_positions (hb_buffer_t *buffer)
 {
+  if (unlikely (hb_object_is_inert (buffer)))
+    return;
+
   buffer->have_output = FALSE;
   buffer->have_positions = TRUE;
 
@@ -320,12 +345,10 @@ _hb_buffer_clear_positions (hb_buffer_t *buffer)
 void
 _hb_buffer_swap (hb_buffer_t *buffer)
 {
-  unsigned int tmp;
+  if (unlikely (buffer->in_error)) return;
 
   assert (buffer->have_output);
 
-  if (unlikely (buffer->in_error)) return;
-
   if (buffer->out_info != buffer->info)
   {
     hb_glyph_info_t *tmp_string;
@@ -335,6 +358,7 @@ _hb_buffer_swap (hb_buffer_t *buffer)
     buffer->pos = (hb_glyph_position_t *) buffer->out_info;
   }
 
+  unsigned int tmp;
   tmp = buffer->len;
   buffer->len = buffer->out_len;
   buffer->out_len = tmp;
commit 15c57e04bf05026ef424f8ae912d2f379301bf93
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 12:28:54 2011 -0400

    [test/buffer] Add test pre_alloc(); hangs in the buffer code right now
    
    Because the following loop overflows:
    
      while (size > new_allocated)
          new_allocated += (new_allocated >> 1) + 32;

diff --git a/test/test-buffer.c b/test/test-buffer.c
index 2f811f5..b79f955 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -53,10 +53,10 @@ static const char *buffer_names[] = {
 typedef struct
 {
   hb_buffer_t *b;
-} Fixture;
+} fixture_t;
 
 static void
-fixture_init (Fixture *fixture, gconstpointer user_data)
+fixture_init (fixture_t *fixture, gconstpointer user_data)
 {
   unsigned int i;
 
@@ -89,14 +89,14 @@ fixture_init (Fixture *fixture, gconstpointer user_data)
 }
 
 static void
-fixture_fini (Fixture *fixture, gconstpointer user_data)
+fixture_fini (fixture_t *fixture, gconstpointer user_data)
 {
   hb_buffer_destroy (fixture->b);
 }
 
 
 static void
-test_buffer_properties (Fixture *fixture, gconstpointer user_data)
+test_buffer_properties (fixture_t *fixture, gconstpointer user_data)
 {
   hb_unicode_funcs_t *ufuncs;
 
@@ -135,7 +135,7 @@ test_buffer_properties (Fixture *fixture, gconstpointer user_data)
 }
 
 static void
-test_buffer_contents (Fixture *fixture, gconstpointer user_data)
+test_buffer_contents (fixture_t *fixture, gconstpointer user_data)
 {
   unsigned int i, len, len2;
   buffer_type_t buffer_type = GPOINTER_TO_INT (user_data);
@@ -241,6 +241,9 @@ test_buffer_contents (Fixture *fixture, gconstpointer user_data)
     g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
 
 
+  g_assert (hb_buffer_allocation_successful (fixture->b));
+
+
   /* test reset clears content */
 
   hb_buffer_reset (fixture->b);
@@ -248,7 +251,7 @@ test_buffer_contents (Fixture *fixture, gconstpointer user_data)
 }
 
 static void
-test_buffer_positions (Fixture *fixture, gconstpointer user_data)
+test_buffer_positions (fixture_t *fixture, gconstpointer user_data)
 {
   unsigned int i, len, len2;
   hb_glyph_position_t *positions;
@@ -271,6 +274,20 @@ test_buffer_positions (Fixture *fixture, gconstpointer user_data)
   g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
 }
 
+static void
+test_buffer_allocation (fixture_t *fixture, gconstpointer user_data)
+{
+  g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
+
+  g_assert (hb_buffer_pre_allocate (fixture->b, 100));
+  g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
+
+  /* lets try a huge allocation, make sure it fails */
+  g_assert (!hb_buffer_pre_allocate (fixture->b, (unsigned int) -1));
+  g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
+}
+
+
 int
 main (int argc, char **argv)
 {
@@ -282,7 +299,7 @@ main (int argc, char **argv)
 #define TEST_ADD(path, func) \
     G_STMT_START { \
       char *s = g_strdup_printf ("%s/%s", path, buffer_names[i]); \
-      g_test_add (s, Fixture, GINT_TO_POINTER (i), fixture_init, func, fixture_fini); \
+      g_test_add (s, fixture_t, GINT_TO_POINTER (i), fixture_init, func, fixture_fini); \
       g_free (s); \
     } G_STMT_END
     TEST_ADD ("/buffer/properties", test_buffer_properties);
@@ -291,6 +308,8 @@ main (int argc, char **argv)
 #undef TEST_ADD
   }
 
+  g_test_add ("/buffer/allocation", fixture_t, GINT_TO_POINTER (BUFFER_EMPTY), fixture_init, test_buffer_allocation, fixture_fini);
+
   /* XXX test invalid UTF-8 / UTF-16 text input (also overlong sequences) */
   /* XXX test pre_allocate(), allocation_successful(), and memory management */
 
commit 1e5527e2d60ed3b4a5adf62b258415ec3aef41fb
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 12:15:24 2011 -0400

    [test/buffer] Test reset(), set_length(), and set/get_unicode_data()

diff --git a/test/test-buffer.c b/test/test-buffer.c
index 75e9a8c..2f811f5 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -98,12 +98,22 @@ fixture_fini (Fixture *fixture, gconstpointer user_data)
 static void
 test_buffer_properties (Fixture *fixture, gconstpointer user_data)
 {
-  /* TODO check unicode_funcs */
+  hb_unicode_funcs_t *ufuncs;
 
+  /* test default properties */
+
+  g_assert (hb_buffer_get_unicode_funcs (fixture->b) == hb_unicode_funcs_get_default ());
   g_assert (hb_buffer_get_direction (fixture->b) == HB_DIRECTION_INVALID);
   g_assert (hb_buffer_get_script (fixture->b) == HB_SCRIPT_INVALID);
   g_assert (hb_buffer_get_language (fixture->b) == NULL);
 
+
+  /* test property changes are retained */
+  ufuncs = hb_unicode_funcs_create (NULL);
+  hb_buffer_set_unicode_funcs (fixture->b, ufuncs);
+  hb_unicode_funcs_destroy (ufuncs);
+  g_assert (hb_buffer_get_unicode_funcs (fixture->b) == ufuncs);
+
   hb_buffer_set_direction (fixture->b, HB_DIRECTION_RTL);
   g_assert (hb_buffer_get_direction (fixture->b) == HB_DIRECTION_RTL);
 
@@ -112,12 +122,22 @@ test_buffer_properties (Fixture *fixture, gconstpointer user_data)
 
   hb_buffer_set_language (fixture->b, hb_language_from_string ("fa"));
   g_assert (hb_buffer_get_language (fixture->b) == hb_language_from_string ("Fa"));
+
+
+  /* test reset clears properties */
+
+  hb_buffer_reset (fixture->b);
+
+  g_assert (hb_buffer_get_unicode_funcs (fixture->b) == hb_unicode_funcs_get_default ());
+  g_assert (hb_buffer_get_direction (fixture->b) == HB_DIRECTION_INVALID);
+  g_assert (hb_buffer_get_script (fixture->b) == HB_SCRIPT_INVALID);
+  g_assert (hb_buffer_get_language (fixture->b) == NULL);
 }
 
 static void
 test_buffer_contents (Fixture *fixture, gconstpointer user_data)
 {
-  unsigned int i, len;
+  unsigned int i, len, len2;
   buffer_type_t buffer_type = GPOINTER_TO_INT (user_data);
   hb_glyph_info_t *glyphs;
 
@@ -126,7 +146,10 @@ test_buffer_contents (Fixture *fixture, gconstpointer user_data)
     return;
   }
 
-  glyphs = hb_buffer_get_glyph_infos (fixture->b, &len);
+  len = hb_buffer_get_length (fixture->b);
+  glyphs = hb_buffer_get_glyph_infos (fixture->b, NULL); /* test NULL */
+  glyphs = hb_buffer_get_glyph_infos (fixture->b, &len2);
+  g_assert_cmpint (len, ==, len2);
   g_assert_cmpint (len, ==, 5);
 
   for (i = 0; i < len; i++) {
@@ -198,16 +221,43 @@ test_buffer_contents (Fixture *fixture, gconstpointer user_data)
   hb_buffer_reverse_clusters (fixture->b);
   for (i = 0; i < len; i++)
     g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+
+  /* test setting length */
+
+  /* enlarge */
+  g_assert (hb_buffer_set_length (fixture->b, 10));
+  glyphs = hb_buffer_get_glyph_infos (fixture->b, NULL);
+  g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 10);
+  for (i = 0; i < 5; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+  for (i = 5; i < 10; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, 0);
+  /* shrink */
+  g_assert (hb_buffer_set_length (fixture->b, 3));
+  glyphs = hb_buffer_get_glyph_infos (fixture->b, NULL);
+  g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 3);
+  for (i = 0; i < 3; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+
+  /* test reset clears content */
+
+  hb_buffer_reset (fixture->b);
+  g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
 }
 
 static void
 test_buffer_positions (Fixture *fixture, gconstpointer user_data)
 {
-  unsigned int i, len;
+  unsigned int i, len, len2;
   hb_glyph_position_t *positions;
 
   /* Without shaping, positions should all be zero */
-  positions = hb_buffer_get_glyph_positions (fixture->b, &len);
+  len = hb_buffer_get_length (fixture->b);
+  positions = hb_buffer_get_glyph_positions (fixture->b, NULL); /* test NULL */
+  positions = hb_buffer_get_glyph_positions (fixture->b, &len2);
+  g_assert_cmpint (len, ==, len2);
   for (i = 0; i < len; i++) {
     g_assert_cmpint (0, ==, positions[i].x_advance);
     g_assert_cmpint (0, ==, positions[i].y_advance);
@@ -215,6 +265,10 @@ test_buffer_positions (Fixture *fixture, gconstpointer user_data)
     g_assert_cmpint (0, ==, positions[i].y_offset);
     g_assert_cmpint (0, ==, positions[i].var.i32);
   }
+
+  /* test reset clears content */
+  hb_buffer_reset (fixture->b);
+  g_assert_cmpint (hb_buffer_get_length (fixture->b), ==, 0);
 }
 
 int
@@ -239,8 +293,6 @@ main (int argc, char **argv)
 
   /* XXX test invalid UTF-8 / UTF-16 text input (also overlong sequences) */
   /* XXX test pre_allocate(), allocation_successful(), and memory management */
-  /* XXX test buffer reset */
-  /* XXX test buffer set length */
 
   return g_test_run();
 }
commit db126b5448ec802285cf2b6f0e7da412d02dfb28
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Apr 28 11:56:29 2011 -0400

    [test/buffer] Test reverse() and reverse_clusters()

diff --git a/test/test-buffer.c b/test/test-buffer.c
index ab7f204..75e9a8c 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -130,6 +130,12 @@ test_buffer_contents (Fixture *fixture, gconstpointer user_data)
   g_assert_cmpint (len, ==, 5);
 
   for (i = 0; i < len; i++) {
+    g_assert_cmphex (glyphs[i].mask,      ==, 1);
+    g_assert_cmphex (glyphs[i].var1.u32,  ==, 0);
+    g_assert_cmphex (glyphs[i].var2.u32,  ==, 0);
+  }
+
+  for (i = 0; i < len; i++) {
     unsigned int cluster;
     cluster = 1+i;
     if (i >= 2) {
@@ -140,10 +146,58 @@ test_buffer_contents (Fixture *fixture, gconstpointer user_data)
     }
     g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
     g_assert_cmphex (glyphs[i].cluster,   ==, cluster);
-    g_assert_cmphex (glyphs[i].mask,      ==, 1);
-    g_assert_cmphex (glyphs[i].var1.u32,  ==, 0);
-    g_assert_cmphex (glyphs[i].var2.u32,  ==, 0);
   }
+
+  /* reverse, test, and reverse back */
+
+  hb_buffer_reverse (fixture->b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[len-i]);
+
+  hb_buffer_reverse (fixture->b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+  /* reverse_clusters works same as reverse for now since each codepoint is
+   * in its own cluster */
+
+  hb_buffer_reverse_clusters (fixture->b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[len-i]);
+
+  hb_buffer_reverse_clusters (fixture->b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+  /* now form a cluster and test again */
+  glyphs[2].cluster = glyphs[1].cluster;
+
+  /* reverse, test, and reverse back */
+
+  hb_buffer_reverse (fixture->b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[len-i]);
+
+  hb_buffer_reverse (fixture->b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+  /* reverse_clusters twice still should return the original string,
+   * but when applied once, the 1-2 cluster should be retained. */
+
+  hb_buffer_reverse_clusters (fixture->b);
+  for (i = 0; i < len; i++) {
+    unsigned int j = len-1-i;
+    if (j == 1)
+      j = 2;
+    else if (j == 2)
+      j = 1;
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+j]);
+  }
+
+  hb_buffer_reverse_clusters (fixture->b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
 }
 
 static void
@@ -184,7 +238,6 @@ main (int argc, char **argv)
   }
 
   /* XXX test invalid UTF-8 / UTF-16 text input (also overlong sequences) */
-  /* XXX test reverse() and reverse_clusters() */
   /* XXX test pre_allocate(), allocation_successful(), and memory management */
   /* XXX test buffer reset */
   /* XXX test buffer set length */
commit 5fa849b77d49da2212825ebb1bea9145713b8449
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Wed Apr 27 21:46:01 2011 -0400

    [API] Add _set/get_user_data() for all objects

diff --git a/src/hb-blob.cc b/src/hb-blob.cc
index 435d37b..83a75f6 100644
--- a/src/hb-blob.cc
+++ b/src/hb-blob.cc
@@ -162,6 +162,23 @@ hb_blob_destroy (hb_blob_t *blob)
   free (blob);
 }
 
+hb_bool_t
+hb_blob_set_user_data (hb_blob_t          *blob,
+		       hb_user_data_key_t *key,
+		       void *              data,
+		       hb_destroy_func_t   destroy)
+{
+  return hb_object_set_user_data (blob, key, data, destroy);
+}
+
+void *
+hb_blob_get_user_data (hb_blob_t          *blob,
+		       hb_user_data_key_t *key)
+{
+  return hb_object_get_user_data (blob, key);
+}
+
+
 unsigned int
 hb_blob_get_length (hb_blob_t *blob)
 {
diff --git a/src/hb-blob.h b/src/hb-blob.h
index 76ea80d..d698ee2 100644
--- a/src/hb-blob.h
+++ b/src/hb-blob.h
@@ -62,6 +62,18 @@ hb_blob_reference (hb_blob_t *blob);
 void
 hb_blob_destroy (hb_blob_t *blob);
 
+hb_bool_t
+hb_blob_set_user_data (hb_blob_t          *blob,
+		       hb_user_data_key_t *key,
+		       void *              data,
+		       hb_destroy_func_t   destroy);
+
+
+void *
+hb_blob_get_user_data (hb_blob_t          *blob,
+		       hb_user_data_key_t *key);
+
+
 unsigned int
 hb_blob_get_length (hb_blob_t *blob);
 
diff --git a/src/hb-buffer.cc b/src/hb-buffer.cc
index 2ae29ca..98ddd96 100644
--- a/src/hb-buffer.cc
+++ b/src/hb-buffer.cc
@@ -167,6 +167,22 @@ hb_buffer_destroy (hb_buffer_t *buffer)
   free (buffer);
 }
 
+hb_bool_t
+hb_buffer_set_user_data (hb_buffer_t        *buffer,
+			 hb_user_data_key_t *key,
+			 void *              data,
+			 hb_destroy_func_t   destroy)
+{
+  return hb_object_set_user_data (buffer, key, data, destroy);
+}
+
+void *
+hb_buffer_get_user_data (hb_buffer_t        *buffer,
+			 hb_user_data_key_t *key)
+{
+  return hb_object_get_user_data (buffer, key);
+}
+
 
 void
 hb_buffer_set_unicode_funcs (hb_buffer_t        *buffer,
diff --git a/src/hb-buffer.h b/src/hb-buffer.h
index e7ef087..47a2123 100644
--- a/src/hb-buffer.h
+++ b/src/hb-buffer.h
@@ -68,6 +68,16 @@ hb_buffer_reference (hb_buffer_t *buffer);
 void
 hb_buffer_destroy (hb_buffer_t *buffer);
 
+hb_bool_t
+hb_buffer_set_user_data (hb_buffer_t        *buffer,
+			 hb_user_data_key_t *key,
+			 void *              data,
+			 hb_destroy_func_t   destroy);
+
+void *
+hb_buffer_get_user_data (hb_buffer_t        *buffer,
+			 hb_user_data_key_t *key);
+
 
 void
 hb_buffer_set_unicode_funcs (hb_buffer_t        *buffer,
diff --git a/src/hb-font.cc b/src/hb-font.cc
index 5506480..b067901 100644
--- a/src/hb-font.cc
+++ b/src/hb-font.cc
@@ -124,6 +124,23 @@ hb_font_funcs_destroy (hb_font_funcs_t *ffuncs)
   free (ffuncs);
 }
 
+hb_bool_t
+hb_font_funcs_set_user_data (hb_font_funcs_t    *ffuncs,
+			     hb_user_data_key_t *key,
+			     void *              data,
+			     hb_destroy_func_t   destroy)
+{
+  return hb_object_set_user_data (ffuncs, key, data, destroy);
+}
+
+void *
+hb_font_funcs_get_user_data (hb_font_funcs_t    *ffuncs,
+			     hb_user_data_key_t *key)
+{
+  return hb_object_get_user_data (ffuncs, key);
+}
+
+
 hb_font_funcs_t *
 hb_font_funcs_copy (hb_font_funcs_t *other_ffuncs)
 {
@@ -408,6 +425,23 @@ hb_face_destroy (hb_face_t *face)
   free (face);
 }
 
+hb_bool_t
+hb_face_set_user_data (hb_face_t          *face,
+		       hb_user_data_key_t *key,
+		       void *              data,
+		       hb_destroy_func_t   destroy)
+{
+  return hb_object_set_user_data (face, key, data, destroy);
+}
+
+void *
+hb_face_get_user_data (hb_face_t          *face,
+		       hb_user_data_key_t *key)
+{
+  return hb_object_get_user_data (face, key);
+}
+
+
 hb_blob_t *
 hb_face_reference_table (hb_face_t *face,
 		   hb_tag_t   tag)
@@ -480,6 +514,23 @@ hb_font_destroy (hb_font_t *font)
   free (font);
 }
 
+hb_bool_t
+hb_font_set_user_data (hb_font_t          *font,
+		       hb_user_data_key_t *key,
+		       void *              data,
+		       hb_destroy_func_t   destroy)
+{
+  return hb_object_set_user_data (font, key, data, destroy);
+}
+
+void *
+hb_font_get_user_data (hb_font_t          *font,
+		       hb_user_data_key_t *key)
+{
+  return hb_object_get_user_data (font, key);
+}
+
+
 void
 hb_font_set_funcs (hb_font_t         *font,
 		   hb_font_funcs_t   *klass,
diff --git a/src/hb-font.h b/src/hb-font.h
index e349591..f018a8b 100644
--- a/src/hb-font.h
+++ b/src/hb-font.h
@@ -58,6 +58,18 @@ hb_face_reference (hb_face_t *face);
 void
 hb_face_destroy (hb_face_t *face);
 
+hb_bool_t
+hb_face_set_user_data (hb_face_t          *face,
+		       hb_user_data_key_t *key,
+		       void *              data,
+		       hb_destroy_func_t   destroy);
+
+
+void *
+hb_face_get_user_data (hb_face_t          *face,
+		       hb_user_data_key_t *key);
+
+
 /* XXX
  *
  * I have two major concerns about this API as it is right now:
@@ -91,6 +103,18 @@ hb_font_funcs_reference (hb_font_funcs_t *ffuncs);
 void
 hb_font_funcs_destroy (hb_font_funcs_t *ffuncs);
 
+hb_bool_t
+hb_font_funcs_set_user_data (hb_font_funcs_t    *ffuncs,
+			     hb_user_data_key_t *key,
+			     void *              data,
+			     hb_destroy_func_t   destroy);
+
+
+void *
+hb_font_funcs_get_user_data (hb_font_funcs_t    *ffuncs,
+			     hb_user_data_key_t *key);
+
+
 hb_font_funcs_t *
 hb_font_funcs_copy (hb_font_funcs_t *ffuncs);
 
@@ -203,6 +227,18 @@ hb_font_reference (hb_font_t *font);
 void
 hb_font_destroy (hb_font_t *font);
 
+hb_bool_t
+hb_font_set_user_data (hb_font_t          *font,
+		       hb_user_data_key_t *key,
+		       void *              data,
+		       hb_destroy_func_t   destroy);
+
+
+void *
+hb_font_get_user_data (hb_font_t          *font,
+		       hb_user_data_key_t *key);
+
+
 void
 hb_font_set_funcs (hb_font_t         *font,
 		   hb_font_funcs_t   *klass,
diff --git a/src/hb-unicode.cc b/src/hb-unicode.cc
index 9922955..0486be3 100644
--- a/src/hb-unicode.cc
+++ b/src/hb-unicode.cc
@@ -155,6 +155,23 @@ hb_unicode_funcs_destroy (hb_unicode_funcs_t *ufuncs)
   free (ufuncs);
 }
 
+hb_bool_t
+hb_unicode_funcs_set_user_data (hb_unicode_funcs_t *ufuncs,
+			        hb_user_data_key_t *key,
+			        void *              data,
+			        hb_destroy_func_t   destroy)
+{
+  return hb_object_set_user_data (ufuncs, key, data, destroy);
+}
+
+void *
+hb_unicode_funcs_get_user_data (hb_unicode_funcs_t *ufuncs,
+			        hb_user_data_key_t *key)
+{
+  return hb_object_get_user_data (ufuncs, key);
+}
+
+
 void
 hb_unicode_funcs_make_immutable (hb_unicode_funcs_t *ufuncs)
 {
diff --git a/src/hb-unicode.h b/src/hb-unicode.h
index d558c22..a46431b 100644
--- a/src/hb-unicode.h
+++ b/src/hb-unicode.h
@@ -59,6 +59,18 @@ hb_unicode_funcs_reference (hb_unicode_funcs_t *ufuncs);
 void
 hb_unicode_funcs_destroy (hb_unicode_funcs_t *ufuncs);
 
+hb_bool_t
+hb_unicode_funcs_set_user_data (hb_unicode_funcs_t *ufuncs,
+			        hb_user_data_key_t *key,
+			        void *              data,
+			        hb_destroy_func_t   destroy);
+
+
+void *
+hb_unicode_funcs_get_user_data (hb_unicode_funcs_t *ufuncs,
+			        hb_user_data_key_t *key);
+
+
 void
 hb_unicode_funcs_make_immutable (hb_unicode_funcs_t *ufuncs);
 
commit 852e08ec8fbfbce1d50e571d0bb0b52ef4d4cc58
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Wed Apr 27 21:45:51 2011 -0400

    Move code around

diff --git a/src/hb-object-private.hh b/src/hb-object-private.hh
index 7bc6b46..d124758 100644
--- a/src/hb-object-private.hh
+++ b/src/hb-object-private.hh
@@ -44,134 +44,28 @@ HB_BEGIN_DECLS
 #endif
 
 
-/* user_data */
-
-HB_END_DECLS
-
-
-template <typename Type, unsigned int StaticSize>
-struct hb_static_array_t {
-
-  unsigned int len;
-  unsigned int allocated;
-  Type *array;
-  Type static_array[StaticSize];
-
-  void finish (void) { for (unsigned i = 0; i < len; i++) array[i].finish (); }
-
-  inline Type& operator [] (unsigned int i)
-  {
-    return array[i];
-  }
-
-  inline Type *push (void)
-  {
-    if (!array) {
-      array = static_array;
-      allocated = ARRAY_LENGTH (static_array);
-    }
-    if (likely (len < allocated))
-      return &array[len++];
-    /* Need to reallocate */
-    unsigned int new_allocated = allocated + (allocated >> 1) + 8;
-    Type *new_array;
-    if (array == static_array) {
-      new_array = (Type *) calloc (new_allocated, sizeof (Type));
-      if (new_array) {
-        memcpy (new_array, array, len * sizeof (Type));
-	array = new_array;
-      }
-    } else {
-      bool overflows = new_allocated >= ((unsigned int) -1) / sizeof (Type);
-      if (unlikely (overflows))
-        new_array = NULL;
-      else
-	new_array = (Type *) realloc (array, new_allocated * sizeof (Type));
-      if (new_array) {
-        free (array);
-	array = new_array;
-      }
-    }
-    if ((len < allocated))
-      return &array[len++];
-    else
-      return NULL;
-  }
-
-  inline void pop (void)
-  {
-    len--;
-    /* TODO: shrink array if needed */
-  }
-};
-
-template <typename Type>
-struct hb_array_t : hb_static_array_t<Type, 2> {};
-
-
-template <typename Key, typename Value>
-struct hb_map_t
-{
-  struct item_t {
-    Key key;
-    /* unsigned int hash; */
-    Value value;
-
-    void finish (void) { value.finish (); }
-  };
+/* reference_count */
 
-  hb_array_t <item_t> items;
-
-  private:
-
-  inline item_t *find (Key key) {
-    if (unlikely (!key)) return NULL;
-    for (unsigned int i = 0; i < items.len; i++)
-      if (key == items[i].key)
-	return &items[i];
-    return NULL;
-  }
-
-  public:
+typedef struct {
+  hb_atomic_int_t ref_count;
 
-  inline bool set (Key   key,
-		   Value &value)
-  {
-    if (unlikely (!key)) return NULL;
-    item_t *item;
-    item = find (key);
-    if (item)
-      item->finish ();
-    else
-      item = items.push ();
-    if (unlikely (!item)) return false;
-    item->key = key;
-    item->value = value;
-    return true;
-  }
+#define HB_REFERENCE_COUNT_INVALID_VALUE ((hb_atomic_int_t) -1)
+#define HB_REFERENCE_COUNT_INVALID {HB_REFERENCE_COUNT_INVALID_VALUE}
 
-  inline void unset (Key &key)
-  {
-    item_t *item;
-    item = find (key);
-    if (!item) return;
+  inline void init (int v) { ref_count = v; /* non-atomic is fine */ }
+  inline int inc (void) { return hb_atomic_int_fetch_and_add (ref_count,  1); }
+  inline int dec (void) { return hb_atomic_int_fetch_and_add (ref_count, -1); }
+  inline void set (int v) { hb_atomic_int_set (ref_count, v); }
 
-    item->finish ();
-    items[items.len - 1] = *item;
-    items.pop ();
-  }
+  inline int get (void) const { return hb_atomic_int_get (ref_count); }
+  inline bool is_invalid (void) const { return get () == HB_REFERENCE_COUNT_INVALID_VALUE; }
 
-  inline Value *get (Key key)
-  {
-    item_t *item = find (key);
-    return item ? &item->value : NULL;
-  }
+} hb_reference_count_t;
 
-  void finish (void) { items.finish (); }
-};
 
+/* user_data */
 
-HB_BEGIN_DECLS
+/* XXX make this thread-safe, somehow! */
 
 typedef struct {
   void *data;
@@ -204,6 +98,7 @@ struct hb_user_data_array_t {
 };
 
 
+/* object_header */
 
 typedef struct _hb_object_header_t hb_object_header_t;
 
@@ -273,6 +168,9 @@ struct _hb_object_header_t {
 
 HB_END_DECLS
 
+
+/* object */
+
 template <typename Type>
 static inline void hb_object_trace (const Type *obj, const char *function)
 {
@@ -303,6 +201,21 @@ static inline bool hb_object_destroy (Type *obj)
   hb_object_trace (obj, HB_FUNC);
   return obj->header.destroy ();
 }
+template <typename Type>
+static inline bool hb_object_set_user_data (Type               *obj,
+					    hb_user_data_key_t *key,
+					    void *              data,
+					    hb_destroy_func_t   destroy)
+{
+  return obj->header.set_user_data (key, data, destroy);
+}
+
+template <typename Type>
+static inline void *hb_object_get_user_data (Type               *obj,
+					     hb_user_data_key_t *key)
+{
+  return obj->header.get_user_data (key);
+}
 
 
 HB_BEGIN_DECLS
diff --git a/src/hb-private.hh b/src/hb-private.hh
index fc06e70..ce09d18 100644
--- a/src/hb-private.hh
+++ b/src/hb-private.hh
@@ -258,23 +258,135 @@ typedef volatile int hb_mutex_t;
 #endif
 
 
-/* A reference count */
+HB_END_DECLS
+
+
+/* arrays and maps */
+
+
+template <typename Type, unsigned int StaticSize>
+struct hb_static_array_t {
+
+  unsigned int len;
+  unsigned int allocated;
+  Type *array;
+  Type static_array[StaticSize];
+
+  void finish (void) { for (unsigned i = 0; i < len; i++) array[i].finish (); }
+
+  inline Type& operator [] (unsigned int i)
+  {
+    return array[i];
+  }
+
+  inline Type *push (void)
+  {
+    if (!array) {
+      array = static_array;
+      allocated = ARRAY_LENGTH (static_array);
+    }
+    if (likely (len < allocated))
+      return &array[len++];
+    /* Need to reallocate */
+    unsigned int new_allocated = allocated + (allocated >> 1) + 8;
+    Type *new_array;
+    if (array == static_array) {
+      new_array = (Type *) calloc (new_allocated, sizeof (Type));
+      if (new_array) {
+        memcpy (new_array, array, len * sizeof (Type));
+	array = new_array;
+      }
+    } else {
+      bool overflows = new_allocated >= ((unsigned int) -1) / sizeof (Type);
+      if (unlikely (overflows))
+        new_array = NULL;
+      else
+	new_array = (Type *) realloc (array, new_allocated * sizeof (Type));
+      if (new_array) {
+        free (array);
+	array = new_array;
+      }
+    }
+    if ((len < allocated))
+      return &array[len++];
+    else
+      return NULL;
+  }
+
+  inline void pop (void)
+  {
+    len--;
+    /* TODO: shrink array if needed */
+  }
+};
+
+template <typename Type>
+struct hb_array_t : hb_static_array_t<Type, 2> {};
 
-typedef struct {
-  hb_atomic_int_t ref_count;
 
-#define HB_REFERENCE_COUNT_INVALID_VALUE ((hb_atomic_int_t) -1)
-#define HB_REFERENCE_COUNT_INVALID {HB_REFERENCE_COUNT_INVALID_VALUE}
+template <typename Key, typename Value>
+struct hb_map_t
+{
+  struct item_t {
+    Key key;
+    /* unsigned int hash; */
+    Value value;
+
+    void finish (void) { value.finish (); }
+  };
+
+  hb_array_t <item_t> items;
+
+  private:
+
+  inline item_t *find (Key key) {
+    if (unlikely (!key)) return NULL;
+    for (unsigned int i = 0; i < items.len; i++)
+      if (key == items[i].key)
+	return &items[i];
+    return NULL;
+  }
+
+  public:
+
+  inline bool set (Key   key,
+		   Value &value)
+  {
+    if (unlikely (!key)) return NULL;
+    item_t *item;
+    item = find (key);
+    if (item)
+      item->finish ();
+    else
+      item = items.push ();
+    if (unlikely (!item)) return false;
+    item->key = key;
+    item->value = value;
+    return true;
+  }
+
+  inline void unset (Key &key)
+  {
+    item_t *item;
+    item = find (key);
+    if (!item) return;
 
-  inline void init (int v) { ref_count = v; /* non-atomic is fine */ }
-  inline int inc (void) { return hb_atomic_int_fetch_and_add (ref_count,  1); }
-  inline int dec (void) { return hb_atomic_int_fetch_and_add (ref_count, -1); }
-  inline void set (int v) { hb_atomic_int_set (ref_count, v); }
+    item->finish ();
+    items[items.len - 1] = *item;
+    items.pop ();
+  }
 
-  inline int get (void) const { return hb_atomic_int_get (ref_count); }
-  inline bool is_invalid (void) const { return get () == HB_REFERENCE_COUNT_INVALID_VALUE; }
+  inline Value *get (Key key)
+  {
+    item_t *item = find (key);
+    return item ? &item->value : NULL;
+  }
 
-} hb_reference_count_t;
+  void finish (void) { items.finish (); }
+};
+
+
+HB_BEGIN_DECLS
 
 
 /* Big-endian handling */
commit 29c67d3f70b081766a6c01353980f457f38aeb12
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Wed Apr 27 21:22:32 2011 -0400

    Add initial implementation of user_data to objects

diff --git a/src/hb-common.h b/src/hb-common.h
index e6f5f2b..74030f4 100644
--- a/src/hb-common.h
+++ b/src/hb-common.h
@@ -68,8 +68,6 @@ typedef union _hb_var_int_t {
   int8_t i8[4];
 } hb_var_int_t;
 
-typedef void (*hb_destroy_func_t) (void *user_data);
-
 
 /* hb_tag_t */
 
@@ -287,6 +285,15 @@ hb_direction_t
 hb_script_get_horizontal_direction (hb_script_t script);
 
 
+/* User data */
+
+typedef struct _hb_user_data_key_t {
+  char unused;
+} hb_user_data_key_t;
+
+typedef void (*hb_destroy_func_t) (void *user_data);
+
+
 HB_END_DECLS
 
 #endif /* HB_COMMON_H */
diff --git a/src/hb-object-private.hh b/src/hb-object-private.hh
index 8fbddd4..7bc6b46 100644
--- a/src/hb-object-private.hh
+++ b/src/hb-object-private.hh
@@ -44,10 +44,172 @@ HB_BEGIN_DECLS
 #endif
 
 
+/* user_data */
+
+HB_END_DECLS
+
+
+template <typename Type, unsigned int StaticSize>
+struct hb_static_array_t {
+
+  unsigned int len;
+  unsigned int allocated;
+  Type *array;
+  Type static_array[StaticSize];
+
+  void finish (void) { for (unsigned i = 0; i < len; i++) array[i].finish (); }
+
+  inline Type& operator [] (unsigned int i)
+  {
+    return array[i];
+  }
+
+  inline Type *push (void)
+  {
+    if (!array) {
+      array = static_array;
+      allocated = ARRAY_LENGTH (static_array);
+    }
+    if (likely (len < allocated))
+      return &array[len++];
+    /* Need to reallocate */
+    unsigned int new_allocated = allocated + (allocated >> 1) + 8;
+    Type *new_array;
+    if (array == static_array) {
+      new_array = (Type *) calloc (new_allocated, sizeof (Type));
+      if (new_array) {
+        memcpy (new_array, array, len * sizeof (Type));
+	array = new_array;
+      }
+    } else {
+      bool overflows = new_allocated >= ((unsigned int) -1) / sizeof (Type);
+      if (unlikely (overflows))
+        new_array = NULL;
+      else
+	new_array = (Type *) realloc (array, new_allocated * sizeof (Type));
+      if (new_array) {
+        free (array);
+	array = new_array;
+      }
+    }
+    if ((len < allocated))
+      return &array[len++];
+    else
+      return NULL;
+  }
+
+  inline void pop (void)
+  {
+    len--;
+    /* TODO: shrink array if needed */
+  }
+};
+
+template <typename Type>
+struct hb_array_t : hb_static_array_t<Type, 2> {};
+
+
+template <typename Key, typename Value>
+struct hb_map_t
+{
+  struct item_t {
+    Key key;
+    /* unsigned int hash; */
+    Value value;
+
+    void finish (void) { value.finish (); }
+  };
+
+  hb_array_t <item_t> items;
+
+  private:
+
+  inline item_t *find (Key key) {
+    if (unlikely (!key)) return NULL;
+    for (unsigned int i = 0; i < items.len; i++)
+      if (key == items[i].key)
+	return &items[i];
+    return NULL;
+  }
+
+  public:
+
+  inline bool set (Key   key,
+		   Value &value)
+  {
+    if (unlikely (!key)) return NULL;
+    item_t *item;
+    item = find (key);
+    if (item)
+      item->finish ();
+    else
+      item = items.push ();
+    if (unlikely (!item)) return false;
+    item->key = key;
+    item->value = value;
+    return true;
+  }
+
+  inline void unset (Key &key)
+  {
+    item_t *item;
+    item = find (key);
+    if (!item) return;
+
+    item->finish ();
+    items[items.len - 1] = *item;
+    items.pop ();
+  }
+
+  inline Value *get (Key key)
+  {
+    item_t *item = find (key);
+    return item ? &item->value : NULL;
+  }
+
+  void finish (void) { items.finish (); }
+};
+
+
+HB_BEGIN_DECLS
+
+typedef struct {
+  void *data;
+  hb_destroy_func_t destroy;
+
+  void finish (void) { if (destroy) destroy (data); }
+} hb_user_data_t;
+
+struct hb_user_data_array_t {
+
+  hb_map_t<hb_user_data_key_t *, hb_user_data_t> map;
+
+  inline bool set (hb_user_data_key_t *key,
+		   void *              data,
+		   hb_destroy_func_t   destroy)
+  {
+    if (!data && !destroy) {
+      map.unset (key);
+      return true;
+    }
+    hb_user_data_t user_data = {data, destroy};
+    return map.set (key, user_data);
+  }
+
+  inline void *get (hb_user_data_key_t *key) {
+    return map.get (key);
+  }
+
+  void finish (void) { map.finish (); }
+};
+
+
+
 typedef struct _hb_object_header_t hb_object_header_t;
 
 struct _hb_object_header_t {
   hb_reference_count_t ref_count;
+  hb_user_data_array_t user_data;
 
 #define HB_OBJECT_HEADER_STATIC {HB_REFERENCE_COUNT_INVALID}
 
@@ -64,7 +226,9 @@ struct _hb_object_header_t {
     ref_count.init (1);
   }
 
-  inline bool is_inert (void) const { return unlikely (ref_count.is_invalid ()); }
+  inline bool is_inert (void) const {
+    return unlikely (ref_count.is_invalid ());
+  }
 
   inline void reference (void) {
     if (unlikely (!this || this->is_inert ()))
@@ -75,7 +239,25 @@ struct _hb_object_header_t {
   inline bool destroy (void) {
     if (unlikely (!this || this->is_inert ()))
       return false;
-    return ref_count.dec () == 1;
+    if (ref_count.dec () != 1)
+      return false;
+
+    user_data.finish ();
+
+    return true;
+  }
+
+  inline bool set_user_data (hb_user_data_key_t *key,
+			     void *              data,
+			     hb_destroy_func_t   destroy) {
+    if (unlikely (!this || this->is_inert ()))
+      return false;
+
+    return user_data.set (key, data, destroy);
+  }
+
+  inline void *get_user_data (hb_user_data_key_t *key) {
+    return user_data.get (key);
   }
 
   inline void trace (const char *function) const {
commit 47e71d9661946a4ffb96026bf1d697d788414ab5
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Wed Apr 27 16:38:03 2011 -0400

    [object] Remove unnecessary use of macros

diff --git a/TODO b/TODO
index 941a8c8..d8d4d56 100644
--- a/TODO
+++ b/TODO
@@ -9,8 +9,6 @@ General fixes:
 
 - Remove fixed-size feature/lookup arrays in hb-ot-map
 
-- Use templates instead of macros for objects?
-
 API issues to fix before 1.0:
 ============================
 
diff --git a/src/hb-blob.cc b/src/hb-blob.cc
index 7d1925f..435d37b 100644
--- a/src/hb-blob.cc
+++ b/src/hb-blob.cc
@@ -87,7 +87,7 @@ hb_blob_create (const char        *data,
 {
   hb_blob_t *blob;
 
-  if (!length || !HB_OBJECT_DO_CREATE (hb_blob_t, blob)) {
+  if (!length || !(blob = hb_object_create<hb_blob_t> ())) {
     if (destroy)
       destroy (user_data);
     return &_hb_blob_nil;
@@ -122,7 +122,7 @@ hb_blob_create_sub_blob (hb_blob_t    *parent,
   hb_blob_t *blob;
   const char *pdata;
 
-  if (!length || offset >= parent->length || !HB_OBJECT_DO_CREATE (hb_blob_t, blob))
+  if (!length || offset >= parent->length || !(blob = hb_object_create<hb_blob_t> ()))
     return &_hb_blob_nil;
 
   pdata = hb_blob_lock (parent);
@@ -149,13 +149,13 @@ hb_blob_create_empty (void)
 hb_blob_t *
 hb_blob_reference (hb_blob_t *blob)
 {
-  HB_OBJECT_DO_REFERENCE (blob);
+  return hb_object_reference (blob);
 }
 
 void
 hb_blob_destroy (hb_blob_t *blob)
 {
-  HB_OBJECT_DO_DESTROY (blob);
+  if (!hb_object_destroy (blob)) return;
 
   _hb_blob_destroy_user_data (blob);
 
@@ -171,7 +171,7 @@ hb_blob_get_length (hb_blob_t *blob)
 const char *
 hb_blob_lock (hb_blob_t *blob)
 {
-  if (HB_OBJECT_IS_INERT (blob))
+  if (hb_object_is_inert (blob))
     return NULL;
 
   hb_mutex_lock (blob->lock);
@@ -190,7 +190,7 @@ hb_blob_lock (hb_blob_t *blob)
 void
 hb_blob_unlock (hb_blob_t *blob)
 {
-  if (HB_OBJECT_IS_INERT (blob))
+  if (hb_object_is_inert (blob))
     return;
 
   hb_mutex_lock (blob->lock);
@@ -210,7 +210,7 @@ hb_blob_is_writable (hb_blob_t *blob)
 {
   hb_memory_mode_t mode;
 
-  if (HB_OBJECT_IS_INERT (blob))
+  if (hb_object_is_inert (blob))
     return FALSE;
 
   hb_mutex_lock (blob->lock);
@@ -292,7 +292,7 @@ hb_blob_try_writable_inplace (hb_blob_t *blob)
 {
   hb_memory_mode_t mode;
 
-  if (HB_OBJECT_IS_INERT (blob))
+  if (hb_object_is_inert (blob))
     return FALSE;
 
   hb_mutex_lock (blob->lock);
@@ -312,7 +312,7 @@ hb_blob_try_writable (hb_blob_t *blob)
 {
   hb_memory_mode_t mode;
 
-  if (HB_OBJECT_IS_INERT (blob))
+  if (hb_object_is_inert (blob))
     return FALSE;
 
   hb_mutex_lock (blob->lock);
diff --git a/src/hb-buffer.cc b/src/hb-buffer.cc
index 5468270..2ae29ca 100644
--- a/src/hb-buffer.cc
+++ b/src/hb-buffer.cc
@@ -137,7 +137,7 @@ hb_buffer_create (unsigned int pre_alloc_size)
 {
   hb_buffer_t *buffer;
 
-  if (!HB_OBJECT_DO_CREATE (hb_buffer_t, buffer))
+  if (!(buffer = hb_object_create<hb_buffer_t> ()))
     return &_hb_buffer_nil;
 
   if (pre_alloc_size)
@@ -151,13 +151,13 @@ hb_buffer_create (unsigned int pre_alloc_size)
 hb_buffer_t *
 hb_buffer_reference (hb_buffer_t *buffer)
 {
-  HB_OBJECT_DO_REFERENCE (buffer);
+  return hb_object_reference (buffer);
 }
 
 void
 hb_buffer_destroy (hb_buffer_t *buffer)
 {
-  HB_OBJECT_DO_DESTROY (buffer);
+  if (!hb_object_destroy (buffer)) return;
 
   hb_unicode_funcs_destroy (buffer->unicode);
 
diff --git a/src/hb-font.cc b/src/hb-font.cc
index 149ca19..5506480 100644
--- a/src/hb-font.cc
+++ b/src/hb-font.cc
@@ -102,7 +102,7 @@ hb_font_funcs_create (void)
 {
   hb_font_funcs_t *ffuncs;
 
-  if (!HB_OBJECT_DO_CREATE (hb_font_funcs_t, ffuncs))
+  if (!(ffuncs = hb_object_create<hb_font_funcs_t> ()))
     return &_hb_font_funcs_nil;
 
   ffuncs->v = _hb_font_funcs_nil.v;
@@ -113,13 +113,13 @@ hb_font_funcs_create (void)
 hb_font_funcs_t *
 hb_font_funcs_reference (hb_font_funcs_t *ffuncs)
 {
-  HB_OBJECT_DO_REFERENCE (ffuncs);
+  return hb_object_reference (ffuncs);
 }
 
 void
 hb_font_funcs_destroy (hb_font_funcs_t *ffuncs)
 {
-  HB_OBJECT_DO_DESTROY (ffuncs);
+  if (!hb_object_destroy (ffuncs)) return;
 
   free (ffuncs);
 }
@@ -129,7 +129,7 @@ hb_font_funcs_copy (hb_font_funcs_t *other_ffuncs)
 {
   hb_font_funcs_t *ffuncs;
 
-  if (!HB_OBJECT_DO_CREATE (hb_font_funcs_t, ffuncs))
+  if (!(ffuncs = hb_object_create<hb_font_funcs_t> ()))
     return &_hb_font_funcs_nil;
 
   ffuncs->v = other_ffuncs->v;
@@ -140,7 +140,7 @@ hb_font_funcs_copy (hb_font_funcs_t *other_ffuncs)
 void
 hb_font_funcs_make_immutable (hb_font_funcs_t *ffuncs)
 {
-  if (HB_OBJECT_IS_INERT (ffuncs))
+  if (hb_object_is_inert (ffuncs))
     return;
 
   ffuncs->immutable = TRUE;
@@ -308,7 +308,7 @@ hb_face_create_for_tables (hb_get_table_func_t  get_table,
 {
   hb_face_t *face;
 
-  if (!HB_OBJECT_DO_CREATE (hb_face_t, face)) {
+  if (!(face = hb_object_create<hb_face_t> ())) {
     if (destroy)
       destroy (user_data);
     return &_hb_face_nil;
@@ -389,13 +389,13 @@ hb_face_create_for_data (hb_blob_t    *blob,
 hb_face_t *
 hb_face_reference (hb_face_t *face)
 {
-  HB_OBJECT_DO_REFERENCE (face);
+  return hb_object_reference (face);
 }
 
 void
 hb_face_destroy (hb_face_t *face)
 {
-  HB_OBJECT_DO_DESTROY (face);
+  if (!hb_object_destroy (face)) return;
 
   _hb_ot_layout_free (face->ot_layout);
 
@@ -454,7 +454,7 @@ hb_font_create (void)
 {
   hb_font_t *font;
 
-  if (!HB_OBJECT_DO_CREATE (hb_font_t, font))
+  if (!(font = hb_object_create<hb_font_t> ()))
     return &_hb_font_nil;
 
   font->klass = &_hb_font_funcs_nil;
@@ -465,13 +465,13 @@ hb_font_create (void)
 hb_font_t *
 hb_font_reference (hb_font_t *font)
 {
-  HB_OBJECT_DO_REFERENCE (font);
+  return hb_object_reference (font);
 }
 
 void
 hb_font_destroy (hb_font_t *font)
 {
-  HB_OBJECT_DO_DESTROY (font);
+  if (!hb_object_destroy (font)) return;
 
   hb_font_funcs_destroy (font->klass);
   if (font->destroy)
@@ -486,7 +486,7 @@ hb_font_set_funcs (hb_font_t         *font,
 		   void              *user_data,
 		   hb_destroy_func_t  destroy)
 {
-  if (HB_OBJECT_IS_INERT (font))
+  if (hb_object_is_inert (font))
     return;
 
   if (font->destroy)
@@ -514,7 +514,7 @@ hb_font_unset_funcs (hb_font_t          *font,
   *user_data = font->user_data;
   *destroy = font->destroy;
 
-  if (HB_OBJECT_IS_INERT (font))
+  if (hb_object_is_inert (font))
     return;
 
   font->klass = NULL;
@@ -527,7 +527,7 @@ hb_font_set_scale (hb_font_t *font,
 		   int x_scale,
 		   int y_scale)
 {
-  if (HB_OBJECT_IS_INERT (font))
+  if (hb_object_is_inert (font))
     return;
 
   font->x_scale = x_scale;
@@ -548,7 +548,7 @@ hb_font_set_ppem (hb_font_t *font,
 		  unsigned int x_ppem,
 		  unsigned int y_ppem)
 {
-  if (HB_OBJECT_IS_INERT (font))
+  if (hb_object_is_inert (font))
     return;
 
   font->x_ppem = x_ppem;
diff --git a/src/hb-object-private.hh b/src/hb-object-private.hh
index c3cdeb5..8fbddd4 100644
--- a/src/hb-object-private.hh
+++ b/src/hb-object-private.hh
@@ -123,18 +123,9 @@ static inline bool hb_object_destroy (Type *obj)
 }
 
 
-
 HB_BEGIN_DECLS
 
 
-/* Object allocation and lifecycle manamgement macros */
-
-#define HB_OBJECT_DO_CREATE(Type, obj) likely (obj = hb_object_create<Type> ())
-#define HB_OBJECT_IS_INERT(obj) hb_object_is_inert (obj)
-#define HB_OBJECT_DO_REFERENCE(obj) return hb_object_reference (obj)
-#define HB_OBJECT_DO_DESTROY(obj) if (!hb_object_destroy (obj)) return
-
-
 HB_END_DECLS
 
 #endif /* HB_OBJECT_PRIVATE_HH */
diff --git a/src/hb-unicode.cc b/src/hb-unicode.cc
index b756461..9922955 100644
--- a/src/hb-unicode.cc
+++ b/src/hb-unicode.cc
@@ -107,7 +107,7 @@ hb_unicode_funcs_create (hb_unicode_funcs_t *parent)
 {
   hb_unicode_funcs_t *ufuncs;
 
-  if (!HB_OBJECT_DO_CREATE (hb_unicode_funcs_t, ufuncs))
+  if (!(ufuncs = hb_object_create<hb_unicode_funcs_t> ()))
     return &_hb_unicode_funcs_nil;
 
   if (parent != NULL)
@@ -133,13 +133,13 @@ hb_unicode_funcs_create (hb_unicode_funcs_t *parent)
 hb_unicode_funcs_t *
 hb_unicode_funcs_reference (hb_unicode_funcs_t *ufuncs)
 {
-  HB_OBJECT_DO_REFERENCE (ufuncs);
+  return hb_object_reference (ufuncs);
 }
 
 void
 hb_unicode_funcs_destroy (hb_unicode_funcs_t *ufuncs)
 {
-  HB_OBJECT_DO_DESTROY (ufuncs);
+  if (!hb_object_destroy (ufuncs)) return;
 
 #define DESTROY(name) if (ufuncs->destroy.name) ufuncs->destroy.name (ufuncs->user_data.name)
   DESTROY (combining_class);
@@ -158,7 +158,7 @@ hb_unicode_funcs_destroy (hb_unicode_funcs_t *ufuncs)
 void
 hb_unicode_funcs_make_immutable (hb_unicode_funcs_t *ufuncs)
 {
-  if (HB_OBJECT_IS_INERT (ufuncs))
+  if (hb_object_is_inert (ufuncs))
     return;
 
   ufuncs->immutable = TRUE;
commit 8be1420f8fd0e5c53282245d6830efbee5c7409d
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Wed Apr 27 16:14:04 2011 -0400

    [blob] Use HB_FUNC instead of __FUNCTION__

diff --git a/src/hb-blob.cc b/src/hb-blob.cc
index 6e9f40f..7d1925f 100644
--- a/src/hb-blob.cc
+++ b/src/hb-blob.cc
@@ -177,7 +177,7 @@ hb_blob_lock (hb_blob_t *blob)
   hb_mutex_lock (blob->lock);
 
   (void) (HB_DEBUG_BLOB &&
-    fprintf (stderr, "%p %s (%d) -> %p\n", blob, __FUNCTION__,
+    fprintf (stderr, "%p %s (%d) -> %p\n", blob, HB_FUNC,
 	     blob->lock_count, blob->data));
 
   blob->lock_count++;
@@ -196,7 +196,7 @@ hb_blob_unlock (hb_blob_t *blob)
   hb_mutex_lock (blob->lock);
 
   (void) (HB_DEBUG_BLOB &&
-    fprintf (stderr, "%p %s (%d) -> %p\n", blob, __FUNCTION__,
+    fprintf (stderr, "%p %s (%d) -> %p\n", blob, HB_FUNC,
 	     blob->lock_count, blob->data));
 
   assert (blob->lock_count > 0);
@@ -240,28 +240,28 @@ _try_make_writable_inplace_unix_locked (hb_blob_t *blob)
 
   if ((uintptr_t) -1L == pagesize) {
     (void) (HB_DEBUG_BLOB &&
-      fprintf (stderr, "%p %s: failed to get pagesize: %s\n", blob, __FUNCTION__, strerror (errno)));
+      fprintf (stderr, "%p %s: failed to get pagesize: %s\n", blob, HB_FUNC, strerror (errno)));
     return FALSE;
   }
   (void) (HB_DEBUG_BLOB &&
-    fprintf (stderr, "%p %s: pagesize is %lu\n", blob, __FUNCTION__, (unsigned long) pagesize));
+    fprintf (stderr, "%p %s: pagesize is %lu\n", blob, HB_FUNC, (unsigned long) pagesize));
 
   mask = ~(pagesize-1);
   addr = (const char *) (((uintptr_t) blob->data) & mask);
   length = (const char *) (((uintptr_t) blob->data + blob->length + pagesize-1) & mask)  - addr;
   (void) (HB_DEBUG_BLOB &&
     fprintf (stderr, "%p %s: calling mprotect on [%p..%p] (%lu bytes)\n",
-	     blob, __FUNCTION__,
+	     blob, HB_FUNC,
 	     addr, addr+length, (unsigned long) length));
   if (-1 == mprotect ((void *) addr, length, PROT_READ | PROT_WRITE)) {
     (void) (HB_DEBUG_BLOB &&
-      fprintf (stderr, "%p %s: %s\n", blob, __FUNCTION__, strerror (errno)));
+      fprintf (stderr, "%p %s: %s\n", blob, HB_FUNC, strerror (errno)));
     return FALSE;
   }
 
   (void) (HB_DEBUG_BLOB &&
     fprintf (stderr, "%p %s: successfully made [%p..%p] (%lu bytes) writable\n",
-	     blob, __FUNCTION__,
+	     blob, HB_FUNC,
 	     addr, addr+length, (unsigned long) length));
   return TRUE;
 #else
@@ -273,15 +273,15 @@ static void
 try_writable_inplace_locked (hb_blob_t *blob)
 {
   (void) (HB_DEBUG_BLOB &&
-    fprintf (stderr, "%p %s: making writable\n", blob, __FUNCTION__));
+    fprintf (stderr, "%p %s: making writable\n", blob, HB_FUNC));
 
   if (_try_make_writable_inplace_unix_locked (blob)) {
     (void) (HB_DEBUG_BLOB &&
-      fprintf (stderr, "%p %s: making writable -> succeeded\n", blob, __FUNCTION__));
+      fprintf (stderr, "%p %s: making writable -> succeeded\n", blob, HB_FUNC));
     blob->mode = HB_MEMORY_MODE_WRITABLE;
   } else {
     (void) (HB_DEBUG_BLOB &&
-      fprintf (stderr, "%p %s: making writable -> FAILED\n", blob, __FUNCTION__));
+      fprintf (stderr, "%p %s: making writable -> FAILED\n", blob, HB_FUNC));
     /* Failed to make writable inplace, mark that */
     blob->mode = HB_MEMORY_MODE_READONLY;
   }
@@ -325,7 +325,7 @@ hb_blob_try_writable (hb_blob_t *blob)
     char *new_data;
 
     (void) (HB_DEBUG_BLOB &&
-      fprintf (stderr, "%p %s (%d) -> %p\n", blob, __FUNCTION__,
+      fprintf (stderr, "%p %s (%d) -> %p\n", blob, HB_FUNC,
 	       blob->lock_count, blob->data));
 
     if (blob->lock_count)
@@ -334,7 +334,7 @@ hb_blob_try_writable (hb_blob_t *blob)
     new_data = (char *) malloc (blob->length);
     if (new_data) {
       (void) (HB_DEBUG_BLOB &&
-	fprintf (stderr, "%p %s: dupped successfully -> %p\n", blob, __FUNCTION__, blob->data));
+	fprintf (stderr, "%p %s: dupped successfully -> %p\n", blob, HB_FUNC, blob->data));
       memcpy (new_data, blob->data, blob->length);
       _hb_blob_destroy_user_data (blob);
       blob->mode = HB_MEMORY_MODE_WRITABLE;
commit ae008b90cfc2028e878100f78b21d70f923a6044
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Wed Apr 27 16:12:12 2011 -0400

    [object] Add tracing support back in

diff --git a/src/hb-object-private.hh b/src/hb-object-private.hh
index 8142bd4..c3cdeb5 100644
--- a/src/hb-object-private.hh
+++ b/src/hb-object-private.hh
@@ -91,21 +91,37 @@ struct _hb_object_header_t {
 
 HB_END_DECLS
 
-
 template <typename Type>
-static inline Type *hb_object_create () { return (Type *) hb_object_header_t::create (sizeof (Type)); }
-
+static inline void hb_object_trace (const Type *obj, const char *function)
+{
+  obj->header.trace (function);
+}
 template <typename Type>
-static inline bool hb_object_is_inert (const Type *obj) { return unlikely (obj->header.is_inert()); }
-
+static inline Type *hb_object_create ()
+{
+  Type *obj = (Type *) hb_object_header_t::create (sizeof (Type));
+  hb_object_trace (obj, HB_FUNC);
+  return obj;
+}
 template <typename Type>
-static inline Type *hb_object_reference (Type *obj) { obj->header.reference (); return obj; }
-
+static inline bool hb_object_is_inert (const Type *obj)
+{
+  return unlikely (obj->header.is_inert());
+}
 template <typename Type>
-static inline bool hb_object_destroy (Type *obj) { return obj->header.destroy (); }
-
+static inline Type *hb_object_reference (Type *obj)
+{
+  hb_object_trace (obj, HB_FUNC);
+  obj->header.reference ();
+  return obj;
+}
 template <typename Type>
-static inline void hb_object_trace (const Type *obj) { obj->header.trace (__FUNCTION__); }
+static inline bool hb_object_destroy (Type *obj)
+{
+  hb_object_trace (obj, HB_FUNC);
+  return obj->header.destroy ();
+}
+
 
 
 HB_BEGIN_DECLS
@@ -113,8 +129,6 @@ HB_BEGIN_DECLS
 
 /* Object allocation and lifecycle manamgement macros */
 
-/* XXX Trace objects.  Got removed in refactoring */
-#define HB_TRACE_OBJECT(obj) hb_object_trace (obj)
 #define HB_OBJECT_DO_CREATE(Type, obj) likely (obj = hb_object_create<Type> ())
 #define HB_OBJECT_IS_INERT(obj) hb_object_is_inert (obj)
 #define HB_OBJECT_DO_REFERENCE(obj) return hb_object_reference (obj)



More information about the HarfBuzz mailing list