* Google Author(s): Behdad Esfahbod
*/
-#include "hb-private.hh"
-
-#include "hb-mutex-private.hh"
-#include "hb-object-private.hh"
+#include "hb.hh"
+#include "hb-machinery.hh"
#include <locale.h>
+#ifdef HB_NO_SETLOCALE
+#define setlocale(Category, Locale) "C"
+#endif
+
+/**
+ * SECTION:hb-common
+ * @title: hb-common
+ * @short_description: Common data types
+ * @include: hb.h
+ *
+ * Common data types used across HarfBuzz are defined here.
+ **/
+
/* hb_options_t */
-hb_options_union_t _hb_options;
+hb_atomic_int_t _hb_options;
void
-_hb_options_init (void)
+_hb_options_init ()
{
hb_options_union_t u;
u.i = 0;
- u.opts.initialized = 1;
+ u.opts.initialized = true;
- char *c = getenv ("HB_OPTIONS");
- u.opts.uniscribe_bug_compatible = c && strstr (c, "uniscribe-bug-compatible");
+ const char *c = getenv ("HB_OPTIONS");
+ if (c)
+ {
+ while (*c)
+ {
+ const char *p = strchr (c, ':');
+ if (!p)
+ p = c + strlen (c);
+
+#define OPTION(name, symbol) \
+ if (0 == strncmp (c, name, p - c) && strlen (name) == static_cast<size_t>(p - c)) do { u.opts.symbol = true; } while (0)
+
+ OPTION ("uniscribe-bug-compatible", uniscribe_bug_compatible);
+
+#undef OPTION
+
+ c = *p ? p + 1 : p;
+ }
+
+ }
/* This is idempotent and threadsafe. */
- _hb_options = u;
+ _hb_options.set_relaxed (u.i);
}
/**
* hb_tag_from_string:
- * @str: (array length=len):
- * @len:
+ * @str: (array length=len) (element-type uint8_t):
+ * @len:
*
- *
*
- * Return value:
*
- * Since: 1.0
+ * Return value:
+ *
+ * Since: 0.9.2
**/
hb_tag_t
hb_tag_from_string (const char *str, int len)
for (; i < 4; i++)
tag[i] = ' ';
- return HB_TAG_CHAR4 (tag);
+ return HB_TAG (tag[0], tag[1], tag[2], tag[3]);
}
/**
* hb_tag_to_string:
- * @tag:
- * @buf: (array fixed-size=4):
+ * @tag:
+ * @buf: (out caller-allocates) (array fixed-size=4) (element-type uint8_t):
+ *
*
- *
*
- * Since: 1.0
+ * Since: 0.9.5
**/
void
hb_tag_to_string (hb_tag_t tag, char *buf)
/**
* hb_direction_from_string:
- * @str: (array length=len):
- * @len:
+ * @str: (array length=len) (element-type uint8_t):
+ * @len:
*
- *
*
- * Return value:
*
- * Since: 1.0
+ * Return value:
+ *
+ * Since: 0.9.2
**/
hb_direction_t
hb_direction_from_string (const char *str, int len)
/**
* hb_direction_to_string:
- * @direction:
+ * @direction:
+ *
*
- *
*
- * Return value: (transfer none):
+ * Return value: (transfer none):
*
- * Since: 1.0
+ * Since: 0.9.2
**/
const char *
hb_direction_to_string (hb_direction_t direction)
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0,
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
- '-', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, '-',
0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0
};
-static hb_bool_t
+static bool
lang_equal (hb_language_t v1,
const void *v2)
{
const unsigned char *p1 = (const unsigned char *) v1;
const unsigned char *p2 = (const unsigned char *) v2;
- while (*p1 && *p1 == canon_map[*p2])
- p1++, p2++;
+ while (*p1 && *p1 == canon_map[*p2]) {
+ p1++;
+ p2++;
+ }
return *p1 == canon_map[*p2];
}
struct hb_language_item_t *next;
hb_language_t lang;
- inline bool operator == (const char *s) const {
- return lang_equal (lang, s);
- }
-
- inline hb_language_item_t & operator = (const char *s) {
- lang = (hb_language_t) strdup (s);
- for (unsigned char *p = (unsigned char *) lang; *p; p++)
- *p = canon_map[*p];
+ bool operator == (const char *s) const
+ { return lang_equal (lang, s); }
+
+ hb_language_item_t & operator = (const char *s) {
+ /* If a custom allocated is used calling strdup() pairs
+ badly with a call to the custom free() in fini() below.
+ Therefore don't call strdup(), implement its behavior.
+ */
+ size_t len = strlen(s) + 1;
+ lang = (hb_language_t) malloc(len);
+ if (likely (lang))
+ {
+ memcpy((unsigned char *) lang, s, len);
+ for (unsigned char *p = (unsigned char *) lang; *p; p++)
+ *p = canon_map[*p];
+ }
return *this;
}
- void finish (void) { free ((void *) lang); }
+ void fini () { free ((void *) lang); }
};
/* Thread-safe lock-free language list */
-static hb_language_item_t *langs;
+static hb_atomic_ptr_t <hb_language_item_t> langs;
-#ifdef HB_USE_ATEXIT
-static inline
-void free_langs (void)
+#if HB_USE_ATEXIT
+static void
+free_langs ()
{
- while (langs) {
- hb_language_item_t *next = langs->next;
- langs->finish ();
- free (langs);
- langs = next;
+retry:
+ hb_language_item_t *first_lang = langs;
+ if (unlikely (!langs.cmpexch (first_lang, nullptr)))
+ goto retry;
+
+ while (first_lang) {
+ hb_language_item_t *next = first_lang->next;
+ first_lang->fini ();
+ free (first_lang);
+ first_lang = next;
}
}
#endif
lang_find_or_insert (const char *key)
{
retry:
- hb_language_item_t *first_lang = (hb_language_item_t *) hb_atomic_ptr_get (&langs);
+ hb_language_item_t *first_lang = langs;
for (hb_language_item_t *lang = first_lang; lang; lang = lang->next)
if (*lang == key)
/* Not found; allocate one. */
hb_language_item_t *lang = (hb_language_item_t *) calloc (1, sizeof (hb_language_item_t));
if (unlikely (!lang))
- return NULL;
+ return nullptr;
lang->next = first_lang;
*lang = key;
+ if (unlikely (!lang->lang))
+ {
+ free (lang);
+ return nullptr;
+ }
- if (!hb_atomic_ptr_cmpexch (&langs, first_lang, lang)) {
+ if (unlikely (!langs.cmpexch (first_lang, lang)))
+ {
+ lang->fini ();
free (lang);
goto retry;
}
-#ifdef HB_USE_ATEXIT
+#if HB_USE_ATEXIT
if (!first_lang)
atexit (free_langs); /* First person registers atexit() callback. */
#endif
/**
* hb_language_from_string:
- * @str: (array length=len):
- * @len:
+ * @str: (array length=len) (element-type uint8_t): a string representing
+ * a BCP 47 language tag
+ * @len: length of the @str, or -1 if it is %NULL-terminated.
*
- *
+ * Converts @str representing a BCP 47 language tag to the corresponding
+ * #hb_language_t.
*
- * Return value:
+ * Return value: (transfer none):
+ * The #hb_language_t corresponding to the BCP 47 language tag.
*
- * Since: 1.0
+ * Since: 0.9.2
**/
hb_language_t
hb_language_from_string (const char *str, int len)
{
- char strbuf[64];
-
if (!str || !len || !*str)
return HB_LANGUAGE_INVALID;
+ hb_language_item_t *item = nullptr;
if (len >= 0)
{
/* NUL-terminate it. */
- len = MIN (len, (int) sizeof (strbuf) - 1);
+ char strbuf[64];
+ len = hb_min (len, (int) sizeof (strbuf) - 1);
memcpy (strbuf, str, len);
strbuf[len] = '\0';
- str = strbuf;
+ item = lang_find_or_insert (strbuf);
}
-
- hb_language_item_t *item = lang_find_or_insert (str);
+ else
+ item = lang_find_or_insert (str);
return likely (item) ? item->lang : HB_LANGUAGE_INVALID;
}
/**
* hb_language_to_string:
- * @language:
+ * @language: an #hb_language_t to convert.
*
- *
+ * See hb_language_from_string().
*
- * Return value: (transfer none):
+ * Return value: (transfer none):
+ * A %NULL-terminated string representing the @language. Must not be freed by
+ * the caller.
*
- * Since: 1.0
+ * Since: 0.9.2
**/
const char *
hb_language_to_string (hb_language_t language)
{
- /* This is actually NULL-safe! */
+ if (unlikely (!language)) return nullptr;
+
return language->s;
}
/**
* hb_language_get_default:
*
- *
+ * Get default language from current locale.
+ *
+ * Note that the first time this function is called, it calls
+ * "setlocale (LC_CTYPE, nullptr)" to fetch current locale. The underlying
+ * setlocale function is, in many implementations, NOT threadsafe. To avoid
+ * problems, call this function once before multiple threads can call it.
+ * This function is only used from hb_buffer_guess_segment_properties() by
+ * HarfBuzz itself.
*
- * Return value:
+ * Return value: (transfer none):
*
- * Since: 1.0
+ * Since: 0.9.2
**/
hb_language_t
-hb_language_get_default (void)
+hb_language_get_default ()
{
- static hb_language_t default_language = HB_LANGUAGE_INVALID;
+ static hb_atomic_ptr_t <hb_language_t> default_language;
- hb_language_t language = (hb_language_t) hb_atomic_ptr_get (&default_language);
- if (unlikely (language == HB_LANGUAGE_INVALID)) {
- language = hb_language_from_string (setlocale (LC_CTYPE, NULL), -1);
- hb_atomic_ptr_cmpexch (&default_language, HB_LANGUAGE_INVALID, language);
+ hb_language_t language = default_language;
+ if (unlikely (language == HB_LANGUAGE_INVALID))
+ {
+ language = hb_language_from_string (setlocale (LC_CTYPE, nullptr), -1);
+ (void) default_language.cmpexch (HB_LANGUAGE_INVALID, language);
}
- return default_language;
+ return language;
}
/**
* hb_script_from_iso15924_tag:
- * @tag:
+ * @tag: an #hb_tag_t representing an ISO 15924 tag.
*
- *
+ * Converts an ISO 15924 script tag to a corresponding #hb_script_t.
*
- * Return value:
+ * Return value:
+ * An #hb_script_t corresponding to the ISO 15924 tag.
*
- * Since: 1.0
+ * Since: 0.9.2
**/
hb_script_t
hb_script_from_iso15924_tag (hb_tag_t tag)
case HB_TAG('Q','a','a','i'): return HB_SCRIPT_INHERITED;
case HB_TAG('Q','a','a','c'): return HB_SCRIPT_COPTIC;
- /* Script variants from http://unicode.org/iso15924/ */
+ /* Script variants from https://unicode.org/iso15924/ */
case HB_TAG('C','y','r','s'): return HB_SCRIPT_CYRILLIC;
case HB_TAG('L','a','t','f'): return HB_SCRIPT_LATIN;
case HB_TAG('L','a','t','g'): return HB_SCRIPT_LATIN;
/**
* hb_script_from_string:
- * @s: (array length=len):
- * @len:
+ * @str: (array length=len) (element-type uint8_t): a string representing an
+ * ISO 15924 tag.
+ * @len: length of the @str, or -1 if it is %NULL-terminated.
*
- *
+ * Converts a string @str representing an ISO 15924 script tag to a
+ * corresponding #hb_script_t. Shorthand for hb_tag_from_string() then
+ * hb_script_from_iso15924_tag().
*
- * Return value:
+ * Return value:
+ * An #hb_script_t corresponding to the ISO 15924 tag.
*
- * Since: 1.0
+ * Since: 0.9.2
**/
hb_script_t
-hb_script_from_string (const char *s, int len)
+hb_script_from_string (const char *str, int len)
{
- return hb_script_from_iso15924_tag (hb_tag_from_string (s, len));
+ return hb_script_from_iso15924_tag (hb_tag_from_string (str, len));
}
/**
* hb_script_to_iso15924_tag:
- * @script:
+ * @script: an #hb_script_t to convert.
*
- *
+ * See hb_script_from_iso15924_tag().
*
- * Return value:
+ * Return value:
+ * An #hb_tag_t representing an ISO 15924 script tag.
*
- * Since: 1.0
+ * Since: 0.9.2
**/
hb_tag_t
hb_script_to_iso15924_tag (hb_script_t script)
/**
* hb_script_get_horizontal_direction:
- * @script:
+ * @script:
+ *
*
- *
*
- * Return value:
+ * Return value:
*
- * Since: 1.0
+ * Since: 0.9.2
**/
hb_direction_t
hb_script_get_horizontal_direction (hb_script_t script)
{
- /* http://goo.gl/x9ilM */
+ /* https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o */
switch ((hb_tag_t) script)
{
/* Unicode-1.1 additions */
case HB_SCRIPT_PALMYRENE:
case HB_SCRIPT_PSALTER_PAHLAVI:
- return HB_DIRECTION_RTL;
- }
+ /* Unicode-8.0 additions */
+ case HB_SCRIPT_HATRAN:
- return HB_DIRECTION_LTR;
-}
+ /* Unicode-9.0 additions */
+ case HB_SCRIPT_ADLAM:
+ /* Unicode-11.0 additions */
+ case HB_SCRIPT_HANIFI_ROHINGYA:
+ case HB_SCRIPT_OLD_SOGDIAN:
+ case HB_SCRIPT_SOGDIAN:
-/* hb_user_data_array_t */
+ /* Unicode-12.0 additions */
+ case HB_SCRIPT_ELYMAIC:
-bool
-hb_user_data_array_t::set (hb_user_data_key_t *key,
- void * data,
- hb_destroy_func_t destroy,
- hb_bool_t replace)
-{
- if (!key)
- return false;
+ /* Unicode-13.0 additions */
+ case HB_SCRIPT_CHORASMIAN:
+ case HB_SCRIPT_YEZIDI:
- if (replace) {
- if (!data && !destroy) {
- items.remove (key, lock);
- return true;
- }
- }
- hb_user_data_item_t item = {key, data, destroy};
- bool ret = !!items.replace_or_insert (item, lock, replace);
+ return HB_DIRECTION_RTL;
- return ret;
-}
-void *
-hb_user_data_array_t::get (hb_user_data_key_t *key)
-{
- hb_user_data_item_t item = {NULL };
+ /* https://github.com/harfbuzz/harfbuzz/issues/1000 */
+ case HB_SCRIPT_OLD_HUNGARIAN:
+ case HB_SCRIPT_OLD_ITALIC:
+ case HB_SCRIPT_RUNIC:
- return items.find (key, &item, lock) ? item.data : NULL;
+ return HB_DIRECTION_INVALID;
+ }
+
+ return HB_DIRECTION_LTR;
}
/* hb_version */
+
+/**
+ * SECTION:hb-version
+ * @title: hb-version
+ * @short_description: Information about the version of HarfBuzz in use
+ * @include: hb.h
+ *
+ * These functions and macros allow accessing version of the HarfBuzz
+ * library used at compile- as well as run-time, and to direct code
+ * conditionally based on those versions, again, at compile- or run-time.
+ **/
+
+
/**
* hb_version:
* @major: (out): Library major version component.
*
* Returns library version as three integer components.
*
- * Since: 1.0
+ * Since: 0.9.2
**/
void
hb_version (unsigned int *major,
*
* Return value: library version string.
*
- * Since: 1.0
+ * Since: 0.9.2
**/
const char *
-hb_version_string (void)
+hb_version_string ()
{
return HB_VERSION_STRING;
}
/**
* hb_version_atleast:
- * @major:
- * @minor:
- * @micro:
+ * @major:
+ * @minor:
+ * @micro:
+ *
*
- *
*
- * Return value:
+ * Return value:
*
- * Since: 1.0
+ * Since: 0.9.30
**/
hb_bool_t
hb_version_atleast (unsigned int major,
{
return HB_VERSION_ATLEAST (major, minor, micro);
}
+
+
+
+/* hb_feature_t and hb_variation_t */
+
+static bool
+parse_space (const char **pp, const char *end)
+{
+ while (*pp < end && ISSPACE (**pp))
+ (*pp)++;
+ return true;
+}
+
+static bool
+parse_char (const char **pp, const char *end, char c)
+{
+ parse_space (pp, end);
+
+ if (*pp == end || **pp != c)
+ return false;
+
+ (*pp)++;
+ return true;
+}
+
+static bool
+parse_uint (const char **pp, const char *end, unsigned int *pv)
+{
+ /* Intentionally use hb_parse_int inside instead of hb_parse_uint,
+ * such that -1 turns into "big number"... */
+ int v;
+ if (unlikely (!hb_parse_int (pp, end, &v))) return false;
+
+ *pv = v;
+ return true;
+}
+
+static bool
+parse_uint32 (const char **pp, const char *end, uint32_t *pv)
+{
+ /* Intentionally use hb_parse_int inside instead of hb_parse_uint,
+ * such that -1 turns into "big number"... */
+ int v;
+ if (unlikely (!hb_parse_int (pp, end, &v))) return false;
+
+ *pv = v;
+ return true;
+}
+
+static bool
+parse_bool (const char **pp, const char *end, uint32_t *pv)
+{
+ parse_space (pp, end);
+
+ const char *p = *pp;
+ while (*pp < end && ISALPHA(**pp))
+ (*pp)++;
+
+ /* CSS allows on/off as aliases 1/0. */
+ if (*pp - p == 2
+ && TOLOWER (p[0]) == 'o'
+ && TOLOWER (p[1]) == 'n')
+ *pv = 1;
+ else if (*pp - p == 3
+ && TOLOWER (p[0]) == 'o'
+ && TOLOWER (p[1]) == 'f'
+ && TOLOWER (p[2]) == 'f')
+ *pv = 0;
+ else
+ return false;
+
+ return true;
+}
+
+/* hb_feature_t */
+
+static bool
+parse_feature_value_prefix (const char **pp, const char *end, hb_feature_t *feature)
+{
+ if (parse_char (pp, end, '-'))
+ feature->value = 0;
+ else {
+ parse_char (pp, end, '+');
+ feature->value = 1;
+ }
+
+ return true;
+}
+
+static bool
+parse_tag (const char **pp, const char *end, hb_tag_t *tag)
+{
+ parse_space (pp, end);
+
+ char quote = 0;
+
+ if (*pp < end && (**pp == '\'' || **pp == '"'))
+ {
+ quote = **pp;
+ (*pp)++;
+ }
+
+ const char *p = *pp;
+ while (*pp < end && (ISALNUM(**pp) || **pp == '_'))
+ (*pp)++;
+
+ if (p == *pp || *pp - p > 4)
+ return false;
+
+ *tag = hb_tag_from_string (p, *pp - p);
+
+ if (quote)
+ {
+ /* CSS expects exactly four bytes. And we only allow quotations for
+ * CSS compatibility. So, enforce the length. */
+ if (*pp - p != 4)
+ return false;
+ if (*pp == end || **pp != quote)
+ return false;
+ (*pp)++;
+ }
+
+ return true;
+}
+
+static bool
+parse_feature_indices (const char **pp, const char *end, hb_feature_t *feature)
+{
+ parse_space (pp, end);
+
+ bool has_start;
+
+ feature->start = HB_FEATURE_GLOBAL_START;
+ feature->end = HB_FEATURE_GLOBAL_END;
+
+ if (!parse_char (pp, end, '['))
+ return true;
+
+ has_start = parse_uint (pp, end, &feature->start);
+
+ if (parse_char (pp, end, ':') || parse_char (pp, end, ';')) {
+ parse_uint (pp, end, &feature->end);
+ } else {
+ if (has_start)
+ feature->end = feature->start + 1;
+ }
+
+ return parse_char (pp, end, ']');
+}
+
+static bool
+parse_feature_value_postfix (const char **pp, const char *end, hb_feature_t *feature)
+{
+ bool had_equal = parse_char (pp, end, '=');
+ bool had_value = parse_uint32 (pp, end, &feature->value) ||
+ parse_bool (pp, end, &feature->value);
+ /* CSS doesn't use equal-sign between tag and value.
+ * If there was an equal-sign, then there *must* be a value.
+ * A value without an equal-sign is ok, but not required. */
+ return !had_equal || had_value;
+}
+
+static bool
+parse_one_feature (const char **pp, const char *end, hb_feature_t *feature)
+{
+ return parse_feature_value_prefix (pp, end, feature) &&
+ parse_tag (pp, end, &feature->tag) &&
+ parse_feature_indices (pp, end, feature) &&
+ parse_feature_value_postfix (pp, end, feature) &&
+ parse_space (pp, end) &&
+ *pp == end;
+}
+
+/**
+ * hb_feature_from_string:
+ * @str: (array length=len) (element-type uint8_t): a string to parse
+ * @len: length of @str, or -1 if string is %NULL terminated
+ * @feature: (out): the #hb_feature_t to initialize with the parsed values
+ *
+ * Parses a string into a #hb_feature_t.
+ *
+ * The format for specifying feature strings follows. All valid CSS
+ * font-feature-settings values other than 'normal' and the global values are
+ * also accepted, though not documented below. CSS string escapes are not
+ * supported.
+ *
+ * The range indices refer to the positions between Unicode characters. The
+ * position before the first character is always 0.
+ *
+ * The format is Python-esque. Here is how it all works:
+ *
+ * <informaltable pgwide='1' align='left' frame='none'>
+ * <tgroup cols='5'>
+ * <thead>
+ * <row><entry>Syntax</entry> <entry>Value</entry> <entry>Start</entry> <entry>End</entry></row>
+ * </thead>
+ * <tbody>
+ * <row><entry>Setting value:</entry></row>
+ * <row><entry>kern</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
+ * <row><entry>+kern</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
+ * <row><entry>-kern</entry> <entry>0</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature off</entry></row>
+ * <row><entry>kern=0</entry> <entry>0</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature off</entry></row>
+ * <row><entry>kern=1</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
+ * <row><entry>aalt=2</entry> <entry>2</entry> <entry>0</entry> <entry>∞</entry> <entry>Choose 2nd alternate</entry></row>
+ * <row><entry>Setting index:</entry></row>
+ * <row><entry>kern[]</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
+ * <row><entry>kern[:]</entry> <entry>1</entry> <entry>0</entry> <entry>∞</entry> <entry>Turn feature on</entry></row>
+ * <row><entry>kern[5:]</entry> <entry>1</entry> <entry>5</entry> <entry>∞</entry> <entry>Turn feature on, partial</entry></row>
+ * <row><entry>kern[:5]</entry> <entry>1</entry> <entry>0</entry> <entry>5</entry> <entry>Turn feature on, partial</entry></row>
+ * <row><entry>kern[3:5]</entry> <entry>1</entry> <entry>3</entry> <entry>5</entry> <entry>Turn feature on, range</entry></row>
+ * <row><entry>kern[3]</entry> <entry>1</entry> <entry>3</entry> <entry>3+1</entry> <entry>Turn feature on, single char</entry></row>
+ * <row><entry>Mixing it all:</entry></row>
+ * <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>
+ * </tbody>
+ * </tgroup>
+ * </informaltable>
+ *
+ * Return value:
+ * %true if @str is successfully parsed, %false otherwise.
+ *
+ * Since: 0.9.5
+ **/
+hb_bool_t
+hb_feature_from_string (const char *str, int len,
+ hb_feature_t *feature)
+{
+ hb_feature_t feat;
+
+ if (len < 0)
+ len = strlen (str);
+
+ if (likely (parse_one_feature (&str, str + len, &feat)))
+ {
+ if (feature)
+ *feature = feat;
+ return true;
+ }
+
+ if (feature)
+ memset (feature, 0, sizeof (*feature));
+ return false;
+}
+
+/**
+ * hb_feature_to_string:
+ * @feature: an #hb_feature_t to convert
+ * @buf: (array length=size) (out): output string
+ * @size: the allocated size of @buf
+ *
+ * Converts a #hb_feature_t into a %NULL-terminated string in the format
+ * understood by hb_feature_from_string(). The client in responsible for
+ * allocating big enough size for @buf, 128 bytes is more than enough.
+ *
+ * Since: 0.9.5
+ **/
+void
+hb_feature_to_string (hb_feature_t *feature,
+ char *buf, unsigned int size)
+{
+ if (unlikely (!size)) return;
+
+ char s[128];
+ unsigned int len = 0;
+ if (feature->value == 0)
+ s[len++] = '-';
+ hb_tag_to_string (feature->tag, s + len);
+ len += 4;
+ while (len && s[len - 1] == ' ')
+ len--;
+ if (feature->start != HB_FEATURE_GLOBAL_START || feature->end != HB_FEATURE_GLOBAL_END)
+ {
+ s[len++] = '[';
+ if (feature->start)
+ len += hb_max (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->start));
+ if (feature->end != feature->start + 1) {
+ s[len++] = ':';
+ if (feature->end != HB_FEATURE_GLOBAL_END)
+ len += hb_max (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->end));
+ }
+ s[len++] = ']';
+ }
+ if (feature->value > 1)
+ {
+ s[len++] = '=';
+ len += hb_max (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->value));
+ }
+ assert (len < ARRAY_LENGTH (s));
+ len = hb_min (len, size - 1);
+ memcpy (buf, s, len);
+ buf[len] = '\0';
+}
+
+/* hb_variation_t */
+
+static bool
+parse_variation_value (const char **pp, const char *end, hb_variation_t *variation)
+{
+ parse_char (pp, end, '='); /* Optional. */
+ double v;
+ if (unlikely (!hb_parse_double (pp, end, &v))) return false;
+
+ variation->value = v;
+ return true;
+}
+
+static bool
+parse_one_variation (const char **pp, const char *end, hb_variation_t *variation)
+{
+ return parse_tag (pp, end, &variation->tag) &&
+ parse_variation_value (pp, end, variation) &&
+ parse_space (pp, end) &&
+ *pp == end;
+}
+
+/**
+ * hb_variation_from_string:
+ *
+ * Since: 1.4.2
+ */
+hb_bool_t
+hb_variation_from_string (const char *str, int len,
+ hb_variation_t *variation)
+{
+ hb_variation_t var;
+
+ if (len < 0)
+ len = strlen (str);
+
+ if (likely (parse_one_variation (&str, str + len, &var)))
+ {
+ if (variation)
+ *variation = var;
+ return true;
+ }
+
+ if (variation)
+ memset (variation, 0, sizeof (*variation));
+ return false;
+}
+
+/**
+ * hb_variation_to_string:
+ *
+ * Since: 1.4.2
+ */
+void
+hb_variation_to_string (hb_variation_t *variation,
+ char *buf, unsigned int size)
+{
+ if (unlikely (!size)) return;
+
+ char s[128];
+ unsigned int len = 0;
+ hb_tag_to_string (variation->tag, s + len);
+ len += 4;
+ while (len && s[len - 1] == ' ')
+ len--;
+ s[len++] = '=';
+ len += hb_max (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%g", (double) variation->value));
+
+ assert (len < ARRAY_LENGTH (s));
+ len = hb_min (len, size - 1);
+ memcpy (buf, s, len);
+ buf[len] = '\0';
+}
+
+/**
+ * hb_color_get_alpha:
+ * color: a #hb_color_t we are interested in its channels.
+ *
+ * Return value: Alpha channel value of the given color
+ *
+ * Since: 2.1.0
+ */
+uint8_t
+(hb_color_get_alpha) (hb_color_t color)
+{
+ return hb_color_get_alpha (color);
+}
+
+/**
+ * hb_color_get_red:
+ * color: a #hb_color_t we are interested in its channels.
+ *
+ * Return value: Red channel value of the given color
+ *
+ * Since: 2.1.0
+ */
+uint8_t
+(hb_color_get_red) (hb_color_t color)
+{
+ return hb_color_get_red (color);
+}
+
+/**
+ * hb_color_get_green:
+ * color: a #hb_color_t we are interested in its channels.
+ *
+ * Return value: Green channel value of the given color
+ *
+ * Since: 2.1.0
+ */
+uint8_t
+(hb_color_get_green) (hb_color_t color)
+{
+ return hb_color_get_green (color);
+}
+
+/**
+ * hb_color_get_blue:
+ * color: a #hb_color_t we are interested in its channels.
+ *
+ * Return value: Blue channel value of the given color
+ *
+ * Since: 2.1.0
+ */
+uint8_t
+(hb_color_get_blue) (hb_color_t color)
+{
+ return hb_color_get_blue (color);
+}
+
+
+/* If there is no visibility control, then hb-static.cc will NOT
+ * define anything. Instead, we get it to define one set in here
+ * only, so only libharfbuzz.so defines them, not other libs. */
+#ifdef HB_NO_VISIBILITY
+#undef HB_NO_VISIBILITY
+#include "hb-static.cc"
+#define HB_NO_VISIBILITY 1
+#endif