Add hb_feature_to/from_string()
diff --git a/src/hb-common.cc b/src/hb-common.cc
index 1301ab2..33a514d 100644
--- a/src/hb-common.cc
+++ b/src/hb-common.cc
@@ -58,6 +58,15 @@
   return HB_TAG_CHAR4 (tag);
 }
 
+void
+hb_tag_to_string (hb_tag_t tag, char *buf)
+{
+  buf[0] = (char) (uint8_t) (tag >> 24);
+  buf[1] = (char) (uint8_t) (tag >> 16);
+  buf[2] = (char) (uint8_t) (tag >>  8);
+  buf[3] = (char) (uint8_t) (tag >>  0);
+}
+
 
 /* hb_direction_t */
 
diff --git a/src/hb-common.h b/src/hb-common.h
index 920bd32..cc221d3 100644
--- a/src/hb-common.h
+++ b/src/hb-common.h
@@ -95,10 +95,14 @@
 
 #define HB_TAG_NONE HB_TAG(0,0,0,0)
 
-/* len=-1 means str is NUL-terminated */
+/* len=-1 means str is NUL-terminated. */
 hb_tag_t
 hb_tag_from_string (const char *str, int len);
 
+/* buf should have 4 bytes. */
+void
+hb_tag_to_string (hb_tag_t tag, char *buf);
+
 
 /* hb_direction_t */
 
diff --git a/src/hb-shape.cc b/src/hb-shape.cc
index 5aa587b..6619c19 100644
--- a/src/hb-shape.cc
+++ b/src/hb-shape.cc
@@ -1,5 +1,6 @@
 /*
  * Copyright © 2009  Red Hat, Inc.
+ * Copyright © 2012  Google, Inc.
  *
  *  This is part of HarfBuzz, a text shaping library.
  *
@@ -22,6 +23,7 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Red Hat Author(s): Behdad Esfahbod
+ * Google Author(s): Behdad Esfahbod
  */
 
 #include "hb-private.hh"
@@ -32,6 +34,172 @@
 #include "hb-font-private.hh"
 
 
+static void
+parse_space (const char **pp, const char *end)
+{
+  char c;
+#define ISSPACE(c) ((c)==' '||(c)=='\f'||(c)=='\n'||(c)=='\r'||(c)=='\t'||(c)=='\v')
+  while (*pp < end && (c = **pp, ISSPACE (c)))
+    (*pp)++;
+#undef ISSPACE
+}
+
+static hb_bool_t
+parse_char (const char **pp, const char *end, char c)
+{
+  parse_space (pp, end);
+
+  if (*pp == end || **pp != c)
+    return false;
+
+  (*pp)++;
+  return true;
+}
+
+static hb_bool_t
+parse_uint (const char **pp, const char *end, unsigned int *pv)
+{
+  char buf[32];
+  strncpy (buf, *pp, end - *pp);
+  buf[ARRAY_LENGTH (buf) - 1] = '\0';
+
+  char *p = buf;
+  char *pend = p;
+  unsigned int v;
+
+  v = strtol (p, &pend, 0);
+
+  if (p == pend)
+    return false;
+
+  *pv = v;
+  *pp += pend - p;
+  return true;
+}
+
+static hb_bool_t
+parse_feature_value_prefix (const char **pp, const char *end, hb_feature_t *feature)
+{
+  if (parse_char (pp, end, '-'))
+    feature->value = 0;
+  else {
+    parse_char (pp, end, '+');
+    feature->value = 1;
+  }
+
+  return true;
+}
+
+static hb_bool_t
+parse_feature_tag (const char **pp, const char *end, hb_feature_t *feature)
+{
+  const char *p = *pp;
+  char c;
+
+  parse_space (pp, end);
+
+#define ISALNUM(c) (('a' <= (c) && (c) <= 'z') || ('A' <= (c) && (c) <= 'Z') || ('0' <= (c) && (c) <= '9'))
+  while (*pp < end && (c = **pp, ISALNUM(c)))
+    (*pp)++;
+#undef ISALNUM
+
+  if (p == *pp)
+    return false;
+
+  feature->tag = hb_tag_from_string (p, *pp - p);
+  return true;
+}
+
+static hb_bool_t
+parse_feature_indices (const char **pp, const char *end, hb_feature_t *feature)
+{
+  parse_space (pp, end);
+
+  hb_bool_t has_start;
+
+  feature->start = 0;
+  feature->end = (unsigned int) -1;
+
+  if (!parse_char (pp, end, '['))
+    return true;
+
+  has_start = parse_uint (pp, end, &feature->start);
+
+  if (parse_char (pp, end, ':')) {
+    parse_uint (pp, end, &feature->end);
+  } else {
+    if (has_start)
+      feature->end = feature->start + 1;
+  }
+
+  return parse_char (pp, end, ']');
+}
+
+static hb_bool_t
+parse_feature_value_postfix (const char **pp, const char *end, hb_feature_t *feature)
+{
+  return !parse_char (pp, end, '=') || parse_uint (pp, end, &feature->value);
+}
+
+
+static hb_bool_t
+parse_one_feature (const char **pp, const char *end, hb_feature_t *feature)
+{
+  return parse_feature_value_prefix (pp, end, feature) &&
+	 parse_feature_tag (pp, end, feature) &&
+	 parse_feature_indices (pp, end, feature) &&
+	 parse_feature_value_postfix (pp, end, feature) &&
+	 *pp == end;
+}
+
+hb_bool_t
+hb_feature_from_string (const char *str, int len,
+			hb_feature_t *feature)
+{
+  if (len < 0)
+    len = strlen (str);
+
+  return parse_one_feature (&str, str + len, feature);
+}
+
+void
+hb_feature_to_string (hb_feature_t *feature,
+		      char *buf, unsigned int size)
+{
+  if (unlikely (!size)) return;
+
+  char s[128];
+  unsigned int len = 0;
+  if (feature->value == 0)
+    s[len++] = '-';
+  hb_tag_to_string (feature->tag, s + len);
+  len += 4;
+  while (len && s[len - 1] == ' ')
+    len--;
+  if (feature->start != 0 || feature->start != (unsigned int) -1)
+  {
+    s[len++] = '[';
+    if (feature->start)
+      len += snprintf (s + len, ARRAY_LENGTH (s) - len, "%d", feature->start);
+    if (feature->end != feature->start + 1) {
+      s[len++] = ':';
+      if (feature->end != (unsigned int) -1)
+	len += snprintf (s + len, ARRAY_LENGTH (s) - len, "%d", feature->end);
+    }
+    s[len++] = ']';
+  }
+  if (feature->value > 1)
+  {
+    s[len++] = '=';
+    len += snprintf (s + len, ARRAY_LENGTH (s) - len, "%d", feature->value);
+  }
+  assert (len < ARRAY_LENGTH (s));
+  len = MIN (len, size - 1);
+  memcpy (buf, s, len);
+  s[len] = '\0';
+}
+
+
 static const char **static_shaper_list;
 
 static
diff --git a/src/hb-shape.h b/src/hb-shape.h
index 84bf3e7..90a188d 100644
--- a/src/hb-shape.h
+++ b/src/hb-shape.h
@@ -1,5 +1,6 @@
 /*
  * Copyright © 2009  Red Hat, Inc.
+ * Copyright © 2012  Google, Inc.
  *
  *  This is part of HarfBuzz, a text shaping library.
  *
@@ -22,6 +23,7 @@
  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * Red Hat Author(s): Behdad Esfahbod
+ * Google Author(s): Behdad Esfahbod
  */
 
 #ifndef HB_H_IN
@@ -45,6 +47,17 @@
   unsigned int  end;
 } hb_feature_t;
 
+/* len=-1 means str is NUL-terminated */
+hb_bool_t
+hb_feature_from_string (const char *str, int len,
+			hb_feature_t *feature);
+
+/* something like 128 bytes is more than enough.
+ * nul-terminates. */
+void
+hb_feature_to_string (hb_feature_t *feature,
+		      char *buf, unsigned int size);
+
 
 void
 hb_shape (hb_font_t           *font,