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