Coverage Report

Created: 2026-10-24 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/read-fonts-0.46.0/src/model/glyph.rs
Line
Count
Source
1
//! Glyph representations: outlines, bitmaps and color.
2
//!
3
//! A font can contain a glyph in several forms. [`GlyphSource`] identifies
4
//! the tables that provide them, and [`GlyphSourceOrder`] sets which ones to
5
//! prefer. Collections and glyphs report a narrower type,
6
//! [`outline::OutlineSource`] or [`bitmap::BitmapSource`], and each converts
7
//! into a [`GlyphSource`].
8
//!
9
//! The collections [`outline::OutlineGlyphs`] and [`bitmap::BitmapStrikes`]
10
//! resolve an order against a font once, and [`color::ColorGlyphs`] reads
11
//! `COLR`. Glyph metrics use an order to measure ink.
12
13
pub mod bitmap;
14
pub mod color;
15
pub mod outline;
16
17
/// A table that can provide a glyph.
18
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
19
#[repr(u8)]
20
pub enum GlyphSource {
21
    /// PNG bitmaps in `sbix`.
22
    Sbix = 1,
23
    /// Color bitmaps in `CBDT`.
24
    Cbdt,
25
    /// Monochrome and grayscale bitmaps in `EBDT`.
26
    Ebdt,
27
    /// Layered color glyphs in `COLR`, version 0 or 1.
28
    Colr,
29
    /// Variable composites in `VARC`.
30
    ///
31
    /// The components are drawn from one of the outline sources below.
32
    Varc,
33
    /// TrueType outlines in `glyf`.
34
    Glyf,
35
    /// Variable PostScript outlines in `CFF2`.
36
    Cff2,
37
    /// PostScript outlines in `CFF`.
38
    Cff,
39
    /// Charstrings of a Type 1 font.
40
    Type1,
41
}
42
43
impl GlyphSource {
44
    const ALL: [Self; 9] = [
45
        Self::Sbix,
46
        Self::Cbdt,
47
        Self::Ebdt,
48
        Self::Colr,
49
        Self::Varc,
50
        Self::Glyf,
51
        Self::Cff2,
52
        Self::Cff,
53
        Self::Type1,
54
    ];
55
56
    /// Returns the source for a packed nibble.
57
870k
    const fn from_nibble(nibble: u8) -> Option<Self> {
58
870k
        let index = nibble as usize;
59
870k
        if index == 0 || index > Self::ALL.len() {
60
120k
            None
61
        } else {
62
749k
            Some(Self::ALL[index - 1])
63
        }
64
870k
    }
65
}
66
67
/// An ordered list of glyph sources, most preferred first.
68
///
69
/// Holds every source, four bits each, in eight bytes. It can be used as a
70
/// constant and passed by value. A source listed more than once keeps only
71
/// its first position.
72
///
73
/// How an order is used depends on the consumer:
74
///
75
/// - Outlines use one base source per font. This is the first of `Glyf`,
76
///   `Cff2`, `Cff` and `Type1` that the font contains. If `Varc` comes before
77
///   the base source, it applies to the glyphs it covers, and its components
78
///   are drawn from the base source.
79
/// - Bitmaps use the first of `Sbix`, `Cbdt` and `Ebdt` that the font
80
///   contains.
81
/// - Glyph extents try each source in the font, in order, until one provides
82
///   extents for the glyph.
83
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
84
pub struct GlyphSourceOrder(u64);
85
86
impl GlyphSourceOrder {
87
    /// The default order for measuring glyphs: `sbix`, `CBDT`, `COLR`,
88
    /// `VARC`, `glyf`, `CFF2`, `CFF`, then Type 1 charstrings.
89
    ///
90
    /// Color bitmaps come first, then color glyphs, then outlines with
91
    /// `VARC` before its base source. `EBDT` is not included, so monochrome
92
    /// and grayscale bitmaps are used only when a caller asks for them.
93
    pub const DEFAULT: Self = Self::new(&[
94
        GlyphSource::Sbix,
95
        GlyphSource::Cbdt,
96
        GlyphSource::Colr,
97
        GlyphSource::Varc,
98
        GlyphSource::Glyf,
99
        GlyphSource::Cff2,
100
        GlyphSource::Cff,
101
        GlyphSource::Type1,
102
    ]);
103
104
    /// Every source: `sbix`, `CBDT`, `EBDT`, `COLR`, `VARC`, `glyf`, `CFF2`,
105
    /// `CFF`, then Type 1 charstrings.
106
    ///
107
    /// The order of [`DEFAULT`](Self::DEFAULT), with `EBDT` after the color
108
    /// bitmaps.
109
    pub const ALL: Self = Self::new(&[
110
        GlyphSource::Sbix,
111
        GlyphSource::Cbdt,
112
        GlyphSource::Ebdt,
113
        GlyphSource::Colr,
114
        GlyphSource::Varc,
115
        GlyphSource::Glyf,
116
        GlyphSource::Cff2,
117
        GlyphSource::Cff,
118
        GlyphSource::Type1,
119
    ]);
120
121
    /// The outline sources: `VARC`, `glyf`, `CFF2`, `CFF`, then Type 1
122
    /// charstrings.
123
    pub const OUTLINES: Self = Self::new(&[
124
        GlyphSource::Varc,
125
        GlyphSource::Glyf,
126
        GlyphSource::Cff2,
127
        GlyphSource::Cff,
128
        GlyphSource::Type1,
129
    ]);
130
131
    /// The outline sources without `VARC`: `glyf`, `CFF2`, `CFF`, then Type
132
    /// 1 charstrings.
133
    ///
134
    /// Every glyph is drawn from the base source.
135
    pub const BASE_OUTLINES: Self = Self::new(&[
136
        GlyphSource::Glyf,
137
        GlyphSource::Cff2,
138
        GlyphSource::Cff,
139
        GlyphSource::Type1,
140
    ]);
141
142
    /// The bitmap sources: `sbix`, `CBDT`, then `EBDT`.
143
    pub const BITMAPS: Self = Self::new(&[GlyphSource::Sbix, GlyphSource::Cbdt, GlyphSource::Ebdt]);
144
145
    /// The color bitmap sources: `sbix`, then `CBDT`.
146
    pub const COLOR_BITMAPS: Self = Self::new(&[GlyphSource::Sbix, GlyphSource::Cbdt]);
147
148
    /// The monochrome and grayscale bitmap source: `EBDT`.
149
    pub const MONO_BITMAPS: Self = Self::new(&[GlyphSource::Ebdt]);
150
151
    /// The color glyph source: `COLR`.
152
    pub const COLOR: Self = Self::new(&[GlyphSource::Colr]);
153
154
    /// No sources.
155
    pub const EMPTY: Self = Self(0);
156
157
    /// The most sources an order holds: each one at most once.
158
    pub(crate) const MAX_LEN: usize = GlyphSource::ALL.len();
159
160
    /// Creates an order from `sources`.
161
    ///
162
    /// A source listed more than once keeps only its first position.
163
45.0k
    pub const fn new(sources: &[GlyphSource]) -> Self {
164
45.0k
        let mut packed = 0u64;
165
45.0k
        let mut len = 0;
166
45.0k
        let mut i = 0;
167
90.1k
        while i < sources.len() && len < Self::MAX_LEN {
168
45.0k
            let source = sources[i];
169
45.0k
            i += 1;
170
45.0k
            if Self(packed).contains(source) {
171
0
                continue;
172
45.0k
            }
173
45.0k
            packed |= (source as u64) << (len * 4);
174
45.0k
            len += 1;
175
        }
176
45.0k
        Self(packed)
177
45.0k
    }
178
179
    /// Returns `true` if the order contains `source`.
180
45.0k
    pub const fn contains(self, source: GlyphSource) -> bool {
181
45.0k
        let mut packed = self.0;
182
45.0k
        while packed != 0 {
183
0
            if (packed & 0xF) as u8 == source as u8 {
184
0
                return true;
185
0
            }
186
0
            packed >>= 4;
187
        }
188
45.0k
        false
189
45.0k
    }
190
191
    /// Returns `true` if the order contains no sources.
192
0
    pub const fn is_empty(self) -> bool {
193
0
        self.0 == 0
194
0
    }
195
196
    /// Returns an iterator over the sources, most preferred first.
197
137k
    pub fn iter(self) -> impl Iterator<Item = GlyphSource> {
198
137k
        let mut packed = self.0;
199
870k
        core::iter::from_fn(move || {
200
870k
            let source = GlyphSource::from_nibble((packed & 0xF) as u8)?;
201
749k
            packed >>= 4;
202
749k
            Some(source)
203
870k
        })
204
137k
    }
205
}
206
207
impl Default for GlyphSourceOrder {
208
0
    fn default() -> Self {
209
0
        Self::DEFAULT
210
0
    }
211
}
212
213
impl core::fmt::Debug for GlyphSourceOrder {
214
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
215
0
        f.debug_list().entries(self.iter()).finish()
216
0
    }
217
}
218
219
#[cfg(test)]
220
mod tests {
221
    use super::*;
222
    use alloc::vec::Vec;
223
    use bitmap::BitmapSource;
224
    use outline::OutlineSource;
225
226
    // Each source converts to the narrow type of its kind and back, and to
227
    // no other.
228
    #[test]
229
    fn narrow_sources_round_trip() {
230
        for source in GlyphSource::ALL {
231
            let outline = OutlineSource::try_from(source);
232
            let bitmap = BitmapSource::try_from(source);
233
            assert_eq!(outline.map(GlyphSource::from).unwrap_or(source), source);
234
            assert_eq!(bitmap.map(GlyphSource::from).unwrap_or(source), source);
235
            let kinds = usize::from(outline.is_ok()) + usize::from(bitmap.is_ok());
236
            assert_eq!(
237
                kinds,
238
                usize::from(source != GlyphSource::Colr),
239
                "{source:?}"
240
            );
241
        }
242
    }
243
244
    // The named orders hold the sources their documentation lists.
245
    #[test]
246
    fn named_orders_hold_their_sources() {
247
        use GlyphSource::*;
248
        let sources = |order: GlyphSourceOrder| order.iter().collect::<Vec<_>>();
249
        assert_eq!(sources(GlyphSourceOrder::ALL), GlyphSource::ALL);
250
        assert_eq!(
251
            sources(GlyphSourceOrder::DEFAULT),
252
            [Sbix, Cbdt, Colr, Varc, Glyf, Cff2, Cff, Type1]
253
        );
254
        assert_eq!(sources(GlyphSourceOrder::BITMAPS), [Sbix, Cbdt, Ebdt]);
255
        assert_eq!(sources(GlyphSourceOrder::COLOR_BITMAPS), [Sbix, Cbdt]);
256
        assert_eq!(sources(GlyphSourceOrder::MONO_BITMAPS), [Ebdt]);
257
        assert_eq!(sources(GlyphSourceOrder::COLOR), [Colr]);
258
        assert_eq!(
259
            sources(GlyphSourceOrder::OUTLINES),
260
            [Varc, Glyf, Cff2, Cff, Type1]
261
        );
262
        assert_eq!(
263
            sources(GlyphSourceOrder::BASE_OUTLINES),
264
            [Glyf, Cff2, Cff, Type1]
265
        );
266
        assert!(GlyphSourceOrder::EMPTY.is_empty());
267
        assert_eq!(
268
            alloc::format!("{:?}", GlyphSourceOrder::COLOR_BITMAPS),
269
            "[Sbix, Cbdt]"
270
        );
271
    }
272
273
    #[test]
274
    fn order_keeps_first_position_of_each_source() {
275
        let order = GlyphSourceOrder::new(&[
276
            GlyphSource::Glyf,
277
            GlyphSource::Varc,
278
            GlyphSource::Glyf,
279
            GlyphSource::Cff,
280
        ]);
281
        let sources: Vec<_> = order.iter().collect();
282
        assert_eq!(
283
            sources,
284
            [GlyphSource::Glyf, GlyphSource::Varc, GlyphSource::Cff]
285
        );
286
        assert!(order.contains(GlyphSource::Cff));
287
        assert!(!order.contains(GlyphSource::Cff2));
288
    }
289
290
    #[test]
291
    fn default_order_is_the_harfbuzz_extents_chain() {
292
        let sources: Vec<_> = GlyphSourceOrder::DEFAULT.iter().collect();
293
        assert_eq!(
294
            sources,
295
            [
296
                GlyphSource::Sbix,
297
                GlyphSource::Cbdt,
298
                GlyphSource::Colr,
299
                GlyphSource::Varc,
300
                GlyphSource::Glyf,
301
                GlyphSource::Cff2,
302
                GlyphSource::Cff,
303
                GlyphSource::Type1,
304
            ]
305
        );
306
        assert!(!GlyphSourceOrder::DEFAULT.contains(GlyphSource::Ebdt));
307
    }
308
309
    #[test]
310
    fn empty_order_has_no_sources() {
311
        assert!(GlyphSourceOrder::EMPTY.is_empty());
312
        assert_eq!(GlyphSourceOrder::EMPTY.iter().count(), 0);
313
        assert!(!GlyphSourceOrder::new(&[]).contains(GlyphSource::Glyf));
314
    }
315
316
    #[test]
317
    fn every_source_round_trips_through_an_order() {
318
        let order = GlyphSourceOrder::new(&GlyphSource::ALL);
319
        let sources: Vec<_> = order.iter().collect();
320
        assert_eq!(sources, GlyphSource::ALL);
321
    }
322
}