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