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
39hb_options_union_t _hb_options;
40
41void
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):
61 * @len:
62 *
63 *
64 *
65 * Return value:
66 *
67 * Since: 1.0
68 **/
69hb_tag_t
70hb_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: (array fixed-size=4):
92 *
93 *
94 *
95 * Since: 1.0
96 **/
97void
98hb_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
109const 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):
119 * @len:
120 *
121 *
122 *
123 * Return value:
124 *
125 * Since: 1.0
126 **/
127hb_direction_t
128hb_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: 1.0
153 **/
154const char *
155hb_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
167struct hb_language_impl_t {
168  const char s[1];
169};
170
171static 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
182static hb_bool_t
183lang_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
196static unsigned int
197lang_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
212struct hb_language_item_t {
213
214  struct hb_language_item_t *next;
215  hb_language_t lang;
216
217  inline bool operator == (const char *s) const {
218    return lang_equal (lang, s);
219  }
220
221  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
229  void finish (void) { free ((void *) lang); }
230};
231
232
233/* Thread-safe lock-free language list */
234
235static hb_language_item_t *langs;
236
237#ifdef HAVE_ATEXIT
238static inline
239void 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
250static hb_language_item_t *
251lang_find_or_insert (const char *key)
252{
253retry:
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    free (lang);
269    goto retry;
270  }
271
272#ifdef HAVE_ATEXIT
273  if (!first_lang)
274    atexit (free_langs); /* First person registers atexit() callback. */
275#endif
276
277  return lang;
278}
279
280
281/**
282 * hb_language_from_string:
283 * @str: (array length=len):
284 * @len:
285 *
286 *
287 *
288 * Return value:
289 *
290 * Since: 1.0
291 **/
292hb_language_t
293hb_language_from_string (const char *str, int len)
294{
295  char strbuf[64];
296
297  if (!str || !len || !*str)
298    return HB_LANGUAGE_INVALID;
299
300  if (len >= 0)
301  {
302    /* NUL-terminate it. */
303    len = MIN (len, (int) sizeof (strbuf) - 1);
304    memcpy (strbuf, str, len);
305    strbuf[len] = '\0';
306    str = strbuf;
307  }
308
309  hb_language_item_t *item = lang_find_or_insert (str);
310
311  return likely (item) ? item->lang : HB_LANGUAGE_INVALID;
312}
313
314/**
315 * hb_language_to_string:
316 * @language:
317 *
318 *
319 *
320 * Return value: (transfer none):
321 *
322 * Since: 1.0
323 **/
324const char *
325hb_language_to_string (hb_language_t language)
326{
327  /* This is actually NULL-safe! */
328  return language->s;
329}
330
331/**
332 * hb_language_get_default:
333 *
334 *
335 *
336 * Return value:
337 *
338 * Since: 1.0
339 **/
340hb_language_t
341hb_language_get_default (void)
342{
343  static hb_language_t default_language = HB_LANGUAGE_INVALID;
344
345  hb_language_t language = (hb_language_t) hb_atomic_ptr_get (&default_language);
346  if (unlikely (language == HB_LANGUAGE_INVALID)) {
347    language = hb_language_from_string (setlocale (LC_CTYPE, NULL), -1);
348    hb_atomic_ptr_cmpexch (&default_language, HB_LANGUAGE_INVALID, language);
349  }
350
351  return default_language;
352}
353
354
355/* hb_script_t */
356
357/**
358 * hb_script_from_iso15924_tag:
359 * @tag:
360 *
361 *
362 *
363 * Return value:
364 *
365 * Since: 1.0
366 **/
367hb_script_t
368hb_script_from_iso15924_tag (hb_tag_t tag)
369{
370  if (unlikely (tag == HB_TAG_NONE))
371    return HB_SCRIPT_INVALID;
372
373  /* Be lenient, adjust case (one capital letter followed by three small letters) */
374  tag = (tag & 0xDFDFDFDFu) | 0x00202020u;
375
376  switch (tag) {
377
378    /* These graduated from the 'Q' private-area codes, but
379     * the old code is still aliased by Unicode, and the Qaai
380     * one in use by ICU. */
381    case HB_TAG('Q','a','a','i'): return HB_SCRIPT_INHERITED;
382    case HB_TAG('Q','a','a','c'): return HB_SCRIPT_COPTIC;
383
384    /* Script variants from http://unicode.org/iso15924/ */
385    case HB_TAG('C','y','r','s'): return HB_SCRIPT_CYRILLIC;
386    case HB_TAG('L','a','t','f'): return HB_SCRIPT_LATIN;
387    case HB_TAG('L','a','t','g'): return HB_SCRIPT_LATIN;
388    case HB_TAG('S','y','r','e'): return HB_SCRIPT_SYRIAC;
389    case HB_TAG('S','y','r','j'): return HB_SCRIPT_SYRIAC;
390    case HB_TAG('S','y','r','n'): return HB_SCRIPT_SYRIAC;
391  }
392
393  /* If it looks right, just use the tag as a script */
394  if (((uint32_t) tag & 0xE0E0E0E0u) == 0x40606060u)
395    return (hb_script_t) tag;
396
397  /* Otherwise, return unknown */
398  return HB_SCRIPT_UNKNOWN;
399}
400
401/**
402 * hb_script_from_string:
403 * @s: (array length=len):
404 * @len:
405 *
406 *
407 *
408 * Return value:
409 *
410 * Since: 1.0
411 **/
412hb_script_t
413hb_script_from_string (const char *s, int len)
414{
415  return hb_script_from_iso15924_tag (hb_tag_from_string (s, len));
416}
417
418/**
419 * hb_script_to_iso15924_tag:
420 * @script:
421 *
422 *
423 *
424 * Return value:
425 *
426 * Since: 1.0
427 **/
428hb_tag_t
429hb_script_to_iso15924_tag (hb_script_t script)
430{
431  return (hb_tag_t) script;
432}
433
434/**
435 * hb_script_get_horizontal_direction:
436 * @script:
437 *
438 *
439 *
440 * Return value:
441 *
442 * Since: 1.0
443 **/
444hb_direction_t
445hb_script_get_horizontal_direction (hb_script_t script)
446{
447  /* http://goo.gl/x9ilM */
448  switch ((hb_tag_t) script)
449  {
450    /* Unicode-1.1 additions */
451    case HB_SCRIPT_ARABIC:
452    case HB_SCRIPT_HEBREW:
453
454    /* Unicode-3.0 additions */
455    case HB_SCRIPT_SYRIAC:
456    case HB_SCRIPT_THAANA:
457
458    /* Unicode-4.0 additions */
459    case HB_SCRIPT_CYPRIOT:
460
461    /* Unicode-4.1 additions */
462    case HB_SCRIPT_KHAROSHTHI:
463
464    /* Unicode-5.0 additions */
465    case HB_SCRIPT_PHOENICIAN:
466    case HB_SCRIPT_NKO:
467
468    /* Unicode-5.1 additions */
469    case HB_SCRIPT_LYDIAN:
470
471    /* Unicode-5.2 additions */
472    case HB_SCRIPT_AVESTAN:
473    case HB_SCRIPT_IMPERIAL_ARAMAIC:
474    case HB_SCRIPT_INSCRIPTIONAL_PAHLAVI:
475    case HB_SCRIPT_INSCRIPTIONAL_PARTHIAN:
476    case HB_SCRIPT_OLD_SOUTH_ARABIAN:
477    case HB_SCRIPT_OLD_TURKIC:
478    case HB_SCRIPT_SAMARITAN:
479
480    /* Unicode-6.0 additions */
481    case HB_SCRIPT_MANDAIC:
482
483    /* Unicode-6.1 additions */
484    case HB_SCRIPT_MEROITIC_CURSIVE:
485    case HB_SCRIPT_MEROITIC_HIEROGLYPHS:
486
487    /* Unicode-7.0 additions */
488    case HB_SCRIPT_MANICHAEAN:
489    case HB_SCRIPT_MENDE_KIKAKUI:
490    case HB_SCRIPT_NABATAEAN:
491    case HB_SCRIPT_OLD_NORTH_ARABIAN:
492    case HB_SCRIPT_PALMYRENE:
493    case HB_SCRIPT_PSALTER_PAHLAVI:
494
495      return HB_DIRECTION_RTL;
496  }
497
498  return HB_DIRECTION_LTR;
499}
500
501
502/* hb_user_data_array_t */
503
504bool
505hb_user_data_array_t::set (hb_user_data_key_t *key,
506			   void *              data,
507			   hb_destroy_func_t   destroy,
508			   hb_bool_t           replace)
509{
510  if (!key)
511    return false;
512
513  if (replace) {
514    if (!data && !destroy) {
515      items.remove (key, lock);
516      return true;
517    }
518  }
519  hb_user_data_item_t item = {key, data, destroy};
520  bool ret = !!items.replace_or_insert (item, lock, replace);
521
522  return ret;
523}
524
525void *
526hb_user_data_array_t::get (hb_user_data_key_t *key)
527{
528  hb_user_data_item_t item = {NULL };
529
530  return items.find (key, &item, lock) ? item.data : NULL;
531}
532
533
534/* hb_version */
535
536/**
537 * hb_version:
538 * @major: (out): Library major version component.
539 * @minor: (out): Library minor version component.
540 * @micro: (out): Library micro version component.
541 *
542 * Returns library version as three integer components.
543 *
544 * Since: 1.0
545 **/
546void
547hb_version (unsigned int *major,
548	    unsigned int *minor,
549	    unsigned int *micro)
550{
551  *major = HB_VERSION_MAJOR;
552  *minor = HB_VERSION_MINOR;
553  *micro = HB_VERSION_MICRO;
554}
555
556/**
557 * hb_version_string:
558 *
559 * Returns library version as a string with three components.
560 *
561 * Return value: library version string.
562 *
563 * Since: 1.0
564 **/
565const char *
566hb_version_string (void)
567{
568  return HB_VERSION_STRING;
569}
570
571/**
572 * hb_version_atleast:
573 * @major:
574 * @minor:
575 * @micro:
576 *
577 *
578 *
579 * Return value:
580 *
581 * Since: 1.0
582 **/
583hb_bool_t
584hb_version_atleast (unsigned int major,
585		    unsigned int minor,
586		    unsigned int micro)
587{
588  return HB_VERSION_ATLEAST (major, minor, micro);
589}
590