harfrust/hb/
common.rs

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/// Defines the direction in which text is to be read.
15#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
16pub enum Direction {
17    /// Initial, unset direction.
18    Invalid,
19    /// Text is set horizontally from left to right.
20    LeftToRight,
21    /// Text is set horizontally from right to left.
22    RightToLeft,
23    /// Text is set vertically from top to bottom.
24    TopToBottom,
25    /// Text is set vertically from bottom to top.
26    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        // https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o
75
76        match script {
77            // Unicode-1.1 additions
78            script::ARABIC |
79            script::HEBREW |
80
81            // Unicode-3.0 additions
82            script::SYRIAC |
83            script::THAANA |
84
85            // Unicode-4.0 additions
86            script::CYPRIOT |
87
88            // Unicode-4.1 additions
89            script::KHAROSHTHI |
90
91            // Unicode-5.0 additions
92            script::PHOENICIAN |
93            script::NKO |
94
95            // Unicode-5.1 additions
96            script::LYDIAN |
97
98            // Unicode-5.2 additions
99            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            // Unicode-6.0 additions
108            script::MANDAIC |
109
110            // Unicode-6.1 additions
111            script::MEROITIC_CURSIVE |
112            script::MEROITIC_HIEROGLYPHS |
113
114            // Unicode-7.0 additions
115            script::MANICHAEAN |
116            script::MENDE_KIKAKUI |
117            script::NABATAEAN |
118            script::OLD_NORTH_ARABIAN |
119            script::PALMYRENE |
120            script::PSALTER_PAHLAVI |
121
122            // Unicode-8.0 additions
123            script::HATRAN |
124
125            // Unicode-9.0 additions
126            script::ADLAM |
127
128            // Unicode-11.0 additions
129            script::HANIFI_ROHINGYA |
130            script::OLD_SOGDIAN |
131            script::SOGDIAN |
132
133            // Unicode-12.0 additions
134            script::ELYMAIC |
135
136            // Unicode-13.0 additions
137            script::CHORASMIAN |
138            script::YEZIDI |
139
140            // Unicode-14.0 additions
141            script::OLD_UYGHUR |
142
143            // Unicode-16.0 additions
144            script::GARAY |
145
146            // Unicode-17.0 additions
147            script::SIDETIC => {
148                Some(Direction::RightToLeft)
149            }
150
151            // https://github.com/harfbuzz/harfbuzz/issues/1000
152            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        // harfbuzz also matches only the first letter.
180        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/// A language tag.
193#[derive(Clone, PartialEq, Eq, Hash, Debug)]
194pub struct Language(SmallVecLanguage);
195
196impl Language {
197    /// Returns the language as a string.
198    #[inline]
199    pub fn as_str(&self) -> &str {
200        core::str::from_utf8(&self.0).unwrap_or_default()
201    }
202
203    fn from_bytes(bytes: &[u8]) -> Self {
204        if bytes.is_empty() {
205            Language(SmallVec::new())
206        } else {
207            let mut bytes = SmallVecLanguage::from_slice(bytes);
208
209            // Convert uppercase to lowercase and replace '_' with '-'.
210            for b in &mut bytes.iter_mut() {
211                if b.is_ascii_uppercase() {
212                    *b = b.to_ascii_lowercase();
213                } else if *b == b'_' {
214                    *b = b'-';
215                }
216            }
217
218            Language(bytes)
219        }
220    }
221}
222
223impl FromStr for Language {
224    type Err = &'static str;
225
226    fn from_str(s: &str) -> Result<Self, Self::Err> {
227        if !s.is_empty() {
228            Ok(Language::from_bytes(s.as_bytes()))
229        } else {
230            Err("invalid language")
231        }
232    }
233}
234
235// In harfbuzz, despite having `hb_script_t`, script can actually have any tag.
236// So we're doing the same.
237// The only difference is that `Script` cannot be set to `HB_SCRIPT_INVALID`.
238/// A text script.
239#[allow(missing_docs)]
240#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
241pub struct Script(pub(crate) Tag);
242
243impl Script {
244    #[inline]
245    pub(crate) const fn from_bytes(bytes: &[u8; 4]) -> Self {
246        Script(Tag::new(bytes))
247    }
248
249    /// Converts an ISO 15924 script tag to a corresponding `Script`.
250    pub fn from_iso15924_tag(tag: Tag) -> Option<Script> {
251        if tag.is_null() {
252            return None;
253        }
254
255        // Be lenient, adjust case (one capital letter followed by three small letters).
256        let tag = Tag::from_u32((tag.as_u32() & 0xDFDF_DFDF) | 0x0020_2020);
257
258        match &tag.to_be_bytes() {
259            // These graduated from the 'Q' private-area codes, but
260            // the old code is still aliased by Unicode, and the Qaai
261            // one in use by ICU.
262            b"Qaai" => return Some(script::INHERITED),
263            b"Qaac" => return Some(script::COPTIC),
264
265            // Script variants from https://unicode.org/iso15924/
266            b"Aran" => return Some(script::ARABIC),
267            b"Cyrs" => return Some(script::CYRILLIC),
268            b"Geok" => return Some(script::GEORGIAN),
269            b"Hans" | b"Hant" => return Some(script::HAN),
270            b"Jamo" => return Some(script::HANGUL),
271            b"Latf" | b"Latg" => return Some(script::LATIN),
272            b"Syre" | b"Syrj" | b"Syrn" => return Some(script::SYRIAC),
273
274            _ => {}
275        }
276
277        if tag.as_u32() & 0xE0E0_E0E0 == 0x4060_6060 {
278            Some(Script(tag))
279        } else {
280            Some(script::UNKNOWN)
281        }
282    }
283
284    /// Returns script's tag.
285    #[inline]
286    pub fn tag(&self) -> Tag {
287        self.0
288    }
289}
290
291impl FromStr for Script {
292    type Err = &'static str;
293
294    fn from_str(s: &str) -> Result<Self, Self::Err> {
295        let tag = Tag::from_bytes_lossy(s.as_bytes());
296        Script::from_iso15924_tag(tag).ok_or("invalid script")
297    }
298}
299
300/// Predefined scripts.
301pub mod script {
302    #![allow(missing_docs)]
303
304    use crate::Script;
305
306    // Since 1.1
307    pub const COMMON: Script = Script::from_bytes(b"Zyyy");
308    pub const INHERITED: Script = Script::from_bytes(b"Zinh");
309    pub const ARABIC: Script = Script::from_bytes(b"Arab");
310    pub const ARMENIAN: Script = Script::from_bytes(b"Armn");
311    pub const BENGALI: Script = Script::from_bytes(b"Beng");
312    pub const CYRILLIC: Script = Script::from_bytes(b"Cyrl");
313    pub const DEVANAGARI: Script = Script::from_bytes(b"Deva");
314    pub const GEORGIAN: Script = Script::from_bytes(b"Geor");
315    pub const GREEK: Script = Script::from_bytes(b"Grek");
316    pub const GUJARATI: Script = Script::from_bytes(b"Gujr");
317    pub const GURMUKHI: Script = Script::from_bytes(b"Guru");
318    pub const HANGUL: Script = Script::from_bytes(b"Hang");
319    pub const HAN: Script = Script::from_bytes(b"Hani");
320    pub const HEBREW: Script = Script::from_bytes(b"Hebr");
321    pub const HIRAGANA: Script = Script::from_bytes(b"Hira");
322    pub const KANNADA: Script = Script::from_bytes(b"Knda");
323    pub const KATAKANA: Script = Script::from_bytes(b"Kana");
324    pub const LAO: Script = Script::from_bytes(b"Laoo");
325    pub const LATIN: Script = Script::from_bytes(b"Latn");
326    pub const MALAYALAM: Script = Script::from_bytes(b"Mlym");
327    pub const ORIYA: Script = Script::from_bytes(b"Orya");
328    pub const TAMIL: Script = Script::from_bytes(b"Taml");
329    pub const TELUGU: Script = Script::from_bytes(b"Telu");
330    pub const THAI: Script = Script::from_bytes(b"Thai");
331    // Since 2.0
332    pub const TIBETAN: Script = Script::from_bytes(b"Tibt");
333    // Since 3.0
334    pub const BOPOMOFO: Script = Script::from_bytes(b"Bopo");
335    pub const BRAILLE: Script = Script::from_bytes(b"Brai");
336    pub const CANADIAN_SYLLABICS: Script = Script::from_bytes(b"Cans");
337    pub const CHEROKEE: Script = Script::from_bytes(b"Cher");
338    pub const ETHIOPIC: Script = Script::from_bytes(b"Ethi");
339    pub const KHMER: Script = Script::from_bytes(b"Khmr");
340    pub const MONGOLIAN: Script = Script::from_bytes(b"Mong");
341    pub const MYANMAR: Script = Script::from_bytes(b"Mymr");
342    pub const OGHAM: Script = Script::from_bytes(b"Ogam");
343    pub const RUNIC: Script = Script::from_bytes(b"Runr");
344    pub const SINHALA: Script = Script::from_bytes(b"Sinh");
345    pub const SYRIAC: Script = Script::from_bytes(b"Syrc");
346    pub const THAANA: Script = Script::from_bytes(b"Thaa");
347    pub const YI: Script = Script::from_bytes(b"Yiii");
348    // Since 3.1
349    pub const DESERET: Script = Script::from_bytes(b"Dsrt");
350    pub const GOTHIC: Script = Script::from_bytes(b"Goth");
351    pub const OLD_ITALIC: Script = Script::from_bytes(b"Ital");
352    // Since 3.2
353    pub const BUHID: Script = Script::from_bytes(b"Buhd");
354    pub const HANUNOO: Script = Script::from_bytes(b"Hano");
355    pub const TAGALOG: Script = Script::from_bytes(b"Tglg");
356    pub const TAGBANWA: Script = Script::from_bytes(b"Tagb");
357    // Since 4.0
358    pub const CYPRIOT: Script = Script::from_bytes(b"Cprt");
359    pub const LIMBU: Script = Script::from_bytes(b"Limb");
360    pub const LINEAR_B: Script = Script::from_bytes(b"Linb");
361    pub const OSMANYA: Script = Script::from_bytes(b"Osma");
362    pub const SHAVIAN: Script = Script::from_bytes(b"Shaw");
363    pub const TAI_LE: Script = Script::from_bytes(b"Tale");
364    pub const UGARITIC: Script = Script::from_bytes(b"Ugar");
365    // Since 4.1
366    pub const BUGINESE: Script = Script::from_bytes(b"Bugi");
367    pub const COPTIC: Script = Script::from_bytes(b"Copt");
368    pub const GLAGOLITIC: Script = Script::from_bytes(b"Glag");
369    pub const KHAROSHTHI: Script = Script::from_bytes(b"Khar");
370    pub const NEW_TAI_LUE: Script = Script::from_bytes(b"Talu");
371    pub const OLD_PERSIAN: Script = Script::from_bytes(b"Xpeo");
372    pub const SYLOTI_NAGRI: Script = Script::from_bytes(b"Sylo");
373    pub const TIFINAGH: Script = Script::from_bytes(b"Tfng");
374    // Since 5.0
375    pub const UNKNOWN: Script = Script::from_bytes(b"Zzzz"); // Script can be Unknown, but not Invalid.
376    pub const BALINESE: Script = Script::from_bytes(b"Bali");
377    pub const CUNEIFORM: Script = Script::from_bytes(b"Xsux");
378    pub const NKO: Script = Script::from_bytes(b"Nkoo");
379    pub const PHAGS_PA: Script = Script::from_bytes(b"Phag");
380    pub const PHOENICIAN: Script = Script::from_bytes(b"Phnx");
381    // Since 5.1
382    pub const CARIAN: Script = Script::from_bytes(b"Cari");
383    pub const CHAM: Script = Script::from_bytes(b"Cham");
384    pub const KAYAH_LI: Script = Script::from_bytes(b"Kali");
385    pub const LEPCHA: Script = Script::from_bytes(b"Lepc");
386    pub const LYCIAN: Script = Script::from_bytes(b"Lyci");
387    pub const LYDIAN: Script = Script::from_bytes(b"Lydi");
388    pub const OL_CHIKI: Script = Script::from_bytes(b"Olck");
389    pub const REJANG: Script = Script::from_bytes(b"Rjng");
390    pub const SAURASHTRA: Script = Script::from_bytes(b"Saur");
391    pub const SUNDANESE: Script = Script::from_bytes(b"Sund");
392    pub const VAI: Script = Script::from_bytes(b"Vaii");
393    // Since 5.2
394    pub const AVESTAN: Script = Script::from_bytes(b"Avst");
395    pub const BAMUM: Script = Script::from_bytes(b"Bamu");
396    pub const EGYPTIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Egyp");
397    pub const IMPERIAL_ARAMAIC: Script = Script::from_bytes(b"Armi");
398    pub const INSCRIPTIONAL_PAHLAVI: Script = Script::from_bytes(b"Phli");
399    pub const INSCRIPTIONAL_PARTHIAN: Script = Script::from_bytes(b"Prti");
400    pub const JAVANESE: Script = Script::from_bytes(b"Java");
401    pub const KAITHI: Script = Script::from_bytes(b"Kthi");
402    pub const LISU: Script = Script::from_bytes(b"Lisu");
403    pub const MEETEI_MAYEK: Script = Script::from_bytes(b"Mtei");
404    pub const OLD_SOUTH_ARABIAN: Script = Script::from_bytes(b"Sarb");
405    pub const OLD_TURKIC: Script = Script::from_bytes(b"Orkh");
406    pub const SAMARITAN: Script = Script::from_bytes(b"Samr");
407    pub const TAI_THAM: Script = Script::from_bytes(b"Lana");
408    pub const TAI_VIET: Script = Script::from_bytes(b"Tavt");
409    // Since 6.0
410    pub const BATAK: Script = Script::from_bytes(b"Batk");
411    pub const BRAHMI: Script = Script::from_bytes(b"Brah");
412    pub const MANDAIC: Script = Script::from_bytes(b"Mand");
413    // Since 6.1
414    pub const CHAKMA: Script = Script::from_bytes(b"Cakm");
415    pub const MEROITIC_CURSIVE: Script = Script::from_bytes(b"Merc");
416    pub const MEROITIC_HIEROGLYPHS: Script = Script::from_bytes(b"Mero");
417    pub const MIAO: Script = Script::from_bytes(b"Plrd");
418    pub const SHARADA: Script = Script::from_bytes(b"Shrd");
419    pub const SORA_SOMPENG: Script = Script::from_bytes(b"Sora");
420    pub const TAKRI: Script = Script::from_bytes(b"Takr");
421    // Since 7.0
422    pub const BASSA_VAH: Script = Script::from_bytes(b"Bass");
423    pub const CAUCASIAN_ALBANIAN: Script = Script::from_bytes(b"Aghb");
424    pub const DUPLOYAN: Script = Script::from_bytes(b"Dupl");
425    pub const ELBASAN: Script = Script::from_bytes(b"Elba");
426    pub const GRANTHA: Script = Script::from_bytes(b"Gran");
427    pub const KHOJKI: Script = Script::from_bytes(b"Khoj");
428    pub const KHUDAWADI: Script = Script::from_bytes(b"Sind");
429    pub const LINEAR_A: Script = Script::from_bytes(b"Lina");
430    pub const MAHAJANI: Script = Script::from_bytes(b"Mahj");
431    pub const MANICHAEAN: Script = Script::from_bytes(b"Mani");
432    pub const MENDE_KIKAKUI: Script = Script::from_bytes(b"Mend");
433    pub const MODI: Script = Script::from_bytes(b"Modi");
434    pub const MRO: Script = Script::from_bytes(b"Mroo");
435    pub const NABATAEAN: Script = Script::from_bytes(b"Nbat");
436    pub const OLD_NORTH_ARABIAN: Script = Script::from_bytes(b"Narb");
437    pub const OLD_PERMIC: Script = Script::from_bytes(b"Perm");
438    pub const PAHAWH_HMONG: Script = Script::from_bytes(b"Hmng");
439    pub const PALMYRENE: Script = Script::from_bytes(b"Palm");
440    pub const PAU_CIN_HAU: Script = Script::from_bytes(b"Pauc");
441    pub const PSALTER_PAHLAVI: Script = Script::from_bytes(b"Phlp");
442    pub const SIDDHAM: Script = Script::from_bytes(b"Sidd");
443    pub const TIRHUTA: Script = Script::from_bytes(b"Tirh");
444    pub const WARANG_CITI: Script = Script::from_bytes(b"Wara");
445    // Since 8.0
446    pub const AHOM: Script = Script::from_bytes(b"Ahom");
447    pub const ANATOLIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Hluw");
448    pub const HATRAN: Script = Script::from_bytes(b"Hatr");
449    pub const MULTANI: Script = Script::from_bytes(b"Mult");
450    pub const OLD_HUNGARIAN: Script = Script::from_bytes(b"Hung");
451    pub const SIGNWRITING: Script = Script::from_bytes(b"Sgnw");
452    // Since 9.0
453    pub const ADLAM: Script = Script::from_bytes(b"Adlm");
454    pub const BHAIKSUKI: Script = Script::from_bytes(b"Bhks");
455    pub const MARCHEN: Script = Script::from_bytes(b"Marc");
456    pub const OSAGE: Script = Script::from_bytes(b"Osge");
457    pub const TANGUT: Script = Script::from_bytes(b"Tang");
458    pub const NEWA: Script = Script::from_bytes(b"Newa");
459    // Since 10.0
460    pub const MASARAM_GONDI: Script = Script::from_bytes(b"Gonm");
461    pub const NUSHU: Script = Script::from_bytes(b"Nshu");
462    pub const SOYOMBO: Script = Script::from_bytes(b"Soyo");
463    pub const ZANABAZAR_SQUARE: Script = Script::from_bytes(b"Zanb");
464    // Since 11.0
465    pub const DOGRA: Script = Script::from_bytes(b"Dogr");
466    pub const GUNJALA_GONDI: Script = Script::from_bytes(b"Gong");
467    pub const HANIFI_ROHINGYA: Script = Script::from_bytes(b"Rohg");
468    pub const MAKASAR: Script = Script::from_bytes(b"Maka");
469    pub const MEDEFAIDRIN: Script = Script::from_bytes(b"Medf");
470    pub const OLD_SOGDIAN: Script = Script::from_bytes(b"Sogo");
471    pub const SOGDIAN: Script = Script::from_bytes(b"Sogd");
472    // Since 12.0
473    pub const ELYMAIC: Script = Script::from_bytes(b"Elym");
474    pub const NANDINAGARI: Script = Script::from_bytes(b"Nand");
475    pub const NYIAKENG_PUACHUE_HMONG: Script = Script::from_bytes(b"Hmnp");
476    pub const WANCHO: Script = Script::from_bytes(b"Wcho");
477    // Since 13.0
478    pub const CHORASMIAN: Script = Script::from_bytes(b"Chrs");
479    pub const DIVES_AKURU: Script = Script::from_bytes(b"Diak");
480    pub const KHITAN_SMALL_SCRIPT: Script = Script::from_bytes(b"Kits");
481    pub const YEZIDI: Script = Script::from_bytes(b"Yezi");
482    // Since 14.0
483    pub const CYPRO_MINOAN: Script = Script::from_bytes(b"Cpmn");
484    pub const OLD_UYGHUR: Script = Script::from_bytes(b"Ougr");
485    pub const TANGSA: Script = Script::from_bytes(b"Tnsa");
486    pub const TOTO: Script = Script::from_bytes(b"Toto");
487    pub const VITHKUQI: Script = Script::from_bytes(b"Vith");
488    // Since 15.0
489    pub const KAWI: Script = Script::from_bytes(b"Kawi");
490    pub const NAG_MUNDARI: Script = Script::from_bytes(b"Nagm");
491    // Since 16.0
492    pub const GARAY: Script = Script::from_bytes(b"Gara");
493    pub const GURUNG_KHEMA: Script = Script::from_bytes(b"Gukh");
494    pub const KIRAT_RAI: Script = Script::from_bytes(b"Krai");
495    pub const OL_ONAL: Script = Script::from_bytes(b"Onao");
496    pub const SUNUWAR: Script = Script::from_bytes(b"Sunu");
497    pub const TODHRI: Script = Script::from_bytes(b"Todr");
498    pub const TULU_TIGALARI: Script = Script::from_bytes(b"Tutg");
499    // Since 17.0
500    pub const BERIA_ERFE: Script = Script::from_bytes(b"Berf");
501    pub const SIDETIC: Script = Script::from_bytes(b"Sidt");
502    pub const TAI_YO: Script = Script::from_bytes(b"Tayo");
503    pub const TOLONG_SIKI: Script = Script::from_bytes(b"Tols");
504
505    pub const MATH: Script = Script::from_bytes(b"Zmth");
506
507    // https://github.com/harfbuzz/harfbuzz/issues/1162
508    pub const MYANMAR_ZAWGYI: Script = Script::from_bytes(b"Qaag");
509}
510
511/// A feature tag with an accompanying range specifying on which subslice of
512/// `shape`s input it should be applied.
513#[repr(C)]
514#[allow(missing_docs)]
515#[derive(Clone, Copy, PartialEq, Hash, Debug)]
516pub struct Feature {
517    pub tag: Tag,
518    pub value: u32,
519    pub start: u32,
520    pub end: u32,
521}
522
523impl Feature {
524    /// Create a new `Feature` struct.
525    pub fn new(tag: Tag, value: u32, range: impl RangeBounds<usize>) -> Feature {
526        let max = u32::MAX as usize;
527        let start = match range.start_bound() {
528            Bound::Included(&included) => included.min(max) as u32,
529            Bound::Excluded(&excluded) => excluded.min(max - 1) as u32 + 1,
530            Bound::Unbounded => 0,
531        };
532        let end = match range.end_bound() {
533            Bound::Included(&included) => included.min(max) as u32,
534            Bound::Excluded(&excluded) => excluded.saturating_sub(1).min(max) as u32,
535            Bound::Unbounded => max as u32,
536        };
537
538        Feature {
539            tag,
540            value,
541            start,
542            end,
543        }
544    }
545
546    pub(crate) fn is_global(&self) -> bool {
547        self.start == 0 && self.end == u32::MAX
548    }
549}
550
551impl FromStr for Feature {
552    type Err = &'static str;
553
554    /// Parses a `Feature` form a string.
555    ///
556    /// Possible values:
557    ///
558    /// - `kern` -> kern .. 1
559    /// - `+kern` -> kern .. 1
560    /// - `-kern` -> kern .. 0
561    /// - `kern=0` -> kern .. 0
562    /// - `kern=1` -> kern .. 1
563    /// - `aalt=2` -> altr .. 2
564    /// - `kern[]` -> kern .. 1
565    /// - `kern[:]` -> kern .. 1
566    /// - `kern[5:]` -> kern 5.. 1
567    /// - `kern[:5]` -> kern ..=5 1
568    /// - `kern[3:5]` -> kern 3..=5 1
569    /// - `kern[3]` -> kern 3..=4 1
570    /// - `aalt[3:5]=2` -> kern 3..=5 1
571    fn from_str(s: &str) -> Result<Self, Self::Err> {
572        fn parse(s: &str) -> Option<Feature> {
573            if s.is_empty() {
574                return None;
575            }
576
577            let mut p = TextParser::new(s);
578
579            // Parse prefix.
580            let mut value = 1;
581            match p.curr_byte()? {
582                b'-' => {
583                    value = 0;
584                    p.advance(1);
585                }
586                b'+' => {
587                    value = 1;
588                    p.advance(1);
589                }
590                _ => {}
591            }
592
593            // Parse tag.
594            p.skip_spaces();
595            let quote = p.consume_quote();
596
597            let tag = p.consume_tag()?;
598
599            // Force closing quote.
600            if let Some(quote) = quote {
601                p.consume_byte(quote)?;
602            }
603
604            // Parse indices.
605            p.skip_spaces();
606
607            let (start, end) = if p.consume_byte(b'[').is_some() {
608                let start_opt = p.consume_i32();
609                let start = start_opt.unwrap_or(0) as u32; // negative value overflow is ok
610
611                let end = if matches!(p.curr_byte(), Some(b':') | Some(b';')) {
612                    p.advance(1);
613                    p.consume_i32().unwrap_or(-1) as u32 // negative value overflow is ok
614                } else {
615                    if start_opt.is_some() && start != u32::MAX {
616                        start + 1
617                    } else {
618                        u32::MAX
619                    }
620                };
621
622                p.consume_byte(b']')?;
623
624                (start, end)
625            } else {
626                (0, u32::MAX)
627            };
628
629            // Parse postfix.
630            let had_equal = p.consume_byte(b'=').is_some();
631            let value1 = p
632                .consume_i32()
633                .or_else(|| p.consume_bool().map(|b| b as i32));
634
635            if had_equal && value1.is_none() {
636                return None;
637            };
638
639            if let Some(value1) = value1 {
640                value = value1 as u32; // negative value overflow is ok
641            }
642
643            p.skip_spaces();
644
645            if !p.at_end() {
646                return None;
647            }
648
649            Some(Feature {
650                tag,
651                value,
652                start,
653                end,
654            })
655        }
656
657        parse(s).ok_or("invalid feature")
658    }
659}
660
661#[cfg(test)]
662mod tests_features {
663    use super::*;
664    use core::str::FromStr;
665
666    macro_rules! test {
667        ($name:ident, $text:expr, $tag:expr, $value:expr, $range:expr) => {
668            #[test]
669            fn $name() {
670                assert_eq!(
671                    Feature::from_str($text).unwrap(),
672                    Feature::new(Tag::new($tag), $value, $range)
673                );
674            }
675        };
676    }
677
678    test!(parse_01, "kern", b"kern", 1, ..);
679    test!(parse_02, "+kern", b"kern", 1, ..);
680    test!(parse_03, "-kern", b"kern", 0, ..);
681    test!(parse_04, "kern=0", b"kern", 0, ..);
682    test!(parse_05, "kern=1", b"kern", 1, ..);
683    test!(parse_06, "kern=2", b"kern", 2, ..);
684    test!(parse_07, "kern[]", b"kern", 1, ..);
685    test!(parse_08, "kern[:]", b"kern", 1, ..);
686    test!(parse_09, "kern[5:]", b"kern", 1, 5..);
687    test!(parse_10, "kern[:5]", b"kern", 1, ..=5);
688    test!(parse_11, "kern[3:5]", b"kern", 1, 3..=5);
689    test!(parse_12, "kern[3]", b"kern", 1, 3..=4);
690    test!(parse_13, "kern[3:5]=2", b"kern", 2, 3..=5);
691    test!(parse_14, "kern[3;5]=2", b"kern", 2, 3..=5);
692    test!(parse_15, "kern[:-1]", b"kern", 1, ..);
693    test!(parse_16, "kern[-1]", b"kern", 1, u32::MAX as usize..);
694    test!(parse_17, "kern=on", b"kern", 1, ..);
695    test!(parse_18, "kern=off", b"kern", 0, ..);
696    test!(parse_19, "kern=oN", b"kern", 1, ..);
697    test!(parse_20, "kern=oFf", b"kern", 0, ..);
698}
699
700/// A font variation.
701#[repr(C)]
702#[allow(missing_docs)]
703#[derive(Clone, Copy, PartialEq, Debug)]
704pub struct Variation {
705    pub tag: Tag,
706    pub value: f32,
707}
708
709impl FromStr for Variation {
710    type Err = &'static str;
711
712    fn from_str(s: &str) -> Result<Self, Self::Err> {
713        fn parse(s: &str) -> Option<Variation> {
714            if s.is_empty() {
715                return None;
716            }
717
718            let mut p = TextParser::new(s);
719
720            // Parse tag.
721            p.skip_spaces();
722            let quote = p.consume_quote();
723
724            let tag = p.consume_tag()?;
725
726            // Force closing quote.
727            if let Some(quote) = quote {
728                p.consume_byte(quote)?;
729            }
730
731            let _ = p.consume_byte(b'=');
732            let value = p.consume_f32()?;
733            p.skip_spaces();
734
735            if !p.at_end() {
736                return None;
737            }
738
739            Some(Variation { tag, value })
740        }
741
742        parse(s).ok_or("invalid variation")
743    }
744}
745
746// The following From impls are designed to match the convenience
747// impls in skrifa which have proven to be fairly useful in practice.
748impl From<&Variation> for Variation {
749    fn from(value: &Variation) -> Self {
750        *value
751    }
752}
753
754impl From<(&str, f32)> for Variation {
755    fn from(value: (&str, f32)) -> Self {
756        Self {
757            tag: Tag::from_str(value.0).unwrap_or_default(),
758            value: value.1,
759        }
760    }
761}
762
763impl From<&(&str, f32)> for Variation {
764    fn from(value: &(&str, f32)) -> Self {
765        (*value).into()
766    }
767}
768
769impl From<(Tag, f32)> for Variation {
770    fn from(value: (Tag, f32)) -> Self {
771        Self {
772            tag: value.0,
773            value: value.1,
774        }
775    }
776}
777
778impl From<&(Tag, f32)> for Variation {
779    fn from(value: &(Tag, f32)) -> Self {
780        (*value).into()
781    }
782}
783
784pub trait TagExt {
785    fn from_bytes_lossy(bytes: &[u8]) -> Self;
786    fn as_u32(self) -> u32;
787    fn is_null(self) -> bool;
788    fn default_script() -> Self;
789    fn default_language() -> Self;
790    #[cfg(test)]
791    fn to_lowercase(&self) -> Self;
792    fn to_uppercase(&self) -> Self;
793}
794
795impl TagExt for Tag {
796    fn from_bytes_lossy(bytes: &[u8]) -> Self {
797        let mut array = [b' '; 4];
798        for (src, dest) in bytes.iter().zip(&mut array) {
799            *dest = *src;
800        }
801        Tag::new(&array)
802    }
803
804    fn as_u32(self) -> u32 {
805        u32::from_be_bytes(self.to_be_bytes())
806    }
807
808    fn is_null(self) -> bool {
809        self.to_be_bytes() == [0, 0, 0, 0]
810    }
811
812    #[inline]
813    fn default_script() -> Self {
814        Tag::new(b"DFLT")
815    }
816
817    #[inline]
818    fn default_language() -> Self {
819        Tag::new(b"dflt")
820    }
821
822    /// Converts tag to lowercase.
823    #[cfg(test)]
824    #[inline]
825    fn to_lowercase(&self) -> Self {
826        let b = self.to_be_bytes();
827        Tag::new(&[
828            b[0].to_ascii_lowercase(),
829            b[1].to_ascii_lowercase(),
830            b[2].to_ascii_lowercase(),
831            b[3].to_ascii_lowercase(),
832        ])
833    }
834
835    /// Converts tag to uppercase.
836    #[inline]
837    fn to_uppercase(&self) -> Self {
838        let b = self.to_be_bytes();
839        Tag::new(&[
840            b[0].to_ascii_uppercase(),
841            b[1].to_ascii_uppercase(),
842            b[2].to_ascii_uppercase(),
843            b[3].to_ascii_uppercase(),
844        ])
845    }
846}