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