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: usize = self.offsets.get(index)?.to_usize();
129 let data: &[u8] = self.data.get(index: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<'_> = Stream::new_at(data, offset:16)?;
247 let width: u32 = s.read::<u32>()?;
248 let height: u32 = 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}
253