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 | |
42 | hb_options_union_t _hb_options; |
43 | |
44 | void |
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 | **/ |
72 | hb_tag_t |
73 | hb_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 | **/ |
100 | void |
101 | hb_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 | |
112 | const 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 | **/ |
130 | hb_direction_t |
131 | hb_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 | **/ |
157 | const char * |
158 | hb_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 | |
170 | struct hb_language_impl_t { |
171 | const char s[1]; |
172 | }; |
173 | |
174 | static 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 | |
185 | static bool |
186 | lang_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 |
201 | static unsigned int |
202 | lang_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 | |
217 | struct 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 | |
249 | static hb_language_item_t *langs; |
250 | |
251 | #ifdef HB_USE_ATEXIT |
252 | static void |
253 | free_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 | |
264 | static hb_language_item_t * |
265 | lang_find_or_insert (const char *key) |
266 | { |
267 | retry: |
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 | **/ |
315 | hb_language_t |
316 | hb_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 | **/ |
349 | const char * |
350 | hb_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 | **/ |
365 | hb_language_t |
366 | hb_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 | **/ |
393 | hb_script_t |
394 | hb_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 | **/ |
442 | hb_script_t |
443 | hb_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 | **/ |
459 | hb_tag_t |
460 | hb_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 | **/ |
475 | hb_direction_t |
476 | hb_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 | |
541 | bool |
542 | hb_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 | |
562 | void * |
563 | hb_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 | **/ |
583 | void |
584 | hb_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 | **/ |
602 | const char * |
603 | hb_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 | **/ |
620 | hb_bool_t |
621 | hb_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 | |
632 | static bool |
633 | parse_space (const char **pp, const char *end) |
634 | { |
635 | while (*pp < end && ISSPACE (c: **pp)) |
636 | (*pp)++; |
637 | return true; |
638 | } |
639 | |
640 | static bool |
641 | parse_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 | |
652 | static bool |
653 | parse_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 | |
676 | static bool |
677 | parse_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 | |
715 | static HB_LOCALE_T C_locale; |
716 | |
717 | #ifdef HB_USE_ATEXIT |
718 | static void |
719 | free_C_locale (void) |
720 | { |
721 | if (C_locale) |
722 | HB_FREE_LOCALE (C_locale); |
723 | } |
724 | #endif |
725 | |
726 | static HB_LOCALE_T |
727 | get_C_locale (void) |
728 | { |
729 | retry: |
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 | |
751 | static bool |
752 | parse_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 | |
777 | static bool |
778 | parse_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 | |
799 | static bool |
800 | parse_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 | |
812 | static bool |
813 | parse_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 | |
848 | static bool |
849 | parse_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 | |
873 | static bool |
874 | parse_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 | |
885 | static bool |
886 | parse_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 | **/ |
911 | hb_bool_t |
912 | hb_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 | **/ |
944 | void |
945 | hb_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 | |
983 | static bool |
984 | parse_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 | |
990 | static bool |
991 | parse_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 | */ |
1004 | hb_bool_t |
1005 | hb_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 | */ |
1030 | void |
1031 | hb_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 | |