1 | /* |
2 | * Copyright © 2009 Red Hat, Inc. |
3 | * Copyright © 2011 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, Roozbeh Pournader |
27 | */ |
28 | |
29 | #include "hb.hh" |
30 | |
31 | #ifndef HB_NO_OT_TAG |
32 | |
33 | |
34 | /* hb_script_t */ |
35 | |
36 | static hb_tag_t |
37 | hb_ot_old_tag_from_script (hb_script_t script) |
38 | { |
39 | /* This seems to be accurate as of end of 2012. */ |
40 | |
41 | switch ((hb_tag_t) script) |
42 | { |
43 | case HB_SCRIPT_INVALID: return HB_OT_TAG_DEFAULT_SCRIPT; |
44 | case HB_SCRIPT_MATH: return HB_OT_TAG_MATH_SCRIPT; |
45 | |
46 | /* KATAKANA and HIRAGANA both map to 'kana' */ |
47 | case HB_SCRIPT_HIRAGANA: return HB_TAG('k','a','n','a'); |
48 | |
49 | /* Spaces at the end are preserved, unlike ISO 15924 */ |
50 | case HB_SCRIPT_LAO: return HB_TAG('l','a','o',' '); |
51 | case HB_SCRIPT_YI: return HB_TAG('y','i',' ',' '); |
52 | /* Unicode-5.0 additions */ |
53 | case HB_SCRIPT_NKO: return HB_TAG('n','k','o',' '); |
54 | /* Unicode-5.1 additions */ |
55 | case HB_SCRIPT_VAI: return HB_TAG('v','a','i',' '); |
56 | } |
57 | |
58 | /* Else, just change first char to lowercase and return */ |
59 | return ((hb_tag_t) script) | 0x20000000u; |
60 | } |
61 | |
62 | static hb_script_t |
63 | hb_ot_old_tag_to_script (hb_tag_t tag) |
64 | { |
65 | if (unlikely (tag == HB_OT_TAG_DEFAULT_SCRIPT)) |
66 | return HB_SCRIPT_INVALID; |
67 | if (unlikely (tag == HB_OT_TAG_MATH_SCRIPT)) |
68 | return HB_SCRIPT_MATH; |
69 | |
70 | /* This side of the conversion is fully algorithmic. */ |
71 | |
72 | /* Any spaces at the end of the tag are replaced by repeating the last |
73 | * letter. Eg 'nko ' -> 'Nkoo' */ |
74 | if (unlikely ((tag & 0x0000FF00u) == 0x00002000u)) |
75 | tag |= (tag >> 8) & 0x0000FF00u; /* Copy second letter to third */ |
76 | if (unlikely ((tag & 0x000000FFu) == 0x00000020u)) |
77 | tag |= (tag >> 8) & 0x000000FFu; /* Copy third letter to fourth */ |
78 | |
79 | /* Change first char to uppercase and return */ |
80 | return (hb_script_t) (tag & ~0x20000000u); |
81 | } |
82 | |
83 | static hb_tag_t |
84 | hb_ot_new_tag_from_script (hb_script_t script) |
85 | { |
86 | switch ((hb_tag_t) script) { |
87 | case HB_SCRIPT_BENGALI: return HB_TAG('b','n','g','2'); |
88 | case HB_SCRIPT_DEVANAGARI: return HB_TAG('d','e','v','2'); |
89 | case HB_SCRIPT_GUJARATI: return HB_TAG('g','j','r','2'); |
90 | case HB_SCRIPT_GURMUKHI: return HB_TAG('g','u','r','2'); |
91 | case HB_SCRIPT_KANNADA: return HB_TAG('k','n','d','2'); |
92 | case HB_SCRIPT_MALAYALAM: return HB_TAG('m','l','m','2'); |
93 | case HB_SCRIPT_ORIYA: return HB_TAG('o','r','y','2'); |
94 | case HB_SCRIPT_TAMIL: return HB_TAG('t','m','l','2'); |
95 | case HB_SCRIPT_TELUGU: return HB_TAG('t','e','l','2'); |
96 | case HB_SCRIPT_MYANMAR: return HB_TAG('m','y','m','2'); |
97 | } |
98 | |
99 | return HB_OT_TAG_DEFAULT_SCRIPT; |
100 | } |
101 | |
102 | static hb_script_t |
103 | hb_ot_new_tag_to_script (hb_tag_t tag) |
104 | { |
105 | switch (tag) { |
106 | case HB_TAG('b','n','g','2'): return HB_SCRIPT_BENGALI; |
107 | case HB_TAG('d','e','v','2'): return HB_SCRIPT_DEVANAGARI; |
108 | case HB_TAG('g','j','r','2'): return HB_SCRIPT_GUJARATI; |
109 | case HB_TAG('g','u','r','2'): return HB_SCRIPT_GURMUKHI; |
110 | case HB_TAG('k','n','d','2'): return HB_SCRIPT_KANNADA; |
111 | case HB_TAG('m','l','m','2'): return HB_SCRIPT_MALAYALAM; |
112 | case HB_TAG('o','r','y','2'): return HB_SCRIPT_ORIYA; |
113 | case HB_TAG('t','m','l','2'): return HB_SCRIPT_TAMIL; |
114 | case HB_TAG('t','e','l','2'): return HB_SCRIPT_TELUGU; |
115 | case HB_TAG('m','y','m','2'): return HB_SCRIPT_MYANMAR; |
116 | } |
117 | |
118 | return HB_SCRIPT_UNKNOWN; |
119 | } |
120 | |
121 | #ifndef HB_DISABLE_DEPRECATED |
122 | /** |
123 | * hb_ot_tags_from_script: |
124 | * @script: an #hb_script_t to convert. |
125 | * @script_tag_1: (out): output #hb_tag_t. |
126 | * @script_tag_2: (out): output #hb_tag_t. |
127 | * |
128 | * Converts an #hb_script_t to script tags. |
129 | * |
130 | * Since: 0.6.0 |
131 | * Deprecated: 2.0.0: use hb_ot_tags_from_script_and_language() instead |
132 | **/ |
133 | void |
134 | hb_ot_tags_from_script (hb_script_t script, |
135 | hb_tag_t *script_tag_1, |
136 | hb_tag_t *script_tag_2) |
137 | { |
138 | unsigned int count = 2; |
139 | hb_tag_t tags[2]; |
140 | hb_ot_tags_from_script_and_language (script, HB_LANGUAGE_INVALID, &count, tags, nullptr, nullptr); |
141 | *script_tag_1 = count > 0 ? tags[0] : HB_OT_TAG_DEFAULT_SCRIPT; |
142 | *script_tag_2 = count > 1 ? tags[1] : HB_OT_TAG_DEFAULT_SCRIPT; |
143 | } |
144 | #endif |
145 | |
146 | /* |
147 | * Complete list at: |
148 | * https://docs.microsoft.com/en-us/typography/opentype/spec/scripttags |
149 | * |
150 | * Most of the script tags are the same as the ISO 15924 tag but lowercased. |
151 | * So we just do that, and handle the exceptional cases in a switch. |
152 | */ |
153 | |
154 | static void |
155 | hb_ot_all_tags_from_script (hb_script_t script, |
156 | unsigned int *count /* IN/OUT */, |
157 | hb_tag_t *tags /* OUT */) |
158 | { |
159 | unsigned int i = 0; |
160 | |
161 | hb_tag_t new_tag = hb_ot_new_tag_from_script (script); |
162 | if (unlikely (new_tag != HB_OT_TAG_DEFAULT_SCRIPT)) |
163 | { |
164 | /* HB_SCRIPT_MYANMAR maps to 'mym2', but there is no 'mym3'. */ |
165 | if (new_tag != HB_TAG('m','y','m','2')) |
166 | tags[i++] = new_tag | '3'; |
167 | if (*count > i) |
168 | tags[i++] = new_tag; |
169 | } |
170 | |
171 | if (*count > i) |
172 | { |
173 | hb_tag_t old_tag = hb_ot_old_tag_from_script (script); |
174 | if (old_tag != HB_OT_TAG_DEFAULT_SCRIPT) |
175 | tags[i++] = old_tag; |
176 | } |
177 | |
178 | *count = i; |
179 | } |
180 | |
181 | /** |
182 | * hb_ot_tag_to_script: |
183 | * @tag: a script tag |
184 | * |
185 | * Converts a script tag to an #hb_script_t. |
186 | * |
187 | * Return value: The #hb_script_t corresponding to @tag. |
188 | * |
189 | **/ |
190 | hb_script_t |
191 | hb_ot_tag_to_script (hb_tag_t tag) |
192 | { |
193 | unsigned char digit = tag & 0x000000FFu; |
194 | if (unlikely (digit == '2' || digit == '3')) |
195 | return hb_ot_new_tag_to_script (tag & 0xFFFFFF32); |
196 | |
197 | return hb_ot_old_tag_to_script (tag); |
198 | } |
199 | |
200 | |
201 | /* hb_language_t */ |
202 | |
203 | static inline bool |
204 | subtag_matches (const char *lang_str, |
205 | const char *limit, |
206 | const char *subtag, |
207 | unsigned subtag_len) |
208 | { |
209 | if (likely ((unsigned) (limit - lang_str) < subtag_len)) |
210 | return false; |
211 | |
212 | do { |
213 | const char *s = strstr (lang_str, subtag); |
214 | if (!s || s >= limit) |
215 | return false; |
216 | if (!ISALNUM (s[subtag_len])) |
217 | return true; |
218 | lang_str = s + subtag_len; |
219 | } while (true); |
220 | } |
221 | |
222 | static bool |
223 | lang_matches (const char *lang_str, |
224 | const char *limit, |
225 | const char *spec, |
226 | unsigned spec_len) |
227 | { |
228 | /* Same as hb_language_matches(); duplicated. */ |
229 | |
230 | if (likely ((unsigned) (limit - lang_str) < spec_len)) |
231 | return false; |
232 | |
233 | return strncmp (lang_str, spec, spec_len) == 0 && |
234 | (lang_str[spec_len] == '\0' || lang_str[spec_len] == '-'); |
235 | } |
236 | |
237 | struct LangTag |
238 | { |
239 | hb_tag_t language; |
240 | hb_tag_t tag; |
241 | |
242 | int cmp (hb_tag_t a) const |
243 | { |
244 | return a < this->language ? -1 : a > this->language ? +1 : 0; |
245 | } |
246 | int cmp (const LangTag *that) const |
247 | { return cmp (that->language); } |
248 | }; |
249 | |
250 | #include "hb-ot-tag-table.hh" |
251 | |
252 | /* The corresponding languages IDs for the following IDs are unclear, |
253 | * overlap, or are architecturally weird. Needs more research. */ |
254 | |
255 | /*{"??", {HB_TAG('B','C','R',' ')}},*/ /* Bible Cree */ |
256 | /*{"zh?", {HB_TAG('C','H','N',' ')}},*/ /* Chinese (seen in Microsoft fonts) */ |
257 | /*{"ar-Syrc?", {HB_TAG('G','A','R',' ')}},*/ /* Garshuni */ |
258 | /*{"??", {HB_TAG('N','G','R',' ')}},*/ /* Nagari */ |
259 | /*{"??", {HB_TAG('Y','I','C',' ')}},*/ /* Yi Classic */ |
260 | /*{"zh?", {HB_TAG('Z','H','P',' ')}},*/ /* Chinese Phonetic */ |
261 | |
262 | #ifndef HB_DISABLE_DEPRECATED |
263 | /** |
264 | * hb_ot_tag_from_language: |
265 | * @language: an #hb_language_t to convert. |
266 | * |
267 | * Converts an #hb_language_t to an #hb_tag_t. |
268 | * |
269 | * Since: 0.6.0 |
270 | * Deprecated: 2.0.0: use hb_ot_tags_from_script_and_language() instead |
271 | **/ |
272 | hb_tag_t |
273 | hb_ot_tag_from_language (hb_language_t language) |
274 | { |
275 | unsigned int count = 1; |
276 | hb_tag_t tags[1]; |
277 | hb_ot_tags_from_script_and_language (HB_SCRIPT_UNKNOWN, language, nullptr, nullptr, &count, tags); |
278 | return count > 0 ? tags[0] : HB_OT_TAG_DEFAULT_LANGUAGE; |
279 | } |
280 | #endif |
281 | |
282 | static void |
283 | hb_ot_tags_from_language (const char *lang_str, |
284 | const char *limit, |
285 | unsigned int *count, |
286 | hb_tag_t *tags) |
287 | { |
288 | |
289 | #ifndef HB_NO_LANGUAGE_LONG |
290 | /* Check for matches of multiple subtags. */ |
291 | if (hb_ot_tags_from_complex_language (lang_str, limit, count, tags)) |
292 | return; |
293 | #endif |
294 | |
295 | /* Find a language matching in the first component. */ |
296 | #ifndef HB_NO_LANGUAGE_LONG |
297 | const char *s; s = strchr (lang_str, '-'); |
298 | #endif |
299 | { |
300 | #ifndef HB_NO_LANGUAGE_LONG |
301 | if (s && limit - lang_str >= 6) |
302 | { |
303 | const char *extlang_end = strchr (s + 1, '-'); |
304 | /* If there is an extended language tag, use it. */ |
305 | if (3 == (extlang_end ? extlang_end - s - 1 : strlen (s + 1)) && |
306 | ISALPHA (s[1])) |
307 | lang_str = s + 1; |
308 | } |
309 | #endif |
310 | const LangTag *ot_languages = nullptr; |
311 | unsigned ot_languages_len = 0; |
312 | const char *dash = strchr (lang_str, '-'); |
313 | unsigned first_len = dash ? dash - lang_str : limit - lang_str; |
314 | if (first_len == 2) |
315 | { |
316 | ot_languages = ot_languages2; |
317 | ot_languages_len = ARRAY_LENGTH (ot_languages2); |
318 | } |
319 | #ifndef HB_NO_LANGUAGE_LONG |
320 | else if (first_len == 3) |
321 | { |
322 | ot_languages = ot_languages3; |
323 | ot_languages_len = ARRAY_LENGTH (ot_languages3); |
324 | } |
325 | #endif |
326 | |
327 | hb_tag_t lang_tag = hb_tag_from_string (lang_str, first_len); |
328 | |
329 | static hb_atomic_int_t last_tag_idx; /* Poor man's cache. */ |
330 | unsigned tag_idx = last_tag_idx; |
331 | |
332 | if (likely (tag_idx < ot_languages_len && ot_languages[tag_idx].language == lang_tag) || |
333 | hb_sorted_array (ot_languages, ot_languages_len).bfind (lang_tag, &tag_idx)) |
334 | { |
335 | last_tag_idx = tag_idx; |
336 | unsigned int i; |
337 | while (tag_idx != 0 && |
338 | ot_languages[tag_idx].language == ot_languages[tag_idx - 1].language) |
339 | tag_idx--; |
340 | for (i = 0; |
341 | i < *count && |
342 | tag_idx + i < ot_languages_len && |
343 | ot_languages[tag_idx + i].tag != HB_TAG_NONE && |
344 | ot_languages[tag_idx + i].language == ot_languages[tag_idx].language; |
345 | i++) |
346 | tags[i] = ot_languages[tag_idx + i].tag; |
347 | *count = i; |
348 | return; |
349 | } |
350 | } |
351 | |
352 | #ifndef HB_NO_LANGUAGE_LONG |
353 | if (!s) |
354 | s = lang_str + strlen (lang_str); |
355 | if (s - lang_str == 3) { |
356 | /* Assume it's ISO-639-3 and upper-case and use it. */ |
357 | tags[0] = hb_tag_from_string (lang_str, s - lang_str) & ~0x20202000u; |
358 | *count = 1; |
359 | return; |
360 | } |
361 | #endif |
362 | |
363 | *count = 0; |
364 | } |
365 | |
366 | static bool |
367 | parse_private_use_subtag (const char *private_use_subtag, |
368 | unsigned int *count, |
369 | hb_tag_t *tags, |
370 | const char *prefix, |
371 | unsigned char (*normalize) (unsigned char)) |
372 | { |
373 | #ifdef HB_NO_LANGUAGE_PRIVATE_SUBTAG |
374 | return false; |
375 | #endif |
376 | |
377 | if (!(private_use_subtag && count && tags && *count)) return false; |
378 | |
379 | const char *s = strstr (private_use_subtag, prefix); |
380 | if (!s) return false; |
381 | |
382 | char tag[4]; |
383 | int i; |
384 | s += strlen (prefix); |
385 | if (s[0] == '-') { |
386 | s += 1; |
387 | char c; |
388 | for (i = 0; i < 8 && ISHEX (s[i]); i++) |
389 | { |
390 | c = FROMHEX (s[i]); |
391 | if (i % 2 == 0) |
392 | tag[i / 2] = c << 4; |
393 | else |
394 | tag[i / 2] += c; |
395 | } |
396 | if (i != 8) return false; |
397 | } else { |
398 | for (i = 0; i < 4 && ISALNUM (s[i]); i++) |
399 | tag[i] = normalize (s[i]); |
400 | if (!i) return false; |
401 | |
402 | for (; i < 4; i++) |
403 | tag[i] = ' '; |
404 | } |
405 | tags[0] = HB_TAG (tag[0], tag[1], tag[2], tag[3]); |
406 | if ((tags[0] & 0xDFDFDFDF) == HB_OT_TAG_DEFAULT_SCRIPT) |
407 | tags[0] ^= ~0xDFDFDFDF; |
408 | *count = 1; |
409 | return true; |
410 | } |
411 | |
412 | /** |
413 | * hb_ot_tags_from_script_and_language: |
414 | * @script: an #hb_script_t to convert. |
415 | * @language: (nullable): an #hb_language_t to convert. |
416 | * @script_count: (inout) (optional): maximum number of script tags to retrieve (IN) |
417 | * and actual number of script tags retrieved (OUT) |
418 | * @script_tags: (out) (optional): array of size at least @script_count to store the |
419 | * script tag results |
420 | * @language_count: (inout) (optional): maximum number of language tags to retrieve |
421 | * (IN) and actual number of language tags retrieved (OUT) |
422 | * @language_tags: (out) (optional): array of size at least @language_count to store |
423 | * the language tag results |
424 | * |
425 | * Converts an #hb_script_t and an #hb_language_t to script and language tags. |
426 | * |
427 | * Since: 2.0.0 |
428 | **/ |
429 | void |
430 | hb_ot_tags_from_script_and_language (hb_script_t script, |
431 | hb_language_t language, |
432 | unsigned int *script_count /* IN/OUT */, |
433 | hb_tag_t *script_tags /* OUT */, |
434 | unsigned int *language_count /* IN/OUT */, |
435 | hb_tag_t *language_tags /* OUT */) |
436 | { |
437 | bool needs_script = true; |
438 | |
439 | if (language == HB_LANGUAGE_INVALID) |
440 | { |
441 | if (language_count && language_tags && *language_count) |
442 | *language_count = 0; |
443 | } |
444 | else |
445 | { |
446 | const char *lang_str, *s, *limit, *private_use_subtag; |
447 | bool needs_language; |
448 | |
449 | lang_str = hb_language_to_string (language); |
450 | limit = nullptr; |
451 | private_use_subtag = nullptr; |
452 | if (lang_str[0] == 'x' && lang_str[1] == '-') |
453 | { |
454 | private_use_subtag = lang_str; |
455 | } else { |
456 | for (s = lang_str + 1; *s; s++) |
457 | { |
458 | if (s[-1] == '-' && s[1] == '-') |
459 | { |
460 | if (s[0] == 'x') |
461 | { |
462 | private_use_subtag = s; |
463 | if (!limit) |
464 | limit = s - 1; |
465 | break; |
466 | } else if (!limit) |
467 | { |
468 | limit = s - 1; |
469 | } |
470 | } |
471 | } |
472 | if (!limit) |
473 | limit = s; |
474 | } |
475 | |
476 | needs_script = !parse_private_use_subtag (private_use_subtag, script_count, script_tags, "-hbsc" , TOLOWER); |
477 | needs_language = !parse_private_use_subtag (private_use_subtag, language_count, language_tags, "-hbot" , TOUPPER); |
478 | |
479 | if (needs_language && language_count && language_tags && *language_count) |
480 | hb_ot_tags_from_language (lang_str, limit, language_count, language_tags); |
481 | } |
482 | |
483 | if (needs_script && script_count && script_tags && *script_count) |
484 | hb_ot_all_tags_from_script (script, script_count, script_tags); |
485 | } |
486 | |
487 | /** |
488 | * hb_ot_tag_to_language: |
489 | * @tag: an language tag |
490 | * |
491 | * Converts a language tag to an #hb_language_t. |
492 | * |
493 | * Return value: (transfer none) (nullable): |
494 | * The #hb_language_t corresponding to @tag. |
495 | * |
496 | * Since: 0.9.2 |
497 | **/ |
498 | hb_language_t |
499 | hb_ot_tag_to_language (hb_tag_t tag) |
500 | { |
501 | unsigned int i; |
502 | |
503 | if (tag == HB_OT_TAG_DEFAULT_LANGUAGE) |
504 | return nullptr; |
505 | |
506 | #ifndef HB_NO_LANGUAGE_LONG |
507 | { |
508 | hb_language_t disambiguated_tag = hb_ot_ambiguous_tag_to_language (tag); |
509 | if (disambiguated_tag != HB_LANGUAGE_INVALID) |
510 | return disambiguated_tag; |
511 | } |
512 | #endif |
513 | |
514 | char buf[4]; |
515 | for (i = 0; i < ARRAY_LENGTH (ot_languages2); i++) |
516 | if (ot_languages2[i].tag == tag) |
517 | { |
518 | hb_tag_to_string (ot_languages2[i].language, buf); |
519 | return hb_language_from_string (buf, 2); |
520 | } |
521 | #ifndef HB_NO_LANGUAGE_LONG |
522 | for (i = 0; i < ARRAY_LENGTH (ot_languages3); i++) |
523 | if (ot_languages3[i].tag == tag) |
524 | { |
525 | hb_tag_to_string (ot_languages3[i].language, buf); |
526 | return hb_language_from_string (buf, 3); |
527 | } |
528 | #endif |
529 | |
530 | /* Return a custom language in the form of "x-hbot-AABBCCDD". |
531 | * If it's three letters long, also guess it's ISO 639-3 and lower-case and |
532 | * prepend it (if it's not a registered tag, the private use subtags will |
533 | * ensure that calling hb_ot_tag_from_language on the result will still return |
534 | * the same tag as the original tag). |
535 | */ |
536 | { |
537 | char buf[20]; |
538 | char *str = buf; |
539 | if (ISALPHA (tag >> 24) |
540 | && ISALPHA ((tag >> 16) & 0xFF) |
541 | && ISALPHA ((tag >> 8) & 0xFF) |
542 | && (tag & 0xFF) == ' ') |
543 | { |
544 | buf[0] = TOLOWER (tag >> 24); |
545 | buf[1] = TOLOWER ((tag >> 16) & 0xFF); |
546 | buf[2] = TOLOWER ((tag >> 8) & 0xFF); |
547 | buf[3] = '-'; |
548 | str += 4; |
549 | } |
550 | snprintf (str, 16, "x-hbot-%08x" , tag); |
551 | return hb_language_from_string (&*buf, -1); |
552 | } |
553 | } |
554 | |
555 | /** |
556 | * hb_ot_tags_to_script_and_language: |
557 | * @script_tag: a script tag |
558 | * @language_tag: a language tag |
559 | * @script: (out) (optional): the #hb_script_t corresponding to @script_tag. |
560 | * @language: (out) (optional): the #hb_language_t corresponding to @script_tag and |
561 | * @language_tag. |
562 | * |
563 | * Converts a script tag and a language tag to an #hb_script_t and an |
564 | * #hb_language_t. |
565 | * |
566 | * Since: 2.0.0 |
567 | **/ |
568 | void |
569 | hb_ot_tags_to_script_and_language (hb_tag_t script_tag, |
570 | hb_tag_t language_tag, |
571 | hb_script_t *script /* OUT */, |
572 | hb_language_t *language /* OUT */) |
573 | { |
574 | hb_script_t script_out = hb_ot_tag_to_script (script_tag); |
575 | if (script) |
576 | *script = script_out; |
577 | if (language) |
578 | { |
579 | unsigned int script_count = 1; |
580 | hb_tag_t primary_script_tag[1]; |
581 | hb_ot_tags_from_script_and_language (script_out, |
582 | HB_LANGUAGE_INVALID, |
583 | &script_count, |
584 | primary_script_tag, |
585 | nullptr, nullptr); |
586 | *language = hb_ot_tag_to_language (language_tag); |
587 | if (script_count == 0 || primary_script_tag[0] != script_tag) |
588 | { |
589 | unsigned char *buf; |
590 | const char *lang_str = hb_language_to_string (*language); |
591 | size_t len = strlen (lang_str); |
592 | buf = (unsigned char *) hb_malloc (len + 16); |
593 | if (unlikely (!buf)) |
594 | { |
595 | *language = nullptr; |
596 | } |
597 | else |
598 | { |
599 | int shift; |
600 | hb_memcpy (buf, lang_str, len); |
601 | if (lang_str[0] != 'x' || lang_str[1] != '-') { |
602 | buf[len++] = '-'; |
603 | buf[len++] = 'x'; |
604 | } |
605 | buf[len++] = '-'; |
606 | buf[len++] = 'h'; |
607 | buf[len++] = 'b'; |
608 | buf[len++] = 's'; |
609 | buf[len++] = 'c'; |
610 | buf[len++] = '-'; |
611 | for (shift = 28; shift >= 0; shift -= 4) |
612 | buf[len++] = TOHEX (script_tag >> shift); |
613 | *language = hb_language_from_string ((char *) buf, len); |
614 | hb_free (buf); |
615 | } |
616 | } |
617 | } |
618 | } |
619 | |
620 | #ifdef MAIN |
621 | static inline void |
622 | test_langs_sorted () |
623 | { |
624 | for (unsigned int i = 1; i < ARRAY_LENGTH (ot_languages2); i++) |
625 | { |
626 | int c = ot_languages2[i].cmp (&ot_languages2[i - 1]); |
627 | if (c > 0) |
628 | { |
629 | fprintf (stderr, "ot_languages2 not sorted at index %u: %08x %d %08x\n" , |
630 | i, ot_languages2[i-1].language, c, ot_languages2[i].language); |
631 | abort(); |
632 | } |
633 | } |
634 | #ifndef HB_NO_LANGUAGE_LONG |
635 | for (unsigned int i = 1; i < ARRAY_LENGTH (ot_languages3); i++) |
636 | { |
637 | int c = ot_languages3[i].cmp (&ot_languages3[i - 1]); |
638 | if (c > 0) |
639 | { |
640 | fprintf (stderr, "ot_languages3 not sorted at index %u: %08x %d %08x\n" , |
641 | i, ot_languages3[i-1].language, c, ot_languages3[i].language); |
642 | abort(); |
643 | } |
644 | } |
645 | #endif |
646 | } |
647 | |
648 | int |
649 | main () |
650 | { |
651 | test_langs_sorted (); |
652 | return 0; |
653 | } |
654 | |
655 | #endif |
656 | |
657 | |
658 | #endif |
659 | |