allsorts_subset_browser/bitmap/
sbix.rs

1#![deny(missing_docs)]
2
3//! Standard Bitmap Graphics Table (`sbix`).
4//!
5//! References:
6//!
7//! * [Microsoft](https://docs.microsoft.com/en-us/typography/opentype/spec/sbix)
8//! * [Apple](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6sbix.html)
9
10use std::convert::TryFrom;
11
12use super::{
13    BitDepth, Bitmap, BitmapGlyph, EncapsulatedBitmap, EncapsulatedFormat, Metrics, OriginOffset,
14};
15use crate::binary::read::{CheckIndex, ReadArray, ReadBinaryDep, ReadCtxt, ReadScope};
16use crate::binary::U32Be;
17use crate::error::ParseError;
18use crate::tag;
19
20/// `sbix` table containing bitmaps.
21pub struct Sbix<'a> {
22    /// Drawing flags.
23    ///
24    /// * Bit 0: Set to 1.
25    /// * Bit 1: Draw outlines.
26    /// * Bits 2 to 15: reserved (set to 0).
27    pub flags: u16,
28    /// Bitmap data for different sizes.
29    pub strikes: Vec<SbixStrike<'a>>,
30}
31
32/// A single strike (ppem/ppi) combination.
33pub struct SbixStrike<'a> {
34    scope: ReadScope<'a>,
35    /// The PPEM size for which this strike was designed.
36    pub ppem: u16,
37    /// The device pixel density (in pixels-per-inch) for which this strike was designed.
38    pub ppi: u16,
39    /// Offset from the beginning of the strike data header to bitmap data for an individual glyph.
40    glyph_data_offsets: ReadArray<'a, U32Be>,
41}
42
43/// An `sbix` bitmap glyph.
44pub struct SbixGlyph<'a> {
45    /// The horizontal (x-axis) offset from the left edge of the graphic to the glyph’s origin.
46    ///
47    /// That is, the x-coordinate of the point on the baseline at the left edge of the glyph.
48    pub origin_offset_x: i16,
49    /// The vertical (y-axis) offset from the bottom edge of the graphic to the glyph’s origin.
50    ///
51    /// That is, the y-coordinate of the point on the baseline at the left edge of the glyph.
52    pub origin_offset_y: i16,
53    /// Indicates the format of the embedded graphic data.
54    ///
55    /// One of `jpg `, `png ` or `tiff`, or the special format `dupe`.
56    pub graphic_type: u32,
57    /// The actual embedded graphic data.
58    pub data: &'a [u8],
59}
60
61impl<'b> ReadBinaryDep for Sbix<'b> {
62    type Args<'a> = usize; // num_glyphs
63    type HostType<'a> = Sbix<'a>;
64
65    /// Read the `sbix` table.
66    ///
67    /// `num_glyphs` should be read from the `maxp` table.
68    fn read_dep<'a>(
69        ctxt: &mut ReadCtxt<'a>,
70        num_glyphs: usize,
71    ) -> Result<Self::HostType<'a>, ParseError> {
72        let scope = ctxt.scope();
73        let version = ctxt.read_u16be()?;
74        ctxt.check(version == 1)?;
75        let flags = ctxt.read_u16be()?;
76        let num_strikes = usize::try_from(ctxt.read_u32be()?)?;
77        let strike_offsets = ctxt.read_array::<U32Be>(num_strikes)?;
78        let strikes = strike_offsets
79            .iter()
80            .map(|offset| {
81                let offset = usize::try_from(offset)?;
82                scope.offset(offset).read_dep::<SbixStrike<'_>>(num_glyphs)
83            })
84            .collect::<Result<Vec<_>, _>>()?;
85
86        Ok(Sbix { flags, strikes })
87    }
88}
89
90impl<'b> ReadBinaryDep for SbixStrike<'b> {
91    type Args<'a> = usize; // num_glyphs
92    type HostType<'a> = SbixStrike<'a>;
93
94    fn read_dep<'a>(
95        ctxt: &mut ReadCtxt<'a>,
96        num_glyphs: usize,
97    ) -> Result<Self::HostType<'a>, ParseError> {
98        let scope = ctxt.scope();
99        let ppem = ctxt.read_u16be()?;
100        let ppi = ctxt.read_u16be()?;
101        // The glyph_data_offset array includes offsets for every glyph ID, plus one extra.
102        // — https://docs.microsoft.com/en-us/typography/opentype/spec/sbix#strikes
103        let glyph_data_offsets = ctxt.read_array::<U32Be>(num_glyphs + 1)?;
104
105        Ok(SbixStrike {
106            scope,
107            ppem,
108            ppi,
109            glyph_data_offsets,
110        })
111    }
112}
113
114impl<'b> ReadBinaryDep for SbixGlyph<'b> {
115    type Args<'a> = usize; // data_len
116    type HostType<'a> = SbixGlyph<'a>;
117
118    fn read_dep<'a>(
119        ctxt: &mut ReadCtxt<'a>,
120        length: usize,
121    ) -> Result<Self::HostType<'a>, ParseError> {
122        let origin_offset_x = ctxt.read_i16be()?;
123        let origin_offset_y = ctxt.read_i16be()?;
124        let graphic_type = ctxt.read_u32be()?;
125        // The length of the glyph data is the length of the glyph minus the preceding
126        // header fields:
127        // * u16 origin_offset_x  = 2
128        // * u16 origin_offset_y  = 2
129        // * u32 graphic_type     = 4
130        // TOTAL:                 = 8
131        let data = ctxt.read_slice(length - 8)?;
132
133        Ok(SbixGlyph {
134            origin_offset_x,
135            origin_offset_y,
136            graphic_type,
137            data,
138        })
139    }
140}
141
142impl<'a> Sbix<'a> {
143    /// Find a strike matching the desired parameters
144    pub fn find_strike(
145        &self,
146        glyph_index: u16,
147        target_ppem: u16,
148        _max_bit_depth: BitDepth,
149    ) -> Option<&SbixStrike<'a>> {
150        // A strike does not need to include data for every glyph, and does not need to include
151        // data for the same set of glyphs as other strikes. If the application is using bitmap
152        // data to draw text and there is bitmap data for a glyph in any strike, then the glyph
153        // must be drawn using a bitmap from some strike.
154        //
155        // If the exact size is not available, implementations may choose a bitmap based on the
156        // closest available larger size, or the closest available integer-multiple larger size, or
157        // on some other basis. The only cases in which a glyph is not drawn using a bitmap are if
158        // the application has not requested that text be drawn using bitmap data or if there is no
159        // bitmap data for the glyph in any strike.
160        //
161        // — https://docs.microsoft.com/en-us/typography/opentype/spec/sbix#strikes
162        let target_ppem = i32::from(target_ppem);
163        let candidates = self
164            .strikes
165            .iter()
166            .filter(|strike| strike.contains_glyph(glyph_index));
167
168        let mut best = None;
169        for strike in candidates {
170            let strike_ppem = i32::from(strike.ppem);
171            let difference = strike_ppem - target_ppem;
172            match best {
173                Some((current_best_difference, _)) => {
174                    if super::bigger_or_closer_to_zero(strike_ppem, current_best_difference) {
175                        best = Some((difference, strike))
176                    }
177                }
178                None => best = Some((difference, strike)),
179            }
180        }
181
182        best.map(|(_, strike)| strike)
183    }
184}
185
186impl<'a> SbixStrike<'a> {
187    /// Read a glyph from this strike specified by `glyph_index`.
188    pub fn read_glyph(&self, glyph_index: u16) -> Result<Option<SbixGlyph<'a>>, ParseError> {
189        let (offset, end) = self.glyph_offset_end(glyph_index)?;
190        match end.checked_sub(offset) {
191            Some(0) => {
192                // If this is zero, there is no bitmap data for that glyph in this strike.
193                // — https://docs.microsoft.com/en-us/typography/opentype/spec/sbix#strikes
194                Ok(None)
195            }
196            Some(length) => {
197                let data = self.scope.offset_length(offset, length)?;
198                data.read_dep::<SbixGlyph<'_>>(length).map(Some)
199            }
200            None => Err(ParseError::BadOffset),
201        }
202    }
203
204    fn glyph_offset_end(&self, glyph_index: u16) -> Result<(usize, usize), ParseError> {
205        // The length of the bitmap data for each glyph is variable, and can be determined from the
206        // difference between two consecutive offsets. Hence, the length of data for glyph N is
207        // glyph_data_offset[N+1] - glyph_data_offset[N].
208        // — https://docs.microsoft.com/en-us/typography/opentype/spec/sbix#strikes
209        let glyph_index = usize::from(glyph_index);
210        self.glyph_data_offsets.check_index(glyph_index)?;
211        self.glyph_data_offsets.check_index(glyph_index + 1)?;
212        let offset = usize::try_from(self.glyph_data_offsets.get_item(glyph_index))?;
213        let end = usize::try_from(self.glyph_data_offsets.get_item(glyph_index + 1))?;
214        Ok((offset, end))
215    }
216
217    fn contains_glyph(&self, glyph_index: u16) -> bool {
218        self.glyph_offset_end(glyph_index)
219            .map(|(offset, end)| end.saturating_sub(offset) > 0)
220            .unwrap_or(false)
221    }
222}
223
224impl<'a> From<(&SbixStrike<'a>, &SbixGlyph<'a>)> for BitmapGlyph {
225    fn from((strike, glyph): (&SbixStrike<'a>, &SbixGlyph<'a>)) -> Self {
226        let encapsulated = EncapsulatedBitmap {
227            format: EncapsulatedFormat::from(glyph.graphic_type),
228            data: Box::from(glyph.data),
229        };
230        BitmapGlyph {
231            bitmap: Bitmap::Encapsulated(encapsulated),
232            metrics: Metrics::HmtxVmtx(OriginOffset::from(glyph)),
233            ppem_x: Some(strike.ppem),
234            ppem_y: Some(strike.ppem),
235        }
236    }
237}
238
239impl From<u32> for EncapsulatedFormat {
240    fn from(tag: u32) -> Self {
241        match tag {
242            tag::JPG => EncapsulatedFormat::Jpeg,
243            tag::PNG => EncapsulatedFormat::Png,
244            tag::TIFF => EncapsulatedFormat::Tiff,
245            _ => EncapsulatedFormat::Other(tag),
246        }
247    }
248}
249
250impl From<&SbixGlyph<'_>> for OriginOffset {
251    fn from(glyph: &SbixGlyph<'_>) -> Self {
252        OriginOffset {
253            x: glyph.origin_offset_x,
254            y: glyph.origin_offset_y,
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    use crate::binary::read::ReadScope;
264    use crate::font_data::FontData;
265    use crate::tables::{FontTableProvider, MaxpTable};
266    use crate::tag;
267
268    use crate::tests::read_fixture;
269
270    #[test]
271    fn test_read_sbix() {
272        let buffer = read_fixture("tests/fonts/woff1/chromacheck-sbix.woff");
273        let scope = ReadScope::new(&buffer);
274        let font_file = scope
275            .read::<FontData<'_>>()
276            .expect("unable to parse font file");
277        let table_provider = font_file
278            .table_provider(0)
279            .expect("unable to create font provider");
280        let maxp_data = table_provider
281            .read_table_data(tag::MAXP)
282            .expect("unable to read maxp table data");
283        let maxp = ReadScope::new(&maxp_data).read::<MaxpTable>().unwrap();
284        let sbix_data = table_provider
285            .read_table_data(tag::SBIX)
286            .expect("unable to read sbix table data");
287        let sbix = ReadScope::new(&sbix_data)
288            .read_dep::<Sbix<'_>>(usize::try_from(maxp.num_glyphs).unwrap())
289            .unwrap();
290
291        // Header
292        assert_eq!(sbix.flags, 1);
293
294        // Strikes
295        assert_eq!(sbix.strikes.len(), 1);
296        let strike = &sbix.strikes[0];
297        assert_eq!(strike.ppem, 300);
298        assert_eq!(strike.ppi, 72);
299
300        // Glyphs
301        let glyphs = (0..maxp.num_glyphs)
302            .map(|glyph_index| strike.read_glyph(glyph_index))
303            .collect::<Result<Vec<_>, _>>()
304            .expect("unable to read glyph");
305
306        assert_eq!(glyphs.len(), 2);
307        assert!(glyphs[0].is_none());
308        if let Some(ref glyph) = glyphs[1] {
309            assert_eq!(glyph.origin_offset_x, 0);
310            assert_eq!(glyph.origin_offset_y, 0);
311            assert_eq!(glyph.graphic_type, tag::PNG);
312            assert_eq!(glyph.data.len(), 224);
313            assert_eq!(*glyph.data.last().unwrap(), 0x82);
314        } else {
315            panic!("expected Some(SbixGlyph) got None");
316        }
317    }
318}