rustybuzz/hb/
common.rs

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; // uint32_t in C++
9
10pub const HB_FEATURE_GLOBAL_START: u32 = 0;
11pub const HB_FEATURE_GLOBAL_END: u32 = u32::MAX;
12
13/// Defines the direction in which text is to be read.
14#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
15pub enum Direction {
16    /// Initial, unset direction.
17    Invalid,
18    /// Text is set horizontally from left to right.
19    LeftToRight,
20    /// Text is set horizontally from right to left.
21    RightToLeft,
22    /// Text is set vertically from top to bottom.
23    TopToBottom,
24    /// Text is set vertically from bottom to top.
25    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        // https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o
74
75        match script {
76            // Unicode-1.1 additions
77            script::ARABIC |
78            script::HEBREW |
79
80            // Unicode-3.0 additions
81            script::SYRIAC |
82            script::THAANA |
83
84            // Unicode-4.0 additions
85            script::CYPRIOT |
86
87            // Unicode-4.1 additions
88            script::KHAROSHTHI |
89
90            // Unicode-5.0 additions
91            script::PHOENICIAN |
92            script::NKO |
93
94            // Unicode-5.1 additions
95            script::LYDIAN |
96
97            // Unicode-5.2 additions
98            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            // Unicode-6.0 additions
107            script::MANDAIC |
108
109            // Unicode-6.1 additions
110            script::MEROITIC_CURSIVE |
111            script::MEROITIC_HIEROGLYPHS |
112
113            // Unicode-7.0 additions
114            script::MANICHAEAN |
115            script::MENDE_KIKAKUI |
116            script::NABATAEAN |
117            script::OLD_NORTH_ARABIAN |
118            script::PALMYRENE |
119            script::PSALTER_PAHLAVI |
120
121            // Unicode-8.0 additions
122            script::HATRAN |
123
124            // Unicode-9.0 additions
125            script::ADLAM |
126
127            // Unicode-11.0 additions
128            script::HANIFI_ROHINGYA |
129            script::OLD_SOGDIAN |
130            script::SOGDIAN |
131
132            // Unicode-12.0 additions
133            script::ELYMAIC |
134
135            // Unicode-13.0 additions
136            script::CHORASMIAN |
137            script::YEZIDI |
138
139            // Unicode-14.0 additions
140            script::OLD_UYGHUR => {
141                Some(Direction::RightToLeft)
142            }
143
144            // https://github.com/harfbuzz/harfbuzz/issues/1000
145            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        // harfbuzz also matches only the first letter.
173        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/// A script language.
184#[derive(Clone, PartialEq, Eq, Hash, Debug)]
185pub struct Language(String);
186
187impl Language {
188    /// Returns the language as a string.
189    #[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// In harfbuzz, despite having `hb_script_t`, script can actually have any tag.
208// So we're doing the same.
209// The only difference is that `Script` cannot be set to `HB_SCRIPT_INVALID`.
210/// A text script.
211#[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    /// Converts an ISO 15924 script tag to a corresponding `Script`.
222    pub fn from_iso15924_tag(tag: Tag) -> Option<Script> {
223        if tag.is_null() {
224            return None;
225        }
226
227        // Be lenient, adjust case (one capital letter followed by three small letters).
228        let tag = Tag((tag.as_u32() & 0xDFDFDFDF) | 0x00202020);
229
230        match &tag.to_bytes() {
231            // These graduated from the 'Q' private-area codes, but
232            // the old code is still aliased by Unicode, and the Qaai
233            // one in use by ICU.
234            b"Qaai" => return Some(script::INHERITED),
235            b"Qaac" => return Some(script::COPTIC),
236
237            // Script variants from https://unicode.org/iso15924/
238            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    /// Returns script's tag.
257    #[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
272/// Predefined scripts.
273pub mod script {
274    #![allow(missing_docs)]
275
276    use crate::Script;
277
278    // Since 1.1
279    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    // Since 2.0
304    pub const TIBETAN: Script = Script::from_bytes(b"Tibt");
305    // Since 3.0
306    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    // Since 3.1
321    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    // Since 3.2
325    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    // Since 4.0
330    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    // Since 4.1
338    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    // Since 5.0
347    pub const UNKNOWN: Script = Script::from_bytes(b"Zzzz"); // Script can be Unknown, but not Invalid.
348    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    // Since 5.1
354    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    // Since 5.2
366    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    // Since 6.0
382    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    // Since 6.1
386    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    // Since 7.0
394    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    // Since 8.0
418    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    // Since 9.0
425    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    // Since 10.0
432    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    // Since 11.0
437    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    // Since 12.0
445    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    // Since 13.0
450    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    // Since 14.0
455    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    // Since 15.0
461    pub const KAWI: Script = Script::from_bytes(b"Kawi");
462    pub const NAG_MUNDARI: Script = Script::from_bytes(b"Nagm");
463    // Since 16.0
464    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    // https://github.com/harfbuzz/harfbuzz/issues/1162
475    pub const MYANMAR_ZAWGYI: Script = Script::from_bytes(b"Qaag");
476}
477
478/// A feature tag with an accompanying range specifying on which subslice of
479/// `shape`s input it should be applied.
480#[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    /// Create a new `Feature` struct.
492    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    /// Parses a `Feature` form a string.
522    ///
523    /// Possible values:
524    ///
525    /// - `kern` -> kern .. 1
526    /// - `+kern` -> kern .. 1
527    /// - `-kern` -> kern .. 0
528    /// - `kern=0` -> kern .. 0
529    /// - `kern=1` -> kern .. 1
530    /// - `aalt=2` -> altr .. 2
531    /// - `kern[]` -> kern .. 1
532    /// - `kern[:]` -> kern .. 1
533    /// - `kern[5:]` -> kern 5.. 1
534    /// - `kern[:5]` -> kern ..=5 1
535    /// - `kern[3:5]` -> kern 3..=5 1
536    /// - `kern[3]` -> kern 3..=4 1
537    /// - `aalt[3:5]=2` -> kern 3..=5 1
538    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            // Parse prefix.
547            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            // Parse tag.
561            p.skip_spaces();
562            let quote = p.consume_quote();
563
564            let tag = p.consume_tag()?;
565
566            // Force closing quote.
567            if let Some(quote) = quote {
568                p.consume_byte(quote)?;
569            }
570
571            // Parse indices.
572            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; // negative value overflow is ok
577
578                let end = if matches!(p.curr_byte(), Some(b':') | Some(b';')) {
579                    p.advance(1);
580                    p.consume_i32().unwrap_or(-1) as u32 // negative value overflow is ok
581                } 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            // Parse postfix.
597            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; // negative value overflow is ok
608            }
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/// A font variation.
668#[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            // Parse tag.
688            p.skip_spaces();
689            let quote = p.consume_quote();
690
691            let tag = p.consume_tag()?;
692
693            // Force closing quote.
694            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    /// Converts tag to lowercase.
733    #[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    /// Converts tag to uppercase.
746    #[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}