ttf_parser/tables/
sbix.rs

1// https://docs.microsoft.com/en-us/typography/opentype/spec/sbix
2
3use core::convert::TryFrom;
4use core::num::NonZeroU16;
5
6use crate::{GlyphId, RasterGlyphImage, RasterImageFormat, Tag};
7use crate::parser::{Stream, FromData, Offset, Offset32};
8
9pub fn parse(
10    data: &[u8],
11    number_of_glyphs: NonZeroU16,
12    glyph_id: GlyphId,
13    pixels_per_em: u16,
14    depth: u8,
15) -> Option<RasterGlyphImage> {
16    if depth == 10 {
17        return None;
18    }
19
20    let total_glyphs = u32::from(number_of_glyphs.get().checked_add(1)?);
21
22    let mut s = Stream::new(data);
23    let version: u16 = s.read()?;
24    if version != 1 {
25        return None;
26    }
27
28    s.skip::<u16>(); // flags
29    let count: u32 = s.read()?;
30    if count == 0 {
31        return None;
32    }
33
34    let strikes = s.read_array32::<Offset32>(count)?;
35
36    // Select a best matching strike based on `pixels_per_em`.
37    let mut idx = 0;
38    let mut max_ppem = 0;
39    {
40        for (i, offset) in strikes.into_iter().enumerate() {
41            let mut s = Stream::new_at(data, offset.to_usize())?;
42            let ppem: u16 = s.read()?;
43            s.skip::<u16>(); // ppi
44
45            if (pixels_per_em <= ppem && ppem < max_ppem) ||
46                (pixels_per_em > max_ppem && ppem > max_ppem)
47            {
48                idx = i as u32;
49                max_ppem = ppem;
50            }
51        }
52    }
53
54    let offset = strikes.get(idx)?;
55    let mut s = Stream::new_at(data, offset.to_usize())?;
56    s.skip::<u16>(); // ppem
57    s.skip::<u16>(); // ppi
58
59    let glyph_offsets = s.read_array32::<Offset32>(total_glyphs)?;
60    let start = glyph_offsets.get(u32::from(glyph_id.0))?.to_usize();
61    let end = glyph_offsets.get(u32::from(glyph_id.0.checked_add(1)?))?.to_usize();
62
63    if start == end {
64        // No bitmap data for that glyph.
65        return None;
66    }
67
68    let data_len = end.checked_sub(start)?.checked_sub(8)?; // 8 is a Glyph data header size.
69
70    let mut s = Stream::new_at(data, offset.to_usize() + start)?;
71    let x: i16 = s.read()?;
72    let y: i16 = s.read()?;
73    let image_type: Tag = s.read()?;
74    let image_data = s.read_bytes(data_len)?;
75
76    // We do ignore `pdf` and `mask` intentionally, because Apple docs state that:
77    // 'Support for the 'pdf ' and 'mask' data types and sbixDrawOutlines flag
78    // are planned for future releases of iOS and OS X.'
79    let format = match &image_type.to_bytes() {
80        b"png " => RasterImageFormat::PNG,
81        b"dupe" => {
82            // 'The special graphicType of 'dupe' indicates that
83            // the data field contains a glyph ID. The bitmap data for
84            // the indicated glyph should be used for the current glyph.'
85            let glyph_id = GlyphId::parse(image_data)?;
86            return parse(data, number_of_glyphs, glyph_id, pixels_per_em, depth + 1);
87        }
88        _ => {
89            // TODO: support JPEG and TIFF
90            return None;
91        }
92    };
93
94    let (width, height) = png_size(image_data)?;
95
96    Some(RasterGlyphImage {
97        x,
98        y,
99        width,
100        height,
101        pixels_per_em: max_ppem,
102        format,
103        data: image_data,
104    })
105}
106
107// The `sbix` table doesn't store the image size, so we have to parse it manually.
108// Which is quite simple in case of PNG, but way more complex for JPEG.
109// Therefore we are omitting it for now.
110fn png_size(data: &[u8]) -> Option<(u16, u16)> {
111    // PNG stores its size as u32 BE at a fixed offset.
112    let mut s = Stream::new_at(data, 16)?;
113    let width: u32 = s.read()?;
114    let height: u32 = s.read()?;
115
116    // PNG size larger than u16::MAX is an error.
117    Some((
118        u16::try_from(width).ok()?,
119        u16::try_from(height).ok()?,
120    ))
121}