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 = Some(st.big_metrics()[0]);
59                }
60                IndexSubtable::Format3(st) => {
61                    location.format = st.image_format();
62                    let start = st.image_data_offset() as usize
63                        + st.sbit_offsets()
64                            .get(glyph_ix)
65                            .ok_or(ReadError::OutOfBounds)?
66                            .get() as usize;
67                    let end = st.image_data_offset() as usize
68                        + st.sbit_offsets()
69                            .get(glyph_ix + 1)
70                            .ok_or(ReadError::OutOfBounds)?
71                            .get() as usize;
72                    location.data_offset = start;
73                    if end < start {
74                        return Err(ReadError::OutOfBounds);
75                    }
76                    location.data_size = end - start;
77                }
78                IndexSubtable::Format4(st) => {
79                    location.format = st.image_format();
80                    let array = st.glyph_array();
81                    let array_ix = match array
82                        .binary_search_by(|x| x.glyph_id().to_u32().cmp(&glyph_id.to_u32()))
83                    {
84                        Ok(ix) => ix,
85                        _ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
86                    };
87                    let start = array[array_ix].sbit_offset() as usize;
88                    let end = array
89                        .get(array_ix + 1)
90                        .ok_or(ReadError::OutOfBounds)?
91                        .sbit_offset() as usize;
92                    location.data_offset = start;
93                    if end < start {
94                        return Err(ReadError::OutOfBounds);
95                    }
96                    location.data_size = end - start;
97                }
98                IndexSubtable::Format5(st) => {
99                    location.format = st.image_format();
100                    let array = st.glyph_array();
101                    let array_ix = match array
102                        .binary_search_by(|gid| gid.get().to_u32().cmp(&glyph_id.to_u32()))
103                    {
104                        Ok(ix) => ix,
105                        _ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
106                    };
107                    let data_size = st.image_size() as usize;
108                    location.data_size = data_size;
109                    location.data_offset = st.image_data_offset() as usize + array_ix * data_size;
110                    location.metrics = Some(st.big_metrics()[0]);
111                }
112            }
113            return Ok(location);
114        }
115        Err(ReadError::OutOfBounds)
116    }
117
118    /// Returns the [IndexSubtableList] associated with this size.
119    ///
120    /// The `offset_data` parameter is provided by the `offset_data()` method
121    /// of the parent `Eblc` or `Cblc` table.
122    pub fn index_subtable_list<'a>(
123        &self,
124        offset_data: FontData<'a>,
125    ) -> Result<IndexSubtableList<'a>, ReadError> {
126        let start = self.index_subtable_list_offset() as usize;
127        let end = start
128            .checked_add(self.index_subtable_list_size() as usize)
129            .ok_or(ReadError::OutOfBounds)?;
130        let data = offset_data
131            .slice(start..end)
132            .ok_or(ReadError::OutOfBounds)?;
133        IndexSubtableList::read(data, self.number_of_index_subtables())
134    }
135}
136
137#[derive(Clone, Default)]
138pub struct BitmapLocation {
139    /// Format of EBDT/CBDT image data.
140    pub format: u16,
141    /// Offset in bytes from the start of the EBDT/CBDT table.
142    pub data_offset: usize,
143    /// Size of the image data in bytes.
144    pub data_size: usize,
145    /// Bit depth from the associated size. Required for computing image data
146    /// size when unspecified.
147    pub bit_depth: u8,
148    /// Full metrics, if present in the EBLC/CBLC table.
149    pub metrics: Option<BigGlyphMetrics>,
150}
151
152impl BitmapLocation {
153    /// Returns true if the location references an empty bitmap glyph such as
154    /// a space.
155    pub fn is_empty(&self) -> bool {
156        self.data_size == 0
157    }
158}
159
160#[derive(Copy, Clone, PartialEq, Eq, Debug)]
161pub enum BitmapDataFormat {
162    /// The full bitmap is tightly packed according to the bit depth.
163    BitAligned,
164    /// Each row of the data is aligned to a byte boundary.
165    ByteAligned,
166    Png,
167}
168
169#[derive(Clone)]
170pub enum BitmapMetrics {
171    Small(SmallGlyphMetrics),
172    Big(BigGlyphMetrics),
173}
174
175#[derive(Clone)]
176pub struct BitmapData<'a> {
177    pub metrics: BitmapMetrics,
178    pub content: BitmapContent<'a>,
179}
180
181#[derive(Clone)]
182pub enum BitmapContent<'a> {
183    Data(BitmapDataFormat, &'a [u8]),
184    Composite(&'a [BdtComponent]),
185}
186
187pub(crate) fn bitmap_data<'a>(
188    offset_data: FontData<'a>,
189    location: &BitmapLocation,
190    is_color: bool,
191) -> Result<BitmapData<'a>, ReadError> {
192    let mut image_data = offset_data
193        .slice(location.data_offset..location.data_offset + location.data_size)
194        .ok_or(ReadError::OutOfBounds)?
195        .cursor();
196    match location.format {
197        // Small metrics, byte-aligned data
198        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-1-small-metrics-byte-aligned-data>
199        1 => {
200            let metrics = read_small_metrics(&mut image_data)?;
201            // The data for each row is padded to a byte boundary
202            let pitch = (metrics.width as usize * location.bit_depth as usize + 7) / 8;
203            let height = metrics.height as usize;
204            let data = image_data.read_array::<u8>(pitch * height)?;
205            Ok(BitmapData {
206                metrics: BitmapMetrics::Small(metrics),
207                content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data),
208            })
209        }
210        // Small metrics, bit-aligned data
211        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-2-small-metrics-bit-aligned-data>
212        2 => {
213            let metrics = read_small_metrics(&mut image_data)?;
214            let width = metrics.width as usize * location.bit_depth as usize;
215            let height = metrics.height as usize;
216            // The data is tightly packed
217            let data = image_data.read_array::<u8>((width * height + 7) / 8)?;
218            Ok(BitmapData {
219                metrics: BitmapMetrics::Small(metrics),
220                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
221            })
222        }
223        // Format 3 is obsolete
224        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-3-obsolete>
225        // Format 4 is not supported
226        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-4-not-supported-metrics-in-eblc-compressed-data>
227        // ---
228        // Metrics in EBLC/CBLC, bit-aligned image data only
229        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-5-metrics-in-eblc-bit-aligned-image-data-only>
230        5 => {
231            let metrics = location.metrics.ok_or(ReadError::MalformedData(
232                "expected metrics from location table",
233            ))?;
234            let width = metrics.width as usize * location.bit_depth as usize;
235            let height = metrics.height as usize;
236            // The data is tightly packed
237            let data = image_data.read_array::<u8>((width * height + 7) / 8)?;
238            Ok(BitmapData {
239                metrics: BitmapMetrics::Big(metrics),
240                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
241            })
242        }
243        // Big metrics, byte-aligned data
244        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-6-big-metrics-byte-aligned-data>
245        6 => {
246            let metrics = read_big_metrics(&mut image_data)?;
247            // The data for each row is padded to a byte boundary
248            let pitch = (metrics.width as usize * location.bit_depth as usize + 7) / 8;
249            let height = metrics.height as usize;
250            let data = image_data.read_array::<u8>(pitch * height)?;
251            Ok(BitmapData {
252                metrics: BitmapMetrics::Big(metrics),
253                content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data),
254            })
255        }
256        // Big metrics, bit-aligned data
257        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format7-big-metrics-bit-aligned-data>
258        7 => {
259            let metrics = read_big_metrics(&mut image_data)?;
260            let width = metrics.width as usize * location.bit_depth as usize;
261            let height = metrics.height as usize;
262            // The data is tightly packed
263            let data = image_data.read_array::<u8>((width * height + 7) / 8)?;
264            Ok(BitmapData {
265                metrics: BitmapMetrics::Big(metrics),
266                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
267            })
268        }
269        // Small metrics, component data
270        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-8-small-metrics-component-data>
271        8 => {
272            let metrics = read_small_metrics(&mut image_data)?;
273            let _pad = image_data.read::<u8>()?;
274            let count = image_data.read::<u16>()? as usize;
275            let components = image_data.read_array::<BdtComponent>(count)?;
276            Ok(BitmapData {
277                metrics: BitmapMetrics::Small(metrics),
278                content: BitmapContent::Composite(components),
279            })
280        }
281        // Big metrics, component data
282        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-9-big-metrics-component-data>
283        9 => {
284            let metrics = read_big_metrics(&mut image_data)?;
285            let count = image_data.read::<u16>()? as usize;
286            let components = image_data.read_array::<BdtComponent>(count)?;
287            Ok(BitmapData {
288                metrics: BitmapMetrics::Big(metrics),
289                content: BitmapContent::Composite(components),
290            })
291        }
292        // Small metrics, PNG image data
293        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-17-small-metrics-png-image-data>
294        17 if is_color => {
295            let metrics = read_small_metrics(&mut image_data)?;
296            let data_len = image_data.read::<u32>()? as usize;
297            let data = image_data.read_array::<u8>(data_len)?;
298            Ok(BitmapData {
299                metrics: BitmapMetrics::Small(metrics),
300                content: BitmapContent::Data(BitmapDataFormat::Png, data),
301            })
302        }
303        // Big metrics, PNG image data
304        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-18-big-metrics-png-image-data>
305        18 if is_color => {
306            let metrics = read_big_metrics(&mut image_data)?;
307            let data_len = image_data.read::<u32>()? as usize;
308            let data = image_data.read_array::<u8>(data_len)?;
309            Ok(BitmapData {
310                metrics: BitmapMetrics::Big(metrics),
311                content: BitmapContent::Data(BitmapDataFormat::Png, data),
312            })
313        }
314        // Metrics in CBLC table, PNG image data
315        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-19-metrics-in-cblc-table-png-image-data>
316        19 if is_color => {
317            let metrics = location.metrics.ok_or(ReadError::MalformedData(
318                "expected metrics from location table",
319            ))?;
320            let data_len = image_data.read::<u32>()? as usize;
321            let data = image_data.read_array::<u8>(data_len)?;
322            Ok(BitmapData {
323                metrics: BitmapMetrics::Big(metrics),
324                content: BitmapContent::Data(BitmapDataFormat::Png, data),
325            })
326        }
327        _ => Err(ReadError::MalformedData("unexpected bitmap data format")),
328    }
329}
330
331fn read_small_metrics(cursor: &mut Cursor) -> Result<SmallGlyphMetrics, ReadError> {
332    Ok(cursor.read_array::<SmallGlyphMetrics>(1)?[0])
333}
334
335fn read_big_metrics(cursor: &mut Cursor) -> Result<BigGlyphMetrics, ReadError> {
336    Ok(cursor.read_array::<BigGlyphMetrics>(1)?[0])
337}
338
339#[cfg(feature = "experimental_traverse")]
340impl SbitLineMetrics {
341    pub(crate) fn traversal_type<'a>(&self, data: FontData<'a>) -> FieldType<'a> {
342        FieldType::Record(self.traverse(data))
343    }
344}
345
346/// [IndexSubtables](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtables) format type.
347#[derive(Clone)]
348pub enum IndexSubtable<'a> {
349    Format1(IndexSubtable1<'a>),
350    Format2(IndexSubtable2<'a>),
351    Format3(IndexSubtable3<'a>),
352    Format4(IndexSubtable4<'a>),
353    Format5(IndexSubtable5<'a>),
354}
355
356impl<'a> IndexSubtable<'a> {
357    ///Return the `FontData` used to resolve offsets for this table.
358    pub fn offset_data(&self) -> FontData<'a> {
359        match self {
360            Self::Format1(item) => item.offset_data(),
361            Self::Format2(item) => item.offset_data(),
362            Self::Format3(item) => item.offset_data(),
363            Self::Format4(item) => item.offset_data(),
364            Self::Format5(item) => item.offset_data(),
365        }
366    }
367
368    /// Format of this IndexSubTable.
369    pub fn index_format(&self) -> u16 {
370        match self {
371            Self::Format1(item) => item.index_format(),
372            Self::Format2(item) => item.index_format(),
373            Self::Format3(item) => item.index_format(),
374            Self::Format4(item) => item.index_format(),
375            Self::Format5(item) => item.index_format(),
376        }
377    }
378
379    /// Format of EBDT image data.
380    pub fn image_format(&self) -> u16 {
381        match self {
382            Self::Format1(item) => item.image_format(),
383            Self::Format2(item) => item.image_format(),
384            Self::Format3(item) => item.image_format(),
385            Self::Format4(item) => item.image_format(),
386            Self::Format5(item) => item.image_format(),
387        }
388    }
389
390    /// Offset to image data in EBDT table.
391    pub fn image_data_offset(&self) -> u32 {
392        match self {
393            Self::Format1(item) => item.image_data_offset(),
394            Self::Format2(item) => item.image_data_offset(),
395            Self::Format3(item) => item.image_data_offset(),
396            Self::Format4(item) => item.image_data_offset(),
397            Self::Format5(item) => item.image_data_offset(),
398        }
399    }
400}
401
402impl ReadArgs for IndexSubtable<'_> {
403    type Args = (GlyphId16, GlyphId16);
404}
405impl<'a> FontReadWithArgs<'a> for IndexSubtable<'a> {
406    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
407        let format: u16 = data.read_at(0usize)?;
408        match format {
409            IndexSubtable1Marker::FORMAT => {
410                Ok(Self::Format1(FontReadWithArgs::read_with_args(data, args)?))
411            }
412            IndexSubtable2Marker::FORMAT => Ok(Self::Format2(FontRead::read(data)?)),
413            IndexSubtable3Marker::FORMAT => {
414                Ok(Self::Format3(FontReadWithArgs::read_with_args(data, args)?))
415            }
416            IndexSubtable4Marker::FORMAT => Ok(Self::Format4(FontRead::read(data)?)),
417            IndexSubtable5Marker::FORMAT => Ok(Self::Format5(FontRead::read(data)?)),
418            other => Err(ReadError::InvalidFormat(other.into())),
419        }
420    }
421}
422
423impl MinByteRange for IndexSubtable<'_> {
424    fn min_byte_range(&self) -> Range<usize> {
425        match self {
426            Self::Format1(item) => item.min_byte_range(),
427            Self::Format2(item) => item.min_byte_range(),
428            Self::Format3(item) => item.min_byte_range(),
429            Self::Format4(item) => item.min_byte_range(),
430            Self::Format5(item) => item.min_byte_range(),
431        }
432    }
433}
434
435#[cfg(feature = "experimental_traverse")]
436impl<'a> IndexSubtable<'a> {
437    fn dyn_inner<'b>(&'b self) -> &'b dyn SomeTable<'a> {
438        match self {
439            Self::Format1(table) => table,
440            Self::Format2(table) => table,
441            Self::Format3(table) => table,
442            Self::Format4(table) => table,
443            Self::Format5(table) => table,
444        }
445    }
446}
447
448#[cfg(feature = "experimental_traverse")]
449impl std::fmt::Debug for IndexSubtable<'_> {
450    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451        self.dyn_inner().fmt(f)
452    }
453}
454
455#[cfg(feature = "experimental_traverse")]
456impl<'a> SomeTable<'a> for IndexSubtable<'a> {
457    fn type_name(&self) -> &str {
458        self.dyn_inner().type_name()
459    }
460    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
461        self.dyn_inner().get_field(idx)
462    }
463}