xref: /aosp_15_r20/external/cronet/third_party/rust/chromium_crates_io/vendor/skrifa-0.15.5/src/string.rs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 //! Localized strings describing font names and other metadata.
2 //!
3 //! This provides higher level interfaces for accessing the data in the
4 //! OpenType [name](https://learn.microsoft.com/en-us/typography/opentype/spec/name)
5 //! table.
6 //!
7 //! # Example
8 //! The following function will print all localized strings from the set
9 //! of predefined identifiers in a font:
10 //! ```
11 //! use skrifa::{string::StringId, MetadataProvider};
12 //!
13 //! fn print_well_known_strings<'a>(font: &impl MetadataProvider<'a>) {
14 //!     for id in StringId::predefined() {
15 //!         let strings = font.localized_strings(id);
16 //!         if strings.clone().next().is_some() {
17 //!             println!("[{:?}]", id);
18 //!             for string in font.localized_strings(id) {
19 //!                 println!("{:?} {}", string.language(), string.to_string());
20 //!             }
21 //!         }
22 //!     }
23 //! }
24 //! ```
25 
26 use read_fonts::{
27     tables::name::{CharIter, Name, NameRecord, NameString},
28     TableProvider,
29 };
30 
31 use core::fmt;
32 
33 #[doc(inline)]
34 pub use read_fonts::types::NameId as StringId;
35 
36 /// Iterator over the characters of a string.
37 #[derive(Clone)]
38 pub struct Chars<'a> {
39     inner: Option<CharIter<'a>>,
40 }
41 
42 impl<'a> Iterator for Chars<'a> {
43     type Item = char;
44 
next(&mut self) -> Option<Self::Item>45     fn next(&mut self) -> Option<Self::Item> {
46         self.inner.as_mut()?.next()
47     }
48 }
49 
50 /// Iterator over a collection of localized strings for a specific identifier.
51 #[derive(Clone)]
52 pub struct LocalizedStrings<'a> {
53     name: Option<Name<'a>>,
54     records: core::slice::Iter<'a, NameRecord>,
55     id: StringId,
56 }
57 
58 impl<'a> LocalizedStrings<'a> {
59     /// Creates a new localized string iterator from the given font and string identifier.
new(font: &impl TableProvider<'a>, id: StringId) -> Self60     pub fn new(font: &impl TableProvider<'a>, id: StringId) -> Self {
61         let name = font.name().ok();
62         let records = name
63             .as_ref()
64             .map(|name| name.name_record().iter())
65             .unwrap_or([].iter());
66         Self { name, records, id }
67     }
68 
69     /// Returns the informational string identifier for this iterator.
id(&self) -> StringId70     pub fn id(&self) -> StringId {
71         self.id
72     }
73 
74     /// Returns the best available English string or the first string in the sequence.
75     ///
76     /// This prefers the following languages, in order: "en-US", "en",
77     /// "" (empty, for bare Unicode platform strings which don't have an associated
78     /// language).
79     ///
80     /// If none of these are found, returns the first string, or `None` if the sequence
81     /// is empty.
english_or_first(self) -> Option<LocalizedString<'a>>82     pub fn english_or_first(self) -> Option<LocalizedString<'a>> {
83         let mut best_rank = -1;
84         let mut best_string = None;
85         for (i, string) in self.enumerate() {
86             let rank = match (i, string.language()) {
87                 (_, Some("en-US")) => return Some(string),
88                 (_, Some("en")) => 2,
89                 (_, None) => 1,
90                 (0, _) => 0,
91                 _ => continue,
92             };
93             if rank > best_rank {
94                 best_rank = rank;
95                 best_string = Some(string);
96             }
97         }
98         best_string
99     }
100 }
101 
102 impl<'a> Iterator for LocalizedStrings<'a> {
103     type Item = LocalizedString<'a>;
104 
next(&mut self) -> Option<Self::Item>105     fn next(&mut self) -> Option<Self::Item> {
106         let name = self.name.as_ref()?;
107         loop {
108             let record = self.records.next()?;
109             if record.name_id() == self.id {
110                 return Some(LocalizedString::new(name, record));
111             }
112         }
113     }
114 }
115 
116 impl Default for LocalizedStrings<'_> {
default() -> Self117     fn default() -> Self {
118         Self {
119             name: None,
120             records: [].iter(),
121             id: StringId::default(),
122         }
123     }
124 }
125 
126 /// String containing a name or other font metadata in a specific language.
127 #[derive(Clone, Debug)]
128 pub struct LocalizedString<'a> {
129     language: Option<Language>,
130     value: Option<NameString<'a>>,
131 }
132 
133 impl<'a> LocalizedString<'a> {
new(name: &Name<'a>, record: &NameRecord) -> Self134     fn new(name: &Name<'a>, record: &NameRecord) -> Self {
135         let language = Language::new(name, record);
136         let value = record.string(name.string_data()).ok();
137         Self { language, value }
138     }
139 
140     /// Returns the BCP-47 language identifier for the localized string.
language(&self) -> Option<&str>141     pub fn language(&self) -> Option<&str> {
142         self.language.as_ref().map(|language| language.as_str())
143     }
144 
145     /// Returns an iterator over the characters of the localized string.
chars(&self) -> Chars<'a>146     pub fn chars(&self) -> Chars<'a> {
147         Chars {
148             inner: self.value.map(|value| value.chars()),
149         }
150     }
151 }
152 
153 impl fmt::Display for LocalizedString<'_> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result154     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155         for ch in self.chars() {
156             ch.fmt(f)?;
157         }
158         Ok(())
159     }
160 }
161 
162 /// This value is chosen arbitrarily to accommodate common language tags that
163 /// are almost always <= 11 bytes (LLL-SSSS-RR where L is primary language, S
164 /// is script and R is region) and to keep the Language enum at a reasonable
165 /// 32 bytes in size.
166 const MAX_INLINE_LANGUAGE_LEN: usize = 30;
167 
168 #[derive(Copy, Clone, Debug)]
169 #[repr(u8)]
170 enum Language {
171     Inline {
172         buf: [u8; MAX_INLINE_LANGUAGE_LEN],
173         len: u8,
174     },
175     Static(&'static str),
176 }
177 
178 impl Language {
new(name: &Name, record: &NameRecord) -> Option<Self>179     fn new(name: &Name, record: &NameRecord) -> Option<Self> {
180         let language_id = record.language_id();
181         // For version 1 name tables, prefer language tags:
182         // https://learn.microsoft.com/en-us/typography/opentype/spec/name#naming-table-version-1
183         const BASE_LANGUAGE_TAG_ID: u16 = 0x8000;
184         if name.version() == 1 && language_id >= BASE_LANGUAGE_TAG_ID {
185             let index = (language_id - BASE_LANGUAGE_TAG_ID) as usize;
186             let language_string = name
187                 .lang_tag_record()?
188                 .get(index)?
189                 .lang_tag(name.string_data())
190                 .ok()?;
191             Self::from_name_string(&language_string)
192         } else {
193             match record.platform_id() {
194                 // We only match Macintosh and Windows language ids.
195                 1 | 3 => Self::from_language_id(language_id),
196                 _ => None,
197             }
198         }
199     }
200 
201     /// Decodes a language tag string into an inline ASCII byte sequence.
from_name_string(s: &NameString) -> Option<Self>202     fn from_name_string(s: &NameString) -> Option<Self> {
203         let mut buf = [0u8; MAX_INLINE_LANGUAGE_LEN];
204         let mut len = 0;
205         for ch in s.chars() {
206             // From "Tags for Identifying Languages" <https://www.rfc-editor.org/rfc/rfc5646.html#page-6>:
207             // "Although [RFC5234] refers to octets, the language tags described in
208             // this document are sequences of characters from the US-ASCII [ISO646]
209             // repertoire"
210             // Therefore we assume that non-ASCII characters signal an invalid language tag.
211             if !ch.is_ascii() || len == MAX_INLINE_LANGUAGE_LEN {
212                 return None;
213             }
214             buf[len] = ch as u8;
215             len += 1;
216         }
217         Some(Self::Inline {
218             buf,
219             len: len as u8,
220         })
221     }
222 
from_language_id(language_id: u16) -> Option<Self>223     fn from_language_id(language_id: u16) -> Option<Self> {
224         Some(Self::Static(language_id_to_bcp47(language_id)?))
225     }
226 
as_str(&self) -> &str227     fn as_str(&self) -> &str {
228         match self {
229             Self::Inline { buf: data, len } => {
230                 let data = &data[..*len as usize];
231                 core::str::from_utf8(data).unwrap_or_default()
232             }
233             Self::Static(str) => str,
234         }
235     }
236 }
237 
238 /// Converts an OpenType language identifier to a BCP-47 language tag.
language_id_to_bcp47(language_id: u16) -> Option<&'static str>239 fn language_id_to_bcp47(language_id: u16) -> Option<&'static str> {
240     match LANGUAGE_ID_TO_BCP47.binary_search_by(|entry| entry.0.cmp(&language_id)) {
241         Ok(ix) => LANGUAGE_ID_TO_BCP47.get(ix).map(|entry| entry.1),
242         _ => None,
243     }
244 }
245 
246 /// Mapping of OpenType name table language identifier to BCP-47 language tag.
247 /// Borrowed from Skia: <https://skia.googlesource.com/skia/+/refs/heads/main/src/sfnt/SkOTTable_name.cpp#98>
248 const LANGUAGE_ID_TO_BCP47: &[(u16, &str)] = &[
249     /* A mapping from Mac Language Designators to BCP 47 codes.
250      *  The following list was constructed more or less manually.
251      *  Apple now uses BCP 47 (post OSX10.4), so there will be no new entries.
252      */
253     (0, "en"),        //English
254     (1, "fr"),        //French
255     (2, "de"),        //German
256     (3, "it"),        //Italian
257     (4, "nl"),        //Dutch
258     (5, "sv"),        //Swedish
259     (6, "es"),        //Spanish
260     (7, "da"),        //Danish
261     (8, "pt"),        //Portuguese
262     (9, "nb"),        //Norwegian
263     (10, "he"),       //Hebrew
264     (11, "ja"),       //Japanese
265     (12, "ar"),       //Arabic
266     (13, "fi"),       //Finnish
267     (14, "el"),       //Greek
268     (15, "is"),       //Icelandic
269     (16, "mt"),       //Maltese
270     (17, "tr"),       //Turkish
271     (18, "hr"),       //Croatian
272     (19, "zh-Hant"),  //Chinese (Traditional)
273     (20, "ur"),       //Urdu
274     (21, "hi"),       //Hindi
275     (22, "th"),       //Thai
276     (23, "ko"),       //Korean
277     (24, "lt"),       //Lithuanian
278     (25, "pl"),       //Polish
279     (26, "hu"),       //Hungarian
280     (27, "et"),       //Estonian
281     (28, "lv"),       //Latvian
282     (29, "se"),       //Sami
283     (30, "fo"),       //Faroese
284     (31, "fa"),       //Farsi (Persian)
285     (32, "ru"),       //Russian
286     (33, "zh-Hans"),  //Chinese (Simplified)
287     (34, "nl"),       //Dutch
288     (35, "ga"),       //Irish(Gaelic)
289     (36, "sq"),       //Albanian
290     (37, "ro"),       //Romanian
291     (38, "cs"),       //Czech
292     (39, "sk"),       //Slovak
293     (40, "sl"),       //Slovenian
294     (41, "yi"),       //Yiddish
295     (42, "sr"),       //Serbian
296     (43, "mk"),       //Macedonian
297     (44, "bg"),       //Bulgarian
298     (45, "uk"),       //Ukrainian
299     (46, "be"),       //Byelorussian
300     (47, "uz"),       //Uzbek
301     (48, "kk"),       //Kazakh
302     (49, "az-Cyrl"),  //Azerbaijani (Cyrillic)
303     (50, "az-Arab"),  //Azerbaijani (Arabic)
304     (51, "hy"),       //Armenian
305     (52, "ka"),       //Georgian
306     (53, "mo"),       //Moldavian
307     (54, "ky"),       //Kirghiz
308     (55, "tg"),       //Tajiki
309     (56, "tk"),       //Turkmen
310     (57, "mn-Mong"),  //Mongolian (Traditional)
311     (58, "mn-Cyrl"),  //Mongolian (Cyrillic)
312     (59, "ps"),       //Pashto
313     (60, "ku"),       //Kurdish
314     (61, "ks"),       //Kashmiri
315     (62, "sd"),       //Sindhi
316     (63, "bo"),       //Tibetan
317     (64, "ne"),       //Nepali
318     (65, "sa"),       //Sanskrit
319     (66, "mr"),       //Marathi
320     (67, "bn"),       //Bengali
321     (68, "as"),       //Assamese
322     (69, "gu"),       //Gujarati
323     (70, "pa"),       //Punjabi
324     (71, "or"),       //Oriya
325     (72, "ml"),       //Malayalam
326     (73, "kn"),       //Kannada
327     (74, "ta"),       //Tamil
328     (75, "te"),       //Telugu
329     (76, "si"),       //Sinhalese
330     (77, "my"),       //Burmese
331     (78, "km"),       //Khmer
332     (79, "lo"),       //Lao
333     (80, "vi"),       //Vietnamese
334     (81, "id"),       //Indonesian
335     (82, "tl"),       //Tagalog
336     (83, "ms-Latn"),  //Malay (Roman)
337     (84, "ms-Arab"),  //Malay (Arabic)
338     (85, "am"),       //Amharic
339     (86, "ti"),       //Tigrinya
340     (87, "om"),       //Oromo
341     (88, "so"),       //Somali
342     (89, "sw"),       //Swahili
343     (90, "rw"),       //Kinyarwanda/Ruanda
344     (91, "rn"),       //Rundi
345     (92, "ny"),       //Nyanja/Chewa
346     (93, "mg"),       //Malagasy
347     (94, "eo"),       //Esperanto
348     (128, "cy"),      //Welsh
349     (129, "eu"),      //Basque
350     (130, "ca"),      //Catalan
351     (131, "la"),      //Latin
352     (132, "qu"),      //Quechua
353     (133, "gn"),      //Guarani
354     (134, "ay"),      //Aymara
355     (135, "tt"),      //Tatar
356     (136, "ug"),      //Uighur
357     (137, "dz"),      //Dzongkha
358     (138, "jv-Latn"), //Javanese (Roman)
359     (139, "su-Latn"), //Sundanese (Roman)
360     (140, "gl"),      //Galician
361     (141, "af"),      //Afrikaans
362     (142, "br"),      //Breton
363     (143, "iu"),      //Inuktitut
364     (144, "gd"),      //Scottish (Gaelic)
365     (145, "gv"),      //Manx (Gaelic)
366     (146, "ga"),      //Irish (Gaelic with Lenition)
367     (147, "to"),      //Tongan
368     (148, "el"),      //Greek (Polytonic) Note: ISO 15924 does not have an equivalent script name.
369     (149, "kl"),      //Greenlandic
370     (150, "az-Latn"), //Azerbaijani (Roman)
371     (151, "nn"),      //Nynorsk
372     /* A mapping from Windows LCID to BCP 47 codes.
373      *  This list is the sorted, curated output of tools/win_lcid.cpp.
374      *  Note that these are sorted by value for quick binary lookup, and not logically by lsb.
375      *  The 'bare' language ids (e.g. 0x0001 for Arabic) are ommitted
376      *  as they do not appear as valid language ids in the OpenType specification.
377      */
378     (0x0401, "ar-SA"),        //Arabic
379     (0x0402, "bg-BG"),        //Bulgarian
380     (0x0403, "ca-ES"),        //Catalan
381     (0x0404, "zh-TW"),        //Chinese (Traditional)
382     (0x0405, "cs-CZ"),        //Czech
383     (0x0406, "da-DK"),        //Danish
384     (0x0407, "de-DE"),        //German
385     (0x0408, "el-GR"),        //Greek
386     (0x0409, "en-US"),        //English
387     (0x040a, "es-ES_tradnl"), //Spanish
388     (0x040b, "fi-FI"),        //Finnish
389     (0x040c, "fr-FR"),        //French
390     (0x040d, "he-IL"),        //Hebrew
391     (0x040d, "he"),           //Hebrew
392     (0x040e, "hu-HU"),        //Hungarian
393     (0x040e, "hu"),           //Hungarian
394     (0x040f, "is-IS"),        //Icelandic
395     (0x0410, "it-IT"),        //Italian
396     (0x0411, "ja-JP"),        //Japanese
397     (0x0412, "ko-KR"),        //Korean
398     (0x0413, "nl-NL"),        //Dutch
399     (0x0414, "nb-NO"),        //Norwegian (Bokmål)
400     (0x0415, "pl-PL"),        //Polish
401     (0x0416, "pt-BR"),        //Portuguese
402     (0x0417, "rm-CH"),        //Romansh
403     (0x0418, "ro-RO"),        //Romanian
404     (0x0419, "ru-RU"),        //Russian
405     (0x041a, "hr-HR"),        //Croatian
406     (0x041b, "sk-SK"),        //Slovak
407     (0x041c, "sq-AL"),        //Albanian
408     (0x041d, "sv-SE"),        //Swedish
409     (0x041e, "th-TH"),        //Thai
410     (0x041f, "tr-TR"),        //Turkish
411     (0x0420, "ur-PK"),        //Urdu
412     (0x0421, "id-ID"),        //Indonesian
413     (0x0422, "uk-UA"),        //Ukrainian
414     (0x0423, "be-BY"),        //Belarusian
415     (0x0424, "sl-SI"),        //Slovenian
416     (0x0425, "et-EE"),        //Estonian
417     (0x0426, "lv-LV"),        //Latvian
418     (0x0427, "lt-LT"),        //Lithuanian
419     (0x0428, "tg-Cyrl-TJ"),   //Tajik (Cyrillic)
420     (0x0429, "fa-IR"),        //Persian
421     (0x042a, "vi-VN"),        //Vietnamese
422     (0x042b, "hy-AM"),        //Armenian
423     (0x042c, "az-Latn-AZ"),   //Azeri (Latin)
424     (0x042d, "eu-ES"),        //Basque
425     (0x042e, "hsb-DE"),       //Upper Sorbian
426     (0x042f, "mk-MK"),        //Macedonian (FYROM)
427     (0x0432, "tn-ZA"),        //Setswana
428     (0x0434, "xh-ZA"),        //isiXhosa
429     (0x0435, "zu-ZA"),        //isiZulu
430     (0x0436, "af-ZA"),        //Afrikaans
431     (0x0437, "ka-GE"),        //Georgian
432     (0x0438, "fo-FO"),        //Faroese
433     (0x0439, "hi-IN"),        //Hindi
434     (0x043a, "mt-MT"),        //Maltese
435     (0x043b, "se-NO"),        //Sami (Northern)
436     (0x043e, "ms-MY"),        //Malay
437     (0x043f, "kk-KZ"),        //Kazakh
438     (0x0440, "ky-KG"),        //Kyrgyz
439     (0x0441, "sw-KE"),        //Kiswahili
440     (0x0442, "tk-TM"),        //Turkmen
441     (0x0443, "uz-Latn-UZ"),   //Uzbek (Latin)
442     (0x0443, "uz"),           //Uzbek
443     (0x0444, "tt-RU"),        //Tatar
444     (0x0445, "bn-IN"),        //Bengali
445     (0x0446, "pa-IN"),        //Punjabi
446     (0x0447, "gu-IN"),        //Gujarati
447     (0x0448, "or-IN"),        //Oriya
448     (0x0449, "ta-IN"),        //Tamil
449     (0x044a, "te-IN"),        //Telugu
450     (0x044b, "kn-IN"),        //Kannada
451     (0x044c, "ml-IN"),        //Malayalam
452     (0x044d, "as-IN"),        //Assamese
453     (0x044e, "mr-IN"),        //Marathi
454     (0x044f, "sa-IN"),        //Sanskrit
455     (0x0450, "mn-Cyrl"),      //Mongolian (Cyrillic)
456     (0x0451, "bo-CN"),        //Tibetan
457     (0x0452, "cy-GB"),        //Welsh
458     (0x0453, "km-KH"),        //Khmer
459     (0x0454, "lo-LA"),        //Lao
460     (0x0456, "gl-ES"),        //Galician
461     (0x0457, "kok-IN"),       //Konkani
462     (0x045a, "syr-SY"),       //Syriac
463     (0x045b, "si-LK"),        //Sinhala
464     (0x045d, "iu-Cans-CA"),   //Inuktitut (Syllabics)
465     (0x045e, "am-ET"),        //Amharic
466     (0x0461, "ne-NP"),        //Nepali
467     (0x0462, "fy-NL"),        //Frisian
468     (0x0463, "ps-AF"),        //Pashto
469     (0x0464, "fil-PH"),       //Filipino
470     (0x0465, "dv-MV"),        //Divehi
471     (0x0468, "ha-Latn-NG"),   //Hausa (Latin)
472     (0x046a, "yo-NG"),        //Yoruba
473     (0x046b, "quz-BO"),       //Quechua
474     (0x046c, "nso-ZA"),       //Sesotho sa Leboa
475     (0x046d, "ba-RU"),        //Bashkir
476     (0x046e, "lb-LU"),        //Luxembourgish
477     (0x046f, "kl-GL"),        //Greenlandic
478     (0x0470, "ig-NG"),        //Igbo
479     (0x0478, "ii-CN"),        //Yi
480     (0x047a, "arn-CL"),       //Mapudungun
481     (0x047c, "moh-CA"),       //Mohawk
482     (0x047e, "br-FR"),        //Breton
483     (0x0480, "ug-CN"),        //Uyghur
484     (0x0481, "mi-NZ"),        //Maori
485     (0x0482, "oc-FR"),        //Occitan
486     (0x0483, "co-FR"),        //Corsican
487     (0x0484, "gsw-FR"),       //Alsatian
488     (0x0485, "sah-RU"),       //Yakut
489     (0x0486, "qut-GT"),       //K'iche
490     (0x0487, "rw-RW"),        //Kinyarwanda
491     (0x0488, "wo-SN"),        //Wolof
492     (0x048c, "prs-AF"),       //Dari
493     (0x0491, "gd-GB"),        //Scottish Gaelic
494     (0x0801, "ar-IQ"),        //Arabic
495     (0x0804, "zh-Hans"),      //Chinese (Simplified)
496     (0x0807, "de-CH"),        //German
497     (0x0809, "en-GB"),        //English
498     (0x080a, "es-MX"),        //Spanish
499     (0x080c, "fr-BE"),        //French
500     (0x0810, "it-CH"),        //Italian
501     (0x0813, "nl-BE"),        //Dutch
502     (0x0814, "nn-NO"),        //Norwegian (Nynorsk)
503     (0x0816, "pt-PT"),        //Portuguese
504     (0x081a, "sr-Latn-CS"),   //Serbian (Latin)
505     (0x081d, "sv-FI"),        //Swedish
506     (0x082c, "az-Cyrl-AZ"),   //Azeri (Cyrillic)
507     (0x082e, "dsb-DE"),       //Lower Sorbian
508     (0x082e, "dsb"),          //Lower Sorbian
509     (0x083b, "se-SE"),        //Sami (Northern)
510     (0x083c, "ga-IE"),        //Irish
511     (0x083e, "ms-BN"),        //Malay
512     (0x0843, "uz-Cyrl-UZ"),   //Uzbek (Cyrillic)
513     (0x0845, "bn-BD"),        //Bengali
514     (0x0850, "mn-Mong-CN"),   //Mongolian (Traditional Mongolian)
515     (0x085d, "iu-Latn-CA"),   //Inuktitut (Latin)
516     (0x085f, "tzm-Latn-DZ"),  //Tamazight (Latin)
517     (0x086b, "quz-EC"),       //Quechua
518     (0x0c01, "ar-EG"),        //Arabic
519     (0x0c04, "zh-Hant"),      //Chinese (Traditional)
520     (0x0c07, "de-AT"),        //German
521     (0x0c09, "en-AU"),        //English
522     (0x0c0a, "es-ES"),        //Spanish
523     (0x0c0c, "fr-CA"),        //French
524     (0x0c1a, "sr-Cyrl-CS"),   //Serbian (Cyrillic)
525     (0x0c3b, "se-FI"),        //Sami (Northern)
526     (0x0c6b, "quz-PE"),       //Quechua
527     (0x1001, "ar-LY"),        //Arabic
528     (0x1004, "zh-SG"),        //Chinese (Simplified)
529     (0x1007, "de-LU"),        //German
530     (0x1009, "en-CA"),        //English
531     (0x100a, "es-GT"),        //Spanish
532     (0x100c, "fr-CH"),        //French
533     (0x101a, "hr-BA"),        //Croatian (Latin)
534     (0x103b, "smj-NO"),       //Sami (Lule)
535     (0x1401, "ar-DZ"),        //Arabic
536     (0x1404, "zh-MO"),        //Chinese (Traditional)
537     (0x1407, "de-LI"),        //German
538     (0x1409, "en-NZ"),        //English
539     (0x140a, "es-CR"),        //Spanish
540     (0x140c, "fr-LU"),        //French
541     (0x141a, "bs-Latn-BA"),   //Bosnian (Latin)
542     (0x141a, "bs"),           //Bosnian
543     (0x143b, "smj-SE"),       //Sami (Lule)
544     (0x143b, "smj"),          //Sami (Lule)
545     (0x1801, "ar-MA"),        //Arabic
546     (0x1809, "en-IE"),        //English
547     (0x180a, "es-PA"),        //Spanish
548     (0x180c, "fr-MC"),        //French
549     (0x181a, "sr-Latn-BA"),   //Serbian (Latin)
550     (0x183b, "sma-NO"),       //Sami (Southern)
551     (0x1c01, "ar-TN"),        //Arabic
552     (0x1c09, "en-ZA"),        //English
553     (0x1c0a, "es-DO"),        //Spanish
554     (0x1c1a, "sr-Cyrl-BA"),   //Serbian (Cyrillic)
555     (0x1c3b, "sma-SE"),       //Sami (Southern)
556     (0x1c3b, "sma"),          //Sami (Southern)
557     (0x2001, "ar-OM"),        //Arabic
558     (0x2009, "en-JM"),        //English
559     (0x200a, "es-VE"),        //Spanish
560     (0x201a, "bs-Cyrl-BA"),   //Bosnian (Cyrillic)
561     (0x201a, "bs-Cyrl"),      //Bosnian (Cyrillic)
562     (0x203b, "sms-FI"),       //Sami (Skolt)
563     (0x203b, "sms"),          //Sami (Skolt)
564     (0x2401, "ar-YE"),        //Arabic
565     (0x2409, "en-029"),       //English
566     (0x240a, "es-CO"),        //Spanish
567     (0x241a, "sr-Latn-RS"),   //Serbian (Latin)
568     (0x243b, "smn-FI"),       //Sami (Inari)
569     (0x2801, "ar-SY"),        //Arabic
570     (0x2809, "en-BZ"),        //English
571     (0x280a, "es-PE"),        //Spanish
572     (0x281a, "sr-Cyrl-RS"),   //Serbian (Cyrillic)
573     (0x2c01, "ar-JO"),        //Arabic
574     (0x2c09, "en-TT"),        //English
575     (0x2c0a, "es-AR"),        //Spanish
576     (0x2c1a, "sr-Latn-ME"),   //Serbian (Latin)
577     (0x3001, "ar-LB"),        //Arabic
578     (0x3009, "en-ZW"),        //English
579     (0x300a, "es-EC"),        //Spanish
580     (0x301a, "sr-Cyrl-ME"),   //Serbian (Cyrillic)
581     (0x3401, "ar-KW"),        //Arabic
582     (0x3409, "en-PH"),        //English
583     (0x340a, "es-CL"),        //Spanish
584     (0x3801, "ar-AE"),        //Arabic
585     (0x380a, "es-UY"),        //Spanish
586     (0x3c01, "ar-BH"),        //Arabic
587     (0x3c0a, "es-PY"),        //Spanish
588     (0x4001, "ar-QA"),        //Arabic
589     (0x4009, "en-IN"),        //English
590     (0x400a, "es-BO"),        //Spanish
591     (0x4409, "en-MY"),        //English
592     (0x440a, "es-SV"),        //Spanish
593     (0x4809, "en-SG"),        //English
594     (0x480a, "es-HN"),        //Spanish
595     (0x4c0a, "es-NI"),        //Spanish
596     (0x500a, "es-PR"),        //Spanish
597     (0x540a, "es-US"),        //Spanish
598 ];
599 
600 #[cfg(test)]
601 mod tests {
602     use crate::MetadataProvider;
603 
604     use super::*;
605     use read_fonts::FontRef;
606 
607     #[test]
localized()608     fn localized() {
609         let font = FontRef::new(font_test_data::NAMES_ONLY).unwrap();
610         let mut subfamily_names = font
611             .localized_strings(StringId::SUBFAMILY_NAME)
612             .map(|s| (s.language().unwrap().to_string(), s.to_string()))
613             .collect::<Vec<_>>();
614         subfamily_names.sort_by(|a, b| a.0.cmp(&b.0));
615         let expected = [
616             (String::from("ar-SA"), String::from("عادي")),
617             (String::from("el-GR"), String::from("Κανονικά")),
618             (String::from("en"), String::from("Regular")),
619             (String::from("eu-ES"), String::from("Arrunta")),
620             (String::from("pl-PL"), String::from("Normalny")),
621             (String::from("zh-Hans"), String::from("正常")),
622         ];
623         assert_eq!(subfamily_names.as_slice(), expected);
624     }
625 
626     #[test]
find_by_language()627     fn find_by_language() {
628         let font = FontRef::new(font_test_data::NAMES_ONLY).unwrap();
629         assert_eq!(
630             font.localized_strings(StringId::SUBFAMILY_NAME)
631                 .find(|s| s.language() == Some("pl-PL"))
632                 .unwrap()
633                 .to_string(),
634             "Normalny"
635         );
636     }
637 
638     #[test]
english_or_first()639     fn english_or_first() {
640         let font = FontRef::new(font_test_data::NAMES_ONLY).unwrap();
641         assert_eq!(
642             font.localized_strings(StringId::SUBFAMILY_NAME)
643                 .english_or_first()
644                 .unwrap()
645                 .to_string(),
646             "Regular"
647         );
648     }
649 }
650