Coverage Report

Created: 2025-08-26 07:14

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