| 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 | |