1use alloc::string::String;
2use core::ops::{Bound, RangeBounds};
3
4use crate::ttf_parser::Tag;
5
6use crate::text_parser::TextParser;
7
8
9#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
11pub enum Direction {
12 Invalid,
14 LeftToRight,
16 RightToLeft,
18 TopToBottom,
20 BottomToTop,
22}
23
24impl Direction {
25 #[inline]
26 pub(crate) fn is_horizontal(self) -> bool {
27 match self {
28 Direction::Invalid => false,
29 Direction::LeftToRight => true,
30 Direction::RightToLeft => true,
31 Direction::TopToBottom => false,
32 Direction::BottomToTop => false,
33 }
34 }
35
36 #[inline]
37 pub(crate) fn is_vertical(self) -> bool {
38 !self.is_horizontal()
39 }
40
41 #[inline]
42 pub(crate) fn is_forward(self) -> bool {
43 match self {
44 Direction::Invalid => false,
45 Direction::LeftToRight => true,
46 Direction::RightToLeft => false,
47 Direction::TopToBottom => true,
48 Direction::BottomToTop => false,
49 }
50 }
51
52 #[inline]
53 pub(crate) fn is_backward(self) -> bool {
54 !self.is_forward()
55 }
56
57 #[inline]
58 pub(crate) fn reverse(self) -> Self {
59 match self {
60 Direction::Invalid => Direction::Invalid,
61 Direction::LeftToRight => Direction::RightToLeft,
62 Direction::RightToLeft => Direction::LeftToRight,
63 Direction::TopToBottom => Direction::BottomToTop,
64 Direction::BottomToTop => Direction::TopToBottom,
65 }
66 }
67
68 pub(crate) fn from_script(script: Script) -> Option<Self> {
69 match script {
72 script::ARABIC |
74 script::HEBREW |
75
76 script::SYRIAC |
78 script::THAANA |
79
80 script::CYPRIOT |
82
83 script::KHAROSHTHI |
85
86 script::PHOENICIAN |
88 script::NKO |
89
90 script::LYDIAN |
92
93 script::AVESTAN |
95 script::IMPERIAL_ARAMAIC |
96 script::INSCRIPTIONAL_PAHLAVI |
97 script::INSCRIPTIONAL_PARTHIAN |
98 script::OLD_SOUTH_ARABIAN |
99 script::OLD_TURKIC |
100 script::SAMARITAN |
101
102 script::MANDAIC |
104
105 script::MEROITIC_CURSIVE |
107 script::MEROITIC_HIEROGLYPHS |
108
109 script::MANICHAEAN |
111 script::MENDE_KIKAKUI |
112 script::NABATAEAN |
113 script::OLD_NORTH_ARABIAN |
114 script::PALMYRENE |
115 script::PSALTER_PAHLAVI |
116
117 script::HATRAN |
119
120 script::ADLAM |
122
123 script::HANIFI_ROHINGYA |
125 script::OLD_SOGDIAN |
126 script::SOGDIAN |
127
128 script::ELYMAIC |
130
131 script::CHORASMIAN |
133 script::YEZIDI => {
134 Some(Direction::RightToLeft)
135 }
136
137 script::OLD_HUNGARIAN |
139 script::OLD_ITALIC |
140 script::RUNIC => {
141 None
142 }
143
144 _ => Some(Direction::LeftToRight),
145 }
146 }
147}
148
149impl Default for Direction {
150 #[inline]
151 fn default() -> Self {
152 Direction::Invalid
153 }
154}
155
156impl core::str::FromStr for Direction {
157 type Err = &'static str;
158
159 fn from_str(s: &str) -> Result<Self, Self::Err> {
160 if s.is_empty() {
161 return Err("invalid direction");
162 }
163
164 match s.as_bytes()[0].to_ascii_lowercase() {
166 b'l' => Ok(Direction::LeftToRight),
167 b'r' => Ok(Direction::RightToLeft),
168 b't' => Ok(Direction::TopToBottom),
169 b'b' => Ok(Direction::BottomToTop),
170 _ => Err("invalid direction"),
171 }
172 }
173}
174
175
176#[derive(Clone, PartialEq, Eq, Hash, Debug)]
178pub struct Language(String);
179
180impl Language {
181 #[inline]
183 pub fn as_str(&self) -> &str {
184 self.0.as_str()
185 }
186}
187
188impl core::str::FromStr for Language {
189 type Err = &'static str;
190
191 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 if !s.is_empty() {
193 Ok(Language(s.to_ascii_lowercase()))
194 } else {
195 Err("invalid language")
196 }
197 }
198}
199
200
201#[allow(missing_docs)]
206#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
207pub struct Script(pub(crate) Tag);
208
209impl Script {
210 #[inline]
211 pub(crate) const fn from_bytes(bytes: &[u8; 4]) -> Self {
212 Script(Tag::from_bytes(bytes))
213 }
214
215 pub fn from_iso15924_tag(tag: Tag) -> Option<Script> {
217 if tag.is_null() {
218 return None;
219 }
220
221 let tag = Tag((tag.as_u32() & 0xDFDFDFDF) | 0x00202020);
223
224 match &tag.to_bytes() {
225 b"Qaai" => return Some(script::INHERITED),
229 b"Qaac" => return Some(script::COPTIC),
230
231 b"Cyrs" => return Some(script::CYRILLIC),
233 b"Latf" | b"Latg" => return Some(script::LATIN),
234 b"Syre" | b"Syrj" | b"Syrn" => return Some(script::SYRIAC),
235
236 _ => {}
237 }
238
239 if tag.as_u32() & 0xE0E0E0E0 == 0x40606060 {
240 Some(Script(tag))
241 } else {
242 Some(script::UNKNOWN)
243 }
244 }
245
246 #[inline]
248 pub fn tag(&self) -> Tag {
249 self.0
250 }
251}
252
253impl core::str::FromStr for Script {
254 type Err = &'static str;
255
256 fn from_str(s: &str) -> Result<Self, Self::Err> {
257 let tag = Tag::from_bytes_lossy(s.as_bytes());
258 Script::from_iso15924_tag(tag).ok_or("invalid script")
259 }
260}
261
262pub mod script {
264 #![allow(missing_docs)]
265
266 use crate::Script;
267
268 pub const COMMON: Script = Script::from_bytes(b"Zyyy");
270 pub const INHERITED: Script = Script::from_bytes(b"Zinh");
271 pub const ARABIC: Script = Script::from_bytes(b"Arab");
272 pub const ARMENIAN: Script = Script::from_bytes(b"Armn");
273 pub const BENGALI: Script = Script::from_bytes(b"Beng");
274 pub const CYRILLIC: Script = Script::from_bytes(b"Cyrl");
275 pub const DEVANAGARI: Script = Script::from_bytes(b"Deva");
276 pub const GEORGIAN: Script = Script::from_bytes(b"Geor");
277 pub const GREEK: Script = Script::from_bytes(b"Grek");
278 pub const GUJARATI: Script = Script::from_bytes(b"Gujr");
279 pub const GURMUKHI: Script = Script::from_bytes(b"Guru");
280 pub const HANGUL: Script = Script::from_bytes(b"Hang");
281 pub const HAN: Script = Script::from_bytes(b"Hani");
282 pub const HEBREW: Script = Script::from_bytes(b"Hebr");
283 pub const HIRAGANA: Script = Script::from_bytes(b"Hira");
284 pub const KANNADA: Script = Script::from_bytes(b"Knda");
285 pub const KATAKANA: Script = Script::from_bytes(b"Kana");
286 pub const LAO: Script = Script::from_bytes(b"Laoo");
287 pub const LATIN: Script = Script::from_bytes(b"Latn");
288 pub const MALAYALAM: Script = Script::from_bytes(b"Mlym");
289 pub const ORIYA: Script = Script::from_bytes(b"Orya");
290 pub const TAMIL: Script = Script::from_bytes(b"Taml");
291 pub const TELUGU: Script = Script::from_bytes(b"Telu");
292 pub const THAI: Script = Script::from_bytes(b"Thai");
293 pub const TIBETAN: Script = Script::from_bytes(b"Tibt");
295 pub const BOPOMOFO: Script = Script::from_bytes(b"Bopo");
297 pub const BRAILLE: Script = Script::from_bytes(b"Brai");
298 pub const CANADIAN_SYLLABICS: Script = Script::from_bytes(b"Cans");
299 pub const CHEROKEE: Script = Script::from_bytes(b"Cher");
300 pub const ETHIOPIC: Script = Script::from_bytes(b"Ethi");
301 pub const KHMER: Script = Script::from_bytes(b"Khmr");
302 pub const MONGOLIAN: Script = Script::from_bytes(b"Mong");
303 pub const MYANMAR: Script = Script::from_bytes(b"Mymr");
304 pub const OGHAM: Script = Script::from_bytes(b"Ogam");
305 pub const RUNIC: Script = Script::from_bytes(b"Runr");
306 pub const SINHALA: Script = Script::from_bytes(b"Sinh");
307 pub const SYRIAC: Script = Script::from_bytes(b"Syrc");
308 pub const THAANA: Script = Script::from_bytes(b"Thaa");
309 pub const YI: Script = Script::from_bytes(b"Yiii");
310 pub const DESERET: Script = Script::from_bytes(b"Dsrt");
312 pub const GOTHIC: Script = Script::from_bytes(b"Goth");
313 pub const OLD_ITALIC: Script = Script::from_bytes(b"Ital");
314 pub const BUHID: Script = Script::from_bytes(b"Buhd");
316 pub const HANUNOO: Script = Script::from_bytes(b"Hano");
317 pub const TAGALOG: Script = Script::from_bytes(b"Tglg");
318 pub const TAGBANWA: Script = Script::from_bytes(b"Tagb");
319 pub const CYPRIOT: Script = Script::from_bytes(b"Cprt");
321 pub const LIMBU: Script = Script::from_bytes(b"Limb");
322 pub const LINEAR_B: Script = Script::from_bytes(b"Linb");
323 pub const OSMANYA: Script = Script::from_bytes(b"Osma");
324 pub const SHAVIAN: Script = Script::from_bytes(b"Shaw");
325 pub const TAI_LE: Script = Script::from_bytes(b"Tale");
326 pub const UGARITIC: Script = Script::from_bytes(b"Ugar");
327 pub const BUGINESE: Script = Script::from_bytes(b"Bugi");
329 pub const COPTIC: Script = Script::from_bytes(b"Copt");
330 pub const GLAGOLITIC: Script = Script::from_bytes(b"Glag");
331 pub const KHAROSHTHI: Script = Script::from_bytes(b"Khar");
332 pub const NEW_TAI_LUE: Script = Script::from_bytes(b"Talu");
333 pub const OLD_PERSIAN: Script = Script::from_bytes(b"Xpeo");
334 pub const SYLOTI_NAGRI: Script = Script::from_bytes(b"Sylo");
335 pub const TIFINAGH: Script = Script::from_bytes(b"Tfng");
336 pub const UNKNOWN: Script = Script::from_bytes(b"Zzzz"); pub const BALINESE: Script = Script::from_bytes(b"Bali");
339 pub const CUNEIFORM: Script = Script::from_bytes(b"Xsux");
340 pub const NKO: Script = Script::from_bytes(b"Nkoo");
341 pub const PHAGS_PA: Script = Script::from_bytes(b"Phag");
342 pub const PHOENICIAN: Script = Script::from_bytes(b"Phnx");
343 pub const CARIAN: Script = Script::from_bytes(b"Cari");
345 pub const CHAM: Script = Script::from_bytes(b"Cham");
346 pub const KAYAH_LI: Script = Script::from_bytes(b"Kali");
347 pub const LEPCHA: Script = Script::from_bytes(b"Lepc");
348 pub const LYCIAN: Script = Script::from_bytes(b"Lyci");
349 pub const LYDIAN: Script = Script::from_bytes(b"Lydi");
350 pub const OL_CHIKI: Script = Script::from_bytes(b"Olck");
351 pub const REJANG: Script = Script::from_bytes(b"Rjng");
352 pub const SAURASHTRA: Script = Script::from_bytes(b"Saur");
353 pub const SUNDANESE: Script = Script::from_bytes(b"Sund");
354 pub const VAI: Script = Script::from_bytes(b"Vaii");
355 pub const AVESTAN: Script = Script::from_bytes(b"Avst");
357 pub const BAMUM: Script = Script::from_bytes(b"Bamu");
358 pub const EGYPTIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Egyp");
359 pub const IMPERIAL_ARAMAIC: Script = Script::from_bytes(b"Armi");
360 pub const INSCRIPTIONAL_PAHLAVI: Script = Script::from_bytes(b"Phli");
361 pub const INSCRIPTIONAL_PARTHIAN: Script = Script::from_bytes(b"Prti");
362 pub const JAVANESE: Script = Script::from_bytes(b"Java");
363 pub const KAITHI: Script = Script::from_bytes(b"Kthi");
364 pub const LISU: Script = Script::from_bytes(b"Lisu");
365 pub const MEETEI_MAYEK: Script = Script::from_bytes(b"Mtei");
366 pub const OLD_SOUTH_ARABIAN: Script = Script::from_bytes(b"Sarb");
367 pub const OLD_TURKIC: Script = Script::from_bytes(b"Orkh");
368 pub const SAMARITAN: Script = Script::from_bytes(b"Samr");
369 pub const TAI_THAM: Script = Script::from_bytes(b"Lana");
370 pub const TAI_VIET: Script = Script::from_bytes(b"Tavt");
371 pub const BATAK: Script = Script::from_bytes(b"Batk");
373 pub const BRAHMI: Script = Script::from_bytes(b"Brah");
374 pub const MANDAIC: Script = Script::from_bytes(b"Mand");
375 pub const CHAKMA: Script = Script::from_bytes(b"Cakm");
377 pub const MEROITIC_CURSIVE: Script = Script::from_bytes(b"Merc");
378 pub const MEROITIC_HIEROGLYPHS: Script = Script::from_bytes(b"Mero");
379 pub const MIAO: Script = Script::from_bytes(b"Plrd");
380 pub const SHARADA: Script = Script::from_bytes(b"Shrd");
381 pub const SORA_SOMPENG: Script = Script::from_bytes(b"Sora");
382 pub const TAKRI: Script = Script::from_bytes(b"Takr");
383 pub const BASSA_VAH: Script = Script::from_bytes(b"Bass");
385 pub const CAUCASIAN_ALBANIAN: Script = Script::from_bytes(b"Aghb");
386 pub const DUPLOYAN: Script = Script::from_bytes(b"Dupl");
387 pub const ELBASAN: Script = Script::from_bytes(b"Elba");
388 pub const GRANTHA: Script = Script::from_bytes(b"Gran");
389 pub const KHOJKI: Script = Script::from_bytes(b"Khoj");
390 pub const KHUDAWADI: Script = Script::from_bytes(b"Sind");
391 pub const LINEAR_A: Script = Script::from_bytes(b"Lina");
392 pub const MAHAJANI: Script = Script::from_bytes(b"Mahj");
393 pub const MANICHAEAN: Script = Script::from_bytes(b"Mani");
394 pub const MENDE_KIKAKUI: Script = Script::from_bytes(b"Mend");
395 pub const MODI: Script = Script::from_bytes(b"Modi");
396 pub const MRO: Script = Script::from_bytes(b"Mroo");
397 pub const NABATAEAN: Script = Script::from_bytes(b"Nbat");
398 pub const OLD_NORTH_ARABIAN: Script = Script::from_bytes(b"Narb");
399 pub const OLD_PERMIC: Script = Script::from_bytes(b"Perm");
400 pub const PAHAWH_HMONG: Script = Script::from_bytes(b"Hmng");
401 pub const PALMYRENE: Script = Script::from_bytes(b"Palm");
402 pub const PAU_CIN_HAU: Script = Script::from_bytes(b"Pauc");
403 pub const PSALTER_PAHLAVI: Script = Script::from_bytes(b"Phlp");
404 pub const SIDDHAM: Script = Script::from_bytes(b"Sidd");
405 pub const TIRHUTA: Script = Script::from_bytes(b"Tirh");
406 pub const WARANG_CITI: Script = Script::from_bytes(b"Wara");
407 pub const AHOM: Script = Script::from_bytes(b"Ahom");
409 pub const ANATOLIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Hluw");
410 pub const HATRAN: Script = Script::from_bytes(b"Hatr");
411 pub const MULTANI: Script = Script::from_bytes(b"Mult");
412 pub const OLD_HUNGARIAN: Script = Script::from_bytes(b"Hung");
413 pub const SIGNWRITING: Script = Script::from_bytes(b"Sgnw");
414 pub const ADLAM: Script = Script::from_bytes(b"Adlm");
416 pub const BHAIKSUKI: Script = Script::from_bytes(b"Bhks");
417 pub const MARCHEN: Script = Script::from_bytes(b"Marc");
418 pub const OSAGE: Script = Script::from_bytes(b"Osge");
419 pub const TANGUT: Script = Script::from_bytes(b"Tang");
420 pub const NEWA: Script = Script::from_bytes(b"Newa");
421 pub const MASARAM_GONDI: Script = Script::from_bytes(b"Gonm");
423 pub const NUSHU: Script = Script::from_bytes(b"Nshu");
424 pub const SOYOMBO: Script = Script::from_bytes(b"Soyo");
425 pub const ZANABAZAR_SQUARE: Script = Script::from_bytes(b"Zanb");
426 pub const DOGRA: Script = Script::from_bytes(b"Dogr");
428 pub const GUNJALA_GONDI: Script = Script::from_bytes(b"Gong");
429 pub const HANIFI_ROHINGYA: Script = Script::from_bytes(b"Rohg");
430 pub const MAKASAR: Script = Script::from_bytes(b"Maka");
431 pub const MEDEFAIDRIN: Script = Script::from_bytes(b"Medf");
432 pub const OLD_SOGDIAN: Script = Script::from_bytes(b"Sogo");
433 pub const SOGDIAN: Script = Script::from_bytes(b"Sogd");
434 pub const ELYMAIC: Script = Script::from_bytes(b"Elym");
436 pub const NANDINAGARI: Script = Script::from_bytes(b"Nand");
437 pub const NYIAKENG_PUACHUE_HMONG: Script = Script::from_bytes(b"Hmnp");
438 pub const WANCHO: Script = Script::from_bytes(b"Wcho");
439 pub const CHORASMIAN: Script = Script::from_bytes(b"Chrs");
441 pub const DIVES_AKURU: Script = Script::from_bytes(b"Diak");
442 pub const KHITAN_SMALL_SCRIPT: Script = Script::from_bytes(b"Kits");
443 pub const YEZIDI: Script = Script::from_bytes(b"Yezi");
444
445 pub const MYANMAR_ZAWGYI: Script = Script::from_bytes(b"Qaag");
447}
448
449
450#[repr(C)]
453#[allow(missing_docs)]
454#[derive(Clone, Copy, PartialEq, Debug)]
455pub struct Feature {
456 pub tag: Tag,
457 pub value: u32,
458 pub start: u32,
459 pub end: u32,
460}
461
462impl Feature {
463 pub fn new(tag: Tag, value: u32, range: impl RangeBounds<usize>) -> Feature {
465 let max = u32::MAX as usize;
466 let start = match range.start_bound() {
467 Bound::Included(&included) => included.min(max) as u32,
468 Bound::Excluded(&excluded) => excluded.min(max - 1) as u32 + 1,
469 Bound::Unbounded => 0,
470 };
471 let end = match range.end_bound() {
472 Bound::Included(&included) => included.min(max) as u32,
473 Bound::Excluded(&excluded) => excluded.saturating_sub(1).min(max) as u32,
474 Bound::Unbounded => max as u32,
475 };
476
477 Feature {
478 tag,
479 value,
480 start,
481 end,
482 }
483 }
484
485 pub(crate) fn is_global(&self) -> bool {
486 self.start == 0 && self.end == u32::MAX
487 }
488}
489
490impl core::str::FromStr for Feature {
491 type Err = &'static str;
492
493 fn from_str(s: &str) -> Result<Self, Self::Err> {
511 fn parse(s: &str) -> Option<Feature> {
512 if s.is_empty() {
513 return None;
514 }
515
516 let mut p = TextParser::new(s);
517
518 let mut value = 1;
520 match p.curr_byte()? {
521 b'-' => { value = 0; p.advance(1); }
522 b'+' => { value = 1; p.advance(1); }
523 _ => {}
524 }
525
526 p.skip_spaces();
528 let quote = p.consume_quote();
529
530 let tag = p.consume_tag()?;
531
532 if let Some(quote) = quote {
534 p.consume_byte(quote)?;
535 }
536
537 p.skip_spaces();
539
540 let (start, end) = if p.consume_byte(b'[').is_some() {
541 let start_opt = p.consume_i32();
542 let start = start_opt.unwrap_or(0) as u32; let end = if matches!(p.curr_byte(), Some(b':') | Some(b';')) {
545 p.advance(1);
546 p.consume_i32().unwrap_or(-1) as u32 } else {
548 if start_opt.is_some() && start != core::u32::MAX {
549 start + 1
550 } else {
551 core::u32::MAX
552 }
553 };
554
555 p.consume_byte(b']')?;
556
557 (start, end)
558 } else {
559 (0, core::u32::MAX)
560 };
561
562 let had_equal = p.consume_byte(b'=').is_some();
564 let value1 = p.consume_i32().or_else(|| p.consume_bool().map(|b| b as i32));
565
566 if had_equal && value1.is_none() {
567 return None;
568 };
569
570 if let Some(value1) = value1 {
571 value = value1 as u32; }
573
574 p.skip_spaces();
575
576 if !p.at_end() {
577 return None;
578 }
579
580 Some(Feature {
581 tag,
582 value,
583 start,
584 end,
585 })
586 }
587
588 parse(s).ok_or("invalid feature")
589 }
590}
591
592#[cfg(test)]
593mod tests_features {
594 use super::*;
595 use core::str::FromStr;
596
597 macro_rules! test {
598 ($name:ident, $text:expr, $tag:expr, $value:expr, $range:expr) => (
599 #[test]
600 fn $name() {
601 assert_eq!(
602 Feature::from_str($text).unwrap(),
603 Feature::new(Tag::from_bytes($tag), $value, $range)
604 );
605 }
606 )
607 }
608
609 test!(parse_01, "kern", b"kern", 1, ..);
610 test!(parse_02, "+kern", b"kern", 1, ..);
611 test!(parse_03, "-kern", b"kern", 0, ..);
612 test!(parse_04, "kern=0", b"kern", 0, ..);
613 test!(parse_05, "kern=1", b"kern", 1, ..);
614 test!(parse_06, "kern=2", b"kern", 2, ..);
615 test!(parse_07, "kern[]", b"kern", 1, ..);
616 test!(parse_08, "kern[:]", b"kern", 1, ..);
617 test!(parse_09, "kern[5:]", b"kern", 1, 5..);
618 test!(parse_10, "kern[:5]", b"kern", 1, ..=5);
619 test!(parse_11, "kern[3:5]", b"kern", 1, 3..=5);
620 test!(parse_12, "kern[3]", b"kern", 1, 3..=4);
621 test!(parse_13, "kern[3:5]=2", b"kern", 2, 3..=5);
622 test!(parse_14, "kern[3;5]=2", b"kern", 2, 3..=5);
623 test!(parse_15, "kern[:-1]", b"kern", 1, ..);
624 test!(parse_16, "kern[-1]", b"kern", 1, core::u32::MAX as usize..);
625 test!(parse_17, "kern=on", b"kern", 1, ..);
626 test!(parse_18, "kern=off", b"kern", 0, ..);
627 test!(parse_19, "kern=oN", b"kern", 1, ..);
628 test!(parse_20, "kern=oFf", b"kern", 0, ..);
629}
630
631
632#[repr(C)]
634#[allow(missing_docs)]
635#[derive(Clone, Copy, PartialEq, Debug)]
636pub struct Variation {
637 pub tag: Tag,
638 pub value: f32,
639}
640
641impl core::str::FromStr for Variation {
642 type Err = &'static str;
643
644 fn from_str(s: &str) -> Result<Self, Self::Err> {
645 fn parse(s: &str) -> Option<Variation> {
646 if s.is_empty() {
647 return None;
648 }
649
650 let mut p = TextParser::new(s);
651
652 p.skip_spaces();
654 let quote = p.consume_quote();
655
656 let tag = p.consume_tag()?;
657
658 if let Some(quote) = quote {
660 p.consume_byte(quote)?;
661 }
662
663 let _ = p.consume_byte(b'=');
664 let value = p.consume_f32()?;
665 p.skip_spaces();
666
667 if !p.at_end() {
668 return None;
669 }
670
671 Some(Variation {
672 tag,
673 value,
674 })
675 }
676
677 parse(s).ok_or("invalid variation")
678 }
679}
680
681
682pub trait TagExt {
683 fn default_script() -> Self;
684 fn default_language() -> Self;
685 fn to_uppercase(&self) -> Self;
687}
688
689impl TagExt for Tag {
690 #[inline]
691 fn default_script() -> Self {
692 Tag::from_bytes(b"DFLT")
693 }
694
695 #[inline]
696 fn default_language() -> Self {
697 Tag::from_bytes(b"dflt")
698 }
699
700 #[inline]
702 fn to_uppercase(&self) -> Self {
715 let b = self.to_bytes();
716 Tag::from_bytes(&[
717 b[0].to_ascii_uppercase(),
718 b[1].to_ascii_uppercase(),
719 b[2].to_ascii_uppercase(),
720 b[3].to_ascii_uppercase(),
721 ])
722 }
723}