1//! A [Color Table](
2//! https://docs.microsoft.com/en-us/typography/opentype/spec/colr) implementation.
3
4use crate::cpal;
5use crate::parser::{FromData, LazyArray16, Offset, Offset32, Stream};
6use 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)]
11struct BaseGlyphRecord {
12 glyph_id: GlyphId,
13 first_layer_index: u16,
14 num_layers: u16,
15}
16
17impl 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)]
33struct LayerRecord {
34 glyph_id: GlyphId,
35 palette_index: u16,
36}
37
38impl 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.
53pub 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)]
67pub struct Table<'a> {
68 pub(crate) palettes: cpal::Table<'a>,
69 base_glyphs: LazyArray16<'a, BaseGlyphRecord>,
70 layers: LazyArray16<'a, LayerRecord>,
71}
72
73impl<'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