/rust/registry/src/index.crates.io-1949cf8c6b5b557f/read-fonts-0.46.0/src/tables/colr.rs
Line | Count | Source |
1 | | //! The [COLR](https://docs.microsoft.com/en-us/typography/opentype/spec/colr) table |
2 | | |
3 | | #[cfg(feature = "std")] |
4 | | mod closure; |
5 | | |
6 | | use super::variations::{DeltaSetIndexMap, ItemVariationStore}; |
7 | | |
8 | | include!("../../generated/generated_colr.rs"); |
9 | | |
10 | | /// Unique paint identifier used for detecting cycles in the paint graph. |
11 | | pub type PaintId = usize; |
12 | | |
13 | | impl<'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 | 1.15k | pub fn v0_base_glyph(&self, glyph_id: GlyphId) -> Option<Range<usize>> { |
20 | 1.15k | let records = self.base_glyph_records()?.ok()?; |
21 | 525 | let glyph_id = glyph_id.try_into().ok()?; |
22 | 525 | let ix = records |
23 | 2.33k | .binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) |
24 | 525 | .ok()?; |
25 | 484 | let record = records.get(ix)?; |
26 | 484 | let start = record.first_layer_index() as usize; |
27 | 484 | Some(start..start.checked_add(record.num_layers() as usize)?) |
28 | 1.15k | } |
29 | | |
30 | | /// Returns the COLRv0 layer at the given index. |
31 | | /// |
32 | | /// The layer is represented by a tuple containing the glyph identifier of |
33 | | /// the associated outline and the palette color index. |
34 | 544 | pub fn v0_layer(&self, index: usize) -> Option<(GlyphId16, u16)> { |
35 | 544 | let layer = self.layer_records()?.ok()?.get(index)?; |
36 | 414 | Some((layer.glyph_id(), layer.palette_index())) |
37 | 544 | } |
38 | | |
39 | | /// Returns the COLRv1 base glyph for the given glyph identifier. |
40 | | /// |
41 | | /// The second value in the tuple is a unique identifier for the paint that |
42 | | /// may be used to detect recursion in the paint graph. |
43 | 1.46k | pub fn v1_base_glyph(&self, glyph_id: GlyphId) -> Option<(Paint<'a>, PaintId)> { |
44 | 1.46k | let glyph_id = glyph_id.try_into().ok()?; |
45 | 1.46k | let list = self.base_glyph_list()?.ok()?; |
46 | 536 | let records = list.base_glyph_paint_records(); |
47 | 536 | let ix = records |
48 | 2.08k | .binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) |
49 | 536 | .ok()?; |
50 | 406 | let record = records.get(ix)?; |
51 | 406 | let offset_data = list.offset_data(); |
52 | | // Use the address of the paint as an identifier for the recursion |
53 | | // blacklist. |
54 | 406 | let id = record.paint_offset().to_u32() as usize + offset_data.as_ref().as_ptr() as usize; |
55 | 406 | Some((record.paint(offset_data).ok()?, id)) |
56 | 1.46k | } |
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. |
62 | 16 | pub fn v1_layer(&self, index: usize) -> Option<(Paint<'a>, PaintId)> { |
63 | 16 | let list = self.layer_list()?.ok()?; |
64 | 0 | let offset = list.paint_offsets().get(index)?.get(); |
65 | 0 | let offset_data = list.offset_data(); |
66 | | // Use the address of the paint as an identifier for the recursion |
67 | | // blacklist. |
68 | 0 | let id = offset.to_u32() as usize + offset_data.as_ref().as_ptr() as usize; |
69 | 0 | Some((offset.resolve(offset_data).ok()?, id)) |
70 | 16 | } |
71 | | |
72 | | /// Returns the COLRv1 clip box for the given glyph identifier. |
73 | 1.10k | pub fn v1_clip_box(&self, glyph_id: GlyphId) -> Option<ClipBox<'a>> { |
74 | | use core::cmp::Ordering; |
75 | 1.10k | let glyph_id: GlyphId16 = glyph_id.try_into().ok()?; |
76 | 1.10k | let list = self.clip_list()?.ok()?; |
77 | 174 | let clips = list.clips(); |
78 | 174 | let ix = clips |
79 | 530 | .binary_search_by(|clip| { |
80 | 530 | if glyph_id < clip.start_glyph_id() { |
81 | 326 | Ordering::Greater |
82 | 204 | } else if glyph_id > clip.end_glyph_id() { |
83 | 0 | Ordering::Less |
84 | | } else { |
85 | 204 | Ordering::Equal |
86 | | } |
87 | 530 | }) |
88 | 174 | .ok()?; |
89 | 84 | let clip = clips.get(ix)?; |
90 | 84 | clip.clip_box(list.offset_data()).ok() |
91 | 1.10k | } |
92 | | } |
93 | | |
94 | | #[cfg(test)] |
95 | | mod tests { |
96 | | use super::*; |
97 | | use font_test_data::bebuffer::BeBuffer; |
98 | | |
99 | | #[test] |
100 | | fn paint_glyph2_reads_full_glyph_id() { |
101 | | for gid in [0, 0xffff, 0x10000, 0x123456, 0xffffff] { |
102 | | let data = BeBuffer::new() |
103 | | .push(33u8) |
104 | | .push(Offset24::new(Uint24::new(7))) |
105 | | .push(GlyphId24::new(gid)) |
106 | | .extend([2u8, 0, 7, 0x40, 0]); |
107 | | let Paint::Glyph2(paint) = Paint::read(FontData::new(&data)).unwrap() else { |
108 | | panic!("expected PaintGlyph2"); |
109 | | }; |
110 | | assert_eq!(paint.glyph_id().to_u32(), gid); |
111 | | assert_eq!(paint.paint_offset().to_u32(), 7); |
112 | | let Paint::Solid(solid) = paint.paint().unwrap() else { |
113 | | panic!("expected PaintSolid"); |
114 | | }; |
115 | | assert_eq!(solid.palette_index(), 7); |
116 | | assert_eq!(solid.alpha(), F2Dot14::ONE); |
117 | | } |
118 | | } |
119 | | |
120 | | #[test] |
121 | | fn paint_glyph2_rejects_truncated_header_and_child() { |
122 | | let data = font_test_data::colr::PAINT_GLYPH2; |
123 | | for len in 0..7 { |
124 | | assert!(Paint::read(FontData::new(&data[..len])).is_err()); |
125 | | } |
126 | | for len in 7..data.len() { |
127 | | let Paint::Glyph2(paint) = Paint::read(FontData::new(&data[..len])).unwrap() else { |
128 | | panic!("expected PaintGlyph2"); |
129 | | }; |
130 | | assert!(paint.paint().is_err()); |
131 | | } |
132 | | } |
133 | | } |