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