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