From 2cb01f2565b99c5f1a388745e2bd245150a45ec0 Mon Sep 17 00:00:00 2001 From: Yevhenii Reizner Date: Sun, 8 Oct 2023 16:44:29 +0300 Subject: [PATCH] Support COLR v1 table. --- examples/font2svg.rs | 305 ++++++++++++++++---- src/lib.rs | 82 +++++- src/parser.rs | 34 +++ src/tables/colr.rs | 670 ++++++++++++++++++++++++++++++++++++++++++- src/tables/cpal.rs | 15 + src/tables/glyf.rs | 83 +----- src/tables/gvar.rs | 4 +- tests/tables/colr.rs | 60 +++- 8 files changed, 1088 insertions(+), 165 deletions(-) diff --git a/examples/font2svg.rs b/examples/font2svg.rs index 2f3e51f2..f825ed56 100644 --- a/examples/font2svg.rs +++ b/examples/font2svg.rs @@ -132,6 +132,7 @@ fn process(args: Args) -> Result<(), Box> { let mut path_buf = String::with_capacity(256); let mut row = 0; let mut column = 0; + let mut gradient_index = 1; for id in 0..face.number_of_glyphs() { let gid = ttf::GlyphId(id); let x = column as f64 * cell_size; @@ -145,7 +146,22 @@ fn process(args: Args) -> Result<(), Box> { svg.write_text_fmt(format_args!("{}", &id)); svg.end_element(); - if let Some(img) = face.glyph_raster_image(gid, std::u16::MAX) { + if face.tables().colr.is_some() { + if face.is_color_glyph(gid) { + color_glyph( + x, + y, + &face, + args.colr_palette, + gid, + cell_size, + scale, + &mut gradient_index, + &mut svg, + &mut path_buf, + ); + } + } else if let Some(img) = face.glyph_raster_image(gid, std::u16::MAX) { svg.start_element("image"); svg.write_attribute("x", &(x + 2.0 + img.x as f64)); svg.write_attribute("y", &(y - img.y as f64)); @@ -173,20 +189,6 @@ fn process(args: Args) -> Result<(), Box> { enc.finish().unwrap(); }); svg.end_element(); - } else if face.tables().colr.is_some() { - if face.is_color_glyph(gid) { - color_glyph( - x, - y, - &face, - args.colr_palette, - gid, - cell_size, - scale, - &mut svg, - &mut path_buf, - ); - } } else { glyph_to_path(x, y, &face, gid, cell_size, scale, &mut svg, &mut path_buf); } @@ -238,6 +240,42 @@ fn draw_grid(n_glyphs: u16, cell_size: f64, svg: &mut xmlwriter::XmlWriter) { svg.end_element(); } +struct Builder<'a>(&'a mut String); + +impl Builder<'_> { + fn finish(&mut self) { + if !self.0.is_empty() { + self.0.pop(); // remove trailing space + } + } +} + +impl ttf::OutlineBuilder for Builder<'_> { + fn move_to(&mut self, x: f32, y: f32) { + use std::fmt::Write; + write!(self.0, "M {} {} ", x, y).unwrap() + } + + fn line_to(&mut self, x: f32, y: f32) { + use std::fmt::Write; + write!(self.0, "L {} {} ", x, y).unwrap() + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + use std::fmt::Write; + write!(self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap() + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + use std::fmt::Write; + write!(self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap() + } + + fn close(&mut self) { + self.0.push_str("Z ") + } +} + fn glyph_to_path( x: f64, y: f64, @@ -254,9 +292,7 @@ fn glyph_to_path( Some(v) => v, None => return, }; - if !path_buf.is_empty() { - path_buf.pop(); // remove trailing space - } + builder.finish(); let bbox_w = (bbox.x_max as f64 - bbox.x_min as f64) * scale; let dx = (cell_size - bbox_w) / 2.0; @@ -285,45 +321,183 @@ fn glyph_to_path( } } +// NOTE: this is not a feature-full implementation and just a demo. struct GlyphPainter<'a> { face: &'a ttf::Face<'a>, svg: &'a mut xmlwriter::XmlWriter, path_buf: &'a mut String, + gradient_index: usize, + palette_index: u16, + transform: ttf::Transform, + outline_transform: ttf::Transform, + transforms_stack: Vec, +} + +impl GlyphPainter<'_> { + fn write_gradient_stops(&mut self, stops: ttf::colr::GradientStopsIter) { + for stop in stops { + self.svg.start_element("stop"); + self.svg.write_attribute("offset", &stop.stop_offset); + self.svg.write_color_attribute("stop-color", stop.color); + let opacity = f32::from(stop.color.alpha) / 255.0; + self.svg.write_attribute("stop-opacity", &opacity); + self.svg.end_element(); + } + } } -impl ttf::colr::Painter for GlyphPainter<'_> { - fn color(&mut self, glyph_id: ttf::GlyphId, color: ttf::colr::BgraColor) { +impl<'a> ttf::colr::Painter<'a> for GlyphPainter<'a> { + fn outline(&mut self, glyph_id: ttf::GlyphId) { self.path_buf.clear(); let mut builder = Builder(self.path_buf); match self.face.outline_glyph(glyph_id, &mut builder) { Some(v) => v, None => return, }; - if !self.path_buf.is_empty() { - self.path_buf.pop(); // remove trailing space - } + builder.finish(); + // We have to write outline using the current transform. + self.outline_transform = self.transform; + } + + fn paint_foreground(&mut self) { + // The caller must provide this color. We simply fallback to black. + self.paint_color(ttf::colr::BgraColor::new(0, 0, 0, 255)); + } + + fn paint_color(&mut self, color: ttf::colr::BgraColor) { self.svg.start_element("path"); - self.svg.write_attribute( - "fill", - &format!("rgb({}, {}, {})", color.red, color.green, color.blue), - ); + self.svg.write_color_attribute("fill", color); let opacity = f32::from(color.alpha) / 255.0; self.svg.write_attribute("fill-opacity", &opacity); + self.svg + .write_transform_attribute("transform", self.outline_transform); self.svg.write_attribute("d", self.path_buf); self.svg.end_element(); } - fn foreground(&mut self, id: ttf::GlyphId) { - self.color( - id, - ttf::colr::BgraColor { - blue: 0, - green: 0, - red: 0, - alpha: 255, - }, - ) + fn paint_linear_gradient(&mut self, gradient: ttf::colr::LinearGradient<'a>) { + let gradient_id = format!("lg{}", self.gradient_index); + self.gradient_index += 1; + + // TODO: We ignore x2, y2. Have to apply them somehow. + self.svg.start_element("linearGradient"); + self.svg.write_attribute("id", &gradient_id); + self.svg.write_attribute("x1", &gradient.x0); + self.svg.write_attribute("y1", &gradient.y0); + self.svg.write_attribute("x2", &gradient.x1); + self.svg.write_attribute("y2", &gradient.y1); + self.svg.write_attribute("gradientUnits", &"userSpaceOnUse"); + self.svg.write_spread_method_attribute(gradient.extend); + self.svg + .write_transform_attribute("gradientTransform", self.transform); + self.write_gradient_stops(gradient.stops(self.palette_index)); + self.svg.end_element(); + + self.svg.start_element("path"); + self.svg + .write_attribute_fmt("fill", format_args!("url(#{})", gradient_id)); + self.svg + .write_transform_attribute("transform", self.outline_transform); + self.svg.write_attribute("d", self.path_buf); + self.svg.end_element(); + } + + fn paint_radial_gradient(&mut self, gradient: ttf::colr::RadialGradient<'a>) { + let gradient_id = format!("rg{}", self.gradient_index); + self.gradient_index += 1; + + self.svg.start_element("radialGradient"); + self.svg.write_attribute("id", &gradient_id); + self.svg.write_attribute("cx", &gradient.x1); + self.svg.write_attribute("cy", &gradient.y1); + self.svg.write_attribute("r", &gradient.r1); + self.svg.write_attribute("fr", &gradient.r0); + self.svg.write_attribute("fx", &gradient.x0); + self.svg.write_attribute("fy", &gradient.y0); + self.svg.write_attribute("gradientUnits", &"userSpaceOnUse"); + self.svg.write_spread_method_attribute(gradient.extend); + self.svg + .write_transform_attribute("gradientTransform", self.transform); + self.write_gradient_stops(gradient.stops(self.palette_index)); + self.svg.end_element(); + + self.svg.start_element("path"); + self.svg + .write_attribute_fmt("fill", format_args!("url(#{})", gradient_id)); + self.svg + .write_transform_attribute("transform", self.outline_transform); + self.svg.write_attribute("d", self.path_buf); + self.svg.end_element(); + } + + fn paint_sweep_gradient(&mut self, _: ttf::colr::SweepGradient<'a>) { + println!("Warning: sweep gradients are not supported.") + } + + fn push_group(&mut self, mode: ttf::colr::CompositeMode) { + self.svg.start_element("g"); + + use ttf::colr::CompositeMode; + let mode = match mode { + CompositeMode::SourceOver => "normal", + CompositeMode::Screen => "screen", + CompositeMode::Overlay => "overlay", + CompositeMode::Darken => "darken", + CompositeMode::Lighten => "lighten", + CompositeMode::ColorDodge => "color-dodge", + CompositeMode::ColorBurn => "color-burn", + CompositeMode::HardLight => "hard-light", + CompositeMode::SoftLight => "soft-light", + CompositeMode::Difference => "difference", + CompositeMode::Exclusion => "exclusion", + CompositeMode::Multiply => "multiply", + CompositeMode::Hue => "hue", + CompositeMode::Saturation => "saturation", + CompositeMode::Color => "color", + CompositeMode::Luminosity => "luminosity", + _ => { + println!("Warning: unsupported blend mode: {:?}", mode); + "normal" + } + }; + self.svg + .write_attribute_fmt("style", format_args!("mix-blend-mode: {}", mode)); + } + + fn pop_group(&mut self) { + self.svg.end_element(); // g + } + + fn translate(&mut self, tx: f32, ty: f32) { + self.transform(ttf::Transform::new(1.0, 0.0, 0.0, 1.0, tx, ty)); + } + + fn scale(&mut self, sx: f32, sy: f32) { + self.transform(ttf::Transform::new(sx, 0.0, 0.0, sy, 0.0, 0.0)); + } + + fn rotate(&mut self, angle: f32) { + let cc = (angle * std::f32::consts::PI).cos(); + let ss = (angle * std::f32::consts::PI).sin(); + self.transform(ttf::Transform::new(cc, ss, -ss, cc, 0.0, 0.0)); + } + + fn skew(&mut self, skew_x: f32, skew_y: f32) { + let x = (-skew_x * std::f32::consts::PI).tan(); + let y = (skew_y * std::f32::consts::PI).tan(); + self.transform(ttf::Transform::new(1.0, y, x, 1.0, 0.0, 0.0)); + } + + fn transform(&mut self, transform: ttf::Transform) { + self.transforms_stack.push(self.transform); + self.transform = ttf::Transform::combine(self.transform, transform); + } + + fn pop_transform(&mut self) { + if let Some(ts) = self.transforms_stack.pop() { + self.transform = ts + } } } @@ -335,6 +509,7 @@ fn color_glyph( glyph_id: ttf::GlyphId, cell_size: f64, scale: f64, + gradient_index: &mut usize, svg: &mut xmlwriter::XmlWriter, path_buf: &mut String, ) { @@ -348,36 +523,54 @@ fn color_glyph( face, svg, path_buf, + gradient_index: *gradient_index, + palette_index, + transform: ttf::Transform::default(), + outline_transform: ttf::Transform::default(), + transforms_stack: vec![ttf::Transform::default()], }; face.paint_color_glyph(glyph_id, palette_index, &mut painter); + *gradient_index = painter.gradient_index; svg.end_element(); } -struct Builder<'a>(&'a mut String); - -impl ttf::OutlineBuilder for Builder<'_> { - fn move_to(&mut self, x: f32, y: f32) { - use std::fmt::Write; - write!(self.0, "M {} {} ", x, y).unwrap() - } +trait XmlWriterExt { + fn write_color_attribute(&mut self, name: &str, ts: ttf::colr::BgraColor); + fn write_transform_attribute(&mut self, name: &str, ts: ttf::Transform); + fn write_spread_method_attribute(&mut self, method: ttf::colr::GradientExtend); +} - fn line_to(&mut self, x: f32, y: f32) { - use std::fmt::Write; - write!(self.0, "L {} {} ", x, y).unwrap() +impl XmlWriterExt for xmlwriter::XmlWriter { + fn write_color_attribute(&mut self, name: &str, color: ttf::colr::BgraColor) { + self.write_attribute_fmt( + name, + format_args!("rgb({}, {}, {})", color.red, color.green, color.blue), + ); } - fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { - use std::fmt::Write; - write!(self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap() - } + fn write_transform_attribute(&mut self, name: &str, ts: ttf::Transform) { + if ts.is_default() { + return; + } - fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { - use std::fmt::Write; - write!(self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap() + self.write_attribute_fmt( + name, + format_args!( + "matrix({} {} {} {} {} {})", + ts.a, ts.b, ts.c, ts.d, ts.e, ts.f + ), + ); } - fn close(&mut self) { - self.0.push_str("Z ") + fn write_spread_method_attribute(&mut self, extend: ttf::colr::GradientExtend) { + self.write_attribute( + "spreadMethod", + match extend { + ttf::colr::GradientExtend::Pad => &"pad", + ttf::colr::GradientExtend::Repeat => &"repeat", + ttf::colr::GradientExtend::Reflect => &"reflect", + }, + ); } } diff --git a/src/lib.rs b/src/lib.rs index 4bc61a7c..ba408b51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ Font parsing starts with a [`Face`]. #![no_std] #![forbid(unsafe_code)] -#![warn(missing_docs)] +// #![warn(missing_docs)] #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] #![allow(clippy::get_first)] // we use it for readability @@ -405,6 +405,84 @@ impl BBox { } } +#[derive(Clone, Copy, PartialEq)] +pub struct Transform { + pub a: f32, + pub b: f32, + pub c: f32, + pub d: f32, + pub e: f32, + pub f: f32, +} + +impl Transform { + #[inline] + pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self { + Transform { a, b, c, d, e, f } + } + + #[cfg(feature = "variable-fonts")] + #[inline] + pub fn new_translate(tx: f32, ty: f32) -> Self { + Transform::new(1.0, 0.0, 0.0, 1.0, tx, ty) + } + + #[inline] + pub fn combine(ts1: Self, ts2: Self) -> Self { + Transform { + a: ts1.a * ts2.a + ts1.c * ts2.b, + b: ts1.b * ts2.a + ts1.d * ts2.b, + c: ts1.a * ts2.c + ts1.c * ts2.d, + d: ts1.b * ts2.c + ts1.d * ts2.d, + e: ts1.a * ts2.e + ts1.c * ts2.f + ts1.e, + f: ts1.b * ts2.e + ts1.d * ts2.f + ts1.f, + } + } + + #[inline] + fn apply_to(&self, x: &mut f32, y: &mut f32) { + let tx = *x; + let ty = *y; + *x = self.a * tx + self.c * ty + self.e; + *y = self.b * tx + self.d * ty + self.f; + } + + #[inline] + pub fn is_default(&self) -> bool { + // A direct float comparison is fine in our case. + self.a == 1.0 + && self.b == 0.0 + && self.c == 0.0 + && self.d == 1.0 + && self.e == 0.0 + && self.f == 0.0 + } +} + +impl Default for Transform { + #[inline] + fn default() -> Self { + Transform { + a: 1.0, + b: 0.0, + c: 0.0, + d: 1.0, + e: 0.0, + f: 0.0, + } + } +} + +impl core::fmt::Debug for Transform { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "Transform({} {} {} {} {} {})", + self.a, self.b, self.c, self.d, self.e, self.f + ) + } +} + /// A trait for glyph outline construction. pub trait OutlineBuilder { /// Appends a MoveTo segment. @@ -2071,7 +2149,7 @@ impl<'a> Face<'a> { &self, glyph_id: GlyphId, palette: u16, - painter: &mut dyn colr::Painter, + painter: &mut dyn colr::Painter<'a>, ) -> Option<()> { self.tables.colr?.paint(glyph_id, palette, painter) } diff --git a/src/parser.rs b/src/parser.rs index dfebbeef..a9a9ab0b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -822,6 +822,40 @@ impl FromData for Option { } } +/// A type-safe u24 offset. +#[derive(Clone, Copy, Debug)] +pub struct Offset24(pub u32); + +impl Offset for Offset24 { + #[inline] + fn to_usize(&self) -> usize { + usize::num_from(self.0) + } +} + +impl FromData for Offset24 { + const SIZE: usize = 3; + + #[inline] + fn parse(data: &[u8]) -> Option { + U24::parse(data).map(|n| Offset24(n.0)) + } +} + +impl FromData for Option { + const SIZE: usize = Offset24::SIZE; + + #[inline] + fn parse(data: &[u8]) -> Option { + let offset = Offset24::parse(data)?; + if offset.0 != 0 { + Some(Some(offset)) + } else { + Some(None) + } + } +} + /// A type-safe u32 offset. #[derive(Clone, Copy, Debug)] pub struct Offset32(pub u32); diff --git a/src/tables/colr.rs b/src/tables/colr.rs index 9c2d2c74..9ff07348 100644 --- a/src/tables/colr.rs +++ b/src/tables/colr.rs @@ -1,9 +1,11 @@ //! A [Color Table]( //! https://docs.microsoft.com/en-us/typography/opentype/spec/colr) implementation. -use crate::cpal; -use crate::parser::{FromData, LazyArray16, Offset, Offset32, Stream}; +use crate::parser::{ + FromData, LazyArray16, LazyArray32, Offset, Offset24, Offset32, Stream, F2DOT14, +}; use crate::GlyphId; +use crate::{cpal, Fixed, Transform}; pub use cpal::BgraColor; @@ -49,13 +51,325 @@ impl FromData for LayerRecord { } } -/// A trait for color glyph painting. -pub trait Painter { +/// A [BaseGlyphPaintRecord]( +/// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyphlist-layerlist-and-cliplist). +#[derive(Clone, Copy, Debug)] +struct BaseGlyphPaintRecord { + glyph_id: GlyphId, + paint_table_offset: Offset32, +} + +impl FromData for BaseGlyphPaintRecord { + const SIZE: usize = 6; + + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(Self { + glyph_id: s.read::()?, + paint_table_offset: s.read::()?, + }) + } +} + +/// A [gradient extend]( +/// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyphlist-layerlist-and-cliplist). +#[derive(Clone, Copy, Debug)] +pub enum GradientExtend { + Pad, + Repeat, + Reflect, +} + +impl FromData for GradientExtend { + const SIZE: usize = 1; + + fn parse(data: &[u8]) -> Option { + match data[0] { + 0 => Some(Self::Pad), + 1 => Some(Self::Repeat), + 2 => Some(Self::Reflect), + _ => None, + } + } +} + +/// A [gradient extend]( +/// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyphlist-layerlist-and-cliplist). +#[derive(Clone, Copy, Debug)] +struct ColorStopRaw { + stop_offset: F2DOT14, + palette_index: u16, + alpha: F2DOT14, +} + +impl FromData for ColorStopRaw { + const SIZE: usize = 6; + + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(Self { + stop_offset: s.read::()?, + palette_index: s.read::()?, + alpha: s.read::()?, + }) + } +} + +#[derive(Clone)] +struct ColorLine<'a> { + extend: GradientExtend, + colors: LazyArray16<'a, ColorStopRaw>, + palettes: cpal::Table<'a>, +} + +impl ColorLine<'_> { + fn get(&self, palette: u16, index: u16) -> Option { + let info = self.colors.get(index)?; + let mut color = self.palettes.get(palette, info.palette_index)?; + color.apply_alpha(info.alpha.to_f32()); + Some(ColorStop { + stop_offset: info.stop_offset.to_f32(), + color, + }) + } +} + +/// A [gradient extend]( +/// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyphlist-layerlist-and-cliplist). +#[derive(Clone, Copy, Debug)] +pub struct ColorStop { + pub stop_offset: f32, + pub color: BgraColor, +} + +#[derive(Clone)] +pub struct LinearGradient<'a> { + pub x0: i16, + pub y0: i16, + pub x1: i16, + pub y1: i16, + pub x2: i16, + pub y2: i16, + pub extend: GradientExtend, + color_line: ColorLine<'a>, +} + +impl<'a> core::fmt::Debug for LinearGradient<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("LinearGradient") + .field("x0", &self.x0) + .field("y0", &self.y0) + .field("x1", &self.x1) + .field("y1", &self.y1) + .field("x2", &self.x2) + .field("y2", &self.y2) + .field("extend", &self.extend) + .field("stops", &self.stops(0)) + .finish() + } +} + +impl<'a> LinearGradient<'a> { + pub fn stops<'b>(&'b self, palette: u16) -> GradientStopsIter<'a, 'b> { + GradientStopsIter { + color_line: &self.color_line, + palette, + index: 0, + } + } +} + +#[derive(Clone)] +pub struct RadialGradient<'a> { + pub x0: i16, + pub y0: i16, + pub r0: u16, + pub r1: u16, + pub x1: i16, + pub y1: i16, + pub extend: GradientExtend, + color_line: ColorLine<'a>, +} + +impl<'a> core::fmt::Debug for RadialGradient<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("RadialGradient") + .field("x0", &self.x0) + .field("y0", &self.y0) + .field("r0", &self.r0) + .field("r1", &self.r1) + .field("x1", &self.x1) + .field("y1", &self.y1) + .field("extend", &self.extend) + .field("stops", &self.stops(0)) + .finish() + } +} + +impl<'a> RadialGradient<'a> { + pub fn stops<'b>(&'b self, palette: u16) -> GradientStopsIter<'a, 'b> { + GradientStopsIter { + color_line: &self.color_line, + palette, + index: 0, + } + } +} + +#[derive(Clone)] +pub struct SweepGradient<'a> { + pub center_x: i16, + pub center_y: i16, + pub start_angle: f32, + pub end_angle: f32, + pub extend: GradientExtend, + color_line: ColorLine<'a>, +} + +impl<'a> core::fmt::Debug for SweepGradient<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SweepGradient") + .field("center_x", &self.center_x) + .field("center_y", &self.center_y) + .field("start_angle", &self.start_angle) + .field("end_angle", &self.end_angle) + .field("extend", &self.extend) + .field("stops", &self.stops(0)) + .finish() + } +} + +impl<'a> SweepGradient<'a> { + pub fn stops<'b>(&'b self, palette: u16) -> GradientStopsIter<'a, 'b> { + GradientStopsIter { + color_line: &self.color_line, + palette, + index: 0, + } + } +} + +#[derive(Clone, Copy)] +pub struct GradientStopsIter<'a, 'b> { + color_line: &'b ColorLine<'a>, + palette: u16, + index: u16, +} + +impl Iterator for GradientStopsIter<'_, '_> { + type Item = ColorStop; + + fn next(&mut self) -> Option { + if self.index == self.color_line.colors.len() { + return None; + } + + let index = self.index; + self.index = self.index.checked_add(1)?; + self.color_line.get(self.palette, index) + } +} + +impl core::fmt::Debug for GradientStopsIter<'_, '_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries(*self).finish() + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum CompositeMode { + Clear, + Source, + Destination, + SourceOver, + DestinationOver, + SourceIn, + DestinationIn, + SourceOut, + DestinationOut, + SourceAtop, + DestinationAtop, + Xor, + Plus, + Screen, + Overlay, + Darken, + Lighten, + ColorDodge, + ColorBurn, + HardLight, + SoftLight, + Difference, + Exclusion, + Multiply, + Hue, + Saturation, + Color, + Luminosity, +} + +impl FromData for CompositeMode { + const SIZE: usize = 1; + + fn parse(data: &[u8]) -> Option { + match data[0] { + 0 => Some(Self::Clear), + 1 => Some(Self::Source), + 2 => Some(Self::Destination), + 3 => Some(Self::SourceOver), + 4 => Some(Self::DestinationOver), + 5 => Some(Self::SourceIn), + 6 => Some(Self::DestinationIn), + 7 => Some(Self::SourceOut), + 8 => Some(Self::DestinationOut), + 9 => Some(Self::SourceAtop), + 10 => Some(Self::DestinationAtop), + 11 => Some(Self::Xor), + 12 => Some(Self::Plus), + 13 => Some(Self::Screen), + 14 => Some(Self::Overlay), + 15 => Some(Self::Darken), + 16 => Some(Self::Lighten), + 17 => Some(Self::ColorDodge), + 18 => Some(Self::ColorBurn), + 19 => Some(Self::HardLight), + 20 => Some(Self::SoftLight), + 21 => Some(Self::Difference), + 22 => Some(Self::Exclusion), + 23 => Some(Self::Multiply), + 24 => Some(Self::Hue), + 25 => Some(Self::Saturation), + 26 => Some(Self::Color), + 27 => Some(Self::Luminosity), + _ => None, + } + } +} + +/// A trait for color glyph painting +/// +/// See [COLR](https://learn.microsoft.com/en-us/typography/opentype/spec/colr) for details. +pub trait Painter<'a> { /// Paints an outline glyph using the given color. - fn color(&mut self, id: GlyphId, color: BgraColor); + fn outline(&mut self, glyph_id: GlyphId); /// Paints an outline glyph using the application provided text foreground color. - fn foreground(&mut self, id: GlyphId); + fn paint_foreground(&mut self); + fn paint_color(&mut self, color: BgraColor); + fn paint_linear_gradient(&mut self, gradient: LinearGradient<'a>); + fn paint_radial_gradient(&mut self, gradient: RadialGradient<'a>); + fn paint_sweep_gradient(&mut self, gradient: SweepGradient<'a>); + + fn push_group(&mut self, mode: CompositeMode); + fn pop_group(&mut self); + + fn translate(&mut self, tx: f32, ty: f32); + fn scale(&mut self, sx: f32, sy: f32); + /// Explain why. + fn rotate(&mut self, angle: f32); + fn skew(&mut self, skew_x: f32, skew_y: f32); + fn transform(&mut self, transform: Transform); + fn pop_transform(&mut self); } /// A [Color Table]( @@ -65,8 +379,16 @@ pub trait Painter { #[derive(Clone, Copy, Debug)] pub struct Table<'a> { pub(crate) palettes: cpal::Table<'a>, + data: &'a [u8], + version: u8, + // v0 base_glyphs: LazyArray16<'a, BaseGlyphRecord>, layers: LazyArray16<'a, LayerRecord>, + // v1 + base_glyph_paints_offset: Offset32, + base_glyph_paints: LazyArray32<'a, BaseGlyphPaintRecord>, + layer_paint_offsets_offset: Offset32, + layer_paint_offsets: LazyArray32<'a, Offset32>, } impl<'a> Table<'a> { @@ -90,27 +412,88 @@ impl<'a> Table<'a> { let layers = Stream::new_at(data, layers_offset.to_usize())? .read_array16::(num_layers)?; - Some(Self { + let mut table = Self { + version: version as u8, + data, palettes, base_glyphs, layers, - }) + base_glyph_paints_offset: Offset32(0), // the actual value doesn't matter + base_glyph_paints: LazyArray32::default(), + layer_paint_offsets_offset: Offset32(0), + layer_paint_offsets: LazyArray32::default(), + }; + + if version == 0 { + return Some(table); + } + + table.base_glyph_paints_offset = s.read::()?; + let layer_list_offset = s.read::>()?; + + { + let mut s = Stream::new_at(data, table.base_glyph_paints_offset.to_usize())?; + let count = s.read::()?; + table.base_glyph_paints = s.read_array32::(count)?; + } + + if let Some(offset) = layer_list_offset { + table.layer_paint_offsets_offset = offset; + let mut s = Stream::new_at(data, offset.to_usize())?; + let count = s.read::()?; + table.layer_paint_offsets = s.read_array32::(count)?; + } + + Some(table) + } + + /// Returns `true` if the current table has version 0. + /// + /// A simple table can only emit `outline`, `paint_foreground` and `paint_color` + /// [`Painter`] methods. + pub fn is_simple(&self) -> bool { + self.version == 0 } - fn get(&self, glyph_id: GlyphId) -> Option { + fn get_v0(&self, glyph_id: GlyphId) -> Option { self.base_glyphs .binary_search_by(|base| base.glyph_id.cmp(&glyph_id)) .map(|v| v.1) } + fn get_v1(&self, glyph_id: GlyphId) -> Option { + self.base_glyph_paints + .binary_search_by(|base| base.glyph_id.cmp(&glyph_id)) + .map(|v| v.1) + } + /// Whether the table contains a definition for the given glyph. pub fn contains(&self, glyph_id: GlyphId) -> bool { - self.get(glyph_id).is_some() + self.get_v1(glyph_id).is_some() || self.get_v0(glyph_id).is_some() } /// Paints the color glyph. - pub fn paint(&self, glyph_id: GlyphId, palette: u16, painter: &mut dyn Painter) -> Option<()> { - let base = self.get(glyph_id)?; + pub fn paint( + &self, + glyph_id: GlyphId, + palette: u16, + painter: &mut dyn Painter<'a>, + ) -> Option<()> { + if let Some(base) = self.get_v1(glyph_id) { + self.paint_v1(base, palette, painter) + } else if let Some(base) = self.get_v0(glyph_id) { + self.paint_v0(base, palette, painter) + } else { + None + } + } + + fn paint_v0( + &self, + base: BaseGlyphRecord, + palette: u16, + painter: &mut dyn Painter, + ) -> Option<()> { let start = base.first_layer_index; let end = start.checked_add(base.num_layers)?; let layers = self.layers.slice(start..end)?; @@ -118,13 +501,272 @@ impl<'a> Table<'a> { for layer in layers { if layer.palette_index == 0xFFFF { // A special case. - painter.foreground(layer.glyph_id); + painter.outline(layer.glyph_id); + painter.paint_foreground(); } else { let color = self.palettes.get(palette, layer.palette_index)?; - painter.color(layer.glyph_id, color); + painter.outline(layer.glyph_id); + painter.paint_color(color); + } + } + + Some(()) + } + + fn paint_v1( + &self, + base: BaseGlyphPaintRecord, + palette: u16, + painter: &mut dyn Painter<'a>, + ) -> Option<()> { + self.parse_paint( + self.base_glyph_paints_offset.to_usize() + base.paint_table_offset.to_usize(), + palette, + painter, + )?; + Some(()) + } + + fn parse_paint( + &self, + offset: usize, + palette: u16, + painter: &mut dyn Painter<'a>, + ) -> Option<()> { + let mut s = Stream::new_at(self.data, offset)?; + let format = s.read::()?; + match format { + 1 => { + // PaintColrLayers + let layers_count = s.read::()?; + let first_layer_index = s.read::()?; + + for i in 0..layers_count { + let index = first_layer_index.checked_add(u32::from(i))?; + let paint_offset = self.layer_paint_offsets.get(index)?; + self.parse_paint( + self.layer_paint_offsets_offset.to_usize() + paint_offset.to_usize(), + palette, + painter, + ); + } + } + 2 => { + // PaintSolid + let palette_index = s.read::()?; + let alpha = s.read::()?; + let mut color = self.palettes.get(palette, palette_index)?; + color.apply_alpha(alpha.to_f32()); + painter.paint_color(color); + } + 4 => { + // PaintLinearGradient + let color_line_offset = s.read::()?; + let color_line = self.parse_color_line(offset + color_line_offset.to_usize())?; + painter.paint_linear_gradient(LinearGradient { + x0: s.read::()?, + y0: s.read::()?, + x1: s.read::()?, + y1: s.read::()?, + x2: s.read::()?, + y2: s.read::()?, + extend: color_line.extend, + color_line, + }) + } + 6 => { + // PaintRadialGradient + let color_line_offset = s.read::()?; + let color_line = self.parse_color_line(offset + color_line_offset.to_usize())?; + painter.paint_radial_gradient(RadialGradient { + x0: s.read::()?, + y0: s.read::()?, + r0: s.read::()?, + x1: s.read::()?, + y1: s.read::()?, + r1: s.read::()?, + extend: color_line.extend, + color_line, + }) + } + 8 => { + // PaintSweepGradient + let color_line_offset = s.read::()?; + let color_line = self.parse_color_line(offset + color_line_offset.to_usize())?; + painter.paint_sweep_gradient(SweepGradient { + center_x: s.read::()?, + center_y: s.read::()?, + start_angle: s.read::()?.to_f32(), + end_angle: s.read::()?.to_f32(), + extend: color_line.extend, + color_line, + }) + } + 10 => { + // PaintGlyph + let paint_offset = s.read::()?; + let glyph_id = s.read::()?; + painter.outline(glyph_id); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + } + 11 => { + // PaintColrGlyph + unimplemented!(); + } + 12 => { + // PaintTransform + let paint_offset = s.read::()?; + let ts_offset = s.read::()?; + let mut s = Stream::new_at(self.data, offset + ts_offset.to_usize())?; + let ts = Transform { + a: s.read::().map(|n| n.0)?, + b: s.read::().map(|n| n.0)?, + c: s.read::().map(|n| n.0)?, + d: s.read::().map(|n| n.0)?, + e: s.read::().map(|n| n.0)?, + f: s.read::().map(|n| n.0)?, + }; + + painter.transform(ts); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + } + 14 => { + // PaintTranslate + let paint_offset = s.read::()?; + let tx = f32::from(s.read::()?); + let ty = f32::from(s.read::()?); + + painter.translate(tx, ty); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + } + 16 => { + // PaintScale + let paint_offset = s.read::()?; + let sx = s.read::()?.to_f32(); + let sy = s.read::()?.to_f32(); + + painter.scale(sx, sy); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); } + 18 => { + // PaintScaleAroundCenter + let paint_offset = s.read::()?; + let sx = s.read::()?.to_f32(); + let sy = s.read::()?.to_f32(); + let center_x = f32::from(s.read::()?); + let center_y = f32::from(s.read::()?); + + painter.translate(center_x, center_y); + painter.scale(sx, sy); + painter.translate(-center_x, -center_y); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + painter.pop_transform(); + painter.pop_transform(); + } + 20 => { + // PaintScaleUniform + let paint_offset = s.read::()?; + let scale = s.read::()?.to_f32(); + + painter.scale(scale, scale); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + } + 22 => { + // PaintScaleUniformAroundCenter + let paint_offset = s.read::()?; + let scale = s.read::()?.to_f32(); + let center_x = f32::from(s.read::()?); + let center_y = f32::from(s.read::()?); + + painter.translate(center_x, center_y); + painter.scale(scale, scale); + painter.translate(-center_x, -center_y); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + painter.pop_transform(); + painter.pop_transform(); + } + 24 => { + // PaintRotate + let paint_offset = s.read::()?; + let angle = s.read::()?.to_f32(); + + painter.rotate(angle); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + } + 26 => { + // PaintRotate + let paint_offset = s.read::()?; + let angle = s.read::()?.to_f32(); + let center_x = f32::from(s.read::()?); + let center_y = f32::from(s.read::()?); + + painter.translate(center_x, center_y); + painter.rotate(angle); + painter.translate(-center_x, -center_y); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + painter.pop_transform(); + painter.pop_transform(); + } + 28 => { + // PaintSkew + let paint_offset = s.read::()?; + let skew_x = s.read::()?.to_f32(); + let skew_y = s.read::()?.to_f32(); + + painter.skew(skew_x, skew_y); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + } + 30 => { + // PaintSkewAroundCenter + let paint_offset = s.read::()?; + let skew_x = s.read::()?.to_f32(); + let skew_y = s.read::()?.to_f32(); + let center_x = f32::from(s.read::()?); + let center_y = f32::from(s.read::()?); + + painter.translate(center_x, center_y); + painter.skew(skew_x, skew_y); + painter.translate(-center_x, -center_y); + self.parse_paint(offset + paint_offset.to_usize(), palette, painter); + painter.pop_transform(); + painter.pop_transform(); + painter.pop_transform(); + } + 32 => { + // PaintComposite + let source_paint_offset = s.read::()?; + let composite_mode = s.read::()?; + let backdrop_paint_offset = s.read::()?; + + self.parse_paint(offset + backdrop_paint_offset.to_usize(), palette, painter); + painter.push_group(composite_mode); + self.parse_paint(offset + source_paint_offset.to_usize(), palette, painter); + painter.pop_group(); + } + _ => {} } Some(()) } + + fn parse_color_line(&self, offset: usize) -> Option> { + let mut s = Stream::new_at(self.data, offset)?; + let extend = s.read::()?; + let count = s.read::()?; + let colors = s.read_array16::(count)?; + Some(ColorLine { + extend, + colors, + palettes: self.palettes, + }) + } } diff --git a/src/tables/cpal.rs b/src/tables/cpal.rs index 0126aaa3..c6402079 100644 --- a/src/tables/cpal.rs +++ b/src/tables/cpal.rs @@ -69,6 +69,21 @@ pub struct BgraColor { pub alpha: u8, } +impl BgraColor { + pub fn new(blue: u8, green: u8, red: u8, alpha: u8) -> Self { + Self { + blue, + green, + red, + alpha, + } + } + + pub(crate) fn apply_alpha(&mut self, alpha: f32) { + self.alpha = (((f32::from(self.alpha) / 255.0) * alpha) * 255.0) as u8; + } +} + impl FromData for BgraColor { const SIZE: usize = 4; diff --git a/src/tables/glyf.rs b/src/tables/glyf.rs index 33d95383..4a70f871 100644 --- a/src/tables/glyf.rs +++ b/src/tables/glyf.rs @@ -4,7 +4,7 @@ use core::num::NonZeroU16; use crate::parser::{LazyArray16, NumFrom, Stream, F2DOT14}; -use crate::{loca, BBox, GlyphId, OutlineBuilder, Rect}; +use crate::{loca, BBox, GlyphId, OutlineBuilder, Rect, Transform}; pub(crate) struct Builder<'a> { pub builder: &'a mut dyn OutlineBuilder, @@ -137,87 +137,6 @@ impl<'a> Builder<'a> { } } -#[derive(Clone, Copy)] -pub(crate) struct Transform { - pub a: f32, - pub b: f32, - pub c: f32, - pub d: f32, - pub e: f32, - pub f: f32, -} - -impl Transform { - #[cfg(feature = "variable-fonts")] - #[inline] - pub fn new_translate(tx: f32, ty: f32) -> Self { - Transform { - a: 1.0, - b: 0.0, - c: 0.0, - d: 1.0, - e: tx, - f: ty, - } - } - - #[inline] - pub fn combine(ts1: Self, ts2: Self) -> Self { - Transform { - a: ts1.a * ts2.a + ts1.c * ts2.b, - b: ts1.b * ts2.a + ts1.d * ts2.b, - c: ts1.a * ts2.c + ts1.c * ts2.d, - d: ts1.b * ts2.c + ts1.d * ts2.d, - e: ts1.a * ts2.e + ts1.c * ts2.f + ts1.e, - f: ts1.b * ts2.e + ts1.d * ts2.f + ts1.f, - } - } - - #[inline] - fn apply_to(&self, x: &mut f32, y: &mut f32) { - let tx = *x; - let ty = *y; - *x = self.a * tx + self.c * ty + self.e; - *y = self.b * tx + self.d * ty + self.f; - } - - #[inline] - fn is_default(&self) -> bool { - // A direct float comparison is fine in our case. - self.a == 1.0 - && self.b == 0.0 - && self.c == 0.0 - && self.d == 1.0 - && self.e == 0.0 - && self.f == 0.0 - } -} - -impl Default for Transform { - #[inline] - fn default() -> Self { - Transform { - a: 1.0, - b: 0.0, - c: 0.0, - d: 1.0, - e: 0.0, - f: 0.0, - } - } -} - -impl core::fmt::Debug for Transform { - #[inline] - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!( - f, - "Transform({} {} {} {} {} {})", - self.a, self.b, self.c, self.d, self.e, self.f - ) - } -} - #[derive(Clone, Copy, Debug)] pub(crate) struct CompositeGlyphInfo { pub glyph_id: GlyphId, diff --git a/src/tables/gvar.rs b/src/tables/gvar.rs index 1086d9d0..3894d955 100644 --- a/src/tables/gvar.rs +++ b/src/tables/gvar.rs @@ -11,9 +11,9 @@ use core::cmp; use core::convert::TryFrom; use core::num::NonZeroU16; -use crate::glyf::{self, Transform}; +use crate::glyf; use crate::parser::{LazyArray16, Offset, Offset16, Offset32, Stream, F2DOT14}; -use crate::{BBox, GlyphId, NormalizedCoordinate, OutlineBuilder, Rect}; +use crate::{BBox, GlyphId, NormalizedCoordinate, OutlineBuilder, Rect, Transform}; /// 'The TrueType rasterizer dynamically generates 'phantom' points for each glyph /// that represent horizontal and vertical advance widths and side bearings, diff --git a/tests/tables/colr.rs b/tests/tables/colr.rs index ce1ac407..b5e92c6a 100644 --- a/tests/tables/colr.rs +++ b/tests/tables/colr.rs @@ -58,19 +58,61 @@ fn basic() { assert!(colr.contains(GlyphId(7))); assert_eq!(paint(1), None); - assert_eq!(paint(2), Some(vec![(12, c), (13, a)])); - assert_eq!(paint(3), Some(vec![(10, c), (11, b), (12, c)])); - assert_eq!(paint(7), Some(vec![(11, b)])); + + assert_eq!(paint(2).unwrap(), vec![ + Command::Outline(12), + Command::PaintColor(c), + Command::Outline(13), + Command::PaintColor(a), + ]); + + assert_eq!(paint(3).unwrap(), vec![ + Command::Outline(10), + Command::PaintColor(c), + Command::Outline(11), + Command::PaintColor(b), + Command::Outline(12), + Command::PaintColor(c), + ]); + + assert_eq!(paint(7).unwrap(), vec![ + Command::Outline(11), + Command::PaintColor(b), + ]); } -struct VecPainter(Vec<(u16, BgraColor)>); +#[derive(Clone, Copy, PartialEq, Debug)] +enum Command { + Outline(u16), + Foreground, + PaintColor(BgraColor), +} + +struct VecPainter(Vec); -impl Painter for VecPainter { - fn color(&mut self, id: GlyphId, color: BgraColor) { - self.0.push((id.0, color)); +impl Painter<'_> for VecPainter { + fn outline(&mut self, glyph_id: GlyphId) { + self.0.push(Command::Outline(glyph_id.0)); } - fn foreground(&mut self, id: GlyphId) { - self.0.push((id.0, BgraColor { blue: 0, green: 0, red: 0, alpha: 255 })); + fn paint_foreground(&mut self) { + self.0.push(Command::Foreground); } + + fn paint_color(&mut self, color: BgraColor) { + self.0.push(Command::PaintColor(color)); + } + + // TODO: test v1 + fn paint_linear_gradient(&mut self, _gradient: colr::LinearGradient) {} + fn paint_radial_gradient(&mut self, _gradient: colr::RadialGradient) {} + fn paint_sweep_gradient(&mut self, _gradient: colr::SweepGradient) {} + fn push_group(&mut self, _mode: colr::CompositeMode) {} + fn pop_group(&mut self) {} + fn translate(&mut self, _tx: f32, _ty: f32) {} + fn scale(&mut self, _sx: f32, _sy: f32) {} + fn rotate(&mut self, _angle: f32) {} + fn skew(&mut self, _skew_x: f32, _skew_y: f32) {} + fn transform(&mut self, _transform: ttf_parser::Transform) {} + fn pop_transform(&mut self) {} }