Skip to main content

read_fonts/tables/
bitmap.rs

1//! Common bitmap (EBLC/EBDT/CBLC/CBDT) types.
2
3include!("../../generated/generated_bitmap.rs");
4
5impl BitmapSize {
6    /// Returns the bitmap location information for the given glyph.
7    ///
8    /// The `offset_data` parameter is provided by the `offset_data()` method
9    /// of the parent `Eblc` or `Cblc` table.
10    ///
11    /// The resulting [`BitmapLocation`] value is used by the `data()` method
12    /// in the associated `Ebdt` or `Cbdt` table to extract the bitmap data.
13    pub fn location(
14        &self,
15        offset_data: FontData,
16        glyph_id: GlyphId,
17    ) -> Result<BitmapLocation, ReadError> {
18        if !(self.start_glyph_index()..=self.end_glyph_index()).contains(&glyph_id) {
19            return Err(ReadError::OutOfBounds);
20        }
21        let subtable_list = self.index_subtable_list(offset_data)?;
22        let mut location = BitmapLocation {
23            bit_depth: self.bit_depth,
24            ..BitmapLocation::default()
25        };
26        for record in subtable_list.index_subtable_records() {
27            let subtable = record.index_subtable(subtable_list.offset_data())?;
28            if !(record.first_glyph_index()..=record.last_glyph_index()).contains(&glyph_id) {
29                continue;
30            }
31            // glyph index relative to the first glyph in the subtable
32            let glyph_ix =
33                glyph_id.to_u32() as usize - record.first_glyph_index().to_u32() as usize;
34            match &subtable {
35                IndexSubtable::Format1(st) => {
36                    location.format = st.image_format();
37                    let start = st.image_data_offset() as usize
38                        + st.sbit_offsets()
39                            .get(glyph_ix)
40                            .ok_or(ReadError::OutOfBounds)?
41                            .get() as usize;
42                    let end = st.image_data_offset() as usize
43                        + st.sbit_offsets()
44                            .get(glyph_ix + 1)
45                            .ok_or(ReadError::OutOfBounds)?
46                            .get() as usize;
47                    location.data_offset = start;
48                    if end < start {
49                        return Err(ReadError::OutOfBounds);
50                    }
51                    location.data_size = end - start;
52                }
53                IndexSubtable::Format2(st) => {
54                    location.format = st.image_format();
55                    let data_size = st.image_size() as usize;
56                    location.data_size = data_size;
57                    location.data_offset = st.image_data_offset() as usize + glyph_ix * data_size;
58                    location.metrics =
59                        Some(*st.big_metrics().first().ok_or(ReadError::OutOfBounds)?);
60                }
61                IndexSubtable::Format3(st) => {
62                    location.format = st.image_format();
63                    let start = st.image_data_offset() as usize
64                        + st.sbit_offsets()
65                            .get(glyph_ix)
66                            .ok_or(ReadError::OutOfBounds)?
67                            .get() as usize;
68                    let end = st.image_data_offset() as usize
69                        + st.sbit_offsets()
70                            .get(glyph_ix + 1)
71                            .ok_or(ReadError::OutOfBounds)?
72                            .get() as usize;
73                    location.data_offset = start;
74                    if end < start {
75                        return Err(ReadError::OutOfBounds);
76                    }
77                    location.data_size = end - start;
78                }
79                IndexSubtable::Format4(st) => {
80                    location.format = st.image_format();
81                    let array = st.glyph_array();
82                    let array_ix = match array
83                        .binary_search_by(|x| x.glyph_id().to_u32().cmp(&glyph_id.to_u32()))
84                    {
85                        Ok(ix) => ix,
86                        _ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
87                    };
88                    let start =
89                        st.image_data_offset() as usize + array[array_ix].sbit_offset() as usize;
90                    let end = st.image_data_offset() as usize
91                        + array
92                            .get(array_ix + 1)
93                            .ok_or(ReadError::OutOfBounds)?
94                            .sbit_offset() as usize;
95                    location.data_offset = start;
96                    if end < start {
97                        return Err(ReadError::OutOfBounds);
98                    }
99                    location.data_size = end - start;
100                }
101                IndexSubtable::Format5(st) => {
102                    location.format = st.image_format();
103                    let array = st.glyph_array();
104                    let array_ix = match array
105                        .binary_search_by(|gid| gid.get().to_u32().cmp(&glyph_id.to_u32()))
106                    {
107                        Ok(ix) => ix,
108                        _ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
109                    };
110                    let data_size = st.image_size() as usize;
111                    location.data_size = data_size;
112                    location.data_offset = st.image_data_offset() as usize + array_ix * data_size;
113                    location.metrics =
114                        Some(*st.big_metrics().first().ok_or(ReadError::OutOfBounds)?);
115                }
116            }
117            return Ok(location);
118        }
119        Err(ReadError::OutOfBounds)
120    }
121
122    /// Returns the [IndexSubtableList] associated with this size.
123    ///
124    /// The `offset_data` parameter is provided by the `offset_data()` method
125    /// of the parent `Eblc` or `Cblc` table.
126    pub fn index_subtable_list<'a>(
127        &self,
128        offset_data: FontData<'a>,
129    ) -> Result<IndexSubtableList<'a>, ReadError> {
130        let start = self.index_subtable_list_offset() as usize;
131        // FreeType ignores the declared size and bounds reads by the end of the
132        // parent table. Some fonts, such as ProggyClean, rely on this behavior
133        // because they set indexTablesSize to the size of the index subtable
134        // record array alone, excluding the referenced subtables.
135        //
136        // See `tt_sbit_decoder_init` and `tt_sbit_decoder_load_image` in FreeType.
137        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/VER-2-13-3/src/sfnt/ttsbit.c#L1241>
138        let data = offset_data.split_off(start).ok_or(ReadError::OutOfBounds)?;
139        IndexSubtableList::read(data, self.number_of_index_subtables())
140    }
141}
142
143#[derive(Clone, Default)]
144pub struct BitmapLocation {
145    /// Format of EBDT/CBDT image data.
146    pub format: u16,
147    /// Offset in bytes from the start of the EBDT/CBDT table.
148    pub data_offset: usize,
149    /// Size of the image data in bytes.
150    pub data_size: usize,
151    /// Bit depth from the associated size. Required for computing image data
152    /// size when unspecified.
153    pub bit_depth: u8,
154    /// Full metrics, if present in the EBLC/CBLC table.
155    pub metrics: Option<BigGlyphMetrics>,
156}
157
158impl BitmapLocation {
159    /// Returns true if the location references an empty bitmap glyph such as
160    /// a space.
161    pub fn is_empty(&self) -> bool {
162        self.data_size == 0
163    }
164}
165
166#[derive(Copy, Clone, PartialEq, Eq, Debug)]
167pub enum BitmapDataFormat {
168    /// The full bitmap is tightly packed according to the bit depth.
169    BitAligned,
170    /// Each row of the data is aligned to a byte boundary.
171    ByteAligned,
172    Png,
173}
174
175#[derive(Clone)]
176pub enum BitmapMetrics {
177    Small(SmallGlyphMetrics),
178    Big(BigGlyphMetrics),
179}
180
181#[derive(Clone)]
182pub struct BitmapData<'a> {
183    pub metrics: BitmapMetrics,
184    pub content: BitmapContent<'a>,
185}
186
187#[derive(Clone)]
188pub enum BitmapContent<'a> {
189    Data(BitmapDataFormat, &'a [u8]),
190    Composite(&'a [BdtComponent]),
191}
192
193pub(crate) fn bitmap_data<'a>(
194    offset_data: FontData<'a>,
195    location: &BitmapLocation,
196    is_color: bool,
197) -> Result<BitmapData<'a>, ReadError> {
198    let mut image_data = offset_data
199        .slice(location.data_offset..location.data_offset + location.data_size)
200        .ok_or(ReadError::OutOfBounds)?
201        .cursor();
202    match location.format {
203        // Small metrics, byte-aligned data
204        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-1-small-metrics-byte-aligned-data>
205        1 => {
206            let metrics = read_small_metrics(&mut image_data)?;
207            // The data for each row is padded to a byte boundary
208            let pitch = (metrics.width as usize * location.bit_depth as usize).div_ceil(8);
209            let height = metrics.height as usize;
210            let data = image_data.read_array::<u8>(pitch * height)?;
211            Ok(BitmapData {
212                metrics: BitmapMetrics::Small(metrics),
213                content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data),
214            })
215        }
216        // Small metrics, bit-aligned data
217        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-2-small-metrics-bit-aligned-data>
218        2 => {
219            let metrics = read_small_metrics(&mut image_data)?;
220            let width = metrics.width as usize * location.bit_depth as usize;
221            let height = metrics.height as usize;
222            // The data is tightly packed
223            let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
224            Ok(BitmapData {
225                metrics: BitmapMetrics::Small(metrics),
226                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
227            })
228        }
229        // Format 3 is obsolete
230        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-3-obsolete>
231        // Format 4 is not supported
232        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-4-not-supported-metrics-in-eblc-compressed-data>
233        // ---
234        // Metrics in EBLC/CBLC, bit-aligned image data only
235        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-5-metrics-in-eblc-bit-aligned-image-data-only>
236        5 => {
237            let metrics = location.metrics.ok_or(ReadError::MalformedData(
238                "expected metrics from location table",
239            ))?;
240            let width = metrics.width as usize * location.bit_depth as usize;
241            let height = metrics.height as usize;
242            // The data is tightly packed
243            let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
244            Ok(BitmapData {
245                metrics: BitmapMetrics::Big(metrics),
246                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
247            })
248        }
249        // Big metrics, byte-aligned data
250        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-6-big-metrics-byte-aligned-data>
251        6 => {
252            let metrics = read_big_metrics(&mut image_data)?;
253            // The data for each row is padded to a byte boundary
254            let pitch = (metrics.width as usize * location.bit_depth as usize).div_ceil(8);
255            let height = metrics.height as usize;
256            let data = image_data.read_array::<u8>(pitch * height)?;
257            Ok(BitmapData {
258                metrics: BitmapMetrics::Big(metrics),
259                content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data),
260            })
261        }
262        // Big metrics, bit-aligned data
263        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format7-big-metrics-bit-aligned-data>
264        7 => {
265            let metrics = read_big_metrics(&mut image_data)?;
266            let width = metrics.width as usize * location.bit_depth as usize;
267            let height = metrics.height as usize;
268            // The data is tightly packed
269            let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
270            Ok(BitmapData {
271                metrics: BitmapMetrics::Big(metrics),
272                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
273            })
274        }
275        // Small metrics, component data
276        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-8-small-metrics-component-data>
277        8 => {
278            let metrics = read_small_metrics(&mut image_data)?;
279            let _pad = image_data.read::<u8>()?;
280            let count = image_data.read::<u16>()? as usize;
281            let components = image_data.read_array::<BdtComponent>(count)?;
282            Ok(BitmapData {
283                metrics: BitmapMetrics::Small(metrics),
284                content: BitmapContent::Composite(components),
285            })
286        }
287        // Big metrics, component data
288        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-9-big-metrics-component-data>
289        9 => {
290            let metrics = read_big_metrics(&mut image_data)?;
291            let count = image_data.read::<u16>()? as usize;
292            let components = image_data.read_array::<BdtComponent>(count)?;
293            Ok(BitmapData {
294                metrics: BitmapMetrics::Big(metrics),
295                content: BitmapContent::Composite(components),
296            })
297        }
298        // Small metrics, PNG image data
299        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-17-small-metrics-png-image-data>
300        17 if is_color => {
301            let metrics = read_small_metrics(&mut image_data)?;
302            let data_len = image_data.read::<u32>()? as usize;
303            let data = image_data.read_array::<u8>(data_len)?;
304            Ok(BitmapData {
305                metrics: BitmapMetrics::Small(metrics),
306                content: BitmapContent::Data(BitmapDataFormat::Png, data),
307            })
308        }
309        // Big metrics, PNG image data
310        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-18-big-metrics-png-image-data>
311        18 if is_color => {
312            let metrics = read_big_metrics(&mut image_data)?;
313            let data_len = image_data.read::<u32>()? as usize;
314            let data = image_data.read_array::<u8>(data_len)?;
315            Ok(BitmapData {
316                metrics: BitmapMetrics::Big(metrics),
317                content: BitmapContent::Data(BitmapDataFormat::Png, data),
318            })
319        }
320        // Metrics in CBLC table, PNG image data
321        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-19-metrics-in-cblc-table-png-image-data>
322        19 if is_color => {
323            let metrics = location.metrics.ok_or(ReadError::MalformedData(
324                "expected metrics from location table",
325            ))?;
326            let data_len = image_data.read::<u32>()? as usize;
327            let data = image_data.read_array::<u8>(data_len)?;
328            Ok(BitmapData {
329                metrics: BitmapMetrics::Big(metrics),
330                content: BitmapContent::Data(BitmapDataFormat::Png, data),
331            })
332        }
333        _ => Err(ReadError::MalformedData("unexpected bitmap data format")),
334    }
335}
336
337fn read_small_metrics(cursor: &mut Cursor) -> Result<SmallGlyphMetrics, ReadError> {
338    Ok(cursor.read_array::<SmallGlyphMetrics>(1)?[0])
339}
340
341fn read_big_metrics(cursor: &mut Cursor) -> Result<BigGlyphMetrics, ReadError> {
342    Ok(cursor.read_array::<BigGlyphMetrics>(1)?[0])
343}
344
345#[cfg(feature = "experimental_traverse")]
346impl SbitLineMetrics {
347    pub(crate) fn traversal_type<'a>(&self, data: FontData<'a>) -> FieldType<'a> {
348        FieldType::Record(self.traverse(data))
349    }
350}
351
352/// [IndexSubtables](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtables) format type.
353#[derive(Clone)]
354pub enum IndexSubtable<'a> {
355    Format1(IndexSubtable1<'a>),
356    Format2(IndexSubtable2<'a>),
357    Format3(IndexSubtable3<'a>),
358    Format4(IndexSubtable4<'a>),
359    Format5(IndexSubtable5<'a>),
360}
361
362impl<'a> IndexSubtable<'a> {
363    ///Return the `FontData` used to resolve offsets for this table.
364    pub fn offset_data(&self) -> FontData<'a> {
365        match self {
366            Self::Format1(item) => item.offset_data(),
367            Self::Format2(item) => item.offset_data(),
368            Self::Format3(item) => item.offset_data(),
369            Self::Format4(item) => item.offset_data(),
370            Self::Format5(item) => item.offset_data(),
371        }
372    }
373
374    /// Format of this IndexSubTable.
375    pub fn index_format(&self) -> u16 {
376        match self {
377            Self::Format1(item) => item.index_format(),
378            Self::Format2(item) => item.index_format(),
379            Self::Format3(item) => item.index_format(),
380            Self::Format4(item) => item.index_format(),
381            Self::Format5(item) => item.index_format(),
382        }
383    }
384
385    /// Format of EBDT image data.
386    pub fn image_format(&self) -> u16 {
387        match self {
388            Self::Format1(item) => item.image_format(),
389            Self::Format2(item) => item.image_format(),
390            Self::Format3(item) => item.image_format(),
391            Self::Format4(item) => item.image_format(),
392            Self::Format5(item) => item.image_format(),
393        }
394    }
395
396    /// Offset to image data in EBDT table.
397    pub fn image_data_offset(&self) -> u32 {
398        match self {
399            Self::Format1(item) => item.image_data_offset(),
400            Self::Format2(item) => item.image_data_offset(),
401            Self::Format3(item) => item.image_data_offset(),
402            Self::Format4(item) => item.image_data_offset(),
403            Self::Format5(item) => item.image_data_offset(),
404        }
405    }
406}
407
408impl ReadArgs for IndexSubtable<'_> {
409    type Args = (GlyphId16, GlyphId16);
410}
411impl<'a> FontRead<'a> for IndexSubtable<'a> {
412    fn read_with_args(data: FontData<'a>, args: Self::Args) -> Result<Self, ReadError> {
413        let format: u16 = data.read_at(0usize)?;
414        match format {
415            IndexSubtable1::FORMAT => FontRead::read_with_args(data, args).map(Self::Format1),
416            IndexSubtable2::FORMAT => FontRead::read(data).map(Self::Format2),
417            IndexSubtable3::FORMAT => FontRead::read_with_args(data, args).map(Self::Format3),
418            IndexSubtable4::FORMAT => FontRead::read(data).map(Self::Format4),
419            IndexSubtable5::FORMAT => FontRead::read(data).map(Self::Format5),
420            other => Err(ReadError::InvalidFormat(other.into())),
421        }
422    }
423}
424
425impl<'a> MinByteRange<'a> for IndexSubtable<'a> {
426    fn min_byte_range(&self) -> Range<usize> {
427        match self {
428            Self::Format1(item) => item.min_byte_range(),
429            Self::Format2(item) => item.min_byte_range(),
430            Self::Format3(item) => item.min_byte_range(),
431            Self::Format4(item) => item.min_byte_range(),
432            Self::Format5(item) => item.min_byte_range(),
433        }
434    }
435
436    fn min_table_bytes(&self) -> &'a [u8] {
437        match self {
438            Self::Format1(item) => item.min_table_bytes(),
439            Self::Format2(item) => item.min_table_bytes(),
440            Self::Format3(item) => item.min_table_bytes(),
441            Self::Format4(item) => item.min_table_bytes(),
442            Self::Format5(item) => item.min_table_bytes(),
443        }
444    }
445}
446
447#[cfg(feature = "experimental_traverse")]
448impl<'a> IndexSubtable<'a> {
449    fn dyn_inner<'b>(&'b self) -> &'b dyn SomeTable<'a> {
450        match self {
451            Self::Format1(table) => table,
452            Self::Format2(table) => table,
453            Self::Format3(table) => table,
454            Self::Format4(table) => table,
455            Self::Format5(table) => table,
456        }
457    }
458}
459
460#[cfg(feature = "experimental_traverse")]
461impl std::fmt::Debug for IndexSubtable<'_> {
462    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
463        self.dyn_inner().fmt(f)
464    }
465}
466
467#[cfg(feature = "experimental_traverse")]
468impl<'a> SomeTable<'a> for IndexSubtable<'a> {
469    fn type_name(&self) -> &str {
470        self.dyn_inner().type_name()
471    }
472    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
473        self.dyn_inner().get_field(idx)
474    }
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480    use crate::{types::GlyphId16, FontData};
481    use font_test_data::bebuffer::BeBuffer;
482
483    fn bitmap_size_location(data: &[u8], glyph_id: GlyphId) -> Result<BitmapLocation, ReadError> {
484        let data = FontData::new(data);
485        let size = data.read_ref_at::<BitmapSize>(0).unwrap();
486        size.location(data, glyph_id)
487    }
488
489    fn bitmap_size_with_subtable(subtable: &[u8]) -> BeBuffer {
490        BeBuffer::new()
491            .push(BitmapSize::RAW_BYTE_LEN as u32)
492            .push((IndexSubtableRecord::RAW_BYTE_LEN + subtable.len()) as u32)
493            .push(1u32)
494            .push(0u32)
495            .extend([0u8; SbitLineMetrics::RAW_BYTE_LEN])
496            .extend([0u8; SbitLineMetrics::RAW_BYTE_LEN])
497            .push(GlyphId16::new(0))
498            .push(GlyphId16::new(0))
499            .push(0u8)
500            .push(0u8)
501            .push(1u8)
502            .push(0u8)
503            .push(GlyphId16::new(0))
504            .push(GlyphId16::new(0))
505            .push(IndexSubtableRecord::RAW_BYTE_LEN as u32)
506            .extend(subtable.iter().copied())
507    }
508
509    #[test]
510    fn short_index_tables_size_is_ignored() {
511        // Some fonts (e.g. ProggyClean) set indexTablesSize to the size of
512        // the index subtable array alone, excluding the subtables it points
513        // to. Ensure we still resolve the subtables.
514        let subtable = BeBuffer::new()
515            .push(2u16) // index format
516            .push(5u16) // image format
517            .push(0u32) // image data offset
518            .push(4u32) // image size
519            .extend([0u8; 8]); // big metrics
520        let subtable = subtable.data();
521        let data = BeBuffer::new()
522            .push(BitmapSize::RAW_BYTE_LEN as u32)
523            // indexTablesSize covering only the record array
524            .push(IndexSubtableRecord::RAW_BYTE_LEN as u32)
525            .push(1u32)
526            .push(0u32)
527            .extend([0u8; SbitLineMetrics::RAW_BYTE_LEN])
528            .extend([0u8; SbitLineMetrics::RAW_BYTE_LEN])
529            .push(GlyphId16::new(0))
530            .push(GlyphId16::new(1))
531            .push(0u8)
532            .push(0u8)
533            .push(1u8)
534            .push(0u8)
535            .push(GlyphId16::new(0))
536            .push(GlyphId16::new(1))
537            .push(IndexSubtableRecord::RAW_BYTE_LEN as u32)
538            .extend(subtable.iter().copied());
539        let location = bitmap_size_location(data.data(), GlyphId::new(1)).unwrap();
540        assert_eq!(location.data_offset, 4);
541        assert_eq!(location.data_size, 4);
542    }
543
544    #[test]
545    fn format_2_truncated_metrics_returns_error_instead_of_panicking() {
546        let data =
547            bitmap_size_with_subtable(&BeBuffer::new().push(2u16).push(5u16).push(0u32).push(1u32));
548        // Just don't panic!
549        let result = bitmap_size_location(data.data(), GlyphId::new(0));
550        assert!(matches!(result, Err(ReadError::OutOfBounds)));
551    }
552
553    #[test]
554    fn format_4_sbit_offsets_are_relative_to_image_data_offset() {
555        let data = bitmap_size_with_subtable(
556            &BeBuffer::new()
557                .push(4u16) // index format
558                .push(17u16) // image format
559                .push(1000u32) // image data offset
560                .push(1u32) // num glyphs
561                .push(GlyphId16::new(0))
562                .push(20u16) // sbit offset
563                .push(GlyphId16::new(1))
564                .push(30u16), // sbit offset of the following glyph
565        );
566        let location = bitmap_size_location(data.data(), GlyphId::new(0)).unwrap();
567        assert_eq!(location.data_offset, 1020);
568        assert_eq!(location.data_size, 10);
569    }
570
571    #[test]
572    fn format_5_truncated_metrics_returns_error_instead_of_panicking() {
573        let data = bitmap_size_with_subtable(
574            &BeBuffer::new()
575                .push(5u16)
576                .push(5u16)
577                .push(0u32)
578                .push(1u32)
579                .push(0u32),
580        );
581        // Just don't panic!
582        let result = bitmap_size_location(data.data(), GlyphId::new(0));
583        assert!(result.is_err());
584    }
585}