makepad_rustybuzz/
common.rs

1use alloc::string::String;
2use core::ops::{Bound, RangeBounds};
3
4use crate::ttf_parser::Tag;
5
6use crate::text_parser::TextParser;
7
8
9/// Defines the direction in which text is to be read.
10#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
11pub enum Direction {
12    /// Initial, unset direction.
13    Invalid,
14    /// Text is set horizontally from left to right.
15    LeftToRight,
16    /// Text is set horizontally from right to left.
17    RightToLeft,
18    /// Text is set vertically from top to bottom.
19    TopToBottom,
20    /// Text is set vertically from bottom to top.
21    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        // https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o
70
71        match script {
72            // Unicode-1.1 additions
73            script::ARABIC |
74            script::HEBREW |
75
76            // Unicode-3.0 additions
77            script::SYRIAC |
78            script::THAANA |
79
80            // Unicode-4.0 additions
81            script::CYPRIOT |
82
83            // Unicode-4.1 additions
84            script::KHAROSHTHI |
85
86            // Unicode-5.0 additions
87            script::PHOENICIAN |
88            script::NKO |
89
90            // Unicode-5.1 additions
91            script::LYDIAN |
92
93            // Unicode-5.2 additions
94            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            // Unicode-6.0 additions
103            script::MANDAIC |
104
105            // Unicode-6.1 additions
106            script::MEROITIC_CURSIVE |
107            script::MEROITIC_HIEROGLYPHS |
108
109            // Unicode-7.0 additions
110            script::MANICHAEAN |
111            script::MENDE_KIKAKUI |
112            script::NABATAEAN |
113            script::OLD_NORTH_ARABIAN |
114            script::PALMYRENE |
115            script::PSALTER_PAHLAVI |
116
117            // Unicode-8.0 additions
118            script::HATRAN |
119
120            // Unicode-9.0 additions
121            script::ADLAM |
122
123            // Unicode-11.0 additions
124            script::HANIFI_ROHINGYA |
125            script::OLD_SOGDIAN |
126            script::SOGDIAN |
127
128            // Unicode-12.0 additions
129            script::ELYMAIC |
130
131            // Unicode-13.0 additions
132            script::CHORASMIAN |
133            script::YEZIDI => {
134                Some(Direction::RightToLeft)
135            }
136
137            // https://github.com/harfbuzz/harfbuzz/issues/1000
138            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        // harfbuzz also matches only the first letter.
165        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/// A script language.
177#[derive(Clone, PartialEq, Eq, Hash, Debug)]
178pub struct Language(String);
179
180impl Language {
181    /// Returns the language as a string.
182    #[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// In harfbuzz, despite having `hb_script_t`, script can actually have any tag.
202// So we're doing the same.
203// The only difference is that `Script` cannot be set to `HB_SCRIPT_INVALID`.
204/// A text script.
205#[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    /// Converts an ISO 15924 script tag to a corresponding `Script`.
216    pub fn from_iso15924_tag(tag: Tag) -> Option<Script> {
217        if tag.is_null() {
218            return None;
219        }
220
221        // Be lenient, adjust case (one capital letter followed by three small letters).
222        let tag = Tag((tag.as_u32() & 0xDFDFDFDF) | 0x00202020);
223
224        match &tag.to_bytes() {
225            // These graduated from the 'Q' private-area codes, but
226            // the old code is still aliased by Unicode, and the Qaai
227            // one in use by ICU.
228            b"Qaai" => return Some(script::INHERITED),
229            b"Qaac" => return Some(script::COPTIC),
230
231            // Script variants from https://unicode.org/iso15924/
232            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    /// Returns script's tag.
247    #[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
262/// Predefined scripts.
263pub mod script {
264    #![allow(missing_docs)]
265
266    use crate::Script;
267
268    // Since 1.1
269    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    // Since 2.0
294    pub const TIBETAN: Script                   = Script::from_bytes(b"Tibt");
295    // Since 3.0
296    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    // Since 3.1
311    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    // Since 3.2
315    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    // Since 4.0
320    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    // Since 4.1
328    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    // Since 5.0
337    pub const UNKNOWN: Script                   = Script::from_bytes(b"Zzzz"); // Script can be Unknown, but not Invalid.
338    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    // Since 5.1
344    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    // Since 5.2
356    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    // Since 6.0
372    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    // Since 6.1
376    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    // Since 7.0
384    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    // Since 8.0
408    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    // Since 9.0
415    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    // Since 10.0
422    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    // Since 11.0
427    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    // Since 12.0
435    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    // Since 13.0
440    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    // https://github.com/harfbuzz/harfbuzz/issues/1162
446    pub const MYANMAR_ZAWGYI: Script            = Script::from_bytes(b"Qaag");
447}
448
449
450/// A feature tag with an accompanying range specifying on which subslice of
451/// `shape`s input it should be applied.
452#[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    /// Create a new `Feature` struct.
464    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    /// Parses a `Feature` form a string.
494    ///
495    /// Possible values:
496    ///
497    /// - `kern` -> kern .. 1
498    /// - `+kern` -> kern .. 1
499    /// - `-kern` -> kern .. 0
500    /// - `kern=0` -> kern .. 0
501    /// - `kern=1` -> kern .. 1
502    /// - `aalt=2` -> altr .. 2
503    /// - `kern[]` -> kern .. 1
504    /// - `kern[:]` -> kern .. 1
505    /// - `kern[5:]` -> kern 5.. 1
506    /// - `kern[:5]` -> kern ..=5 1
507    /// - `kern[3:5]` -> kern 3..=5 1
508    /// - `kern[3]` -> kern 3..=4 1
509    /// - `aalt[3:5]=2` -> kern 3..=5 1
510    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            // Parse prefix.
519            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            // Parse tag.
527            p.skip_spaces();
528            let quote = p.consume_quote();
529
530            let tag = p.consume_tag()?;
531
532            // Force closing quote.
533            if let Some(quote) = quote {
534                p.consume_byte(quote)?;
535            }
536
537            // Parse indices.
538            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; // negative value overflow is ok
543
544                let end = if matches!(p.curr_byte(), Some(b':') | Some(b';')) {
545                    p.advance(1);
546                    p.consume_i32().unwrap_or(-1) as u32 // negative value overflow is ok
547                } 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            // Parse postfix.
563            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; // negative value overflow is ok
572            }
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/// A font variation.
633#[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            // Parse tag.
653            p.skip_spaces();
654            let quote = p.consume_quote();
655
656            let tag = p.consume_tag()?;
657
658            // Force closing quote.
659            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_lowercase(&self) -> Self;
686    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    /// Converts tag to lowercase.
701    #[inline]
702    /*fn to_lowercase(&self) -> Self {
703        let b = self.to_bytes();
704        Tag::from_bytes(&[
705            b[0].to_ascii_lowercase(),
706            b[1].to_ascii_lowercase(),
707            b[2].to_ascii_lowercase(),
708            b[3].to_ascii_lowercase(),
709        ])
710    }*/
711
712    /// Converts tag to uppercase.
713    //#[inline]
714    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}