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