Coverage Report

Created: 2025-12-14 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ttf-parser/src/tables/sbix.rs
Line
Count
Source
1
//! A [Standard Bitmap Graphics Table](
2
//! https://docs.microsoft.com/en-us/typography/opentype/spec/sbix) implementation.
3
4
use core::convert::TryFrom;
5
use core::num::NonZeroU16;
6
7
use crate::parser::{FromData, LazyArray16, LazyArray32, Offset, Offset32, Stream};
8
use crate::{GlyphId, RasterGlyphImage, RasterImageFormat, Tag};
9
10
/// A strike of glyphs.
11
#[derive(Clone, Copy)]
12
pub struct Strike<'a> {
13
    /// The pixels per EM size for which this strike was designed.
14
    pub pixels_per_em: u16,
15
    /// The device pixel density (in PPI) for which this strike was designed.
16
    pub ppi: u16,
17
    offsets: LazyArray16<'a, Offset32>,
18
    /// Data from the beginning of the `Strikes` table.
19
    data: &'a [u8],
20
}
21
22
impl<'a> Strike<'a> {
23
905
    fn parse(number_of_glyphs: u16, data: &'a [u8]) -> Option<Self> {
24
905
        let mut s = Stream::new(data);
25
905
        let pixels_per_em = s.read::<u16>()?;
26
903
        let ppi = s.read::<u16>()?;
27
899
        let offsets = s.read_array16(number_of_glyphs)?;
28
848
        Some(Strike {
29
848
            pixels_per_em,
30
848
            ppi,
31
848
            offsets,
32
848
            data,
33
848
        })
34
905
    }
35
36
    /// Returns a glyph data.
37
240
    pub fn get(&self, glyph_id: GlyphId) -> Option<RasterGlyphImage<'a>> {
38
240
        self.get_inner(glyph_id, 0)
39
240
    }
40
41
240
    fn get_inner(&self, glyph_id: GlyphId, depth: u8) -> Option<RasterGlyphImage<'a>> {
42
        // Recursive `dupe`. Bail.
43
240
        if depth == 10 {
44
0
            return None;
45
240
        }
46
47
240
        let start = self.offsets.get(glyph_id.0)?.to_usize();
48
230
        let end = self.offsets.get(glyph_id.0.checked_add(1)?)?.to_usize();
49
50
228
        if start == end {
51
26
            return None;
52
202
        }
53
54
202
        let data_len = end.checked_sub(start)?.checked_sub(8)?; // 8 is a Glyph data header size.
55
56
177
        let mut s = Stream::new_at(self.data, start)?;
57
116
        let x = s.read::<i16>()?;
58
115
        let y = s.read::<i16>()?;
59
114
        let image_type = s.read::<Tag>()?;
60
111
        let image_data = s.read_bytes(data_len)?;
61
62
        // We do ignore `pdf` and `mask` intentionally, because Apple docs state that:
63
        // 'Support for the 'pdf ' and 'mask' data types and sbixDrawOutlines flag
64
        // are planned for future releases of iOS and OS X.'
65
64
        let format = match &image_type.to_bytes() {
66
1
            b"png " => RasterImageFormat::PNG,
67
            b"dupe" => {
68
                // 'The special graphicType of 'dupe' indicates that
69
                // the data field contains a glyph ID. The bitmap data for
70
                // the indicated glyph should be used for the current glyph.'
71
0
                let glyph_id = GlyphId::parse(image_data)?;
72
                // TODO: The spec isn't clear about which x/y values should we use.
73
                //       The current glyph or the referenced one.
74
0
                return self.get_inner(glyph_id, depth + 1);
75
            }
76
            _ => {
77
                // TODO: support JPEG and TIFF
78
63
                return None;
79
            }
80
        };
81
82
1
        let (width, height) = png_size(image_data)?;
83
84
0
        Some(RasterGlyphImage {
85
0
            x,
86
0
            y,
87
0
            width,
88
0
            height,
89
0
            pixels_per_em: self.pixels_per_em,
90
0
            format,
91
0
            data: image_data,
92
0
        })
93
240
    }
94
95
    /// Returns the number of glyphs in this strike.
96
    #[inline]
97
45
    pub fn len(&self) -> u16 {
98
        // The last offset simply indicates the glyph data end. We don't need it.
99
45
        self.offsets.len() - 1
100
45
    }
Unexecuted instantiation: <ttf_parser::tables::sbix::Strike>::len
<ttf_parser::tables::sbix::Strike>::len
Line
Count
Source
97
45
    pub fn len(&self) -> u16 {
98
        // The last offset simply indicates the glyph data end. We don't need it.
99
45
        self.offsets.len() - 1
100
45
    }
101
102
    /// Checks if there are any glyphs.
103
0
    pub fn is_empty(&self) -> bool {
104
0
        self.len() == 0
105
0
    }
106
}
107
108
impl core::fmt::Debug for Strike<'_> {
109
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
110
0
        write!(f, "Strike {{ ... }}")
111
0
    }
112
}
113
114
/// A list of [`Strike`]s.
115
#[derive(Clone, Copy)]
116
pub struct Strikes<'a> {
117
    /// `sbix` table data.
118
    data: &'a [u8],
119
    // Offsets from the beginning of the `sbix` table.
120
    offsets: LazyArray32<'a, Offset32>,
121
    // The total number of glyphs in the face + 1. From the `maxp` table.
122
    number_of_glyphs: u16,
123
}
124
125
impl<'a> Strikes<'a> {
126
    /// Returns a strike at the index.
127
1.11k
    pub fn get(&self, index: u32) -> Option<Strike<'a>> {
128
1.11k
        let offset = self.offsets.get(index)?.to_usize();
129
1.11k
        let data = self.data.get(offset..)?;
130
905
        Strike::parse(self.number_of_glyphs, data)
131
1.11k
    }
132
133
    /// Returns the number of strikes.
134
    #[inline]
135
903
    pub fn len(&self) -> u32 {
136
903
        self.offsets.len()
137
903
    }
138
139
    /// Checks if there are any strikes.
140
0
    pub fn is_empty(&self) -> bool {
141
0
        self.offsets.is_empty()
142
0
    }
143
}
144
145
impl core::fmt::Debug for Strikes<'_> {
146
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
147
0
        write!(f, "Strikes {{ ... }}")
148
0
    }
149
}
150
151
impl<'a> IntoIterator for Strikes<'a> {
152
    type Item = Strike<'a>;
153
    type IntoIter = StrikesIter<'a>;
154
155
    #[inline]
156
250
    fn into_iter(self) -> Self::IntoIter {
157
250
        StrikesIter {
158
250
            strikes: self,
159
250
            index: 0,
160
250
        }
161
250
    }
<ttf_parser::tables::sbix::Strikes as core::iter::traits::collect::IntoIterator>::into_iter
Line
Count
Source
156
248
    fn into_iter(self) -> Self::IntoIter {
157
248
        StrikesIter {
158
248
            strikes: self,
159
248
            index: 0,
160
248
        }
161
248
    }
<ttf_parser::tables::sbix::Strikes as core::iter::traits::collect::IntoIterator>::into_iter
Line
Count
Source
156
2
    fn into_iter(self) -> Self::IntoIter {
157
2
        StrikesIter {
158
2
            strikes: self,
159
2
            index: 0,
160
2
        }
161
2
    }
162
}
163
164
/// An iterator over [`Strikes`].
165
#[allow(missing_debug_implementations)]
166
pub struct StrikesIter<'a> {
167
    strikes: Strikes<'a>,
168
    index: u32,
169
}
170
171
impl<'a> Iterator for StrikesIter<'a> {
172
    type Item = Strike<'a>;
173
174
903
    fn next(&mut self) -> Option<Self::Item> {
175
903
        if self.index < self.strikes.len() {
176
869
            self.index += 1;
177
869
            self.strikes.get(self.index - 1)
178
        } else {
179
34
            None
180
        }
181
903
    }
182
}
183
184
/// A [Standard Bitmap Graphics Table](
185
/// https://docs.microsoft.com/en-us/typography/opentype/spec/sbix).
186
#[derive(Clone, Copy, Debug)]
187
pub struct Table<'a> {
188
    /// A list of [`Strike`]s.
189
    pub strikes: Strikes<'a>,
190
}
191
192
impl<'a> Table<'a> {
193
    /// Parses a table from raw data.
194
    ///
195
    /// - `number_of_glyphs` is from the `maxp` table.
196
525
    pub fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> {
197
525
        let number_of_glyphs = number_of_glyphs.get().checked_add(1)?;
198
199
522
        let mut s = Stream::new(data);
200
201
522
        let version = s.read::<u16>()?;
202
519
        if version != 1 {
203
59
            return None;
204
460
        }
205
206
460
        s.skip::<u16>(); // flags
207
208
460
        let strikes_count = s.read::<u32>()?;
209
453
        if strikes_count == 0 {
210
3
            return None;
211
450
        }
212
213
450
        let offsets = s.read_array32::<Offset32>(strikes_count)?;
214
215
252
        Some(Table {
216
252
            strikes: Strikes {
217
252
                data,
218
252
                offsets,
219
252
                number_of_glyphs,
220
252
            },
221
252
        })
222
525
    }
223
224
    /// Selects the best matching [`Strike`] based on `pixels_per_em`.
225
248
    pub fn best_strike(&self, pixels_per_em: u16) -> Option<Strike<'a>> {
226
248
        let mut idx = 0;
227
248
        let mut max_ppem = 0;
228
608
        for (i, strike) in self.strikes.into_iter().enumerate() {
229
608
            if (pixels_per_em <= strike.pixels_per_em && strike.pixels_per_em < max_ppem)
230
608
                || (pixels_per_em > max_ppem && strike.pixels_per_em > max_ppem)
231
297
            {
232
297
                idx = i as u32;
233
297
                max_ppem = strike.pixels_per_em;
234
311
            }
235
        }
236
237
248
        self.strikes.get(idx)
238
248
    }
239
}
240
241
// The `sbix` table doesn't store the image size, so we have to parse it manually.
242
// Which is quite simple in case of PNG, but way more complex for JPEG.
243
// Therefore we are omitting it for now.
244
1
fn png_size(data: &[u8]) -> Option<(u16, u16)> {
245
    // PNG stores its size as u32 BE at a fixed offset.
246
1
    let mut s = Stream::new_at(data, 16)?;
247
0
    let width = s.read::<u32>()?;
248
0
    let height = s.read::<u32>()?;
249
250
    // PNG size larger than u16::MAX is an error.
251
0
    Some((u16::try_from(width).ok()?, u16::try_from(height).ok()?))
252
1
}