2 * Copyright © 2009,2010 Red Hat, Inc.
3 * Copyright © 2011,2012 Google, Inc.
5 * This is part of HarfBuzz, a text shaping library.
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.
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
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.
25 * Red Hat Author(s): Behdad Esfahbod
26 * Google Author(s): Behdad Esfahbod
30 #include "hb-machinery.hh"
34 #ifdef HB_NO_SETLOCALE
35 #define setlocale(Category, Locale) "C"
41 * @short_description: Common data types
44 * Common data types used across HarfBuzz are defined here.
50 hb_atomic_int_t _hb_options;
57 u.opts.initialized = true;
59 const char *c = getenv ("HB_OPTIONS");
64 const char *p = strchr (c, ':');
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)
71 OPTION ("uniscribe-bug-compatible", uniscribe_bug_compatible);
81 /* This is idempotent and threadsafe. */
82 _hb_options.set_relaxed (u.i);
90 * @str: (array length=len) (element-type uint8_t):
100 hb_tag_from_string (const char *str, int len)
105 if (!str || !len || !*str)
108 if (len < 0 || len > 4)
110 for (i = 0; i < (unsigned) len && str[i]; i++)
115 return HB_TAG (tag[0], tag[1], tag[2], tag[3]);
121 * @buf: (out caller-allocates) (array fixed-size=4) (element-type uint8_t):
128 hb_tag_to_string (hb_tag_t tag, char *buf)
130 buf[0] = (char) (uint8_t) (tag >> 24);
131 buf[1] = (char) (uint8_t) (tag >> 16);
132 buf[2] = (char) (uint8_t) (tag >> 8);
133 buf[3] = (char) (uint8_t) (tag >> 0);
139 const char direction_strings[][4] = {
147 * hb_direction_from_string:
148 * @str: (array length=len) (element-type uint8_t):
158 hb_direction_from_string (const char *str, int len)
160 if (unlikely (!str || !len || !*str))
161 return HB_DIRECTION_INVALID;
163 /* Lets match loosely: just match the first letter, such that
164 * all of "ltr", "left-to-right", etc work!
166 char c = TOLOWER (str[0]);
167 for (unsigned int i = 0; i < ARRAY_LENGTH (direction_strings); i++)
168 if (c == direction_strings[i][0])
169 return (hb_direction_t) (HB_DIRECTION_LTR + i);
171 return HB_DIRECTION_INVALID;
175 * hb_direction_to_string:
180 * Return value: (transfer none):
185 hb_direction_to_string (hb_direction_t direction)
187 if (likely ((unsigned int) (direction - HB_DIRECTION_LTR)
188 < ARRAY_LENGTH (direction_strings)))
189 return direction_strings[direction - HB_DIRECTION_LTR];
197 struct hb_language_impl_t {
201 static const char canon_map[256] = {
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, 0,
204 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0,
205 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
206 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
207 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, '-',
208 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
209 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0
213 lang_equal (hb_language_t v1,
216 const unsigned char *p1 = (const unsigned char *) v1;
217 const unsigned char *p2 = (const unsigned char *) v2;
219 while (*p1 && *p1 == canon_map[*p2]) {
224 return *p1 == canon_map[*p2];
229 lang_hash (const void *key)
231 const unsigned char *p = key;
233 while (canon_map[*p])
235 h = (h << 5) - h + canon_map[*p];
244 struct hb_language_item_t {
246 struct hb_language_item_t *next;
249 bool operator == (const char *s) const
250 { return lang_equal (lang, s); }
252 hb_language_item_t & operator = (const char *s) {
253 /* If a custom allocated is used calling strdup() pairs
254 badly with a call to the custom free() in fini() below.
255 Therefore don't call strdup(), implement its behavior.
257 size_t len = strlen(s) + 1;
258 lang = (hb_language_t) malloc(len);
261 memcpy((unsigned char *) lang, s, len);
262 for (unsigned char *p = (unsigned char *) lang; *p; p++)
269 void fini () { free ((void *) lang); }
273 /* Thread-safe lock-free language list */
275 static hb_atomic_ptr_t <hb_language_item_t> langs;
282 hb_language_item_t *first_lang = langs;
283 if (unlikely (!langs.cmpexch (first_lang, nullptr)))
287 hb_language_item_t *next = first_lang->next;
295 static hb_language_item_t *
296 lang_find_or_insert (const char *key)
299 hb_language_item_t *first_lang = langs;
301 for (hb_language_item_t *lang = first_lang; lang; lang = lang->next)
305 /* Not found; allocate one. */
306 hb_language_item_t *lang = (hb_language_item_t *) calloc (1, sizeof (hb_language_item_t));
307 if (unlikely (!lang))
309 lang->next = first_lang;
311 if (unlikely (!lang->lang))
317 if (unlikely (!langs.cmpexch (first_lang, lang)))
326 atexit (free_langs); /* First person registers atexit() callback. */
334 * hb_language_from_string:
335 * @str: (array length=len) (element-type uint8_t): a string representing
336 * a BCP 47 language tag
337 * @len: length of the @str, or -1 if it is %NULL-terminated.
339 * Converts @str representing a BCP 47 language tag to the corresponding
342 * Return value: (transfer none):
343 * The #hb_language_t corresponding to the BCP 47 language tag.
348 hb_language_from_string (const char *str, int len)
350 if (!str || !len || !*str)
351 return HB_LANGUAGE_INVALID;
353 hb_language_item_t *item = nullptr;
356 /* NUL-terminate it. */
358 len = hb_min (len, (int) sizeof (strbuf) - 1);
359 memcpy (strbuf, str, len);
361 item = lang_find_or_insert (strbuf);
364 item = lang_find_or_insert (str);
366 return likely (item) ? item->lang : HB_LANGUAGE_INVALID;
370 * hb_language_to_string:
371 * @language: an #hb_language_t to convert.
373 * See hb_language_from_string().
375 * Return value: (transfer none):
376 * A %NULL-terminated string representing the @language. Must not be freed by
382 hb_language_to_string (hb_language_t language)
384 if (unlikely (!language)) return nullptr;
390 * hb_language_get_default:
392 * Get default language from current locale.
394 * Note that the first time this function is called, it calls
395 * "setlocale (LC_CTYPE, nullptr)" to fetch current locale. The underlying
396 * setlocale function is, in many implementations, NOT threadsafe. To avoid
397 * problems, call this function once before multiple threads can call it.
398 * This function is only used from hb_buffer_guess_segment_properties() by
401 * Return value: (transfer none):
406 hb_language_get_default ()
408 static hb_atomic_ptr_t <hb_language_t> default_language;
410 hb_language_t language = default_language;
411 if (unlikely (language == HB_LANGUAGE_INVALID))
413 language = hb_language_from_string (setlocale (LC_CTYPE, nullptr), -1);
414 (void) default_language.cmpexch (HB_LANGUAGE_INVALID, language);
424 * hb_script_from_iso15924_tag:
425 * @tag: an #hb_tag_t representing an ISO 15924 tag.
427 * Converts an ISO 15924 script tag to a corresponding #hb_script_t.
430 * An #hb_script_t corresponding to the ISO 15924 tag.
435 hb_script_from_iso15924_tag (hb_tag_t tag)
437 if (unlikely (tag == HB_TAG_NONE))
438 return HB_SCRIPT_INVALID;
440 /* Be lenient, adjust case (one capital letter followed by three small letters) */
441 tag = (tag & 0xDFDFDFDFu) | 0x00202020u;
445 /* These graduated from the 'Q' private-area codes, but
446 * the old code is still aliased by Unicode, and the Qaai
447 * one in use by ICU. */
448 case HB_TAG('Q','a','a','i'): return HB_SCRIPT_INHERITED;
449 case HB_TAG('Q','a','a','c'): return HB_SCRIPT_COPTIC;
451 /* Script variants from https://unicode.org/iso15924/ */
452 case HB_TAG('C','y','r','s'): return HB_SCRIPT_CYRILLIC;
453 case HB_TAG('L','a','t','f'): return HB_SCRIPT_LATIN;
454 case HB_TAG('L','a','t','g'): return HB_SCRIPT_LATIN;
455 case HB_TAG('S','y','r','e'): return HB_SCRIPT_SYRIAC;
456 case HB_TAG('S','y','r','j'): return HB_SCRIPT_SYRIAC;
457 case HB_TAG('S','y','r','n'): return HB_SCRIPT_SYRIAC;
460 /* If it looks right, just use the tag as a script */
461 if (((uint32_t) tag & 0xE0E0E0E0u) == 0x40606060u)
462 return (hb_script_t) tag;
464 /* Otherwise, return unknown */
465 return HB_SCRIPT_UNKNOWN;
469 * hb_script_from_string:
470 * @str: (array length=len) (element-type uint8_t): a string representing an
472 * @len: length of the @str, or -1 if it is %NULL-terminated.
474 * Converts a string @str representing an ISO 15924 script tag to a
475 * corresponding #hb_script_t. Shorthand for hb_tag_from_string() then
476 * hb_script_from_iso15924_tag().
479 * An #hb_script_t corresponding to the ISO 15924 tag.
484 hb_script_from_string (const char *str, int len)
486 return hb_script_from_iso15924_tag (hb_tag_from_string (str, len));
490 * hb_script_to_iso15924_tag:
491 * @script: an #hb_script_t to convert.
493 * See hb_script_from_iso15924_tag().
496 * An #hb_tag_t representing an ISO 15924 script tag.
501 hb_script_to_iso15924_tag (hb_script_t script)
503 return (hb_tag_t) script;
507 * hb_script_get_horizontal_direction:
517 hb_script_get_horizontal_direction (hb_script_t script)
519 /* https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o */
520 switch ((hb_tag_t) script)
522 /* Unicode-1.1 additions */
523 case HB_SCRIPT_ARABIC:
524 case HB_SCRIPT_HEBREW:
526 /* Unicode-3.0 additions */
527 case HB_SCRIPT_SYRIAC:
528 case HB_SCRIPT_THAANA:
530 /* Unicode-4.0 additions */
531 case HB_SCRIPT_CYPRIOT:
533 /* Unicode-4.1 additions */
534 case HB_SCRIPT_KHAROSHTHI:
536 /* Unicode-5.0 additions */
537 case HB_SCRIPT_PHOENICIAN:
540 /* Unicode-5.1 additions */
541 case HB_SCRIPT_LYDIAN:
543 /* Unicode-5.2 additions */
544 case HB_SCRIPT_AVESTAN:
545 case HB_SCRIPT_IMPERIAL_ARAMAIC:
546 case HB_SCRIPT_INSCRIPTIONAL_PAHLAVI:
547 case HB_SCRIPT_INSCRIPTIONAL_PARTHIAN:
548 case HB_SCRIPT_OLD_SOUTH_ARABIAN:
549 case HB_SCRIPT_OLD_TURKIC:
550 case HB_SCRIPT_SAMARITAN:
552 /* Unicode-6.0 additions */
553 case HB_SCRIPT_MANDAIC:
555 /* Unicode-6.1 additions */
556 case HB_SCRIPT_MEROITIC_CURSIVE:
557 case HB_SCRIPT_MEROITIC_HIEROGLYPHS:
559 /* Unicode-7.0 additions */
560 case HB_SCRIPT_MANICHAEAN:
561 case HB_SCRIPT_MENDE_KIKAKUI:
562 case HB_SCRIPT_NABATAEAN:
563 case HB_SCRIPT_OLD_NORTH_ARABIAN:
564 case HB_SCRIPT_PALMYRENE:
565 case HB_SCRIPT_PSALTER_PAHLAVI:
567 /* Unicode-8.0 additions */
568 case HB_SCRIPT_HATRAN:
570 /* Unicode-9.0 additions */
571 case HB_SCRIPT_ADLAM:
573 /* Unicode-11.0 additions */
574 case HB_SCRIPT_HANIFI_ROHINGYA:
575 case HB_SCRIPT_OLD_SOGDIAN:
576 case HB_SCRIPT_SOGDIAN:
578 return HB_DIRECTION_RTL;
581 /* https://github.com/harfbuzz/harfbuzz/issues/1000 */
582 case HB_SCRIPT_OLD_HUNGARIAN:
583 case HB_SCRIPT_OLD_ITALIC:
584 case HB_SCRIPT_RUNIC:
586 return HB_DIRECTION_INVALID;
589 return HB_DIRECTION_LTR;
593 /* hb_user_data_array_t */
596 hb_user_data_array_t::set (hb_user_data_key_t *key,
598 hb_destroy_func_t destroy,
605 if (!data && !destroy) {
606 items.remove (key, lock);
610 hb_user_data_item_t item = {key, data, destroy};
611 bool ret = !!items.replace_or_insert (item, lock, (bool) replace);
617 hb_user_data_array_t::get (hb_user_data_key_t *key)
619 hb_user_data_item_t item = {nullptr, nullptr, nullptr};
621 return items.find (key, &item, lock) ? item.data : nullptr;
631 * @short_description: Information about the version of HarfBuzz in use
634 * These functions and macros allow accessing version of the HarfBuzz
635 * library used at compile- as well as run-time, and to direct code
636 * conditionally based on those versions, again, at compile- or run-time.
642 * @major: (out): Library major version component.
643 * @minor: (out): Library minor version component.
644 * @micro: (out): Library micro version component.
646 * Returns library version as three integer components.
651 hb_version (unsigned int *major,
655 *major = HB_VERSION_MAJOR;
656 *minor = HB_VERSION_MINOR;
657 *micro = HB_VERSION_MICRO;
663 * Returns library version as a string with three components.
665 * Return value: library version string.
672 return HB_VERSION_STRING;
676 * hb_version_atleast:
688 hb_version_atleast (unsigned int major,
692 return HB_VERSION_ATLEAST (major, minor, micro);
697 /* hb_feature_t and hb_variation_t */
700 parse_space (const char **pp, const char *end)
702 while (*pp < end && ISSPACE (**pp))
708 parse_char (const char **pp, const char *end, char c)
710 parse_space (pp, end);
712 if (*pp == end || **pp != c)
720 parse_uint (const char **pp, const char *end, unsigned int *pv)
722 /* Intentionally use hb_parse_int inside instead of hb_parse_uint,
723 * such that -1 turns into "big number"... */
725 if (unlikely (!hb_parse_int (pp, end, &v))) return false;
732 parse_uint32 (const char **pp, const char *end, uint32_t *pv)
734 /* Intentionally use hb_parse_int inside instead of hb_parse_uint,
735 * such that -1 turns into "big number"... */
737 if (unlikely (!hb_parse_int (pp, end, &v))) return false;
744 parse_bool (const char **pp, const char *end, uint32_t *pv)
746 parse_space (pp, end);
749 while (*pp < end && ISALPHA(**pp))
752 /* CSS allows on/off as aliases 1/0. */
754 && TOLOWER (p[0]) == 'o'
755 && TOLOWER (p[1]) == 'n')
757 else if (*pp - p == 3
758 && TOLOWER (p[0]) == 'o'
759 && TOLOWER (p[1]) == 'f'
760 && TOLOWER (p[2]) == 'f')
771 parse_feature_value_prefix (const char **pp, const char *end, hb_feature_t *feature)
773 if (parse_char (pp, end, '-'))
776 parse_char (pp, end, '+');
784 parse_tag (const char **pp, const char *end, hb_tag_t *tag)
786 parse_space (pp, end);
790 if (*pp < end && (**pp == '\'' || **pp == '"'))
797 while (*pp < end && (ISALNUM(**pp) || **pp == '_'))
800 if (p == *pp || *pp - p > 4)
803 *tag = hb_tag_from_string (p, *pp - p);
807 /* CSS expects exactly four bytes. And we only allow quotations for
808 * CSS compatibility. So, enforce the length. */
811 if (*pp == end || **pp != quote)
820 parse_feature_indices (const char **pp, const char *end, hb_feature_t *feature)
822 parse_space (pp, end);
826 feature->start = HB_FEATURE_GLOBAL_START;
827 feature->end = HB_FEATURE_GLOBAL_END;
829 if (!parse_char (pp, end, '['))
832 has_start = parse_uint (pp, end, &feature->start);
834 if (parse_char (pp, end, ':') || parse_char (pp, end, ';')) {
835 parse_uint (pp, end, &feature->end);
838 feature->end = feature->start + 1;
841 return parse_char (pp, end, ']');
845 parse_feature_value_postfix (const char **pp, const char *end, hb_feature_t *feature)
847 bool had_equal = parse_char (pp, end, '=');
848 bool had_value = parse_uint32 (pp, end, &feature->value) ||
849 parse_bool (pp, end, &feature->value);
850 /* CSS doesn't use equal-sign between tag and value.
851 * If there was an equal-sign, then there *must* be a value.
852 * A value without an equal-sign is ok, but not required. */
853 return !had_equal || had_value;
857 parse_one_feature (const char **pp, const char *end, hb_feature_t *feature)
859 return parse_feature_value_prefix (pp, end, feature) &&
860 parse_tag (pp, end, &feature->tag) &&
861 parse_feature_indices (pp, end, feature) &&
862 parse_feature_value_postfix (pp, end, feature) &&
863 parse_space (pp, end) &&
868 * hb_feature_from_string:
869 * @str: (array length=len) (element-type uint8_t): a string to parse
870 * @len: length of @str, or -1 if string is %NULL terminated
871 * @feature: (out): the #hb_feature_t to initialize with the parsed values
873 * Parses a string into a #hb_feature_t.
875 * The format for specifying feature strings follows. All valid CSS
876 * font-feature-settings values other than 'normal' and the global values are
877 * also accepted, though not documented below. CSS string escapes are not
880 * The range indices refer to the positions between Unicode characters. The
881 * position before the first character is always 0.
883 * The format is Python-esque. Here is how it all works:
885 * <informaltable pgwide='1' align='left' frame='none'>
888 * <row><entry>Syntax</entry> <entry>Value</entry> <entry>Start</entry> <entry>End</entry></row>
891 * <row><entry>Setting value:</entry></row>
892 * <row><entry>kern</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
893 * <row><entry>+kern</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
894 * <row><entry>-kern</entry> <entry>0</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature off</entry></row>
895 * <row><entry>kern=0</entry> <entry>0</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature off</entry></row>
896 * <row><entry>kern=1</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
897 * <row><entry>aalt=2</entry> <entry>2</entry> <entry>0</entry> <entry>∞</entry> <entry>Choose 2nd alternate</entry></row>
898 * <row><entry>Setting index:</entry></row>
899 * <row><entry>kern[]</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
900 * <row><entry>kern[:]</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
901 * <row><entry>kern[5:]</entry> <entry>1</entry> <entry>5</entry> <entry>∞</entry> <entry>Turn feature on, partial</entry></row>
902 * <row><entry>kern[:5]</entry> <entry>1</entry> <entry>0</entry> <entry>5</entry> <entry>Turn feature on, partial</entry></row>
903 * <row><entry>kern[3:5]</entry> <entry>1</entry> <entry>3</entry> <entry>5</entry> <entry>Turn feature on, range</entry></row>
904 * <row><entry>kern[3]</entry> <entry>1</entry> <entry>3</entry> <entry>3+1</entry> <entry>Turn feature on, single char</entry></row>
905 * <row><entry>Mixing it all:</entry></row>
906 * <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>
912 * %true if @str is successfully parsed, %false otherwise.
917 hb_feature_from_string (const char *str, int len,
918 hb_feature_t *feature)
925 if (likely (parse_one_feature (&str, str + len, &feat)))
933 memset (feature, 0, sizeof (*feature));
938 * hb_feature_to_string:
939 * @feature: an #hb_feature_t to convert
940 * @buf: (array length=size) (out): output string
941 * @size: the allocated size of @buf
943 * Converts a #hb_feature_t into a %NULL-terminated string in the format
944 * understood by hb_feature_from_string(). The client in responsible for
945 * allocating big enough size for @buf, 128 bytes is more than enough.
950 hb_feature_to_string (hb_feature_t *feature,
951 char *buf, unsigned int size)
953 if (unlikely (!size)) return;
956 unsigned int len = 0;
957 if (feature->value == 0)
959 hb_tag_to_string (feature->tag, s + len);
961 while (len && s[len - 1] == ' ')
963 if (feature->start != 0 || feature->end != (unsigned int) -1)
967 len += hb_max (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->start));
968 if (feature->end != feature->start + 1) {
970 if (feature->end != (unsigned int) -1)
971 len += hb_max (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->end));
975 if (feature->value > 1)
978 len += hb_max (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->value));
980 assert (len < ARRAY_LENGTH (s));
981 len = hb_min (len, size - 1);
982 memcpy (buf, s, len);
989 parse_variation_value (const char **pp, const char *end, hb_variation_t *variation)
991 parse_char (pp, end, '='); /* Optional. */
993 if (unlikely (!hb_parse_double (pp, end, &v))) return false;
995 variation->value = v;
1000 parse_one_variation (const char **pp, const char *end, hb_variation_t *variation)
1002 return parse_tag (pp, end, &variation->tag) &&
1003 parse_variation_value (pp, end, variation) &&
1004 parse_space (pp, end) &&
1009 * hb_variation_from_string:
1014 hb_variation_from_string (const char *str, int len,
1015 hb_variation_t *variation)
1022 if (likely (parse_one_variation (&str, str + len, &var)))
1030 memset (variation, 0, sizeof (*variation));
1035 * hb_variation_to_string:
1040 hb_variation_to_string (hb_variation_t *variation,
1041 char *buf, unsigned int size)
1043 if (unlikely (!size)) return;
1046 unsigned int len = 0;
1047 hb_tag_to_string (variation->tag, s + len);
1049 while (len && s[len - 1] == ' ')
1052 len += hb_max (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%g", (double) variation->value));
1054 assert (len < ARRAY_LENGTH (s));
1055 len = hb_min (len, size - 1);
1056 memcpy (buf, s, len);
1061 * hb_color_get_alpha:
1062 * color: a #hb_color_t we are interested in its channels.
1064 * Return value: Alpha channel value of the given color
1069 (hb_color_get_alpha) (hb_color_t color)
1071 return hb_color_get_alpha (color);
1076 * color: a #hb_color_t we are interested in its channels.
1078 * Return value: Red channel value of the given color
1083 (hb_color_get_red) (hb_color_t color)
1085 return hb_color_get_red (color);
1089 * hb_color_get_green:
1090 * color: a #hb_color_t we are interested in its channels.
1092 * Return value: Green channel value of the given color
1097 (hb_color_get_green) (hb_color_t color)
1099 return hb_color_get_green (color);
1103 * hb_color_get_blue:
1104 * color: a #hb_color_t we are interested in its channels.
1106 * Return value: Blue channel value of the given color
1111 (hb_color_get_blue) (hb_color_t color)
1113 return hb_color_get_blue (color);
1117 /* If there is no visibility control, then hb-static.cc will NOT
1118 * define anything. Instead, we get it to define one set in here
1119 * only, so only libharfbuzz.so defines them, not other libs. */
1120 #ifdef HB_NO_VISIBILITY
1121 #undef HB_NO_VISIBILITY
1122 #include "hb-static.cc"
1123 #define HB_NO_VISIBILITY 1