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