ttf_parser/tables/
cblc.rs

1//! A [Color Bitmap Location Table](
2//! https://docs.microsoft.com/en-us/typography/opentype/spec/cblc) implementation.
3
4use crate::GlyphId;
5use crate::parser::{Stream, FromData, Offset, Offset16, Offset32, NumFrom};
6
7#[derive(Clone, Copy, PartialEq, Debug)]
8pub(crate) enum BitmapFormat {
9    Format17,
10    Format18,
11    Format19,
12}
13
14#[derive(Clone, Copy, Default, Debug)]
15pub(crate) struct Metrics {
16    pub x: i8,
17    pub y: i8,
18    pub width: u8,
19    pub height: u8,
20}
21
22#[derive(Clone, Copy, Debug)]
23pub(crate) struct Location {
24    pub format: BitmapFormat,
25    pub offset: usize,
26    pub metrics: Metrics,
27    pub ppem: u16,
28}
29
30#[derive(Clone, Copy)]
31struct BitmapSizeTable {
32    subtable_array_offset: Offset32,
33    number_of_subtables: u32,
34    ppem: u16,
35    // Many fields are omitted.
36}
37
38fn select_bitmap_size_table(
39    glyph_id: GlyphId,
40    pixels_per_em: u16,
41    mut s: Stream,
42) -> Option<BitmapSizeTable> {
43    let subtable_count = s.read::<u32>()?;
44    let orig_s = s.clone();
45
46    let mut idx = None;
47    let mut max_ppem = 0;
48    for i in 0..subtable_count {
49        // Check that the current subtable contains a provided glyph id.
50        s.advance(40); // Jump to `start_glyph_index`.
51        let start_glyph_id = s.read::<GlyphId>()?;
52        let end_glyph_id = s.read::<GlyphId>()?;
53        let ppem = u16::from(s.read::<u8>()?);
54
55        if !(start_glyph_id..=end_glyph_id).contains(&glyph_id) {
56            s.advance(4); // Jump to the end of the subtable.
57            continue;
58        }
59
60        // Select a best matching subtable based on `pixels_per_em`.
61        if (pixels_per_em <= ppem && ppem < max_ppem) || (pixels_per_em > max_ppem && ppem > max_ppem) {
62            idx = Some(usize::num_from(i));
63            max_ppem = ppem;
64        }
65    }
66
67    let mut s = orig_s;
68    s.advance(idx? * 48); // 48 is BitmapSize Table size
69
70    let subtable_array_offset = s.read::<Offset32>()?;
71    s.skip::<u32>(); // index_tables_size
72    let number_of_subtables = s.read::<u32>()?;
73
74    Some(BitmapSizeTable {
75        subtable_array_offset,
76        number_of_subtables,
77        ppem: max_ppem,
78    })
79}
80
81
82#[derive(Clone, Copy)]
83struct IndexSubtableInfo {
84    start_glyph_id: GlyphId,
85    offset: usize, // absolute offset
86}
87
88fn select_index_subtable(
89    data: &[u8],
90    size_table: BitmapSizeTable,
91    glyph_id: GlyphId,
92) -> Option<IndexSubtableInfo> {
93    let mut s = Stream::new_at(data, size_table.subtable_array_offset.to_usize())?;
94    for _ in 0..size_table.number_of_subtables {
95        let start_glyph_id = s.read::<GlyphId>()?;
96        let end_glyph_id = s.read::<GlyphId>()?;
97        let offset = s.read::<Offset32>()?;
98
99        if (start_glyph_id..=end_glyph_id).contains(&glyph_id) {
100            let offset = size_table.subtable_array_offset.to_usize() + offset.to_usize();
101            return Some(IndexSubtableInfo {
102                start_glyph_id,
103                offset,
104            })
105        }
106    }
107
108    None
109}
110
111
112#[derive(Clone, Copy)]
113struct GlyphIdOffsetPair {
114    glyph_id: GlyphId,
115    offset: Offset16,
116}
117
118impl FromData for GlyphIdOffsetPair {
119    const SIZE: usize = 4;
120
121    #[inline]
122    fn parse(data: &[u8]) -> Option<Self> {
123        let mut s = Stream::new(data);
124        Some(GlyphIdOffsetPair {
125            glyph_id: s.read::<GlyphId>()?,
126            offset: s.read::<Offset16>()?,
127        })
128    }
129}
130
131// TODO: rewrite
132
133/// A [Color Bitmap Location Table](
134/// https://docs.microsoft.com/en-us/typography/opentype/spec/cblc).
135#[derive(Clone, Copy)]
136pub struct Table<'a> {
137    data: &'a [u8],
138}
139
140impl<'a> Table<'a> {
141    /// Parses a table from raw data.
142    pub fn parse(data: &'a [u8]) -> Option<Self> {
143        Some(Self { data })
144    }
145
146    pub(crate) fn get(
147        &self,
148        glyph_id: GlyphId,
149        pixels_per_em: u16,
150    ) -> Option<Location> {
151        let mut s = Stream::new(self.data);
152
153        // The CBLC table version is a bit tricky, so we are ignoring it for now.
154        // The CBLC table is based on EBLC table, which was based on the `bloc` table.
155        // And before the CBLC table specification was finished, some fonts,
156        // notably Noto Emoji, have used version 2.0, but the final spec allows only 3.0.
157        // So there are perfectly valid fonts in the wild, which have an invalid version.
158        s.skip::<u32>(); // version
159
160        let size_table = select_bitmap_size_table(glyph_id, pixels_per_em, s)?;
161        let info = select_index_subtable(self.data, size_table, glyph_id)?;
162
163        let mut s = Stream::new_at(self.data, info.offset)?;
164        let index_format = s.read::<u16>()?;
165        let image_format = s.read::<u16>()?;
166        let mut image_offset = s.read::<Offset32>()?.to_usize();
167
168        let image_format = match image_format {
169            17 => BitmapFormat::Format17,
170            18 => BitmapFormat::Format18,
171            19 => BitmapFormat::Format19,
172            _ => return None, // Invalid format.
173        };
174
175        // TODO: I wasn't able to find fonts with index 4 and 5, so they are untested.
176
177        let glyph_diff = glyph_id.0.checked_sub(info.start_glyph_id.0)?;
178        let metrics = Metrics::default();
179        match index_format {
180            1 => {
181                s.advance(usize::from(glyph_diff) * Offset32::SIZE);
182                let offset = s.read::<Offset32>()?;
183                image_offset += offset.to_usize();
184            }
185            2 => {
186                let image_size = s.read::<u32>()?;
187                image_offset += usize::from(glyph_diff).checked_mul(usize::num_from(image_size))?;
188            }
189            3 => {
190                s.advance(usize::from(glyph_diff) * Offset16::SIZE);
191                let offset = s.read::<Offset16>()?;
192                image_offset += offset.to_usize();
193            }
194            4 => {
195                let num_glyphs = s.read::<u32>()?;
196                let num_glyphs = num_glyphs.checked_add(1)?;
197                let pairs = s.read_array32::<GlyphIdOffsetPair>(num_glyphs)?;
198                let pair = pairs.into_iter().find(|pair| pair.glyph_id == glyph_id)?;
199                image_offset += pair.offset.to_usize();
200            }
201            5 => {
202                let image_size = s.read::<u32>()?;
203                s.advance(8); // big metrics
204                let num_glyphs = s.read::<u32>()?;
205                let glyphs = s.read_array32::<GlyphId>(num_glyphs)?;
206                let (index, _) = glyphs.binary_search(&glyph_id)?;
207                image_offset = image_offset
208                    .checked_add(usize::num_from(index).checked_mul(usize::num_from(image_size))?)?;
209            }
210            _ => return None, // Invalid format.
211        }
212
213        Some(Location {
214            format: image_format,
215            offset: image_offset,
216            metrics,
217            ppem: size_table.ppem,
218        })
219    }
220}
221
222impl core::fmt::Debug for Table<'_> {
223    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
224        write!(f, "Table {{ ... }}")
225    }
226}