1use core::{
2 ops::{Bound, RangeBounds},
3 str::FromStr,
4};
5use smallvec::SmallVec;
6
7use read_fonts::types::Tag;
8
9use super::text_parser::TextParser;
10
11pub const HB_FEATURE_GLOBAL_START: u32 = 0;
12pub const HB_FEATURE_GLOBAL_END: u32 = u32::MAX;
13
14#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
16pub enum Direction {
17 Invalid,
19 LeftToRight,
21 RightToLeft,
23 TopToBottom,
25 BottomToTop,
27}
28
29impl Direction {
30 #[inline]
31 pub(crate) fn is_horizontal(self) -> bool {
32 match self {
33 Direction::Invalid => false,
34 Direction::LeftToRight => true,
35 Direction::RightToLeft => true,
36 Direction::TopToBottom => false,
37 Direction::BottomToTop => false,
38 }
39 }
40
41 #[inline]
42 pub(crate) fn is_vertical(self) -> bool {
43 !self.is_horizontal()
44 }
45
46 #[inline]
47 pub(crate) fn is_forward(self) -> bool {
48 match self {
49 Direction::Invalid => false,
50 Direction::LeftToRight => true,
51 Direction::RightToLeft => false,
52 Direction::TopToBottom => true,
53 Direction::BottomToTop => false,
54 }
55 }
56
57 #[inline]
58 pub(crate) fn is_backward(self) -> bool {
59 !self.is_forward()
60 }
61
62 #[inline]
63 pub(crate) fn reverse(self) -> Self {
64 match self {
65 Direction::Invalid => Direction::Invalid,
66 Direction::LeftToRight => Direction::RightToLeft,
67 Direction::RightToLeft => Direction::LeftToRight,
68 Direction::TopToBottom => Direction::BottomToTop,
69 Direction::BottomToTop => Direction::TopToBottom,
70 }
71 }
72
73 pub(crate) fn from_script(script: Script) -> Option<Self> {
74 match script {
77 script::ARABIC |
79 script::HEBREW |
80
81 script::SYRIAC |
83 script::THAANA |
84
85 script::CYPRIOT |
87
88 script::KHAROSHTHI |
90
91 script::PHOENICIAN |
93 script::NKO |
94
95 script::LYDIAN |
97
98 script::AVESTAN |
100 script::IMPERIAL_ARAMAIC |
101 script::INSCRIPTIONAL_PAHLAVI |
102 script::INSCRIPTIONAL_PARTHIAN |
103 script::OLD_SOUTH_ARABIAN |
104 script::OLD_TURKIC |
105 script::SAMARITAN |
106
107 script::MANDAIC |
109
110 script::MEROITIC_CURSIVE |
112 script::MEROITIC_HIEROGLYPHS |
113
114 script::MANICHAEAN |
116 script::MENDE_KIKAKUI |
117 script::NABATAEAN |
118 script::OLD_NORTH_ARABIAN |
119 script::PALMYRENE |
120 script::PSALTER_PAHLAVI |
121
122 script::HATRAN |
124
125 script::ADLAM |
127
128 script::HANIFI_ROHINGYA |
130 script::OLD_SOGDIAN |
131 script::SOGDIAN |
132
133 script::ELYMAIC |
135
136 script::CHORASMIAN |
138 script::YEZIDI |
139
140 script::OLD_UYGHUR |
142
143 script::GARAY |
145
146 script::SIDETIC => {
148 Some(Direction::RightToLeft)
149 }
150
151 script::OLD_HUNGARIAN |
153 script::OLD_ITALIC |
154 script::RUNIC |
155 script::TIFINAGH => {
156 None
157 }
158
159 _ => Some(Direction::LeftToRight),
160 }
161 }
162}
163
164impl Default for Direction {
165 #[inline]
166 fn default() -> Self {
167 Direction::Invalid
168 }
169}
170
171impl FromStr for Direction {
172 type Err = &'static str;
173
174 fn from_str(s: &str) -> Result<Self, Self::Err> {
175 if s.is_empty() {
176 return Err("invalid direction");
177 }
178
179 match s.as_bytes()[0].to_ascii_lowercase() {
181 b'l' => Ok(Direction::LeftToRight),
182 b'r' => Ok(Direction::RightToLeft),
183 b't' => Ok(Direction::TopToBottom),
184 b'b' => Ok(Direction::BottomToTop),
185 _ => Err("invalid direction"),
186 }
187 }
188}
189
190type SmallVecLanguage = SmallVec<[u8; 8]>;
191
192#[derive(Clone, PartialEq, Eq, Hash, Debug)]
194pub struct Language(SmallVecLanguage);
195
196impl Language {
197 #[inline]
199 pub fn new(bytes: impl AsRef<[u8]>) -> Option<Self> {
200 let bytes = bytes.as_ref();
201 (!bytes.is_empty()).then(|| Language::from_bytes(bytes))
202 }
203
204 #[inline]
206 pub fn as_bytes(&self) -> &[u8] {
207 &self.0
208 }
209
210 #[inline]
212 pub fn as_str(&self) -> &str {
213 core::str::from_utf8(&self.0).unwrap_or_default()
214 }
215
216 fn from_bytes(bytes: &[u8]) -> Self {
217 if bytes.is_empty() {
218 Language(SmallVec::new())
219 } else {
220 let mut bytes = SmallVecLanguage::from_slice(bytes);
221
222 for b in &mut bytes.iter_mut() {
224 if b.is_ascii_uppercase() {
225 *b = b.to_ascii_lowercase();
226 } else if *b == b'_' {
227 *b = b'-';
228 }
229 }
230
231 Language(bytes)
232 }
233 }
234}
235
236impl FromStr for Language {
237 type Err = &'static str;
238
239 fn from_str(s: &str) -> Result<Self, Self::Err> {
240 if !s.is_empty() {
241 Ok(Language::from_bytes(s.as_bytes()))
242 } else {
243 Err("invalid language")
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests_language {
250 use super::*;
251 use alloc::string::String;
252 use alloc::vec::Vec;
253
254 #[test]
255 fn new_empty() {
256 assert_eq!(Language::new(""), None);
257 }
258
259 #[test]
260 fn new_basic() {
261 let lang = Language::new("en").unwrap();
262 assert_eq!(lang.as_str(), "en");
263 assert_eq!(lang.as_bytes(), b"en");
264 }
265
266 #[test]
267 fn new_lowercases() {
268 let lang = Language::new("EN-US").unwrap();
269 assert_eq!(lang.as_str(), "en-us");
270 assert_eq!(lang.as_bytes(), b"en-us");
271 }
272
273 #[test]
274 fn new_replaces_underscore() {
275 let lang = Language::new("en_US").unwrap();
276 assert_eq!(lang.as_str(), "en-us");
277 assert_eq!(lang.as_bytes(), b"en-us");
278 }
279
280 #[test]
281 fn new_accepts_str() {
282 let lang = Language::new("zh-Hant").unwrap();
283 assert_eq!(lang.as_str(), "zh-hant");
284 }
285
286 #[test]
287 fn new_accepts_byte_slice() {
288 let lang = Language::new(b"zh-Hant" as &[u8]).unwrap();
289 assert_eq!(lang.as_str(), "zh-hant");
290 }
291
292 #[test]
293 fn new_accepts_byte_array() {
294 let lang = Language::new(*b"zh-Hant").unwrap();
295 assert_eq!(lang.as_str(), "zh-hant");
296 }
297
298 #[test]
299 fn new_accepts_string() {
300 let lang = Language::new(String::from("zh-Hant")).unwrap();
301 assert_eq!(lang.as_str(), "zh-hant");
302 }
303
304 #[test]
305 fn new_accepts_vec() {
306 let lang = Language::new(Vec::from(b"zh-Hant" as &[u8])).unwrap();
307 assert_eq!(lang.as_str(), "zh-hant");
308 }
309
310 #[test]
311 fn new_matches_from_str() {
312 assert_eq!(
313 Language::new("en-US"),
314 Some(Language::from_str("en-US").unwrap())
315 );
316 assert_eq!(
317 Language::new("zh_Hant_TW"),
318 Some(Language::from_str("zh_Hant_TW").unwrap())
319 );
320 }
321
322 #[test]
323 fn new_empty_matches_from_str() {
324 assert_eq!(Language::new(""), None);
325 assert!(Language::from_str("").is_err());
326 }
327}
328
329#[allow(missing_docs)]
334#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
335pub struct Script(pub(crate) Tag);
336
337impl Script {
338 #[inline]
339 pub(crate) const fn from_bytes(bytes: &[u8; 4]) -> Self {
340 Script(Tag::new(bytes))
341 }
342
343 pub const fn from_iso15924_tag(tag: Tag) -> Option<Script> {
345 let tag = u32::from_be_bytes(tag.to_be_bytes());
346
347 if tag == 0 {
348 return None;
349 }
350
351 let tag = (tag & 0xDFDF_DFDF) | 0x0020_2020;
353
354 if tag & 0xE0E0_E0E0 != 0x4060_6060 {
355 return Some(script::UNKNOWN);
356 }
357
358 Some(match &tag.to_be_bytes() {
359 b"Qaai" => script::INHERITED,
363 b"Qaac" => script::COPTIC,
364
365 b"Aran" => script::ARABIC,
367 b"Cyrs" => script::CYRILLIC,
368 b"Geok" => script::GEORGIAN,
369 b"Hans" | b"Hant" => script::HAN,
370 b"Jamo" => script::HANGUL,
371 b"Latf" | b"Latg" => script::LATIN,
372 b"Syre" | b"Syrj" | b"Syrn" => script::SYRIAC,
373 &t => Script(Tag::from_be_bytes(t)),
374 })
375 }
376
377 #[inline]
379 pub fn tag(&self) -> Tag {
380 self.0
381 }
382}
383
384impl FromStr for Script {
385 type Err = &'static str;
386
387 fn from_str(s: &str) -> Result<Self, Self::Err> {
388 let tag = Tag::from_bytes_lossy(s.as_bytes());
389 Script::from_iso15924_tag(tag).ok_or("invalid script")
390 }
391}
392
393pub mod script {
395 #![allow(missing_docs)]
396
397 use crate::Script;
398
399 pub const COMMON: Script = Script::from_bytes(b"Zyyy");
401 pub const INHERITED: Script = Script::from_bytes(b"Zinh");
402 pub const ARABIC: Script = Script::from_bytes(b"Arab");
403 pub const ARMENIAN: Script = Script::from_bytes(b"Armn");
404 pub const BENGALI: Script = Script::from_bytes(b"Beng");
405 pub const CYRILLIC: Script = Script::from_bytes(b"Cyrl");
406 pub const DEVANAGARI: Script = Script::from_bytes(b"Deva");
407 pub const GEORGIAN: Script = Script::from_bytes(b"Geor");
408 pub const GREEK: Script = Script::from_bytes(b"Grek");
409 pub const GUJARATI: Script = Script::from_bytes(b"Gujr");
410 pub const GURMUKHI: Script = Script::from_bytes(b"Guru");
411 pub const HANGUL: Script = Script::from_bytes(b"Hang");
412 pub const HAN: Script = Script::from_bytes(b"Hani");
413 pub const HEBREW: Script = Script::from_bytes(b"Hebr");
414 pub const HIRAGANA: Script = Script::from_bytes(b"Hira");
415 pub const KANNADA: Script = Script::from_bytes(b"Knda");
416 pub const KATAKANA: Script = Script::from_bytes(b"Kana");
417 pub const LAO: Script = Script::from_bytes(b"Laoo");
418 pub const LATIN: Script = Script::from_bytes(b"Latn");
419 pub const MALAYALAM: Script = Script::from_bytes(b"Mlym");
420 pub const ORIYA: Script = Script::from_bytes(b"Orya");
421 pub const TAMIL: Script = Script::from_bytes(b"Taml");
422 pub const TELUGU: Script = Script::from_bytes(b"Telu");
423 pub const THAI: Script = Script::from_bytes(b"Thai");
424 pub const TIBETAN: Script = Script::from_bytes(b"Tibt");
426 pub const BOPOMOFO: Script = Script::from_bytes(b"Bopo");
428 pub const BRAILLE: Script = Script::from_bytes(b"Brai");
429 pub const CANADIAN_SYLLABICS: Script = Script::from_bytes(b"Cans");
430 pub const CHEROKEE: Script = Script::from_bytes(b"Cher");
431 pub const ETHIOPIC: Script = Script::from_bytes(b"Ethi");
432 pub const KHMER: Script = Script::from_bytes(b"Khmr");
433 pub const MONGOLIAN: Script = Script::from_bytes(b"Mong");
434 pub const MYANMAR: Script = Script::from_bytes(b"Mymr");
435 pub const OGHAM: Script = Script::from_bytes(b"Ogam");
436 pub const RUNIC: Script = Script::from_bytes(b"Runr");
437 pub const SINHALA: Script = Script::from_bytes(b"Sinh");
438 pub const SYRIAC: Script = Script::from_bytes(b"Syrc");
439 pub const THAANA: Script = Script::from_bytes(b"Thaa");
440 pub const YI: Script = Script::from_bytes(b"Yiii");
441 pub const DESERET: Script = Script::from_bytes(b"Dsrt");
443 pub const GOTHIC: Script = Script::from_bytes(b"Goth");
444 pub const OLD_ITALIC: Script = Script::from_bytes(b"Ital");
445 pub const BUHID: Script = Script::from_bytes(b"Buhd");
447 pub const HANUNOO: Script = Script::from_bytes(b"Hano");
448 pub const TAGALOG: Script = Script::from_bytes(b"Tglg");
449 pub const TAGBANWA: Script = Script::from_bytes(b"Tagb");
450 pub const CYPRIOT: Script = Script::from_bytes(b"Cprt");
452 pub const LIMBU: Script = Script::from_bytes(b"Limb");
453 pub const LINEAR_B: Script = Script::from_bytes(b"Linb");
454 pub const OSMANYA: Script = Script::from_bytes(b"Osma");
455 pub const SHAVIAN: Script = Script::from_bytes(b"Shaw");
456 pub const TAI_LE: Script = Script::from_bytes(b"Tale");
457 pub const UGARITIC: Script = Script::from_bytes(b"Ugar");
458 pub const BUGINESE: Script = Script::from_bytes(b"Bugi");
460 pub const COPTIC: Script = Script::from_bytes(b"Copt");
461 pub const GLAGOLITIC: Script = Script::from_bytes(b"Glag");
462 pub const KHAROSHTHI: Script = Script::from_bytes(b"Khar");
463 pub const NEW_TAI_LUE: Script = Script::from_bytes(b"Talu");
464 pub const OLD_PERSIAN: Script = Script::from_bytes(b"Xpeo");
465 pub const SYLOTI_NAGRI: Script = Script::from_bytes(b"Sylo");
466 pub const TIFINAGH: Script = Script::from_bytes(b"Tfng");
467 pub const UNKNOWN: Script = Script::from_bytes(b"Zzzz"); pub const BALINESE: Script = Script::from_bytes(b"Bali");
470 pub const CUNEIFORM: Script = Script::from_bytes(b"Xsux");
471 pub const NKO: Script = Script::from_bytes(b"Nkoo");
472 pub const PHAGS_PA: Script = Script::from_bytes(b"Phag");
473 pub const PHOENICIAN: Script = Script::from_bytes(b"Phnx");
474 pub const CARIAN: Script = Script::from_bytes(b"Cari");
476 pub const CHAM: Script = Script::from_bytes(b"Cham");
477 pub const KAYAH_LI: Script = Script::from_bytes(b"Kali");
478 pub const LEPCHA: Script = Script::from_bytes(b"Lepc");
479 pub const LYCIAN: Script = Script::from_bytes(b"Lyci");
480 pub const LYDIAN: Script = Script::from_bytes(b"Lydi");
481 pub const OL_CHIKI: Script = Script::from_bytes(b"Olck");
482 pub const REJANG: Script = Script::from_bytes(b"Rjng");
483 pub const SAURASHTRA: Script = Script::from_bytes(b"Saur");
484 pub const SUNDANESE: Script = Script::from_bytes(b"Sund");
485 pub const VAI: Script = Script::from_bytes(b"Vaii");
486 pub const AVESTAN: Script = Script::from_bytes(b"Avst");
488 pub const BAMUM: Script = Script::from_bytes(b"Bamu");
489 pub const EGYPTIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Egyp");
490 pub const IMPERIAL_ARAMAIC: Script = Script::from_bytes(b"Armi");
491 pub const INSCRIPTIONAL_PAHLAVI: Script = Script::from_bytes(b"Phli");
492 pub const INSCRIPTIONAL_PARTHIAN: Script = Script::from_bytes(b"Prti");
493 pub const JAVANESE: Script = Script::from_bytes(b"Java");
494 pub const KAITHI: Script = Script::from_bytes(b"Kthi");
495 pub const LISU: Script = Script::from_bytes(b"Lisu");
496 pub const MEETEI_MAYEK: Script = Script::from_bytes(b"Mtei");
497 pub const OLD_SOUTH_ARABIAN: Script = Script::from_bytes(b"Sarb");
498 pub const OLD_TURKIC: Script = Script::from_bytes(b"Orkh");
499 pub const SAMARITAN: Script = Script::from_bytes(b"Samr");
500 pub const TAI_THAM: Script = Script::from_bytes(b"Lana");
501 pub const TAI_VIET: Script = Script::from_bytes(b"Tavt");
502 pub const BATAK: Script = Script::from_bytes(b"Batk");
504 pub const BRAHMI: Script = Script::from_bytes(b"Brah");
505 pub const MANDAIC: Script = Script::from_bytes(b"Mand");
506 pub const CHAKMA: Script = Script::from_bytes(b"Cakm");
508 pub const MEROITIC_CURSIVE: Script = Script::from_bytes(b"Merc");
509 pub const MEROITIC_HIEROGLYPHS: Script = Script::from_bytes(b"Mero");
510 pub const MIAO: Script = Script::from_bytes(b"Plrd");
511 pub const SHARADA: Script = Script::from_bytes(b"Shrd");
512 pub const SORA_SOMPENG: Script = Script::from_bytes(b"Sora");
513 pub const TAKRI: Script = Script::from_bytes(b"Takr");
514 pub const BASSA_VAH: Script = Script::from_bytes(b"Bass");
516 pub const CAUCASIAN_ALBANIAN: Script = Script::from_bytes(b"Aghb");
517 pub const DUPLOYAN: Script = Script::from_bytes(b"Dupl");
518 pub const ELBASAN: Script = Script::from_bytes(b"Elba");
519 pub const GRANTHA: Script = Script::from_bytes(b"Gran");
520 pub const KHOJKI: Script = Script::from_bytes(b"Khoj");
521 pub const KHUDAWADI: Script = Script::from_bytes(b"Sind");
522 pub const LINEAR_A: Script = Script::from_bytes(b"Lina");
523 pub const MAHAJANI: Script = Script::from_bytes(b"Mahj");
524 pub const MANICHAEAN: Script = Script::from_bytes(b"Mani");
525 pub const MENDE_KIKAKUI: Script = Script::from_bytes(b"Mend");
526 pub const MODI: Script = Script::from_bytes(b"Modi");
527 pub const MRO: Script = Script::from_bytes(b"Mroo");
528 pub const NABATAEAN: Script = Script::from_bytes(b"Nbat");
529 pub const OLD_NORTH_ARABIAN: Script = Script::from_bytes(b"Narb");
530 pub const OLD_PERMIC: Script = Script::from_bytes(b"Perm");
531 pub const PAHAWH_HMONG: Script = Script::from_bytes(b"Hmng");
532 pub const PALMYRENE: Script = Script::from_bytes(b"Palm");
533 pub const PAU_CIN_HAU: Script = Script::from_bytes(b"Pauc");
534 pub const PSALTER_PAHLAVI: Script = Script::from_bytes(b"Phlp");
535 pub const SIDDHAM: Script = Script::from_bytes(b"Sidd");
536 pub const TIRHUTA: Script = Script::from_bytes(b"Tirh");
537 pub const WARANG_CITI: Script = Script::from_bytes(b"Wara");
538 pub const AHOM: Script = Script::from_bytes(b"Ahom");
540 pub const ANATOLIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Hluw");
541 pub const HATRAN: Script = Script::from_bytes(b"Hatr");
542 pub const MULTANI: Script = Script::from_bytes(b"Mult");
543 pub const OLD_HUNGARIAN: Script = Script::from_bytes(b"Hung");
544 pub const SIGNWRITING: Script = Script::from_bytes(b"Sgnw");
545 pub const ADLAM: Script = Script::from_bytes(b"Adlm");
547 pub const BHAIKSUKI: Script = Script::from_bytes(b"Bhks");
548 pub const MARCHEN: Script = Script::from_bytes(b"Marc");
549 pub const OSAGE: Script = Script::from_bytes(b"Osge");
550 pub const TANGUT: Script = Script::from_bytes(b"Tang");
551 pub const NEWA: Script = Script::from_bytes(b"Newa");
552 pub const MASARAM_GONDI: Script = Script::from_bytes(b"Gonm");
554 pub const NUSHU: Script = Script::from_bytes(b"Nshu");
555 pub const SOYOMBO: Script = Script::from_bytes(b"Soyo");
556 pub const ZANABAZAR_SQUARE: Script = Script::from_bytes(b"Zanb");
557 pub const DOGRA: Script = Script::from_bytes(b"Dogr");
559 pub const GUNJALA_GONDI: Script = Script::from_bytes(b"Gong");
560 pub const HANIFI_ROHINGYA: Script = Script::from_bytes(b"Rohg");
561 pub const MAKASAR: Script = Script::from_bytes(b"Maka");
562 pub const MEDEFAIDRIN: Script = Script::from_bytes(b"Medf");
563 pub const OLD_SOGDIAN: Script = Script::from_bytes(b"Sogo");
564 pub const SOGDIAN: Script = Script::from_bytes(b"Sogd");
565 pub const ELYMAIC: Script = Script::from_bytes(b"Elym");
567 pub const NANDINAGARI: Script = Script::from_bytes(b"Nand");
568 pub const NYIAKENG_PUACHUE_HMONG: Script = Script::from_bytes(b"Hmnp");
569 pub const WANCHO: Script = Script::from_bytes(b"Wcho");
570 pub const CHORASMIAN: Script = Script::from_bytes(b"Chrs");
572 pub const DIVES_AKURU: Script = Script::from_bytes(b"Diak");
573 pub const KHITAN_SMALL_SCRIPT: Script = Script::from_bytes(b"Kits");
574 pub const YEZIDI: Script = Script::from_bytes(b"Yezi");
575 pub const CYPRO_MINOAN: Script = Script::from_bytes(b"Cpmn");
577 pub const OLD_UYGHUR: Script = Script::from_bytes(b"Ougr");
578 pub const TANGSA: Script = Script::from_bytes(b"Tnsa");
579 pub const TOTO: Script = Script::from_bytes(b"Toto");
580 pub const VITHKUQI: Script = Script::from_bytes(b"Vith");
581 pub const KAWI: Script = Script::from_bytes(b"Kawi");
583 pub const NAG_MUNDARI: Script = Script::from_bytes(b"Nagm");
584 pub const GARAY: Script = Script::from_bytes(b"Gara");
586 pub const GURUNG_KHEMA: Script = Script::from_bytes(b"Gukh");
587 pub const KIRAT_RAI: Script = Script::from_bytes(b"Krai");
588 pub const OL_ONAL: Script = Script::from_bytes(b"Onao");
589 pub const SUNUWAR: Script = Script::from_bytes(b"Sunu");
590 pub const TODHRI: Script = Script::from_bytes(b"Todr");
591 pub const TULU_TIGALARI: Script = Script::from_bytes(b"Tutg");
592 pub const BERIA_ERFE: Script = Script::from_bytes(b"Berf");
594 pub const SIDETIC: Script = Script::from_bytes(b"Sidt");
595 pub const TAI_YO: Script = Script::from_bytes(b"Tayo");
596 pub const TOLONG_SIKI: Script = Script::from_bytes(b"Tols");
597
598 pub const MATH: Script = Script::from_bytes(b"Zmth");
599
600 pub const MYANMAR_ZAWGYI: Script = Script::from_bytes(b"Qaag");
602}
603
604#[repr(C)]
607#[allow(missing_docs)]
608#[derive(Clone, Copy, PartialEq, Hash, Debug)]
609pub struct Feature {
610 pub tag: Tag,
611 pub value: u32,
612 pub start: u32,
613 pub end: u32,
614}
615
616impl Feature {
617 pub fn new(tag: Tag, value: u32, range: impl RangeBounds<usize>) -> Feature {
619 let max = u32::MAX as usize;
620 let start = match range.start_bound() {
621 Bound::Included(&included) => included.min(max) as u32,
622 Bound::Excluded(&excluded) => excluded.min(max - 1) as u32 + 1,
623 Bound::Unbounded => 0,
624 };
625 let end = match range.end_bound() {
626 Bound::Included(&included) => included.min(max) as u32,
627 Bound::Excluded(&excluded) => excluded.saturating_sub(1).min(max) as u32,
628 Bound::Unbounded => max as u32,
629 };
630
631 Feature {
632 tag,
633 value,
634 start,
635 end,
636 }
637 }
638
639 pub(crate) fn is_global(&self) -> bool {
640 self.start == 0 && self.end == u32::MAX
641 }
642}
643
644impl FromStr for Feature {
645 type Err = &'static str;
646
647 fn from_str(s: &str) -> Result<Self, Self::Err> {
665 fn parse(s: &str) -> Option<Feature> {
666 if s.is_empty() {
667 return None;
668 }
669
670 let mut p = TextParser::new(s);
671
672 let mut value = 1;
674 match p.curr_byte()? {
675 b'-' => {
676 value = 0;
677 p.advance(1);
678 }
679 b'+' => {
680 value = 1;
681 p.advance(1);
682 }
683 _ => {}
684 }
685
686 p.skip_spaces();
688 let quote = p.consume_quote();
689
690 let tag = p.consume_tag()?;
691
692 if let Some(quote) = quote {
694 p.consume_byte(quote)?;
695 }
696
697 p.skip_spaces();
699
700 let (start, end) = if p.consume_byte(b'[').is_some() {
701 let start_opt = p.consume_i32();
702 let start = start_opt.unwrap_or(0) as u32; let end = if matches!(p.curr_byte(), Some(b':' | b';')) {
705 p.advance(1);
706 p.consume_i32().unwrap_or(-1) as u32 } else {
708 if start_opt.is_some() && start != u32::MAX {
709 start + 1
710 } else {
711 u32::MAX
712 }
713 };
714
715 p.consume_byte(b']')?;
716
717 (start, end)
718 } else {
719 (0, u32::MAX)
720 };
721
722 let had_equal = p.consume_byte(b'=').is_some();
724 let value1 = p
725 .consume_i32()
726 .or_else(|| p.consume_bool().map(|b| b as i32));
727
728 if had_equal && value1.is_none() {
729 return None;
730 }
731
732 if let Some(value1) = value1 {
733 value = value1 as u32; }
735
736 p.skip_spaces();
737
738 if !p.at_end() {
739 return None;
740 }
741
742 Some(Feature {
743 tag,
744 value,
745 start,
746 end,
747 })
748 }
749
750 parse(s).ok_or("invalid feature")
751 }
752}
753
754#[cfg(test)]
755mod tests_features {
756 use super::*;
757 use core::str::FromStr;
758
759 macro_rules! test {
760 ($name:ident, $text:expr, $tag:expr, $value:expr, $range:expr) => {
761 #[test]
762 fn $name() {
763 assert_eq!(
764 Feature::from_str($text).unwrap(),
765 Feature::new(Tag::new($tag), $value, $range)
766 );
767 }
768 };
769 }
770
771 test!(parse_01, "kern", b"kern", 1, ..);
772 test!(parse_02, "+kern", b"kern", 1, ..);
773 test!(parse_03, "-kern", b"kern", 0, ..);
774 test!(parse_04, "kern=0", b"kern", 0, ..);
775 test!(parse_05, "kern=1", b"kern", 1, ..);
776 test!(parse_06, "kern=2", b"kern", 2, ..);
777 test!(parse_07, "kern[]", b"kern", 1, ..);
778 test!(parse_08, "kern[:]", b"kern", 1, ..);
779 test!(parse_09, "kern[5:]", b"kern", 1, 5..);
780 test!(parse_10, "kern[:5]", b"kern", 1, ..=5);
781 test!(parse_11, "kern[3:5]", b"kern", 1, 3..=5);
782 test!(parse_12, "kern[3]", b"kern", 1, 3..=4);
783 test!(parse_13, "kern[3:5]=2", b"kern", 2, 3..=5);
784 test!(parse_14, "kern[3;5]=2", b"kern", 2, 3..=5);
785 test!(parse_15, "kern[:-1]", b"kern", 1, ..);
786 test!(parse_16, "kern[-1]", b"kern", 1, u32::MAX as usize..);
787 test!(parse_17, "kern=on", b"kern", 1, ..);
788 test!(parse_18, "kern=off", b"kern", 0, ..);
789 test!(parse_19, "kern=oN", b"kern", 1, ..);
790 test!(parse_20, "kern=oFf", b"kern", 0, ..);
791}
792
793#[repr(C)]
795#[allow(missing_docs)]
796#[derive(Clone, Copy, PartialEq, Debug)]
797pub struct Variation {
798 pub tag: Tag,
799 pub value: f32,
800}
801
802impl FromStr for Variation {
803 type Err = &'static str;
804
805 fn from_str(s: &str) -> Result<Self, Self::Err> {
806 fn parse(s: &str) -> Option<Variation> {
807 if s.is_empty() {
808 return None;
809 }
810
811 let mut p = TextParser::new(s);
812
813 p.skip_spaces();
815 let quote = p.consume_quote();
816
817 let tag = p.consume_tag()?;
818
819 if let Some(quote) = quote {
821 p.consume_byte(quote)?;
822 }
823
824 let _ = p.consume_byte(b'=');
825 let value = p.consume_f32()?;
826 p.skip_spaces();
827
828 if !p.at_end() {
829 return None;
830 }
831
832 Some(Variation { tag, value })
833 }
834
835 parse(s).ok_or("invalid variation")
836 }
837}
838
839impl From<&Variation> for Variation {
842 fn from(value: &Variation) -> Self {
843 *value
844 }
845}
846
847impl From<(&str, f32)> for Variation {
848 fn from(value: (&str, f32)) -> Self {
849 Self {
850 tag: Tag::from_str(value.0).unwrap_or_default(),
851 value: value.1,
852 }
853 }
854}
855
856impl From<&(&str, f32)> for Variation {
857 fn from(value: &(&str, f32)) -> Self {
858 (*value).into()
859 }
860}
861
862impl From<(Tag, f32)> for Variation {
863 fn from(value: (Tag, f32)) -> Self {
864 Self {
865 tag: value.0,
866 value: value.1,
867 }
868 }
869}
870
871impl From<&(Tag, f32)> for Variation {
872 fn from(value: &(Tag, f32)) -> Self {
873 (*value).into()
874 }
875}
876
877pub trait TagExt {
878 fn from_bytes_lossy(bytes: &[u8]) -> Self;
879 fn as_u32(self) -> u32;
880 fn is_null(self) -> bool;
881 fn default_script() -> Self;
882 fn default_language() -> Self;
883 #[cfg(test)]
884 fn to_lowercase(&self) -> Self;
885 fn to_uppercase(&self) -> Self;
886}
887
888impl TagExt for Tag {
889 fn from_bytes_lossy(bytes: &[u8]) -> Self {
890 let mut array = [b' '; 4];
891 for (src, dest) in bytes.iter().zip(&mut array) {
892 *dest = *src;
893 }
894 Tag::new(&array)
895 }
896
897 fn as_u32(self) -> u32 {
898 u32::from_be_bytes(self.to_be_bytes())
899 }
900
901 fn is_null(self) -> bool {
902 self.to_be_bytes() == [0, 0, 0, 0]
903 }
904
905 #[inline]
906 fn default_script() -> Self {
907 Tag::new(b"DFLT")
908 }
909
910 #[inline]
911 fn default_language() -> Self {
912 Tag::new(b"dflt")
913 }
914
915 #[cfg(test)]
917 #[inline]
918 fn to_lowercase(&self) -> Self {
919 let b = self.to_be_bytes();
920 Tag::new(&[
921 b[0].to_ascii_lowercase(),
922 b[1].to_ascii_lowercase(),
923 b[2].to_ascii_lowercase(),
924 b[3].to_ascii_lowercase(),
925 ])
926 }
927
928 #[inline]
930 fn to_uppercase(&self) -> Self {
931 let b = self.to_be_bytes();
932 Tag::new(&[
933 b[0].to_ascii_uppercase(),
934 b[1].to_ascii_uppercase(),
935 b[2].to_ascii_uppercase(),
936 b[3].to_ascii_uppercase(),
937 ])
938 }
939}