1use smallvec::SmallVec;
2
3use super::common::TagExt;
4use super::{hb_tag_t, script, tag_table, Language, Script};
5
6type ThreeTags = SmallVec<[hb_tag_t; 3]>;
7
8trait SmallVecExt {
9 fn left(&self) -> usize;
10 fn is_full(&self) -> bool;
11}
12
13impl<A: smallvec::Array> SmallVecExt for SmallVec<A> {
14 fn left(&self) -> usize {
15 self.inline_size() - self.len()
16 }
17
18 fn is_full(&self) -> bool {
19 self.len() == self.inline_size()
20 }
21}
22
23pub fn tags_from_script_and_language(
25 script: Option<Script>,
26 language: Option<&Language>,
27) -> (ThreeTags, ThreeTags) {
28 let mut needs_script = true;
29 let mut scripts = SmallVec::new();
30 let mut languages = SmallVec::new();
31
32 let mut private_use_subtag = None;
33 let mut prefix = b"" as &[u8];
34 if let Some(language) = language {
35 let language = language.as_bytes();
36 if language.starts_with(b"x-") {
37 private_use_subtag = Some(language);
38 } else {
39 let mut i = 1;
40 while i < language.len() {
41 if language.get(i - 1) == Some(&b'-') && language.get(i + 1) == Some(&b'-') {
42 if language[i] == b'x' {
43 private_use_subtag = Some(&language[i..]);
44 if prefix.is_empty() {
45 prefix = &language[..i - 1];
46 }
47
48 break;
49 } else {
50 prefix = &language[..i - 1];
51 }
52 }
53
54 i += 1;
55 }
56
57 if prefix.is_empty() {
58 prefix = &language[..i];
59 }
60 }
61
62 needs_script = !parse_private_use_subtag(
63 private_use_subtag,
64 b"-hbsc",
65 u8::to_ascii_lowercase,
66 &mut scripts,
67 );
68
69 let needs_language = !parse_private_use_subtag(
70 private_use_subtag,
71 b"-hbot",
72 u8::to_ascii_uppercase,
73 &mut languages,
74 );
75
76 if needs_language {
77 if let Some(language) = Language::new(prefix) {
78 tags_from_language(&language, &mut languages);
79 }
80 }
81 }
82
83 if needs_script {
84 all_tags_from_script(script, &mut scripts);
85 }
86
87 (scripts, languages)
88}
89
90fn parse_private_use_subtag(
91 private_use_subtag: Option<&[u8]>,
92 prefix: &[u8],
93 normalize: fn(&u8) -> u8,
94 tags: &mut ThreeTags,
95) -> bool {
96 let Some(private_use_subtag) = private_use_subtag else {
97 return false;
98 };
99
100 let private_use_subtag = match private_use_subtag
101 .windows(prefix.len())
102 .position(|window| window == prefix)
103 {
104 Some(idx) => &private_use_subtag[idx + prefix.len()..],
105 None => return false,
106 };
107
108 let mut tag = SmallVec::<[u8; 4]>::new();
109 for c in private_use_subtag.iter().take(4) {
110 if c.is_ascii_alphanumeric() {
111 tag.push((normalize)(c));
112 } else {
113 break;
114 }
115 }
116
117 if tag.is_empty() {
118 return false;
119 }
120
121 let mut tag = hb_tag_t::from_bytes_lossy(tag.as_slice());
122
123 if tag.as_u32() & 0xDFDF_DFDF == hb_tag_t::default_script().as_u32() {
125 tag = hb_tag_t::from_u32(tag.as_u32() ^ !0xDFDF_DFDF);
126 }
127
128 tags.push(tag);
129
130 true
131}
132
133fn lang_cmp(s1: &[u8], s2: &[u8]) -> core::cmp::Ordering {
134 let da = s1
135 .iter()
136 .position(|&c| c == b'-' || c == b'\0')
137 .unwrap_or(s1.len());
138 let db = s2
139 .iter()
140 .position(|&c| c == b'-' || c == b'\0')
141 .unwrap_or(s2.len());
142 let n = core::cmp::max(da, db);
143 let ea = core::cmp::min(n, s1.len());
144 let eb = core::cmp::min(n, s2.len());
145 s1[..ea].cmp(&s2[..eb])
146}
147
148pub(super) fn subtag_matches(language: impl AsRef<[u8]>, subtag: impl AsRef<[u8]>) -> bool {
149 let language = language.as_ref();
150 let subtag = subtag.as_ref();
151
152 for i in language
153 .windows(subtag.len())
154 .enumerate()
155 .filter_map(|(i, window)| (window == subtag).then_some(i))
156 {
157 if let Some(c) = language.get(i + subtag.len()) {
158 if !c.is_ascii_alphanumeric() {
159 return true;
160 }
161 } else {
162 return true;
163 }
164 }
165
166 false
167}
168
169pub(super) fn lang_matches(language: impl AsRef<[u8]>, spec: impl AsRef<[u8]>) -> bool {
170 let language = language.as_ref();
171 let spec = spec.as_ref();
172
173 if language.starts_with(spec) {
174 return language.len() == spec.len() || language.get(spec.len()) == Some(&b'-');
175 }
176
177 false
178}
179
180pub(super) fn strncmp(s1: impl AsRef<[u8]>, s2: impl AsRef<[u8]>, n: usize) -> bool {
181 let s1 = s1.as_ref();
182 let s2 = s2.as_ref();
183
184 let n1 = core::cmp::min(n, s1.len());
185 let n2 = core::cmp::min(n, s2.len());
186 s1[..n1] == s2[..n2]
187}
188
189fn tags_from_language(language: &Language, tags: &mut ThreeTags) {
190 let language = language.as_bytes();
191
192 if let Ok(language_str) = core::str::from_utf8(language) {
194 if tag_table::tags_from_complex_language(language_str, tags) {
195 return;
196 }
197 }
198
199 let mut sublang = language;
200
201 if let Some(i) = language.iter().position(|&c| c == b'-') {
203 if language.len() >= 6 {
205 let extlang = match language[i + 1..].iter().position(|&c| c == b'-') {
206 Some(idx) => idx == 3,
207 None => language.len() - i - 1 == 3,
208 };
209
210 if extlang && language[i + 1].is_ascii_alphabetic() {
211 sublang = &language[i + 1..];
212 }
213 }
214 }
215
216 use tag_table::OPEN_TYPE_LANGUAGES as LANGUAGES;
217
218 if let Ok(mut idx) = LANGUAGES.binary_search_by(|v| lang_cmp(&v.language, sublang)) {
219 while idx != 0 && LANGUAGES[idx].language == LANGUAGES[idx - 1].language {
220 idx -= 1;
221 }
222
223 let len = core::cmp::min(tags.left(), LANGUAGES.len() - idx - 1);
224 for i in 0..len {
225 if LANGUAGES[idx + i].language != LANGUAGES[idx].language {
226 break;
227 }
228
229 if LANGUAGES[idx + i].tag.is_null() {
230 break;
231 }
232
233 if tags.is_full() {
234 break;
235 }
236
237 tags.push(LANGUAGES[idx + i].tag);
238 }
239
240 return;
241 }
242
243 if language.len() == 3 {
244 tags.push(hb_tag_t::from_bytes_lossy(language).to_uppercase());
245 }
246}
247
248fn all_tags_from_script(script: Option<Script>, tags: &mut ThreeTags) {
249 if let Some(script) = script {
250 if let Some(tag) = new_tag_from_script(script) {
251 if tag != hb_tag_t::new(b"mym2") {
253 let mut tag3 = tag.to_be_bytes();
254 tag3[3] = b'3';
255 tags.push(hb_tag_t::new(&tag3));
256 }
257
258 if !tags.is_full() {
259 tags.push(tag);
260 }
261 }
262
263 if !tags.is_full() {
264 tags.push(old_tag_from_script(script));
265 }
266 }
267}
268
269fn new_tag_from_script(script: Script) -> Option<hb_tag_t> {
270 match script {
271 script::BENGALI => Some(hb_tag_t::new(b"bng2")),
272 script::DEVANAGARI => Some(hb_tag_t::new(b"dev2")),
273 script::GUJARATI => Some(hb_tag_t::new(b"gjr2")),
274 script::GURMUKHI => Some(hb_tag_t::new(b"gur2")),
275 script::KANNADA => Some(hb_tag_t::new(b"knd2")),
276 script::MALAYALAM => Some(hb_tag_t::new(b"mlm2")),
277 script::ORIYA => Some(hb_tag_t::new(b"ory2")),
278 script::TAMIL => Some(hb_tag_t::new(b"tml2")),
279 script::TELUGU => Some(hb_tag_t::new(b"tel2")),
280 script::MYANMAR => Some(hb_tag_t::new(b"mym2")),
281 _ => None,
282 }
283}
284
285fn old_tag_from_script(script: Script) -> hb_tag_t {
286 match script {
288 script::MATH => hb_tag_t::new(b"math"),
289
290 script::HIRAGANA => hb_tag_t::new(b"kana"),
292
293 script::LAO => hb_tag_t::new(b"lao "),
295 script::YI => hb_tag_t::new(b"yi "),
296 script::NKO => hb_tag_t::new(b"nko "),
298 script::VAI => hb_tag_t::new(b"vai "),
300
301 _ => hb_tag_t::from_u32(script.tag().as_u32() | 0x2000_0000),
303 }
304}
305
306#[rustfmt::skip]
307#[cfg(test)]
308mod tests {
309 #![allow(non_snake_case)]
310
311 use super::*;
312 use alloc::vec::Vec;
313
314 fn new_tag_to_script(tag: hb_tag_t) -> Option<Script> {
315 match &tag.to_be_bytes() {
316 b"bng2" => Some(script::BENGALI),
317 b"dev2" => Some(script::DEVANAGARI),
318 b"gjr2" => Some(script::GUJARATI),
319 b"gur2" => Some(script::GURMUKHI),
320 b"knd2" => Some(script::KANNADA),
321 b"mlm2" => Some(script::MALAYALAM),
322 b"ory2" => Some(script::ORIYA),
323 b"tml2" => Some(script::TAMIL),
324 b"tel2" => Some(script::TELUGU),
325 b"mym2" => Some(script::MYANMAR),
326 _ => Some(script::UNKNOWN),
327 }
328 }
329
330 fn old_tag_to_script(tag: hb_tag_t) -> Option<Script> {
331 if tag == hb_tag_t::default_script() {
332 return None;
333 }
334
335 let mut bytes = tag.to_be_bytes();
336
337 if bytes[2] == b' ' {
342 bytes[2] = bytes[1];
343 }
344 if bytes[3] == b' ' {
345 bytes[3] = bytes[2];
346 }
347
348 bytes[0] = bytes[0].to_ascii_uppercase();
350
351 Some(Script(hb_tag_t::new(&bytes)))
352 }
353
354 fn tag_to_script(tag: hb_tag_t) -> Option<Script> {
355 let bytes = tag.to_be_bytes();
356 if bytes[3] == b'2' || bytes[3] == b'3' {
357 let mut tag2 = bytes;
358 tag2[3] = b'2';
359 return new_tag_to_script(hb_tag_t::new(&tag2));
360 }
361
362 old_tag_to_script(tag)
363 }
364
365 fn test_simple_tags(tag: &str, script: Script) {
366 let tag = hb_tag_t::from_bytes_lossy(tag.as_bytes());
367
368 let (scripts, _) = tags_from_script_and_language(Some(script), None);
369 if !scripts.is_empty() {
370 assert_eq!(tag, scripts[0]);
371 } else {
372 assert_eq!(tag, hb_tag_t::default_script());
373 }
374
375 assert_eq!(tag_to_script(tag), Some(script));
376 }
377
378 #[test]
379 fn tag_to_uppercase() {
380 assert_eq!(hb_tag_t::new(b"abcd").to_uppercase(), hb_tag_t::new(b"ABCD"));
381 assert_eq!(hb_tag_t::new(b"abc ").to_uppercase(), hb_tag_t::new(b"ABC "));
382 assert_eq!(hb_tag_t::new(b"ABCD").to_uppercase(), hb_tag_t::new(b"ABCD"));
383 }
384
385 #[test]
386 fn tag_to_lowercase() {
387 assert_eq!(hb_tag_t::new(b"abcd").to_lowercase(), hb_tag_t::new(b"abcd"));
388 assert_eq!(hb_tag_t::new(b"abc ").to_lowercase(), hb_tag_t::new(b"abc "));
389 assert_eq!(hb_tag_t::new(b"ABCD").to_lowercase(), hb_tag_t::new(b"abcd"));
390 }
391
392 #[test]
393 fn script_degenerate() {
394 assert_eq!(hb_tag_t::new(b"DFLT"), hb_tag_t::default_script());
395
396 test_simple_tags("kana", script::KATAKANA);
398
399 let (scripts, _) = tags_from_script_and_language(Some(script::HIRAGANA), None);
400 assert_eq!(scripts.as_slice(), &[hb_tag_t::new(b"kana")]);
401
402 assert_eq!(tag_to_script(hb_tag_t::new(b"be ")), Script::from_iso15924_tag(hb_tag_t::new(b"Beee")));
404 }
405
406 #[test]
407 fn script_simple() {
408 test_simple_tags("wwyz", Script::from_iso15924_tag(hb_tag_t::new(b"wWyZ")).unwrap());
410
411 test_simple_tags("zyyy", script::COMMON);
413 test_simple_tags("zinh", script::INHERITED);
414 test_simple_tags("zzzz", script::UNKNOWN);
415
416 test_simple_tags("arab", script::ARABIC);
417 test_simple_tags("copt", script::COPTIC);
418 test_simple_tags("kana", script::KATAKANA);
419 test_simple_tags("latn", script::LATIN);
420
421 test_simple_tags("lao ", script::LAO);
423 test_simple_tags("yi ", script::YI);
424 test_simple_tags("nko ", script::NKO);
426 test_simple_tags("vai ", script::VAI);
428
429 test_simple_tags("mtei", script::MEETEI_MAYEK);
433 test_simple_tags("mand", script::MANDAIC);
435 }
436
437 macro_rules! test_script_from_language {
438 ($name:ident, $tag:expr, $lang:expr, $script:expr) => {
439 #[test]
440 fn $name() {
441 let tag = hb_tag_t::from_bytes_lossy($tag.as_bytes());
442 let (scripts, _) =
443 tags_from_script_and_language($script, Language::new($lang).as_ref());
444 if !scripts.is_empty() {
445 assert_eq!(scripts.as_slice(), &[tag]);
446 }
447 }
448 };
449 }
450
451 test_script_from_language!(script_from_language_01, "", "", None);
452 test_script_from_language!(script_from_language_02, "", "en", None);
453 test_script_from_language!(script_from_language_03, "copt", "en", Some(script::COPTIC));
454 test_script_from_language!(script_from_language_04, "", "x-hbsc", None);
455 test_script_from_language!(script_from_language_05, "copt", "x-hbsc", Some(script::COPTIC));
456 test_script_from_language!(script_from_language_06, "abc ", "x-hbscabc", None);
457 test_script_from_language!(script_from_language_07, "deva", "x-hbscdeva", None);
458 test_script_from_language!(script_from_language_08, "dev2", "x-hbscdev2", None);
459 test_script_from_language!(script_from_language_09, "dev3", "x-hbscdev3", None);
460 test_script_from_language!(script_from_language_10, "copt", "x-hbotpap0-hbsccopt", None);
461 test_script_from_language!(script_from_language_11, "", "en-x-hbsc", None);
462 test_script_from_language!(script_from_language_12, "copt", "en-x-hbsc", Some(script::COPTIC));
463 test_script_from_language!(script_from_language_13, "abc ", "en-x-hbscabc", None);
464 test_script_from_language!(script_from_language_14, "deva", "en-x-hbscdeva", None);
465 test_script_from_language!(script_from_language_15, "dev2", "en-x-hbscdev2", None);
466 test_script_from_language!(script_from_language_16, "dev3", "en-x-hbscdev3", None);
467 test_script_from_language!(script_from_language_17, "copt", "en-x-hbotpap0-hbsccopt", None);
468
469 #[test]
470 fn script_indic() {
471 fn check(tag1: &str, tag2: &str, tag3: &str, script: Script) {
472 let tag1 = hb_tag_t::from_bytes_lossy(tag1.as_bytes());
473 let tag2 = hb_tag_t::from_bytes_lossy(tag2.as_bytes());
474 let tag3 = hb_tag_t::from_bytes_lossy(tag3.as_bytes());
475
476 let (scripts, _) = tags_from_script_and_language(Some(script), None);
477 assert_eq!(scripts.as_slice(), &[tag1, tag2, tag3]);
478 assert_eq!(tag_to_script(tag1), Some(script));
479 assert_eq!(tag_to_script(tag2), Some(script));
480 assert_eq!(tag_to_script(tag3), Some(script));
481 }
482
483 check("bng3", "bng2", "beng", script::BENGALI);
484 check("dev3", "dev2", "deva", script::DEVANAGARI);
485 check("gjr3", "gjr2", "gujr", script::GUJARATI);
486 check("gur3", "gur2", "guru", script::GURMUKHI);
487 check("knd3", "knd2", "knda", script::KANNADA);
488 check("mlm3", "mlm2", "mlym", script::MALAYALAM);
489 check("ory3", "ory2", "orya", script::ORIYA);
490 check("tml3", "tml2", "taml", script::TAMIL);
491 check("tel3", "tel2", "telu", script::TELUGU);
492 }
493
494 macro_rules! test_tag_from_language {
496 ($name:ident, $tag:expr, $lang:expr) => {
497 #[test]
498 fn $name() {
499 let tag = hb_tag_t::from_bytes_lossy($tag.as_bytes());
500 let (_, languages) = tags_from_script_and_language(
501 None,
502 Language::new(&$lang.to_lowercase()).as_ref(),
503 );
504 if !languages.is_empty() {
505 assert_eq!(languages[0], tag);
506 }
507 }
508 };
509 }
510
511 test_tag_from_language!(tag_from_language_dflt, "dflt", "");
512 test_tag_from_language!(tag_from_language_ALT, "ALT", "alt");
513 test_tag_from_language!(tag_from_language_ARA, "ARA", "ar");
514 test_tag_from_language!(tag_from_language_AZE, "AZE", "az");
515 test_tag_from_language!(tag_from_language_az_ir, "AZE", "az-ir");
516 test_tag_from_language!(tag_from_language_az_az, "AZE", "az-az");
517 test_tag_from_language!(tag_from_language_ENG, "ENG", "en");
518 test_tag_from_language!(tag_from_language_en_US, "ENG", "en_US");
519 test_tag_from_language!(tag_from_language_CJA, "CJA", "cja"); test_tag_from_language!(tag_from_language_CJM, "CJM", "cjm"); test_tag_from_language!(tag_from_language_ENV, "EVN", "eve");
522 test_tag_from_language!(tag_from_language_HAL, "HAL", "cfm"); test_tag_from_language!(tag_from_language_flm, "HAL", "flm"); test_tag_from_language!(tag_from_language_hy, "HYE0", "hy");
525 test_tag_from_language!(tag_from_language_hyw, "HYE", "hyw");
526 test_tag_from_language!(tag_from_language_bgr, "QIN", "bgr"); test_tag_from_language!(tag_from_language_cbl, "QIN", "cbl"); test_tag_from_language!(tag_from_language_cka, "QIN", "cka"); test_tag_from_language!(tag_from_language_cmr, "QIN", "cmr"); test_tag_from_language!(tag_from_language_cnb, "QIN", "cnb"); test_tag_from_language!(tag_from_language_cnh, "QIN", "cnh"); test_tag_from_language!(tag_from_language_cnk, "QIN", "cnk"); test_tag_from_language!(tag_from_language_cnw, "QIN", "cnw"); test_tag_from_language!(tag_from_language_csh, "QIN", "csh"); test_tag_from_language!(tag_from_language_csy, "QIN", "csy"); test_tag_from_language!(tag_from_language_ctd, "QIN", "ctd"); test_tag_from_language!(tag_from_language_czt, "QIN", "czt"); test_tag_from_language!(tag_from_language_dao, "QIN", "dao"); test_tag_from_language!(tag_from_language_htl, "QIN", "hlt"); test_tag_from_language!(tag_from_language_mrh, "QIN", "mrh"); test_tag_from_language!(tag_from_language_pck, "QIN", "pck"); test_tag_from_language!(tag_from_language_sez, "QIN", "sez"); test_tag_from_language!(tag_from_language_tcp, "QIN", "tcp"); test_tag_from_language!(tag_from_language_tcz, "QIN", "tcz"); test_tag_from_language!(tag_from_language_yos, "QIN", "yos"); test_tag_from_language!(tag_from_language_zom, "QIN", "zom"); test_tag_from_language!(tag_from_language_FAR, "FAR", "fa");
548 test_tag_from_language!(tag_from_language_fa_IR, "FAR", "fa_IR");
549 test_tag_from_language!(tag_from_language_man, "MNK", "man");
550 test_tag_from_language!(tag_from_language_SWA, "SWA", "aii"); test_tag_from_language!(tag_from_language_SYR, "SYR", "syr"); test_tag_from_language!(tag_from_language_amw, "SYR", "amw"); test_tag_from_language!(tag_from_language_cld, "SYR", "cld"); test_tag_from_language!(tag_from_language_syc, "SYR", "syc"); test_tag_from_language!(tag_from_language_TUA, "TUA", "tru"); test_tag_from_language!(tag_from_language_zh, "ZHS", "zh"); test_tag_from_language!(tag_from_language_zh_cn, "ZHS", "zh-cn"); test_tag_from_language!(tag_from_language_zh_sg, "ZHS", "zh-sg"); test_tag_from_language!(tag_from_language_zh_mo, "ZHTM", "zh-mo"); test_tag_from_language!(tag_from_language_zh_hant_mo, "ZHTM", "zh-hant-mo"); test_tag_from_language!(tag_from_language_zh_hans_mo, "ZHS", "zh-hans-mo"); test_tag_from_language!(tag_from_language_ZHH, "ZHH", "zh-HK"); test_tag_from_language!(tag_from_language_zh_HanT_hK, "ZHH", "zH-HanT-hK"); test_tag_from_language!(tag_from_language_zh_HanS_hK, "ZHS", "zH-HanS-hK"); test_tag_from_language!(tag_from_language_zh_tw, "ZHT", "zh-tw"); test_tag_from_language!(tag_from_language_ZHS, "ZHS", "zh-Hans"); test_tag_from_language!(tag_from_language_ZHT, "ZHT", "zh-Hant"); test_tag_from_language!(tag_from_language_zh_xx, "ZHS", "zh-xx"); test_tag_from_language!(tag_from_language_zh_Hans_TW, "ZHS", "zh-Hans-TW");
570 test_tag_from_language!(tag_from_language_yue, "ZHH", "yue");
571 test_tag_from_language!(tag_from_language_yue_Hant, "ZHH", "yue-Hant");
572 test_tag_from_language!(tag_from_language_yue_Hans, "ZHS", "yue-Hans");
573 test_tag_from_language!(tag_from_language_ABC, "ABC", "abc");
574 test_tag_from_language!(tag_from_language_ABCD, "ABCD", "x-hbotabcd");
575 test_tag_from_language!(tag_from_language_asdf_asdf_wer_x_hbotabc_zxc, "ABC", "asdf-asdf-wer-x-hbotabc-zxc");
576 test_tag_from_language!(tag_from_language_asdf_asdf_wer_x_hbotabc, "ABC", "asdf-asdf-wer-x-hbotabc");
577 test_tag_from_language!(tag_from_language_asdf_asdf_wer_x_hbotabcd, "ABCD", "asdf-asdf-wer-x-hbotabcd");
578 test_tag_from_language!(tag_from_language_asdf_asdf_wer_x_hbot_zxc, "dflt", "asdf-asdf-wer-x-hbot-zxc");
579 test_tag_from_language!(tag_from_language_xy, "dflt", "xy");
580 test_tag_from_language!(tag_from_language_xyz, "XYZ", "xyz"); test_tag_from_language!(tag_from_language_xyz_qw, "XYZ", "xyz-qw"); test_tag_from_language!(tag_from_language__fonipa, "IPPH", "-fonipa");
588
589 test_tag_from_language!(tag_from_language_en_fonipax, "ENG", "en-fonipax");
594 test_tag_from_language!(tag_from_language_en_x_fonipa, "ENG", "en-x-fonipa");
595 test_tag_from_language!(tag_from_language_en_a_fonipa, "ENG", "en-a-fonipa");
596 test_tag_from_language!(tag_from_language_en_a_qwe_b_fonipa, "ENG", "en-a-qwe-b-fonipa");
597
598 test_tag_from_language!(tag_from_language_en_fonipa, "IPPH", "en-fonipa");
600 test_tag_from_language!(tag_from_language_en_fonipax_fonipa, "IPPH", "en-fonipax-fonipa");
601 test_tag_from_language!(tag_from_language_rm_ch_fonipa_sursilv_x_foobar, "IPPH", "rm-CH-fonipa-sursilv-x-foobar");
602 test_tag_from_language!(tag_from_language_IPPH, "IPPH", "und-fonipa");
603 test_tag_from_language!(tag_from_language_zh_fonipa, "IPPH", "zh-fonipa");
604
605 test_tag_from_language!(tag_from_language_en_fonnapa, "APPH", "en-fonnapa");
607 test_tag_from_language!(tag_from_language_chr_fonnapa, "APPH", "chr-fonnapa");
608 test_tag_from_language!(tag_from_language_APPH, "APPH", "und-fonnapa");
609
610 test_tag_from_language!(tag_from_language_ka_geok, "KGE", "ka-Geok");
612 test_tag_from_language!(tag_from_language_KGE, "KGE", "und-Geok");
613
614 test_tag_from_language!(tag_from_language_IRT, "IRT", "ga-Latg");
616
617 test_tag_from_language!(tag_from_language_MOL, "MOL", "ro-MD");
619
620 test_tag_from_language!(tag_from_language_PGR, "PGR", "el-polyton");
622 test_tag_from_language!(tag_from_language_el_CY_polyton, "PGR", "el-CY-polyton");
623
624 test_tag_from_language!(tag_from_language_aii_Syre, "SYRE", "aii-Syre");
626 test_tag_from_language!(tag_from_language_de_Syre, "SYRE", "de-Syre");
627 test_tag_from_language!(tag_from_language_syr_Syre, "SYRE", "syr-Syre");
628 test_tag_from_language!(tag_from_language_und_Syre, "SYRE", "und-Syre");
629
630 test_tag_from_language!(tag_from_language_aii_Syrj, "SYRJ", "aii-Syrj");
632 test_tag_from_language!(tag_from_language_de_Syrj, "SYRJ", "de-Syrj");
633 test_tag_from_language!(tag_from_language_syr_Syrj, "SYRJ", "syr-Syrj");
634 test_tag_from_language!(tag_from_language_SYRJ, "SYRJ", "und-Syrj");
635
636 test_tag_from_language!(tag_from_language_aii_Syrn, "SYRN", "aii-Syrn");
638 test_tag_from_language!(tag_from_language_de_Syrn, "SYRN", "de-Syrn");
639 test_tag_from_language!(tag_from_language_syr_Syrn, "SYRN", "syr-Syrn");
640 test_tag_from_language!(tag_from_language_SYRN, "SYRN", "und-Syrn");
641
642 test_tag_from_language!(tag_from_language_fa_x_hbotabc_zxc, "ABC", "fa-x-hbotabc-zxc");
644 test_tag_from_language!(tag_from_language_fa_ir_x_hbotabc_zxc, "ABC", "fa-ir-x-hbotabc-zxc");
645 test_tag_from_language!(tag_from_language_zh_x_hbotabc_zxc, "ABC", "zh-x-hbotabc-zxc");
646 test_tag_from_language!(tag_from_language_zh_cn_x_hbotabc_zxc, "ABC", "zh-cn-x-hbotabc-zxc");
647 test_tag_from_language!(tag_from_language_zh_xy_x_hbotabc_zxc, "ABC", "zh-xy-x-hbotabc-zxc");
648 test_tag_from_language!(tag_from_language_xyz_xy_x_hbotabc_zxc, "ABC", "xyz-xy-x-hbotabc-zxc");
649
650 test_tag_from_language!(tag_from_language_ar_aao, "ARA", "ar-aao");
652 test_tag_from_language!(tag_from_language_art_lojban, "JBO", "art-lojban");
653 test_tag_from_language!(tag_from_language_kok_gom, "KOK", "kok-gom");
654 test_tag_from_language!(tag_from_language_i_lux, "LTZ", "i-lux");
655 test_tag_from_language!(tag_from_language_drh, "MNG", "drh");
656 test_tag_from_language!(tag_from_language_ar_ary1, "MOR", "ar-ary");
657 test_tag_from_language!(tag_from_language_ar_ary_DZ, "MOR", "ar-ary-DZ");
658 test_tag_from_language!(tag_from_language_no_bok, "NOR", "no-bok");
659 test_tag_from_language!(tag_from_language_no_nyn, "NYN", "no-nyn");
660 test_tag_from_language!(tag_from_language_i_hak, "ZHS", "i-hak");
661 test_tag_from_language!(tag_from_language_zh_guoyu, "ZHS", "zh-guoyu");
662 test_tag_from_language!(tag_from_language_zh_min, "ZHS", "zh-min");
663 test_tag_from_language!(tag_from_language_zh_min_nan, "ZHS", "zh-min-nan");
664 test_tag_from_language!(tag_from_language_zh_xiang, "ZHS", "zh-xiang");
665
666 test_tag_from_language!(tag_from_language_als, "SQI", "als");
668 test_tag_from_language!(tag_from_language_far, "dflt", "far");
669
670 test_tag_from_language!(tag_from_language_ar_001, "ARA", "ar-001");
672
673 test_tag_from_language!(tag_from_language_invalid, "TRK", "tr@foo=bar");
675
676 macro_rules! test_tags {
677 ($name:ident, $script:expr, $lang:expr, $scripts:expr, $langs:expr) => {
678 #[test]
679 fn $name() {
680 let (scripts, languages) =
681 tags_from_script_and_language($script, Language::new($lang).as_ref());
682
683 let exp_scripts: Vec<hb_tag_t> = $scripts.iter().map(|v| hb_tag_t::from_bytes_lossy(*v)).collect();
684 let exp_langs: Vec<hb_tag_t> = $langs.iter().map(|v| hb_tag_t::from_bytes_lossy(*v)).collect();
685
686 assert_eq!(exp_scripts, scripts.as_slice());
687 assert_eq!(exp_langs, languages.as_slice());
688 }
689 };
690 }
691
692 test_tags!(tag_full_en, None, "en", &[], &[b"ENG"]);
693 test_tags!(tag_full_en_x_hbscdflt, None, "en-x-hbscdflt", &[b"DFLT"], &[b"ENG"]);
694 test_tags!(tag_full_en_latin, Some(script::LATIN), "en", &[b"latn"], &[b"ENG"]);
695 test_tags!(tag_full_und_fonnapa, None, "und-fonnapa", &[], &[b"APPH"]);
696 test_tags!(tag_full_en_fonnapa, None, "en-fonnapa", &[], &[b"APPH"]);
697 test_tags!(tag_full_x_hbot1234_hbsc5678, None, "x-hbot1234-hbsc5678", &[b"5678"], &[b"1234"]);
698 test_tags!(tag_full_x_hbsc5678_hbot1234, None, "x-hbsc5678-hbot1234", &[b"5678"], &[b"1234"]);
699 test_tags!(tag_full_ml, Some(script::MALAYALAM), "ml", &[b"mlm3", b"mlm2", b"mlym"], &[b"MAL", b"MLR"]);
700 test_tags!(tag_full_xyz, None, "xyz", &[], &[b"XYZ"]);
701 test_tags!(tag_full_xy, None, "xy", &[], &[]);
702}