1 | //! A [Color Table]( |
2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/colr) implementation. |
3 | |
4 | use crate::cpal; |
5 | use crate::parser::{FromData, LazyArray16, Offset, Offset32, Stream}; |
6 | use crate::{GlyphId, RgbaColor}; |
7 | |
8 | /// A [base glyph]( |
9 | /// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyph-and-layer-records). |
10 | #[derive (Clone, Copy, Debug)] |
11 | struct BaseGlyphRecord { |
12 | glyph_id: GlyphId, |
13 | first_layer_index: u16, |
14 | num_layers: u16, |
15 | } |
16 | |
17 | impl FromData for BaseGlyphRecord { |
18 | const SIZE: usize = 6; |
19 | |
20 | fn parse(data: &[u8]) -> Option<Self> { |
21 | let mut s: Stream<'_> = Stream::new(data); |
22 | Some(Self { |
23 | glyph_id: s.read::<GlyphId>()?, |
24 | first_layer_index: s.read::<u16>()?, |
25 | num_layers: s.read::<u16>()?, |
26 | }) |
27 | } |
28 | } |
29 | |
30 | /// A [layer]( |
31 | /// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyph-and-layer-records). |
32 | #[derive (Clone, Copy, Debug)] |
33 | struct LayerRecord { |
34 | glyph_id: GlyphId, |
35 | palette_index: u16, |
36 | } |
37 | |
38 | impl FromData for LayerRecord { |
39 | const SIZE: usize = 4; |
40 | |
41 | fn parse(data: &[u8]) -> Option<Self> { |
42 | let mut s: Stream<'_> = Stream::new(data); |
43 | Some(Self { |
44 | glyph_id: s.read::<GlyphId>()?, |
45 | palette_index: s.read::<u16>()?, |
46 | }) |
47 | } |
48 | } |
49 | |
50 | /// A trait for color glyph painting. |
51 | /// |
52 | /// See [COLR](https://learn.microsoft.com/en-us/typography/opentype/spec/colr) for details. |
53 | pub trait Painter { |
54 | /// Outlines a glyph and stores it until the next paint command. |
55 | fn outline(&mut self, glyph_id: GlyphId); |
56 | /// Paints the current glyph outline using the application provided text foreground color. |
57 | fn paint_foreground(&mut self); |
58 | /// Paints the current glyph outline using the provided color. |
59 | fn paint_color(&mut self, color: RgbaColor); |
60 | } |
61 | |
62 | /// A [Color Table]( |
63 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/colr). |
64 | /// |
65 | /// Currently, only version 0 is supported. |
66 | #[derive (Clone, Copy, Debug)] |
67 | pub struct Table<'a> { |
68 | pub(crate) palettes: cpal::Table<'a>, |
69 | base_glyphs: LazyArray16<'a, BaseGlyphRecord>, |
70 | layers: LazyArray16<'a, LayerRecord>, |
71 | } |
72 | |
73 | impl<'a> Table<'a> { |
74 | /// Parses a table from raw data. |
75 | pub fn parse(palettes: cpal::Table<'a>, data: &'a [u8]) -> Option<Self> { |
76 | let mut s = Stream::new(data); |
77 | |
78 | let version = s.read::<u16>()?; |
79 | if version != 0 { |
80 | return None; |
81 | } |
82 | |
83 | let num_base_glyphs = s.read::<u16>()?; |
84 | let base_glyphs_offset = s.read::<Offset32>()?; |
85 | let layers_offset = s.read::<Offset32>()?; |
86 | let num_layers = s.read::<u16>()?; |
87 | |
88 | let base_glyphs = Stream::new_at(data, base_glyphs_offset.to_usize())? |
89 | .read_array16::<BaseGlyphRecord>(num_base_glyphs)?; |
90 | |
91 | let layers = Stream::new_at(data, layers_offset.to_usize())? |
92 | .read_array16::<LayerRecord>(num_layers)?; |
93 | |
94 | Some(Self { |
95 | palettes, |
96 | base_glyphs, |
97 | layers, |
98 | }) |
99 | } |
100 | |
101 | fn get(&self, glyph_id: GlyphId) -> Option<BaseGlyphRecord> { |
102 | self.base_glyphs |
103 | .binary_search_by(|base| base.glyph_id.cmp(&glyph_id)) |
104 | .map(|v| v.1) |
105 | } |
106 | |
107 | /// Whether the table contains a definition for the given glyph. |
108 | pub fn contains(&self, glyph_id: GlyphId) -> bool { |
109 | self.get(glyph_id).is_some() |
110 | } |
111 | |
112 | /// Paints the color glyph. |
113 | pub fn paint(&self, glyph_id: GlyphId, palette: u16, painter: &mut dyn Painter) -> Option<()> { |
114 | let base = self.get(glyph_id)?; |
115 | let start = base.first_layer_index; |
116 | let end = start.checked_add(base.num_layers)?; |
117 | let layers = self.layers.slice(start..end)?; |
118 | |
119 | for layer in layers { |
120 | if layer.palette_index == 0xFFFF { |
121 | // A special case. |
122 | painter.outline(layer.glyph_id); |
123 | painter.paint_foreground(); |
124 | } else { |
125 | let color = self.palettes.get(palette, layer.palette_index)?; |
126 | painter.outline(layer.glyph_id); |
127 | painter.paint_color(color); |
128 | } |
129 | } |
130 | |
131 | Some(()) |
132 | } |
133 | } |
134 | |