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

source code of qtbase/src/3rdparty/harfbuzz-ng/src/hb-common.cc