read_fonts/tables/
colr.rs

1//! The [COLR](https://docs.microsoft.com/en-us/typography/opentype/spec/colr) table
2
3#[cfg(feature = "std")]
4mod closure;
5
6use super::variations::{DeltaSetIndexMap, ItemVariationStore};
7
8include!("../../generated/generated_colr.rs");
9
10/// Unique paint identifier used for detecting cycles in the paint graph.
11pub type PaintId = usize;
12
13impl<'a> Colr<'a> {
14    /// Returns the COLRv0 base glyph for the given glyph identifier.
15    ///
16    /// The return value is a range of layer indices that can be passed to
17    /// [`v0_layer`](Self::v0_layer) to retrieve the layer glyph identifiers
18    /// and palette color indices.
19    pub fn v0_base_glyph(&self, glyph_id: GlyphId) -> Result<Option<Range<usize>>, ReadError> {
20        let records = self.base_glyph_records().ok_or(ReadError::NullOffset)??;
21        let Ok(glyph_id) = glyph_id.try_into() else {
22            return Ok(None);
23        };
24        let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
25            Ok(ix) => &records[ix],
26            _ => return Ok(None),
27        };
28        let start = record.first_layer_index() as usize;
29        let end = start + record.num_layers() as usize;
30        Ok(Some(start..end))
31    }
32
33    /// Returns the COLRv0 layer at the given index.
34    ///
35    /// The layer is represented by a tuple containing the glyph identifier of
36    /// the associated outline and the palette color index.
37    pub fn v0_layer(&self, index: usize) -> Result<(GlyphId16, u16), ReadError> {
38        let layers = self.layer_records().ok_or(ReadError::NullOffset)??;
39        let layer = layers.get(index).ok_or(ReadError::OutOfBounds)?;
40        Ok((layer.glyph_id(), layer.palette_index()))
41    }
42
43    /// Returns the COLRv1 base glyph for the given glyph identifier.
44    ///
45    /// The second value in the tuple is a unique identifier for the paint that
46    /// may be used to detect recursion in the paint graph.
47    pub fn v1_base_glyph(
48        &self,
49        glyph_id: GlyphId,
50    ) -> Result<Option<(Paint<'a>, PaintId)>, ReadError> {
51        let Ok(glyph_id) = glyph_id.try_into() else {
52            return Ok(None);
53        };
54        let list = self.base_glyph_list().ok_or(ReadError::NullOffset)??;
55        let records = list.base_glyph_paint_records();
56        let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
57            Ok(ix) => &records[ix],
58            _ => return Ok(None),
59        };
60        let offset_data = list.offset_data();
61        // Use the address of the paint as an identifier for the recursion
62        // blacklist.
63        let id = record.paint_offset().to_u32() as usize + offset_data.as_ref().as_ptr() as usize;
64        Ok(Some((record.paint(offset_data)?, id)))
65    }
66
67    /// Returns the COLRv1 layer at the given index.
68    ///
69    /// The second value in the tuple is a unique identifier for the paint that
70    /// may be used to detect recursion in the paint graph.
71    pub fn v1_layer(&self, index: usize) -> Result<(Paint<'a>, PaintId), ReadError> {
72        let list = self.layer_list().ok_or(ReadError::NullOffset)??;
73        let offset = list
74            .paint_offsets()
75            .get(index)
76            .ok_or(ReadError::OutOfBounds)?
77            .get();
78        let offset_data = list.offset_data();
79        // Use the address of the paint as an identifier for the recursion
80        // blacklist.
81        let id = offset.to_u32() as usize + offset_data.as_ref().as_ptr() as usize;
82        Ok((offset.resolve(offset_data)?, id))
83    }
84
85    /// Returns the COLRv1 clip box for the given glyph identifier.
86    pub fn v1_clip_box(&self, glyph_id: GlyphId) -> Result<Option<ClipBox<'a>>, ReadError> {
87        use core::cmp::Ordering;
88        let Ok(glyph_id): Result<GlyphId16, _> = glyph_id.try_into() else {
89            return Ok(None);
90        };
91        let list = self.clip_list().ok_or(ReadError::NullOffset)??;
92        let clips = list.clips();
93        let clip = match clips.binary_search_by(|clip| {
94            if glyph_id < clip.start_glyph_id() {
95                Ordering::Greater
96            } else if glyph_id > clip.end_glyph_id() {
97                Ordering::Less
98            } else {
99                Ordering::Equal
100            }
101        }) {
102            Ok(ix) => &clips[ix],
103            _ => return Ok(None),
104        };
105        Ok(Some(clip.clip_box(list.offset_data())?))
106    }
107}