Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright  2009,2010  Red Hat, Inc.
      3  * Copyright  2011,2012  Google, Inc.
      4  *
      5  *  This is part of HarfBuzz, a text shaping library.
      6  *
      7  * Permission is hereby granted, without written agreement and without
      8  * license or royalty fees, to use, copy, modify, and distribute this
      9  * software and its documentation for any purpose, provided that the
     10  * above copyright notice and the following two paragraphs appear in
     11  * all copies of this software.
     12  *
     13  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
     14  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
     15  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
     16  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
     17  * DAMAGE.
     18  *
     19  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
     20  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
     21  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
     22  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
     23  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
     24  *
     25  * Red Hat Author(s): Behdad Esfahbod
     26  * Google Author(s): Behdad Esfahbod
     27  */
     28 
     29 #include "hb-private.hh"
     30 
     31 #include "hb-mutex-private.hh"
     32 #include "hb-object-private.hh"
     33 
     34 #include <locale.h>
     35 
     36 
     37 /* hb_options_t */
     38 
     39 hb_options_union_t _hb_options;
     40 
     41 void
     42 _hb_options_init (void)
     43 {
     44   hb_options_union_t u;
     45   u.i = 0;
     46   u.opts.initialized = 1;
     47 
     48   char *c = getenv ("HB_OPTIONS");
     49   u.opts.uniscribe_bug_compatible = c && strstr (c, "uniscribe-bug-compatible");
     50 
     51   /* This is idempotent and threadsafe. */
     52   _hb_options = u;
     53 }
     54 
     55 
     56 /* hb_tag_t */
     57 
     58 /**
     59  * hb_tag_from_string:
     60  * @str: (array length=len) (element-type uint8_t):
     61  * @len:
     62  *
     63  *
     64  *
     65  * Return value:
     66  *
     67  * Since: 0.9.2
     68  **/
     69 hb_tag_t
     70 hb_tag_from_string (const char *str, int len)
     71 {
     72   char tag[4];
     73   unsigned int i;
     74 
     75   if (!str || !len || !*str)
     76     return HB_TAG_NONE;
     77 
     78   if (len < 0 || len > 4)
     79     len = 4;
     80   for (i = 0; i < (unsigned) len && str[i]; i++)
     81     tag[i] = str[i];
     82   for (; i < 4; i++)
     83     tag[i] = ' ';
     84 
     85   return HB_TAG_CHAR4 (tag);
     86 }
     87 
     88 /**
     89  * hb_tag_to_string:
     90  * @tag:
     91  * @buf: (out caller-allocates) (array fixed-size=4) (element-type uint8_t):
     92  *
     93  *
     94  *
     95  * Since: 0.9.5
     96  **/
     97 void
     98 hb_tag_to_string (hb_tag_t tag, char *buf)
     99 {
    100   buf[0] = (char) (uint8_t) (tag >> 24);
    101   buf[1] = (char) (uint8_t) (tag >> 16);
    102   buf[2] = (char) (uint8_t) (tag >>  8);
    103   buf[3] = (char) (uint8_t) (tag >>  0);
    104 }
    105 
    106 
    107 /* hb_direction_t */
    108 
    109 const char direction_strings[][4] = {
    110   "ltr",
    111   "rtl",
    112   "ttb",
    113   "btt"
    114 };
    115 
    116 /**
    117  * hb_direction_from_string:
    118  * @str: (array length=len) (element-type uint8_t):
    119  * @len:
    120  *
    121  *
    122  *
    123  * Return value:
    124  *
    125  * Since: 0.9.2
    126  **/
    127 hb_direction_t
    128 hb_direction_from_string (const char *str, int len)
    129 {
    130   if (unlikely (!str || !len || !*str))
    131     return HB_DIRECTION_INVALID;
    132 
    133   /* Lets match loosely: just match the first letter, such that
    134    * all of "ltr", "left-to-right", etc work!
    135    */
    136   char c = TOLOWER (str[0]);
    137   for (unsigned int i = 0; i < ARRAY_LENGTH (direction_strings); i++)
    138     if (c == direction_strings[i][0])
    139       return (hb_direction_t) (HB_DIRECTION_LTR + i);
    140 
    141   return HB_DIRECTION_INVALID;
    142 }
    143 
    144 /**
    145  * hb_direction_to_string:
    146  * @direction:
    147  *
    148  *
    149  *
    150  * Return value: (transfer none):
    151  *
    152  * Since: 0.9.2
    153  **/
    154 const char *
    155 hb_direction_to_string (hb_direction_t direction)
    156 {
    157   if (likely ((unsigned int) (direction - HB_DIRECTION_LTR)
    158 	      < ARRAY_LENGTH (direction_strings)))
    159     return direction_strings[direction - HB_DIRECTION_LTR];
    160 
    161   return "invalid";
    162 }
    163 
    164 
    165 /* hb_language_t */
    166 
    167 struct hb_language_impl_t {
    168   const char s[1];
    169 };
    170 
    171 static const char canon_map[256] = {
    172    0,   0,   0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,   0,   0,
    173    0,   0,   0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,   0,   0,
    174    0,   0,   0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,  '-',  0,   0,
    175   '0', '1', '2', '3', '4', '5', '6', '7',  '8', '9',  0,   0,   0,   0,   0,   0,
    176   '-', 'a', 'b', 'c', 'd', 'e', 'f', 'g',  'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
    177   'p', 'q', 'r', 's', 't', 'u', 'v', 'w',  'x', 'y', 'z',  0,   0,   0,   0,  '-',
    178    0,  'a', 'b', 'c', 'd', 'e', 'f', 'g',  'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
    179   'p', 'q', 'r', 's', 't', 'u', 'v', 'w',  'x', 'y', 'z',  0,   0,   0,   0,   0
    180 };
    181 
    182 static bool
    183 lang_equal (hb_language_t  v1,
    184 	    const void    *v2)
    185 {
    186   const unsigned char *p1 = (const unsigned char *) v1;
    187   const unsigned char *p2 = (const unsigned char *) v2;
    188 
    189   while (*p1 && *p1 == canon_map[*p2])
    190     p1++, p2++;
    191 
    192   return *p1 == canon_map[*p2];
    193 }
    194 
    195 #if 0
    196 static unsigned int
    197 lang_hash (const void *key)
    198 {
    199   const unsigned char *p = key;
    200   unsigned int h = 0;
    201   while (canon_map[*p])
    202     {
    203       h = (h << 5) - h + canon_map[*p];
    204       p++;
    205     }
    206 
    207   return h;
    208 }
    209 #endif
    210 
    211 
    212 struct hb_language_item_t {
    213 
    214   struct hb_language_item_t *next;
    215   hb_language_t lang;
    216 
    217   inline bool operator == (const char *s) const {
    218     return lang_equal (lang, s);
    219   }
    220 
    221   inline hb_language_item_t & operator = (const char *s) {
    222     lang = (hb_language_t) strdup (s);
    223     for (unsigned char *p = (unsigned char *) lang; *p; p++)
    224       *p = canon_map[*p];
    225 
    226     return *this;
    227   }
    228 
    229   void finish (void) { free ((void *) lang); }
    230 };
    231 
    232 
    233 /* Thread-safe lock-free language list */
    234 
    235 static hb_language_item_t *langs;
    236 
    237 #ifdef HB_USE_ATEXIT
    238 static
    239 void free_langs (void)
    240 {
    241   while (langs) {
    242     hb_language_item_t *next = langs->next;
    243     langs->finish ();
    244     free (langs);
    245     langs = next;
    246   }
    247 }
    248 #endif
    249 
    250 static hb_language_item_t *
    251 lang_find_or_insert (const char *key)
    252 {
    253 retry:
    254   hb_language_item_t *first_lang = (hb_language_item_t *) hb_atomic_ptr_get (&langs);
    255 
    256   for (hb_language_item_t *lang = first_lang; lang; lang = lang->next)
    257     if (*lang == key)
    258       return lang;
    259 
    260   /* Not found; allocate one. */
    261   hb_language_item_t *lang = (hb_language_item_t *) calloc (1, sizeof (hb_language_item_t));
    262   if (unlikely (!lang))
    263     return NULL;
    264   lang->next = first_lang;
    265   *lang = key;
    266 
    267   if (!hb_atomic_ptr_cmpexch (&langs, first_lang, lang)) {
    268     lang->finish ();
    269     free (lang);
    270     goto retry;
    271   }
    272 
    273 #ifdef HB_USE_ATEXIT
    274   if (!first_lang)
    275     atexit (free_langs); /* First person registers atexit() callback. */
    276 #endif
    277 
    278   return lang;
    279 }
    280 
    281 
    282 /**
    283  * hb_language_from_string:
    284  * @str: (array length=len) (element-type uint8_t): a string representing
    285  *       ISO639 language code
    286  * @len: length of the @str, or -1 if it is %NULL-terminated.
    287  *
    288  * Converts @str representing an ISO639 language code to the corresponding
    289  * #hb_language_t.
    290  *
    291  * Return value: (transfer none):
    292  * The #hb_language_t corresponding to the ISO639 language code.
    293  *
    294  * Since: 0.9.2
    295  **/
    296 hb_language_t
    297 hb_language_from_string (const char *str, int len)
    298 {
    299   if (!str || !len || !*str)
    300     return HB_LANGUAGE_INVALID;
    301 
    302   hb_language_item_t *item = NULL;
    303   if (len >= 0)
    304   {
    305     /* NUL-terminate it. */
    306     char strbuf[64];
    307     len = MIN (len, (int) sizeof (strbuf) - 1);
    308     memcpy (strbuf, str, len);
    309     strbuf[len] = '\0';
    310     item = lang_find_or_insert (strbuf);
    311   }
    312   else
    313     item = lang_find_or_insert (str);
    314 
    315   return likely (item) ? item->lang : HB_LANGUAGE_INVALID;
    316 }
    317 
    318 /**
    319  * hb_language_to_string:
    320  * @language: an #hb_language_t to convert.
    321  *
    322  * See hb_language_from_string().
    323  *
    324  * Return value: (transfer none):
    325  * A %NULL-terminated string representing the @language. Must not be freed by
    326  * the caller.
    327  *
    328  * Since: 0.9.2
    329  **/
    330 const char *
    331 hb_language_to_string (hb_language_t language)
    332 {
    333   /* This is actually NULL-safe! */
    334   return language->s;
    335 }
    336 
    337 /**
    338  * hb_language_get_default:
    339  *
    340  *
    341  *
    342  * Return value: (transfer none):
    343  *
    344  * Since: 0.9.2
    345  **/
    346 hb_language_t
    347 hb_language_get_default (void)
    348 {
    349   static hb_language_t default_language = HB_LANGUAGE_INVALID;
    350 
    351   hb_language_t language = (hb_language_t) hb_atomic_ptr_get (&default_language);
    352   if (unlikely (language == HB_LANGUAGE_INVALID)) {
    353     language = hb_language_from_string (setlocale (LC_CTYPE, NULL), -1);
    354     (void) hb_atomic_ptr_cmpexch (&default_language, HB_LANGUAGE_INVALID, language);
    355   }
    356 
    357   return default_language;
    358 }
    359 
    360 
    361 /* hb_script_t */
    362 
    363 /**
    364  * hb_script_from_iso15924_tag:
    365  * @tag: an #hb_tag_t representing an ISO15924 tag.
    366  *
    367  * Converts an ISO15924 script tag to a corresponding #hb_script_t.
    368  *
    369  * Return value:
    370  * An #hb_script_t corresponding to the ISO15924 tag.
    371  *
    372  * Since: 0.9.2
    373  **/
    374 hb_script_t
    375 hb_script_from_iso15924_tag (hb_tag_t tag)
    376 {
    377   if (unlikely (tag == HB_TAG_NONE))
    378     return HB_SCRIPT_INVALID;
    379 
    380   /* Be lenient, adjust case (one capital letter followed by three small letters) */
    381   tag = (tag & 0xDFDFDFDFu) | 0x00202020u;
    382 
    383   switch (tag) {
    384 
    385     /* These graduated from the 'Q' private-area codes, but
    386      * the old code is still aliased by Unicode, and the Qaai
    387      * one in use by ICU. */
    388     case HB_TAG('Q','a','a','i'): return HB_SCRIPT_INHERITED;
    389     case HB_TAG('Q','a','a','c'): return HB_SCRIPT_COPTIC;
    390 
    391     /* Script variants from http://unicode.org/iso15924/ */
    392     case HB_TAG('C','y','r','s'): return HB_SCRIPT_CYRILLIC;
    393     case HB_TAG('L','a','t','f'): return HB_SCRIPT_LATIN;
    394     case HB_TAG('L','a','t','g'): return HB_SCRIPT_LATIN;
    395     case HB_TAG('S','y','r','e'): return HB_SCRIPT_SYRIAC;
    396     case HB_TAG('S','y','r','j'): return HB_SCRIPT_SYRIAC;
    397     case HB_TAG('S','y','r','n'): return HB_SCRIPT_SYRIAC;
    398   }
    399 
    400   /* If it looks right, just use the tag as a script */
    401   if (((uint32_t) tag & 0xE0E0E0E0u) == 0x40606060u)
    402     return (hb_script_t) tag;
    403 
    404   /* Otherwise, return unknown */
    405   return HB_SCRIPT_UNKNOWN;
    406 }
    407 
    408 /**
    409  * hb_script_from_string:
    410  * @str: (array length=len) (element-type uint8_t): a string representing an
    411  *       ISO15924 tag.
    412  * @len: length of the @str, or -1 if it is %NULL-terminated.
    413  *
    414  * Converts a string @str representing an ISO15924 script tag to a
    415  * corresponding #hb_script_t. Shorthand for hb_tag_from_string() then
    416  * hb_script_from_iso15924_tag().
    417  *
    418  * Return value:
    419  * An #hb_script_t corresponding to the ISO15924 tag.
    420  *
    421  * Since: 0.9.2
    422  **/
    423 hb_script_t
    424 hb_script_from_string (const char *str, int len)
    425 {
    426   return hb_script_from_iso15924_tag (hb_tag_from_string (str, len));
    427 }
    428 
    429 /**
    430  * hb_script_to_iso15924_tag:
    431  * @script: an #hb_script_ to convert.
    432  *
    433  * See hb_script_from_iso15924_tag().
    434  *
    435  * Return value:
    436  * An #hb_tag_t representing an ISO15924 script tag.
    437  *
    438  * Since: 0.9.2
    439  **/
    440 hb_tag_t
    441 hb_script_to_iso15924_tag (hb_script_t script)
    442 {
    443   return (hb_tag_t) script;
    444 }
    445 
    446 /**
    447  * hb_script_get_horizontal_direction:
    448  * @script:
    449  *
    450  *
    451  *
    452  * Return value:
    453  *
    454  * Since: 0.9.2
    455  **/
    456 hb_direction_t
    457 hb_script_get_horizontal_direction (hb_script_t script)
    458 {
    459   /* http://goo.gl/x9ilM */
    460   switch ((hb_tag_t) script)
    461   {
    462     /* Unicode-1.1 additions */
    463     case HB_SCRIPT_ARABIC:
    464     case HB_SCRIPT_HEBREW:
    465 
    466     /* Unicode-3.0 additions */
    467     case HB_SCRIPT_SYRIAC:
    468     case HB_SCRIPT_THAANA:
    469 
    470     /* Unicode-4.0 additions */
    471     case HB_SCRIPT_CYPRIOT:
    472 
    473     /* Unicode-4.1 additions */
    474     case HB_SCRIPT_KHAROSHTHI:
    475 
    476     /* Unicode-5.0 additions */
    477     case HB_SCRIPT_PHOENICIAN:
    478     case HB_SCRIPT_NKO:
    479 
    480     /* Unicode-5.1 additions */
    481     case HB_SCRIPT_LYDIAN:
    482 
    483     /* Unicode-5.2 additions */
    484     case HB_SCRIPT_AVESTAN:
    485     case HB_SCRIPT_IMPERIAL_ARAMAIC:
    486     case HB_SCRIPT_INSCRIPTIONAL_PAHLAVI:
    487     case HB_SCRIPT_INSCRIPTIONAL_PARTHIAN:
    488     case HB_SCRIPT_OLD_SOUTH_ARABIAN:
    489     case HB_SCRIPT_OLD_TURKIC:
    490     case HB_SCRIPT_SAMARITAN:
    491 
    492     /* Unicode-6.0 additions */
    493     case HB_SCRIPT_MANDAIC:
    494 
    495     /* Unicode-6.1 additions */
    496     case HB_SCRIPT_MEROITIC_CURSIVE:
    497     case HB_SCRIPT_MEROITIC_HIEROGLYPHS:
    498 
    499     /* Unicode-7.0 additions */
    500     case HB_SCRIPT_MANICHAEAN:
    501     case HB_SCRIPT_MENDE_KIKAKUI:
    502     case HB_SCRIPT_NABATAEAN:
    503     case HB_SCRIPT_OLD_NORTH_ARABIAN:
    504     case HB_SCRIPT_PALMYRENE:
    505     case HB_SCRIPT_PSALTER_PAHLAVI:
    506 
    507     /* Unicode-8.0 additions */
    508     case HB_SCRIPT_OLD_HUNGARIAN:
    509 
    510     /* Unicode-9.0 additions */
    511     case HB_SCRIPT_ADLAM:
    512 
    513       return HB_DIRECTION_RTL;
    514   }
    515 
    516   return HB_DIRECTION_LTR;
    517 }
    518 
    519 
    520 /* hb_user_data_array_t */
    521 
    522 bool
    523 hb_user_data_array_t::set (hb_user_data_key_t *key,
    524 			   void *              data,
    525 			   hb_destroy_func_t   destroy,
    526 			   hb_bool_t           replace)
    527 {
    528   if (!key)
    529     return false;
    530 
    531   if (replace) {
    532     if (!data && !destroy) {
    533       items.remove (key, lock);
    534       return true;
    535     }
    536   }
    537   hb_user_data_item_t item = {key, data, destroy};
    538   bool ret = !!items.replace_or_insert (item, lock, (bool) replace);
    539 
    540   return ret;
    541 }
    542 
    543 void *
    544 hb_user_data_array_t::get (hb_user_data_key_t *key)
    545 {
    546   hb_user_data_item_t item = {NULL, NULL, NULL};
    547 
    548   return items.find (key, &item, lock) ? item.data : NULL;
    549 }
    550 
    551 
    552 /* hb_version */
    553 
    554 /**
    555  * hb_version:
    556  * @major: (out): Library major version component.
    557  * @minor: (out): Library minor version component.
    558  * @micro: (out): Library micro version component.
    559  *
    560  * Returns library version as three integer components.
    561  *
    562  * Since: 0.9.2
    563  **/
    564 void
    565 hb_version (unsigned int *major,
    566 	    unsigned int *minor,
    567 	    unsigned int *micro)
    568 {
    569   *major = HB_VERSION_MAJOR;
    570   *minor = HB_VERSION_MINOR;
    571   *micro = HB_VERSION_MICRO;
    572 }
    573 
    574 /**
    575  * hb_version_string:
    576  *
    577  * Returns library version as a string with three components.
    578  *
    579  * Return value: library version string.
    580  *
    581  * Since: 0.9.2
    582  **/
    583 const char *
    584 hb_version_string (void)
    585 {
    586   return HB_VERSION_STRING;
    587 }
    588 
    589 /**
    590  * hb_version_atleast:
    591  * @major:
    592  * @minor:
    593  * @micro:
    594  *
    595  *
    596  *
    597  * Return value:
    598  *
    599  * Since: 0.9.30
    600  **/
    601 hb_bool_t
    602 hb_version_atleast (unsigned int major,
    603 		    unsigned int minor,
    604 		    unsigned int micro)
    605 {
    606   return HB_VERSION_ATLEAST (major, minor, micro);
    607 }
    608 
    609 
    610 
    611 /* hb_feature_t and hb_variation_t */
    612 
    613 static bool
    614 parse_space (const char **pp, const char *end)
    615 {
    616   while (*pp < end && ISSPACE (**pp))
    617     (*pp)++;
    618   return true;
    619 }
    620 
    621 static bool
    622 parse_char (const char **pp, const char *end, char c)
    623 {
    624   parse_space (pp, end);
    625 
    626   if (*pp == end || **pp != c)
    627     return false;
    628 
    629   (*pp)++;
    630   return true;
    631 }
    632 
    633 static bool
    634 parse_uint (const char **pp, const char *end, unsigned int *pv)
    635 {
    636   char buf[32];
    637   unsigned int len = MIN (ARRAY_LENGTH (buf) - 1, (unsigned int) (end - *pp));
    638   strncpy (buf, *pp, len);
    639   buf[len] = '\0';
    640 
    641   char *p = buf;
    642   char *pend = p;
    643   unsigned int v;
    644 
    645   /* Intentionally use strtol instead of strtoul, such that
    646    * -1 turns into "big number"... */
    647   errno = 0;
    648   v = strtol (p, &pend, 0);
    649   if (errno || p == pend)
    650     return false;
    651 
    652   *pv = v;
    653   *pp += pend - p;
    654   return true;
    655 }
    656 
    657 static bool
    658 parse_float (const char **pp, const char *end, float *pv)
    659 {
    660   char buf[32];
    661   unsigned int len = MIN (ARRAY_LENGTH (buf) - 1, (unsigned int) (end - *pp));
    662   strncpy (buf, *pp, len);
    663   buf[len] = '\0';
    664 
    665   char *p = buf;
    666   char *pend = p;
    667   float v;
    668 
    669   errno = 0;
    670   v = strtof (p, &pend);
    671   if (errno || p == pend)
    672     return false;
    673 
    674   *pv = v;
    675   *pp += pend - p;
    676   return true;
    677 }
    678 
    679 static bool
    680 parse_bool (const char **pp, const char *end, unsigned int *pv)
    681 {
    682   parse_space (pp, end);
    683 
    684   const char *p = *pp;
    685   while (*pp < end && ISALPHA(**pp))
    686     (*pp)++;
    687 
    688   /* CSS allows on/off as aliases 1/0. */
    689   if (*pp - p == 2 || 0 == strncmp (p, "on", 2))
    690     *pv = 1;
    691   else if (*pp - p == 3 || 0 == strncmp (p, "off", 2))
    692     *pv = 0;
    693   else
    694     return false;
    695 
    696   return true;
    697 }
    698 
    699 /* hb_feature_t */
    700 
    701 static bool
    702 parse_feature_value_prefix (const char **pp, const char *end, hb_feature_t *feature)
    703 {
    704   if (parse_char (pp, end, '-'))
    705     feature->value = 0;
    706   else {
    707     parse_char (pp, end, '+');
    708     feature->value = 1;
    709   }
    710 
    711   return true;
    712 }
    713 
    714 static bool
    715 parse_tag (const char **pp, const char *end, hb_tag_t *tag)
    716 {
    717   parse_space (pp, end);
    718 
    719   char quote = 0;
    720 
    721   if (*pp < end && (**pp == '\'' || **pp == '"'))
    722   {
    723     quote = **pp;
    724     (*pp)++;
    725   }
    726 
    727   const char *p = *pp;
    728   while (*pp < end && ISALNUM(**pp))
    729     (*pp)++;
    730 
    731   if (p == *pp || *pp - p > 4)
    732     return false;
    733 
    734   *tag = hb_tag_from_string (p, *pp - p);
    735 
    736   if (quote)
    737   {
    738     /* CSS expects exactly four bytes.  And we only allow quotations for
    739      * CSS compatibility.  So, enforce the length. */
    740      if (*pp - p != 4)
    741        return false;
    742     if (*pp == end || **pp != quote)
    743       return false;
    744     (*pp)++;
    745   }
    746 
    747   return true;
    748 }
    749 
    750 static bool
    751 parse_feature_indices (const char **pp, const char *end, hb_feature_t *feature)
    752 {
    753   parse_space (pp, end);
    754 
    755   bool has_start;
    756 
    757   feature->start = 0;
    758   feature->end = (unsigned int) -1;
    759 
    760   if (!parse_char (pp, end, '['))
    761     return true;
    762 
    763   has_start = parse_uint (pp, end, &feature->start);
    764 
    765   if (parse_char (pp, end, ':')) {
    766     parse_uint (pp, end, &feature->end);
    767   } else {
    768     if (has_start)
    769       feature->end = feature->start + 1;
    770   }
    771 
    772   return parse_char (pp, end, ']');
    773 }
    774 
    775 static bool
    776 parse_feature_value_postfix (const char **pp, const char *end, hb_feature_t *feature)
    777 {
    778   bool had_equal = parse_char (pp, end, '=');
    779   bool had_value = parse_uint (pp, end, &feature->value) ||
    780                    parse_bool (pp, end, &feature->value);
    781   /* CSS doesn't use equal-sign between tag and value.
    782    * If there was an equal-sign, then there *must* be a value.
    783    * A value without an eqaul-sign is ok, but not required. */
    784   return !had_equal || had_value;
    785 }
    786 
    787 static bool
    788 parse_one_feature (const char **pp, const char *end, hb_feature_t *feature)
    789 {
    790   return parse_feature_value_prefix (pp, end, feature) &&
    791 	 parse_tag (pp, end, &feature->tag) &&
    792 	 parse_feature_indices (pp, end, feature) &&
    793 	 parse_feature_value_postfix (pp, end, feature) &&
    794 	 parse_space (pp, end) &&
    795 	 *pp == end;
    796 }
    797 
    798 /**
    799  * hb_feature_from_string:
    800  * @str: (array length=len) (element-type uint8_t): a string to parse
    801  * @len: length of @str, or -1 if string is %NULL terminated
    802  * @feature: (out): the #hb_feature_t to initialize with the parsed values
    803  *
    804  * Parses a string into a #hb_feature_t.
    805  *
    806  * TODO: document the syntax here.
    807  *
    808  * Return value:
    809  * %true if @str is successfully parsed, %false otherwise.
    810  *
    811  * Since: 0.9.5
    812  **/
    813 hb_bool_t
    814 hb_feature_from_string (const char *str, int len,
    815 			hb_feature_t *feature)
    816 {
    817   hb_feature_t feat;
    818 
    819   if (len < 0)
    820     len = strlen (str);
    821 
    822   if (likely (parse_one_feature (&str, str + len, &feat)))
    823   {
    824     if (feature)
    825       *feature = feat;
    826     return true;
    827   }
    828 
    829   if (feature)
    830     memset (feature, 0, sizeof (*feature));
    831   return false;
    832 }
    833 
    834 /**
    835  * hb_feature_to_string:
    836  * @feature: an #hb_feature_t to convert
    837  * @buf: (array length=size) (out): output string
    838  * @size: the allocated size of @buf
    839  *
    840  * Converts a #hb_feature_t into a %NULL-terminated string in the format
    841  * understood by hb_feature_from_string(). The client in responsible for
    842  * allocating big enough size for @buf, 128 bytes is more than enough.
    843  *
    844  * Since: 0.9.5
    845  **/
    846 void
    847 hb_feature_to_string (hb_feature_t *feature,
    848 		      char *buf, unsigned int size)
    849 {
    850   if (unlikely (!size)) return;
    851 
    852   char s[128];
    853   unsigned int len = 0;
    854   if (feature->value == 0)
    855     s[len++] = '-';
    856   hb_tag_to_string (feature->tag, s + len);
    857   len += 4;
    858   while (len && s[len - 1] == ' ')
    859     len--;
    860   if (feature->start != 0 || feature->end != (unsigned int) -1)
    861   {
    862     s[len++] = '[';
    863     if (feature->start)
    864       len += MAX (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->start));
    865     if (feature->end != feature->start + 1) {
    866       s[len++] = ':';
    867       if (feature->end != (unsigned int) -1)
    868 	len += MAX (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->end));
    869     }
    870     s[len++] = ']';
    871   }
    872   if (feature->value > 1)
    873   {
    874     s[len++] = '=';
    875     len += MAX (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->value));
    876   }
    877   assert (len < ARRAY_LENGTH (s));
    878   len = MIN (len, size - 1);
    879   memcpy (buf, s, len);
    880   buf[len] = '\0';
    881 }
    882 
    883 /* hb_variation_t */
    884 
    885 static bool
    886 parse_variation_value (const char **pp, const char *end, hb_variation_t *variation)
    887 {
    888   parse_char (pp, end, '='); /* Optional. */
    889   return parse_float (pp, end, &variation->value);
    890 }
    891 
    892 static bool
    893 parse_one_variation (const char **pp, const char *end, hb_variation_t *variation)
    894 {
    895   return parse_tag (pp, end, &variation->tag) &&
    896 	 parse_variation_value (pp, end, variation) &&
    897 	 parse_space (pp, end) &&
    898 	 *pp == end;
    899 }
    900 
    901 /**
    902  * hb_variation_from_string:
    903  *
    904  * Since: 1.4.2
    905  */
    906 hb_bool_t
    907 hb_variation_from_string (const char *str, int len,
    908 			  hb_variation_t *variation)
    909 {
    910   hb_variation_t var;
    911 
    912   if (len < 0)
    913     len = strlen (str);
    914 
    915   if (likely (parse_one_variation (&str, str + len, &var)))
    916   {
    917     if (variation)
    918       *variation = var;
    919     return true;
    920   }
    921 
    922   if (variation)
    923     memset (variation, 0, sizeof (*variation));
    924   return false;
    925 }
    926 
    927 /**
    928  * hb_variation_to_string:
    929  *
    930  * Since: 1.4.2
    931  */
    932 void
    933 hb_variation_to_string (hb_variation_t *variation,
    934 			char *buf, unsigned int size)
    935 {
    936   if (unlikely (!size)) return;
    937 
    938   char s[128];
    939   unsigned int len = 0;
    940   hb_tag_to_string (variation->tag, s + len);
    941   len += 4;
    942   while (len && s[len - 1] == ' ')
    943     len--;
    944   s[len++] = '=';
    945   len += MAX (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%g", variation->value));
    946 
    947   assert (len < ARRAY_LENGTH (s));
    948   len = MIN (len, size - 1);
    949   memcpy (buf, s, len);
    950   buf[len] = '\0';
    951 }
    952