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