ttf_parser/tables/
sbix.rs

1//! A [Standard Bitmap Graphics Table](
2//! https://docs.microsoft.com/en-us/typography/opentype/spec/sbix) implementation.
3
4use core::convert::TryFrom;
5use core::num::NonZeroU16;
6
7use crate::parser::{FromData, LazyArray16, LazyArray32, Offset, Offset32, Stream};
8use crate::{GlyphId, RasterGlyphImage, RasterImageFormat, Tag};
9
10/// A strike of glyphs.
11#[derive(Clone, Copy)]
12pub 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
22impl<'a> Strike<'a> {
23    fn parse(number_of_glyphs: u16, data: &'a [u8]) -> Option<Self> {
24        let mut s = Stream::new(data);
25        let pixels_per_em = s.read::<u16>()?;
26        let ppi = s.read::<u16>()?;
27        let offsets = s.read_array16(number_of_glyphs)?;
28        Some(Strike {
29            pixels_per_em,
30            ppi,
31            offsets,
32            data,
33        })
34    }
35
36    /// Returns a glyph data.
37    pub fn get(&self, glyph_id: GlyphId) -> Option<RasterGlyphImage<'a>> {
38        self.get_inner(glyph_id, 0)
39    }
40
41    fn get_inner(&self, glyph_id: GlyphId, depth: u8) -> Option<RasterGlyphImage<'a>> {
42        // Recursive `dupe`. Bail.
43        if depth == 10 {
44            return None;
45        }
46
47        let start = self.offsets.get(glyph_id.0)?.to_usize();
48        let end = self.offsets.get(glyph_id.0.checked_add(1)?)?.to_usize();
49
50        if start == end {
51            return None;
52        }
53
54        let data_len = end.checked_sub(start)?.checked_sub(8)?; // 8 is a Glyph data header size.
55
56        let mut s = Stream::new_at(self.data, start)?;
57        let x = s.read::<i16>()?;
58        let y = s.read::<i16>()?;
59        let image_type = s.read::<Tag>()?;
60        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        let format = match &image_type.to_bytes() {
66            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                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                return self.get_inner(glyph_id, depth + 1);
75            }
76            _ => {
77                // TODO: support JPEG and TIFF
78                return None;
79            }
80        };
81
82        let (width, height) = png_size(image_data)?;
83
84        Some(RasterGlyphImage {
85            x,
86            y,
87            width,
88            height,
89            pixels_per_em: self.pixels_per_em,
90            format,
91            data: image_data,
92        })
93    }
94
95    /// Returns the number of glyphs in this strike.
96    #[inline]
97    pub fn len(&self) -> u16 {
98        // The last offset simply indicates the glyph data end. We don't need it.
99        self.offsets.len() - 1
100    }
101
102    /// Checks if there are any glyphs.
103    pub fn is_empty(&self) -> bool {
104        self.len() == 0
105    }
106}
107
108impl core::fmt::Debug for Strike<'_> {
109    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
110        write!(f, "Strike {{ ... }}")
111    }
112}
113
114/// A list of [`Strike`]s.
115#[derive(Clone, Copy)]
116pub 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
125impl<'a> Strikes<'a> {
126    /// Returns a strike at the index.
127    pub fn get(&self, index: u32) -> Option<Strike<'a>> {
128        let offset = self.offsets.get(index)?.to_usize();
129        let data = self.data.get(offset..)?;
130        Strike::parse(self.number_of_glyphs, data)
131    }
132
133    /// Returns the number of strikes.
134    #[inline]
135    pub fn len(&self) -> u32 {
136        self.offsets.len()
137    }
138
139    /// Checks if there are any strikes.
140    pub fn is_empty(&self) -> bool {
141        self.offsets.is_empty()
142    }
143}
144
145impl core::fmt::Debug for Strikes<'_> {
146    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
147        write!(f, "Strikes {{ ... }}")
148    }
149}
150
151impl<'a> IntoIterator for Strikes<'a> {
152    type Item = Strike<'a>;
153    type IntoIter = StrikesIter<'a>;
154
155    #[inline]
156    fn into_iter(self) -> Self::IntoIter {
157        StrikesIter {
158            strikes: self,
159            index: 0,
160        }
161    }
162}
163
164/// An iterator over [`Strikes`].
165#[allow(missing_debug_implementations)]
166pub struct StrikesIter<'a> {
167    strikes: Strikes<'a>,
168    index: u32,
169}
170
171impl<'a> Iterator for StrikesIter<'a> {
172    type Item = Strike<'a>;
173
174    fn next(&mut self) -> Option<Self::Item> {
175        if self.index < self.strikes.len() {
176            self.index += 1;
177            self.strikes.get(self.index - 1)
178        } else {
179            None
180        }
181    }
182}
183
184/// A [Standard Bitmap Graphics Table](
185/// https://docs.microsoft.com/en-us/typography/opentype/spec/sbix).
186#[derive(Clone, Copy, Debug)]
187pub struct Table<'a> {
188    /// A list of [`Strike`]s.
189    pub strikes: Strikes<'a>,
190}
191
192impl<'a> Table<'a> {
193    /// Parses a table from raw data.
194    ///
195    /// - `number_of_glyphs` is from the `maxp` table.
196    pub fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> {
197        let number_of_glyphs = number_of_glyphs.get().checked_add(1)?;
198
199        let mut s = Stream::new(data);
200
201        let version = s.read::<u16>()?;
202        if version != 1 {
203            return None;
204        }
205
206        s.skip::<u16>(); // flags
207
208        let strikes_count = s.read::<u32>()?;
209        if strikes_count == 0 {
210            return None;
211        }
212
213        let offsets = s.read_array32::<Offset32>(strikes_count)?;
214
215        Some(Table {
216            strikes: Strikes {
217                data,
218                offsets,
219                number_of_glyphs,
220            },
221        })
222    }
223
224    /// Selects the best matching [`Strike`] based on `pixels_per_em`.
225    pub fn best_strike(&self, pixels_per_em: u16) -> Option<Strike<'a>> {
226        let mut idx = 0;
227        let mut max_ppem = 0;
228        for (i, strike) in self.strikes.into_iter().enumerate() {
229            if (pixels_per_em <= strike.pixels_per_em && strike.pixels_per_em < max_ppem)
230                || (pixels_per_em > max_ppem && strike.pixels_per_em > max_ppem)
231            {
232                idx = i as u32;
233                max_ppem = strike.pixels_per_em;
234            }
235        }
236
237        self.strikes.get(idx)
238    }
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.
244fn png_size(data: &[u8]) -> Option<(u16, u16)> {
245    // PNG stores its size as u32 BE at a fixed offset.
246    let mut s = Stream::new_at(data, 16)?;
247    let width = s.read::<u32>()?;
248    let height = s.read::<u32>()?;
249
250    // PNG size larger than u16::MAX is an error.
251    Some((u16::try_from(width).ok()?, u16::try_from(height).ok()?))
252}