Skip to main content

fea_rs/
common.rs

1//! Types and helpers shared across modules
2
3use std::fmt::{Display, Formatter};
4
5use fontdrasil::types::GlyphName;
6use smol_str::SmolStr;
7use write_fonts::tables::gpos::builders::AnchorBuilder;
8pub use write_fonts::types::GlyphId16;
9
10mod glyph_class;
11mod glyph_map;
12
13pub(crate) use glyph_class::GlyphClass;
14
15pub use glyph_class::GlyphSet;
16pub use glyph_map::GlyphMap;
17
18/// A glyph or glyph class.
19///
20/// Various places in the FEA spec accept either a single glyph or a glyph class.
21#[derive(Debug, Clone, PartialEq)]
22pub(crate) enum GlyphOrClass {
23    /// A resolved GlyphId
24    Glyph(GlyphId16),
25    /// A resolved glyph class
26    Class(GlyphClass),
27    /// An explicit `<NULL>` glyph
28    Null,
29}
30
31/// Either a glyph name or a CID
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub enum GlyphIdent {
34    /// A glyph name
35    Name(GlyphName),
36    /// a CID
37    Cid(u16),
38}
39
40#[derive(Clone, Debug, Default)]
41pub(crate) struct MarkClass {
42    pub(crate) members: Vec<(GlyphClass, Option<AnchorBuilder>)>,
43}
44
45impl MarkClass {
46    /// `true` if no member of this class contributes any glyphs.
47    ///
48    /// Such a class never registers its name with the GPOS mark builders, so
49    /// attaching to it would panic; see `CompilationCtx::define_mark_class`.
50    pub(crate) fn is_empty(&self) -> bool {
51        self.members.iter().all(|(glyphs, _)| glyphs.is_empty())
52    }
53}
54
55impl From<u16> for GlyphIdent {
56    fn from(src: u16) -> GlyphIdent {
57        GlyphIdent::Cid(src)
58    }
59}
60
61impl From<GlyphName> for GlyphIdent {
62    fn from(src: GlyphName) -> GlyphIdent {
63        GlyphIdent::Name(src)
64    }
65}
66
67impl From<&str> for GlyphIdent {
68    fn from(src: &str) -> GlyphIdent {
69        GlyphIdent::Name(src.into())
70    }
71}
72
73impl From<SmolStr> for GlyphIdent {
74    fn from(src: SmolStr) -> GlyphIdent {
75        GlyphIdent::Name(src.into())
76    }
77}
78
79impl Display for GlyphIdent {
80    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
81        match self {
82            GlyphIdent::Name(name) => write!(f, "{name}"),
83            GlyphIdent::Cid(cid) => write!(f, "Cid({cid})"),
84        }
85    }
86}
87
88impl GlyphOrClass {
89    pub(crate) fn len(&self) -> usize {
90        match self {
91            GlyphOrClass::Class(cls) => cls.len(),
92            _ => 1,
93        }
94    }
95
96    pub(crate) fn is_empty(&self) -> bool {
97        self.len() == 0
98    }
99
100    pub(crate) fn is_class(&self) -> bool {
101        matches!(self, GlyphOrClass::Class(_))
102    }
103
104    pub(crate) fn is_null(&self) -> bool {
105        matches!(self, GlyphOrClass::Null)
106    }
107
108    pub(crate) fn to_class(&self) -> Option<GlyphClass> {
109        match self {
110            GlyphOrClass::Glyph(gid) => Some((*gid).into()),
111            GlyphOrClass::Class(class) => Some(class.clone()),
112            GlyphOrClass::Null => None,
113        }
114    }
115
116    pub(crate) fn to_glyph(&self) -> Option<GlyphId16> {
117        match self {
118            GlyphOrClass::Glyph(gid) => Some(*gid),
119            _ => None,
120        }
121    }
122
123    /// If this is a glyph or a class with exactly one, return it.
124    pub(crate) fn single_glyph(&self) -> Option<GlyphId16> {
125        match self {
126            GlyphOrClass::Glyph(gid) => Some(*gid),
127            GlyphOrClass::Class(class) if class.len() == 1 => class.iter().next(),
128            _ => None,
129        }
130    }
131
132    /// Combine the glyphs from `other` into this value.
133    ///
134    /// After this call, `self` contains glyphs from both operands (appended
135    /// in order) as a `Class` variant.
136    pub(crate) fn extend(&mut self, other: &GlyphOrClass) {
137        *self = GlyphOrClass::Class(self.iter().chain(other.iter()).collect());
138    }
139
140    pub(crate) fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
141        let mut idx = 0;
142        std::iter::from_fn(move || {
143            let next = match &self {
144                GlyphOrClass::Glyph(id) if idx == 0 => Some(*id),
145                GlyphOrClass::Class(cls) => cls.items().get(idx).copied(),
146                _ => None,
147            };
148            idx += 1;
149            next
150        })
151    }
152
153    /// an iterator that loops forever, and which returns NOTDEF for null.
154    ///
155    /// this is used to create the replacement targets for class -> glyph or
156    /// class -> null substitutions.
157    pub(crate) fn into_iter_for_target(self) -> impl Iterator<Item = GlyphId16> {
158        let mut idx = 0;
159        std::iter::from_fn(move || {
160            let next = match &self {
161                GlyphOrClass::Glyph(id) if idx == 0 => Some(*id),
162                GlyphOrClass::Null if idx == 0 => Some(GlyphId16::NOTDEF),
163                GlyphOrClass::Class(cls) => cls.items().get(idx).copied(),
164                _ => None,
165            };
166            idx += 1;
167            idx %= self.len();
168            next
169        })
170    }
171}