diff --git a/read-fonts/src/tables/colr.rs b/read-fonts/src/tables/colr.rs index ae98eaba0..fb39e412e 100644 --- a/read-fonts/src/tables/colr.rs +++ b/read-fonts/src/tables/colr.rs @@ -1,13 +1,10 @@ //! The [COLR](https://docs.microsoft.com/en-us/typography/opentype/spec/colr) table -use super::variations::{DeltaSetIndex, DeltaSetIndexMap, ItemVariationStore}; -use types::BoundingBox; - -use std::ops::Deref; +use super::variations::{DeltaSetIndexMap, ItemVariationStore}; include!("../../generated/generated_colr.rs"); -/// Identifier used for representing a paint on the recursion blacklist. +/// Unique paint identifier used for detecting cycles in the paint graph. pub type PaintId = usize; impl<'a> Colr<'a> { @@ -96,605 +93,3 @@ impl<'a> Colr<'a> { Ok(Some(clip.clip_box(list.offset_data())?)) } } - -/// Combination of a `COLR` table and a location in variation space for -/// resolving paints. -/// -/// See [`Paint::resolve`], [`ColorStops::resolve`] and [`ClipBox::resolve`]. -pub struct ColrInstance<'a> { - colr: Colr<'a>, - index_map: Option>, - var_store: Option>, - coords: &'a [F2Dot14], -} - -impl<'a> ColrInstance<'a> { - /// Creates a new instance for the given `COLR` table and normalized variation - /// coordinates. - pub fn new(colr: Colr<'a>, coords: &'a [F2Dot14]) -> Self { - let index_map = colr.var_index_map().and_then(|res| res.ok()); - let var_store = colr.item_variation_store().and_then(|res| res.ok()); - Self { - colr, - coords, - index_map, - var_store, - } - } - - /// Computes a sequence of N variation deltas starting at the given - /// `var_base` index. - fn var_deltas(&self, var_index_base: u32) -> [i32; N] { - // Magic value that indicates deltas should not be applied. - const NO_VARIATION_DELTAS: u32 = 0xFFFFFFFF; - // Note: FreeType never returns an error for these lookups, so - // we do the same and just `unwrap_or_default` on var store - // errors. - // See - let mut deltas = [0; N]; - if self.coords.is_empty() - || self.var_store.is_none() - || var_index_base == NO_VARIATION_DELTAS - { - return deltas; - } - let var_store = self.var_store.as_ref().unwrap(); - if let Some(index_map) = self.index_map.as_ref() { - for (i, delta) in deltas.iter_mut().enumerate() { - let var_index = var_index_base + i as u32; - *delta = index_map - .get(var_index) - .and_then(|delta_index| var_store.compute_delta(delta_index, self.coords)) - .unwrap_or_default(); - } - } else { - for (i, delta) in deltas.iter_mut().enumerate() { - let var_index = var_index_base + i as u32; - // If we don't have a var index map, use our index as the inner - // component and set the outer to 0. - let delta_index = DeltaSetIndex { - outer: 0, - inner: var_index as u16, - }; - *delta = var_store - .compute_delta(delta_index, self.coords) - .unwrap_or_default(); - } - } - deltas - } -} - -impl<'a> Deref for ColrInstance<'a> { - type Target = Colr<'a>; - - fn deref(&self) -> &Self::Target { - &self.colr - } -} - -impl<'a> ClipBox<'a> { - /// Resolves a clip box, applying variation deltas using the given - /// instance. - pub fn resolve(&self, instance: &ColrInstance<'a>) -> BoundingBox { - match self { - Self::Format1(cbox) => BoundingBox { - x_min: cbox.x_min().to_fixed(), - y_min: cbox.y_min().to_fixed(), - x_max: cbox.x_max().to_fixed(), - y_max: cbox.y_max().to_fixed(), - }, - Self::Format2(cbox) => { - let deltas = instance.var_deltas::<4>(cbox.var_index_base()); - BoundingBox { - x_min: cbox.x_min().apply_delta(deltas[0]), - y_min: cbox.y_min().apply_delta(deltas[1]), - x_max: cbox.x_max().apply_delta(deltas[2]), - y_max: cbox.y_max().apply_delta(deltas[3]), - } - } - } - } -} - -/// Simplified version of a [`ColorStop`] or [`VarColorStop`] with applied -/// variation deltas. -#[derive(Clone, Debug)] -pub struct ResolvedColorStop { - pub offset: Fixed, - pub palette_index: u16, - pub alpha: Fixed, -} - -/// Collection of [`ColorStop`] or [`VarColorStop`]. -// Note: only one of these fields is used at any given time, but this structure -// was chosen over the obvious enum approach for simplicity in generating a -// single concrete type for the `impl Iterator` return type of the `resolve` -// method. -pub struct ColorStops<'a> { - stops: &'a [ColorStop], - var_stops: &'a [VarColorStop], -} - -impl<'a> From> for ColorStops<'a> { - fn from(value: ColorLine<'a>) -> Self { - Self { - stops: value.color_stops(), - var_stops: &[], - } - } -} - -impl<'a> From> for ColorStops<'a> { - fn from(value: VarColorLine<'a>) -> Self { - Self { - stops: &[], - var_stops: value.color_stops(), - } - } -} - -impl<'a> ColorStops<'a> { - /// Returns an iterator yielding resolved color stops with variation deltas - /// applied. - pub fn resolve( - &self, - instance: &'a ColrInstance<'a>, - ) -> impl Iterator + 'a { - self.stops - .iter() - .map(|stop| ResolvedColorStop { - offset: stop.stop_offset().to_fixed(), - palette_index: stop.palette_index(), - alpha: stop.alpha().to_fixed(), - }) - .chain(self.var_stops.iter().map(|stop| { - let deltas = instance.var_deltas::<2>(stop.var_index_base()); - ResolvedColorStop { - offset: stop.stop_offset().apply_delta(deltas[0]), - palette_index: stop.palette_index(), - alpha: stop.alpha().apply_delta(deltas[1]), - } - })) - } -} - -/// Simplified version of `Paint` with applied variation deltas. -/// -/// These are constructed with [`Paint::resolve`] method. See the documentation -/// on that method for further detail. -/// -/// This is roughly equivalent to FreeType's -/// [`FT_COLR_Paint`](https://freetype.org/freetype2/docs/reference/ft2-layer_management.html#ft_colr_paint) -/// type. -pub enum ResolvedPaint<'a> { - ColrLayers { - range: Range, - }, - Solid { - palette_index: u16, - alpha: Fixed, - }, - LinearGradient { - x0: Fixed, - y0: Fixed, - x1: Fixed, - y1: Fixed, - x2: Fixed, - y2: Fixed, - color_stops: ColorStops<'a>, - extend: Extend, - }, - RadialGradient { - x0: Fixed, - y0: Fixed, - radius0: Fixed, - x1: Fixed, - y1: Fixed, - radius1: Fixed, - color_stops: ColorStops<'a>, - extend: Extend, - }, - SweepGradient { - center_x: Fixed, - center_y: Fixed, - start_angle: Fixed, - end_angle: Fixed, - color_stops: ColorStops<'a>, - extend: Extend, - }, - Glyph { - glyph_id: GlyphId, - paint: Paint<'a>, - }, - ColrGlyph { - glyph_id: GlyphId, - }, - Transform { - xx: Fixed, - yx: Fixed, - xy: Fixed, - yy: Fixed, - dx: Fixed, - dy: Fixed, - paint: Paint<'a>, - }, - Translate { - dx: Fixed, - dy: Fixed, - paint: Paint<'a>, - }, - Scale { - scale_x: Fixed, - scale_y: Fixed, - around_center: Option>, - paint: Paint<'a>, - }, - Rotate { - angle: Fixed, - around_center: Option>, - paint: Paint<'a>, - }, - Skew { - x_skew_angle: Fixed, - y_skew_angle: Fixed, - around_center: Option>, - paint: Paint<'a>, - }, - Composite { - source_paint: Paint<'a>, - mode: CompositeMode, - backdrop_paint: Paint<'a>, - }, -} - -impl<'a> Paint<'a> { - /// Resolves this paint with the given instance. - /// - /// Resolving means that all numeric values are converted to 16.16 fixed - /// point, variation deltas are applied, and the various transform - /// paints are collapsed into a single value for their category (transform, - /// translate, scale, rotate and skew). - /// - /// This provides a simpler type for consumers that are more interested - /// in extracting the semantics of the graph rather than working with the - /// raw encoded structures. - pub fn resolve(&self, instance: &ColrInstance<'a>) -> Result, ReadError> { - Ok(match self { - Self::ColrLayers(layers) => { - let start = layers.first_layer_index() as usize; - ResolvedPaint::ColrLayers { - range: start..start + layers.num_layers() as usize, - } - } - Self::Solid(solid) => ResolvedPaint::Solid { - palette_index: solid.palette_index(), - alpha: solid.alpha().to_fixed(), - }, - Self::VarSolid(solid) => { - let deltas = instance.var_deltas::<1>(solid.var_index_base()); - ResolvedPaint::Solid { - palette_index: solid.palette_index(), - alpha: solid.alpha().apply_delta(deltas[0]), - } - } - Self::LinearGradient(gradient) => { - let color_line = gradient.color_line()?; - let extend = color_line.extend(); - ResolvedPaint::LinearGradient { - x0: gradient.x0().to_fixed(), - y0: gradient.y0().to_fixed(), - x1: gradient.x1().to_fixed(), - y1: gradient.y1().to_fixed(), - x2: gradient.x2().to_fixed(), - y2: gradient.y2().to_fixed(), - color_stops: color_line.into(), - extend, - } - } - Self::VarLinearGradient(gradient) => { - let color_line = gradient.color_line()?; - let extend = color_line.extend(); - let deltas = instance.var_deltas::<6>(gradient.var_index_base()); - ResolvedPaint::LinearGradient { - x0: gradient.x0().apply_delta(deltas[0]), - y0: gradient.y0().apply_delta(deltas[1]), - x1: gradient.x1().apply_delta(deltas[2]), - y1: gradient.y1().apply_delta(deltas[3]), - x2: gradient.x2().apply_delta(deltas[4]), - y2: gradient.y2().apply_delta(deltas[5]), - color_stops: color_line.into(), - extend, - } - } - Self::RadialGradient(gradient) => { - let color_line = gradient.color_line()?; - let extend = color_line.extend(); - ResolvedPaint::RadialGradient { - x0: gradient.x0().to_fixed(), - y0: gradient.y0().to_fixed(), - radius0: gradient.radius0().to_fixed(), - x1: gradient.x1().to_fixed(), - y1: gradient.y1().to_fixed(), - radius1: gradient.radius1().to_fixed(), - color_stops: color_line.into(), - extend, - } - } - Self::VarRadialGradient(gradient) => { - let color_line = gradient.color_line()?; - let extend = color_line.extend(); - let deltas = instance.var_deltas::<6>(gradient.var_index_base()); - ResolvedPaint::RadialGradient { - x0: gradient.x0().apply_delta(deltas[0]), - y0: gradient.y0().apply_delta(deltas[1]), - radius0: gradient.radius0().apply_delta(deltas[2]), - x1: gradient.x1().apply_delta(deltas[3]), - y1: gradient.y1().apply_delta(deltas[4]), - radius1: gradient.radius1().apply_delta(deltas[5]), - color_stops: color_line.into(), - extend, - } - } - Self::SweepGradient(gradient) => { - let color_line = gradient.color_line()?; - let extend = color_line.extend(); - ResolvedPaint::SweepGradient { - center_x: gradient.center_x().to_fixed(), - center_y: gradient.center_y().to_fixed(), - start_angle: gradient.start_angle().to_fixed(), - end_angle: gradient.end_angle().to_fixed(), - color_stops: color_line.into(), - extend, - } - } - Self::VarSweepGradient(gradient) => { - let color_line = gradient.color_line()?; - let extend = color_line.extend(); - let deltas = instance.var_deltas::<4>(gradient.var_index_base()); - ResolvedPaint::SweepGradient { - center_x: gradient.center_x().apply_delta(deltas[0]), - center_y: gradient.center_y().apply_delta(deltas[1]), - start_angle: gradient.start_angle().apply_delta(deltas[2]), - end_angle: gradient.end_angle().apply_delta(deltas[3]), - color_stops: color_line.into(), - extend, - } - } - Self::Glyph(glyph) => ResolvedPaint::Glyph { - glyph_id: glyph.glyph_id(), - paint: glyph.paint()?, - }, - Self::ColrGlyph(glyph) => ResolvedPaint::ColrGlyph { - glyph_id: glyph.glyph_id(), - }, - Self::Transform(transform) => { - let affine = transform.transform()?; - let paint = transform.paint()?; - ResolvedPaint::Transform { - xx: affine.xx(), - yx: affine.yx(), - xy: affine.xy(), - yy: affine.yy(), - dx: affine.dx(), - dy: affine.dy(), - paint, - } - } - Self::VarTransform(transform) => { - let affine = transform.transform()?; - let paint = transform.paint()?; - let deltas = instance.var_deltas::<6>(affine.var_index_base()); - ResolvedPaint::Transform { - xx: affine.xx().apply_delta(deltas[0]), - yx: affine.yx().apply_delta(deltas[1]), - xy: affine.xy().apply_delta(deltas[2]), - yy: affine.yy().apply_delta(deltas[3]), - dx: affine.dx().apply_delta(deltas[4]), - dy: affine.dy().apply_delta(deltas[5]), - paint, - } - } - Self::Translate(transform) => ResolvedPaint::Translate { - dx: transform.dx().to_fixed(), - dy: transform.dy().to_fixed(), - paint: transform.paint()?, - }, - Self::VarTranslate(transform) => { - let deltas = instance.var_deltas::<2>(transform.var_index_base()); - ResolvedPaint::Translate { - dx: transform.dx().apply_delta(deltas[0]), - dy: transform.dy().apply_delta(deltas[1]), - paint: transform.paint()?, - } - } - Self::Scale(transform) => ResolvedPaint::Scale { - scale_x: transform.scale_x().to_fixed(), - scale_y: transform.scale_y().to_fixed(), - around_center: None, - paint: transform.paint()?, - }, - Self::VarScale(transform) => { - let deltas = instance.var_deltas::<2>(transform.var_index_base()); - ResolvedPaint::Scale { - scale_x: transform.scale_x().apply_delta(deltas[0]), - scale_y: transform.scale_y().apply_delta(deltas[1]), - around_center: None, - paint: transform.paint()?, - } - } - Self::ScaleAroundCenter(transform) => ResolvedPaint::Scale { - scale_x: transform.scale_x().to_fixed(), - scale_y: transform.scale_y().to_fixed(), - around_center: Some(Point::new( - transform.center_x().to_fixed(), - transform.center_y().to_fixed(), - )), - paint: transform.paint()?, - }, - Self::VarScaleAroundCenter(transform) => { - let deltas = instance.var_deltas::<4>(transform.var_index_base()); - ResolvedPaint::Scale { - scale_x: transform.scale_x().apply_delta(deltas[0]), - scale_y: transform.scale_y().apply_delta(deltas[1]), - around_center: Some(Point::new( - transform.center_x().apply_delta(deltas[2]), - transform.center_y().apply_delta(deltas[3]), - )), - paint: transform.paint()?, - } - } - Self::ScaleUniform(transform) => { - let scale = transform.scale().to_fixed(); - ResolvedPaint::Scale { - scale_x: scale, - scale_y: scale, - around_center: None, - paint: transform.paint()?, - } - } - Self::VarScaleUniform(transform) => { - let deltas = instance.var_deltas::<1>(transform.var_index_base()); - let scale = transform.scale().apply_delta(deltas[0]); - ResolvedPaint::Scale { - scale_x: scale, - scale_y: scale, - around_center: None, - paint: transform.paint()?, - } - } - Self::ScaleUniformAroundCenter(transform) => { - let scale = transform.scale().to_fixed(); - ResolvedPaint::Scale { - scale_x: scale, - scale_y: scale, - around_center: Some(Point::new( - transform.center_x().to_fixed(), - transform.center_y().to_fixed(), - )), - paint: transform.paint()?, - } - } - Self::VarScaleUniformAroundCenter(transform) => { - let deltas = instance.var_deltas::<3>(transform.var_index_base()); - let scale = transform.scale().apply_delta(deltas[0]); - ResolvedPaint::Scale { - scale_x: scale, - scale_y: scale, - around_center: Some(Point::new( - transform.center_x().apply_delta(deltas[1]), - transform.center_y().apply_delta(deltas[2]), - )), - paint: transform.paint()?, - } - } - Self::Rotate(transform) => ResolvedPaint::Rotate { - angle: transform.angle().to_fixed(), - around_center: None, - paint: transform.paint()?, - }, - Self::VarRotate(transform) => { - let deltas = instance.var_deltas::<1>(transform.var_index_base()); - ResolvedPaint::Rotate { - angle: transform.angle().apply_delta(deltas[0]), - around_center: None, - paint: transform.paint()?, - } - } - Self::RotateAroundCenter(transform) => ResolvedPaint::Rotate { - angle: transform.angle().to_fixed(), - around_center: Some(Point::new( - transform.center_x().to_fixed(), - transform.center_y().to_fixed(), - )), - paint: transform.paint()?, - }, - Self::VarRotateAroundCenter(transform) => { - let deltas = instance.var_deltas::<3>(transform.var_index_base()); - ResolvedPaint::Rotate { - angle: transform.angle().apply_delta(deltas[0]), - around_center: Some(Point::new( - transform.center_x().apply_delta(deltas[1]), - transform.center_y().apply_delta(deltas[2]), - )), - paint: transform.paint()?, - } - } - Self::Skew(transform) => ResolvedPaint::Skew { - x_skew_angle: transform.x_skew_angle().to_fixed(), - y_skew_angle: transform.y_skew_angle().to_fixed(), - around_center: None, - paint: transform.paint()?, - }, - Self::VarSkew(transform) => { - let deltas = instance.var_deltas::<2>(transform.var_index_base()); - ResolvedPaint::Skew { - x_skew_angle: transform.x_skew_angle().apply_delta(deltas[0]), - y_skew_angle: transform.y_skew_angle().apply_delta(deltas[1]), - around_center: None, - paint: transform.paint()?, - } - } - Self::SkewAroundCenter(transform) => ResolvedPaint::Skew { - x_skew_angle: transform.x_skew_angle().to_fixed(), - y_skew_angle: transform.y_skew_angle().to_fixed(), - around_center: Some(Point::new( - transform.center_x().to_fixed(), - transform.center_y().to_fixed(), - )), - paint: transform.paint()?, - }, - Self::VarSkewAroundCenter(transform) => { - let deltas = instance.var_deltas::<4>(transform.var_index_base()); - ResolvedPaint::Skew { - x_skew_angle: transform.x_skew_angle().apply_delta(deltas[0]), - y_skew_angle: transform.y_skew_angle().apply_delta(deltas[1]), - around_center: Some(Point::new( - transform.center_x().apply_delta(deltas[2]), - transform.center_y().apply_delta(deltas[3]), - )), - paint: transform.paint()?, - } - } - Self::Composite(composite) => ResolvedPaint::Composite { - source_paint: composite.source_paint()?, - mode: composite.composite_mode(), - backdrop_paint: composite.backdrop_paint()?, - }, - }) - } -} - -/// Trait to augment all types in the `COLR` table with an appropriate -/// `apply_delta` method that both adds the delta and converts to 16.16 -/// fixed point. -/// -/// It might be worth moving this to `font-types` at some point, but -/// the conversion to `Fixed` may not be generally useful. -trait ApplyDelta { - fn apply_delta(self, delta: i32) -> Fixed; -} - -impl ApplyDelta for Fixed { - fn apply_delta(self, delta: i32) -> Fixed { - self + Fixed::from_bits(delta) - } -} - -impl ApplyDelta for F2Dot14 { - fn apply_delta(self, delta: i32) -> Fixed { - self.to_fixed() + F2Dot14::from_bits(delta as i16).to_fixed() - } -} - -impl ApplyDelta for FWord { - fn apply_delta(self, delta: i32) -> Fixed { - self.to_fixed() + Fixed::from_i32(delta) - } -} - -impl ApplyDelta for UfWord { - fn apply_delta(self, delta: i32) -> Fixed { - self.to_fixed() + Fixed::from_i32(delta) - } -} diff --git a/read-fonts/src/tables/variations.rs b/read-fonts/src/tables/variations.rs index ec2403abd..9cf921d85 100644 --- a/read-fonts/src/tables/variations.rs +++ b/read-fonts/src/tables/variations.rs @@ -577,6 +577,76 @@ impl<'a> ItemVariationStore<'a> { } Ok(((accum + 0x8000) >> 16) as i32) } + + /// Computes the delta value in floating point for the specified index and set + /// of normalized variation coordinates. + pub fn compute_float_delta( + &self, + index: DeltaSetIndex, + coords: &[F2Dot14], + ) -> Result { + let data = match self.item_variation_data().get(index.outer as usize) { + Some(data) => data?, + None => return Ok(FloatItemDelta::ZERO), + }; + let regions = self.variation_region_list()?.variation_regions(); + let region_indices = data.region_indexes(); + // Compute deltas in 64-bit floating point. + let mut accum = 0f64; + for (i, region_delta) in data.delta_set(index.inner).enumerate() { + let region_index = region_indices + .get(i) + .ok_or(ReadError::MalformedData( + "invalid delta sets in ItemVariationStore", + ))? + .get() as usize; + let region = regions.get(region_index)?; + let scalar = region.compute_scalar_f32(coords); + accum += region_delta as f64 * scalar as f64; + } + Ok(FloatItemDelta(accum)) + } +} + +/// Floating point item delta computed by an item variation store. +/// +/// These can be applied to types that implement [`FloatItemDeltaTarget`]. +#[derive(Copy, Clone, Default, Debug)] +pub struct FloatItemDelta(f64); + +impl FloatItemDelta { + pub const ZERO: Self = Self(0.0); +} + +/// Trait for applying floating point item deltas to target values. +pub trait FloatItemDeltaTarget { + fn apply_float_delta(&self, delta: FloatItemDelta) -> f32; +} + +impl FloatItemDeltaTarget for Fixed { + fn apply_float_delta(&self, delta: FloatItemDelta) -> f32 { + const FIXED_TO_FLOAT: f64 = 1.0 / 65536.0; + self.to_f32() + (delta.0 * FIXED_TO_FLOAT) as f32 + } +} + +impl FloatItemDeltaTarget for FWord { + fn apply_float_delta(&self, delta: FloatItemDelta) -> f32 { + self.to_i16() as f32 + delta.0 as f32 + } +} + +impl FloatItemDeltaTarget for UfWord { + fn apply_float_delta(&self, delta: FloatItemDelta) -> f32 { + self.to_u16() as f32 + delta.0 as f32 + } +} + +impl FloatItemDeltaTarget for F2Dot14 { + fn apply_float_delta(&self, delta: FloatItemDelta) -> f32 { + const F2DOT14_TO_FLOAT: f64 = 1.0 / 16384.0; + self.to_f32() + (delta.0 * F2DOT14_TO_FLOAT) as f32 + } } impl<'a> VariationRegion<'a> { @@ -604,6 +674,30 @@ impl<'a> VariationRegion<'a> { } scalar } + + /// Computes a floating point scalar value for this region and the + /// specified normalized variation coordinates. + pub fn compute_scalar_f32(&self, coords: &[F2Dot14]) -> f32 { + let mut scalar = 1.0; + for (i, axis_coords) in self.region_axes().iter().enumerate() { + let coord = coords.get(i).map(|coord| coord.to_f32()).unwrap_or(0.0); + let start = axis_coords.start_coord.get().to_f32(); + let end = axis_coords.end_coord.get().to_f32(); + let peak = axis_coords.peak_coord.get().to_f32(); + if start > peak || peak > end || peak == 0.0 || start < 0.0 && end > 0.0 { + continue; + } else if coord < start || coord > end { + return 0.0; + } else if coord == peak { + continue; + } else if coord < peak { + scalar = (scalar * (coord - start)) / (peak - start); + } else { + scalar = (scalar * (end - coord)) / (end - peak); + } + } + scalar + } } impl<'a> ItemVariationData<'a> { @@ -841,4 +935,51 @@ mod tests { // in which case the iterator just keeps incrementing until u16::MAX assert_eq!(all_points.iter().count(), u16::MAX as _); } + + /// We don't have a reference for our float delta computation, so this is + /// a sanity test to ensure that floating point deltas are within a + /// reasonable margin of the same in fixed point. + #[test] + fn ivs_float_deltas_nearly_match_fixed_deltas() { + let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap(); + let axis_count = font.fvar().unwrap().axis_count() as usize; + let colr = font.colr().unwrap(); + let ivs = colr.item_variation_store().unwrap().unwrap(); + // Generate a set of coords from -1 to 1 in 0.1 increments + for coord in (0..=20).map(|x| F2Dot14::from_f32((x as f32) / 10.0 - 1.0)) { + // For testing purposes, just splat the coord to all axes + let coords = vec![coord; axis_count]; + for (outer_ix, data) in ivs.item_variation_data().iter().enumerate() { + let outer_ix = outer_ix as u16; + let Some(Ok(data)) = data else { + continue; + }; + for inner_ix in 0..data.item_count() { + let delta_ix = DeltaSetIndex { + outer: outer_ix, + inner: inner_ix, + }; + // Check the deltas against all possible target values + let orig_delta = ivs.compute_delta(delta_ix, &coords).unwrap(); + let float_delta = ivs.compute_float_delta(delta_ix, &coords).unwrap(); + // For font unit types, we need to accept both rounding and + // truncation to account for the additional accumulation of + // fractional bits in floating point + assert!( + orig_delta == float_delta.0.round() as i32 + || orig_delta == float_delta.0.trunc() as i32 + ); + // For the fixed point types, check with an epsilon + const EPSILON: f32 = 1e12; + let fixed_delta = Fixed::ZERO.apply_float_delta(float_delta); + assert!((Fixed::from_bits(orig_delta).to_f32() - fixed_delta).abs() < EPSILON); + let f2dot14_delta = F2Dot14::ZERO.apply_float_delta(float_delta); + assert!( + (F2Dot14::from_bits(orig_delta as i16).to_f32() - f2dot14_delta).abs() + < EPSILON + ); + } + } + } + } } diff --git a/skrifa/src/color/instance.rs b/skrifa/src/color/instance.rs new file mode 100644 index 000000000..c678908a5 --- /dev/null +++ b/skrifa/src/color/instance.rs @@ -0,0 +1,593 @@ +//! COLR table instance. + +use read_fonts::{ + tables::{ + colr::*, + variations::{ + DeltaSetIndex, DeltaSetIndexMap, FloatItemDelta, FloatItemDeltaTarget, + ItemVariationStore, + }, + }, + types::{BoundingBox, F2Dot14, GlyphId, Point}, + ReadError, +}; + +use core::ops::{Deref, Range}; + +/// Unique paint identifier used for detecting cycles in the paint graph. +pub type PaintId = usize; + +/// Combination of a `COLR` table and a location in variation space for +/// resolving paints. +/// +/// See [`resolve_paint`], [`ColorStops::resolve`] and [`resolve_clip_box`]. +#[derive(Clone)] +pub struct ColrInstance<'a> { + colr: Colr<'a>, + index_map: Option>, + var_store: Option>, + coords: &'a [F2Dot14], +} + +impl<'a> ColrInstance<'a> { + /// Creates a new instance for the given `COLR` table and normalized variation + /// coordinates. + pub fn new(colr: Colr<'a>, coords: &'a [F2Dot14]) -> Self { + let index_map = colr.var_index_map().and_then(|res| res.ok()); + let var_store = colr.item_variation_store().and_then(|res| res.ok()); + Self { + colr, + coords, + index_map, + var_store, + } + } + + /// Computes a sequence of N variation deltas starting at the given + /// `var_base` index. + fn var_deltas(&self, var_index_base: u32) -> [FloatItemDelta; N] { + // Magic value that indicates deltas should not be applied. + const NO_VARIATION_DELTAS: u32 = 0xFFFFFFFF; + // Note: FreeType never returns an error for these lookups, so + // we do the same and just `unwrap_or_default` on var store + // errors. + // See + let mut deltas = [FloatItemDelta::ZERO; N]; + if self.coords.is_empty() + || self.var_store.is_none() + || var_index_base == NO_VARIATION_DELTAS + { + return deltas; + } + let var_store = self.var_store.as_ref().unwrap(); + if let Some(index_map) = self.index_map.as_ref() { + for (i, delta) in deltas.iter_mut().enumerate() { + let var_index = var_index_base + i as u32; + if let Ok(delta_ix) = index_map.get(var_index) { + *delta = var_store + .compute_float_delta(delta_ix, self.coords) + .unwrap_or_default(); + } + } + } else { + for (i, delta) in deltas.iter_mut().enumerate() { + let var_index = var_index_base + i as u32; + // If we don't have a var index map, use our index as the inner + // component and set the outer to 0. + let delta_ix = DeltaSetIndex { + outer: 0, + inner: var_index as u16, + }; + *delta = var_store + .compute_float_delta(delta_ix, self.coords) + .unwrap_or_default(); + } + } + deltas + } +} + +impl<'a> Deref for ColrInstance<'a> { + type Target = Colr<'a>; + + fn deref(&self) -> &Self::Target { + &self.colr + } +} + +/// Resolves a clip box, applying variation deltas using the given +/// instance. +pub fn resolve_clip_box(instance: &ColrInstance, clip_box: &ClipBox) -> BoundingBox { + match clip_box { + ClipBox::Format1(cbox) => BoundingBox { + x_min: cbox.x_min().to_i16() as f32, + y_min: cbox.y_min().to_i16() as f32, + x_max: cbox.x_max().to_i16() as f32, + y_max: cbox.y_max().to_i16() as f32, + }, + ClipBox::Format2(cbox) => { + let deltas = instance.var_deltas::<4>(cbox.var_index_base()); + BoundingBox { + x_min: cbox.x_min().apply_float_delta(deltas[0]), + y_min: cbox.y_min().apply_float_delta(deltas[1]), + x_max: cbox.x_max().apply_float_delta(deltas[2]), + y_max: cbox.y_max().apply_float_delta(deltas[3]), + } + } + } +} + +/// Simplified version of a [`ColorStop`] or [`VarColorStop`] with applied +/// variation deltas. +#[derive(Clone, Debug)] +pub struct ResolvedColorStop { + pub offset: f32, + pub palette_index: u16, + pub alpha: f32, +} + +/// Collection of [`ColorStop`] or [`VarColorStop`]. +// Note: only one of these fields is used at any given time, but this structure +// was chosen over the obvious enum approach for simplicity in generating a +// single concrete type for the `impl Iterator` return type of the `resolve` +// method. +#[derive(Clone)] +pub struct ColorStops<'a> { + stops: &'a [ColorStop], + var_stops: &'a [VarColorStop], +} + +impl<'a> ColorStops<'a> { + pub fn len(&self) -> usize { + self.stops.len() + self.var_stops.len() + } +} + +impl<'a> From> for ColorStops<'a> { + fn from(value: ColorLine<'a>) -> Self { + Self { + stops: value.color_stops(), + var_stops: &[], + } + } +} + +impl<'a> From> for ColorStops<'a> { + fn from(value: VarColorLine<'a>) -> Self { + Self { + stops: &[], + var_stops: value.color_stops(), + } + } +} + +impl<'a> ColorStops<'a> { + /// Returns an iterator yielding resolved color stops with variation deltas + /// applied. + pub fn resolve( + &self, + instance: &'a ColrInstance<'a>, + ) -> impl Iterator + 'a { + self.stops + .iter() + .map(|stop| ResolvedColorStop { + offset: stop.stop_offset().to_f32(), + palette_index: stop.palette_index(), + alpha: stop.alpha().to_f32(), + }) + .chain(self.var_stops.iter().map(|stop| { + let deltas = instance.var_deltas::<2>(stop.var_index_base()); + ResolvedColorStop { + offset: stop.stop_offset().apply_float_delta(deltas[0]), + palette_index: stop.palette_index(), + alpha: stop.alpha().apply_float_delta(deltas[1]), + } + })) + } +} + +/// Simplified version of `Paint` with applied variation deltas. +/// +/// These are constructed with the [`resolve_paint`] function. +/// +/// This is roughly equivalent to FreeType's +/// [`FT_COLR_Paint`](https://freetype.org/freetype2/docs/reference/ft2-layer_management.html#ft_colr_paint) +/// type. +pub enum ResolvedPaint<'a> { + ColrLayers { + range: Range, + }, + Solid { + palette_index: u16, + alpha: f32, + }, + LinearGradient { + x0: f32, + y0: f32, + x1: f32, + y1: f32, + x2: f32, + y2: f32, + color_stops: ColorStops<'a>, + extend: Extend, + }, + RadialGradient { + x0: f32, + y0: f32, + radius0: f32, + x1: f32, + y1: f32, + radius1: f32, + color_stops: ColorStops<'a>, + extend: Extend, + }, + SweepGradient { + center_x: f32, + center_y: f32, + start_angle: f32, + end_angle: f32, + color_stops: ColorStops<'a>, + extend: Extend, + }, + Glyph { + glyph_id: GlyphId, + paint: Paint<'a>, + }, + ColrGlyph { + glyph_id: GlyphId, + }, + Transform { + xx: f32, + yx: f32, + xy: f32, + yy: f32, + dx: f32, + dy: f32, + paint: Paint<'a>, + }, + Translate { + dx: f32, + dy: f32, + paint: Paint<'a>, + }, + Scale { + scale_x: f32, + scale_y: f32, + around_center: Option>, + paint: Paint<'a>, + }, + Rotate { + angle: f32, + around_center: Option>, + paint: Paint<'a>, + }, + Skew { + x_skew_angle: f32, + y_skew_angle: f32, + around_center: Option>, + paint: Paint<'a>, + }, + Composite { + source_paint: Paint<'a>, + mode: CompositeMode, + backdrop_paint: Paint<'a>, + }, +} + +/// Resolves this paint with the given instance. +/// +/// Resolving means that all numeric values are converted to 32-bit floating +/// point, variation deltas are applied (also computed fully in floating +/// point), and the various transform paints are collapsed into a single value +/// for their category (transform, translate, scale, rotate and skew). +/// +/// This provides a simpler type for consumers that are more interested +/// in extracting the semantics of the graph rather than working with the +/// raw encoded structures. +pub fn resolve_paint<'a>( + instance: &ColrInstance<'a>, + paint: &Paint<'a>, +) -> Result, ReadError> { + Ok(match paint { + Paint::ColrLayers(layers) => { + let start = layers.first_layer_index() as usize; + ResolvedPaint::ColrLayers { + range: start..start + layers.num_layers() as usize, + } + } + Paint::Solid(solid) => ResolvedPaint::Solid { + palette_index: solid.palette_index(), + alpha: solid.alpha().to_f32(), + }, + Paint::VarSolid(solid) => { + let deltas = instance.var_deltas::<1>(solid.var_index_base()); + ResolvedPaint::Solid { + palette_index: solid.palette_index(), + alpha: solid.alpha().apply_float_delta(deltas[0]), + } + } + Paint::LinearGradient(gradient) => { + let color_line = gradient.color_line()?; + let extend = color_line.extend(); + ResolvedPaint::LinearGradient { + x0: gradient.x0().to_i16() as f32, + y0: gradient.y0().to_i16() as f32, + x1: gradient.x1().to_i16() as f32, + y1: gradient.y1().to_i16() as f32, + x2: gradient.x2().to_i16() as f32, + y2: gradient.y2().to_i16() as f32, + color_stops: color_line.into(), + extend, + } + } + Paint::VarLinearGradient(gradient) => { + let color_line = gradient.color_line()?; + let extend = color_line.extend(); + let deltas = instance.var_deltas::<6>(gradient.var_index_base()); + ResolvedPaint::LinearGradient { + x0: gradient.x0().apply_float_delta(deltas[0]), + y0: gradient.y0().apply_float_delta(deltas[1]), + x1: gradient.x1().apply_float_delta(deltas[2]), + y1: gradient.y1().apply_float_delta(deltas[3]), + x2: gradient.x2().apply_float_delta(deltas[4]), + y2: gradient.y2().apply_float_delta(deltas[5]), + color_stops: color_line.into(), + extend, + } + } + Paint::RadialGradient(gradient) => { + let color_line = gradient.color_line()?; + let extend = color_line.extend(); + ResolvedPaint::RadialGradient { + x0: gradient.x0().to_i16() as f32, + y0: gradient.y0().to_i16() as f32, + radius0: gradient.radius0().to_u16() as f32, + x1: gradient.x1().to_i16() as f32, + y1: gradient.y1().to_i16() as f32, + radius1: gradient.radius1().to_u16() as f32, + color_stops: color_line.into(), + extend, + } + } + Paint::VarRadialGradient(gradient) => { + let color_line = gradient.color_line()?; + let extend = color_line.extend(); + let deltas = instance.var_deltas::<6>(gradient.var_index_base()); + ResolvedPaint::RadialGradient { + x0: gradient.x0().apply_float_delta(deltas[0]), + y0: gradient.y0().apply_float_delta(deltas[1]), + radius0: gradient.radius0().apply_float_delta(deltas[2]), + x1: gradient.x1().apply_float_delta(deltas[3]), + y1: gradient.y1().apply_float_delta(deltas[4]), + radius1: gradient.radius1().apply_float_delta(deltas[5]), + color_stops: color_line.into(), + extend, + } + } + Paint::SweepGradient(gradient) => { + let color_line = gradient.color_line()?; + let extend = color_line.extend(); + ResolvedPaint::SweepGradient { + center_x: gradient.center_x().to_i16() as f32, + center_y: gradient.center_y().to_i16() as f32, + start_angle: gradient.start_angle().to_f32(), + end_angle: gradient.end_angle().to_f32(), + color_stops: color_line.into(), + extend, + } + } + Paint::VarSweepGradient(gradient) => { + let color_line = gradient.color_line()?; + let extend = color_line.extend(); + let deltas = instance.var_deltas::<4>(gradient.var_index_base()); + ResolvedPaint::SweepGradient { + center_x: gradient.center_x().apply_float_delta(deltas[0]), + center_y: gradient.center_y().apply_float_delta(deltas[1]), + start_angle: gradient.start_angle().apply_float_delta(deltas[2]), + end_angle: gradient.end_angle().apply_float_delta(deltas[3]), + color_stops: color_line.into(), + extend, + } + } + Paint::Glyph(glyph) => ResolvedPaint::Glyph { + glyph_id: glyph.glyph_id(), + paint: glyph.paint()?, + }, + Paint::ColrGlyph(glyph) => ResolvedPaint::ColrGlyph { + glyph_id: glyph.glyph_id(), + }, + Paint::Transform(transform) => { + let affine = transform.transform()?; + let paint = transform.paint()?; + ResolvedPaint::Transform { + xx: affine.xx().to_f32(), + yx: affine.yx().to_f32(), + xy: affine.xy().to_f32(), + yy: affine.yy().to_f32(), + dx: affine.dx().to_f32(), + dy: affine.dy().to_f32(), + paint, + } + } + Paint::VarTransform(transform) => { + let affine = transform.transform()?; + let paint = transform.paint()?; + let deltas = instance.var_deltas::<6>(affine.var_index_base()); + ResolvedPaint::Transform { + xx: affine.xx().apply_float_delta(deltas[0]), + yx: affine.yx().apply_float_delta(deltas[1]), + xy: affine.xy().apply_float_delta(deltas[2]), + yy: affine.yy().apply_float_delta(deltas[3]), + dx: affine.dx().apply_float_delta(deltas[4]), + dy: affine.dy().apply_float_delta(deltas[5]), + paint, + } + } + Paint::Translate(transform) => ResolvedPaint::Translate { + dx: transform.dx().to_i16() as f32, + dy: transform.dy().to_i16() as f32, + paint: transform.paint()?, + }, + Paint::VarTranslate(transform) => { + let deltas = instance.var_deltas::<2>(transform.var_index_base()); + ResolvedPaint::Translate { + dx: transform.dx().apply_float_delta(deltas[0]), + dy: transform.dy().apply_float_delta(deltas[1]), + paint: transform.paint()?, + } + } + Paint::Scale(transform) => ResolvedPaint::Scale { + scale_x: transform.scale_x().to_f32(), + scale_y: transform.scale_y().to_f32(), + around_center: None, + paint: transform.paint()?, + }, + Paint::VarScale(transform) => { + let deltas = instance.var_deltas::<2>(transform.var_index_base()); + ResolvedPaint::Scale { + scale_x: transform.scale_x().apply_float_delta(deltas[0]), + scale_y: transform.scale_y().apply_float_delta(deltas[1]), + around_center: None, + paint: transform.paint()?, + } + } + Paint::ScaleAroundCenter(transform) => ResolvedPaint::Scale { + scale_x: transform.scale_x().to_f32(), + scale_y: transform.scale_y().to_f32(), + around_center: Some(Point::new( + transform.center_x().to_i16() as f32, + transform.center_y().to_i16() as f32, + )), + paint: transform.paint()?, + }, + Paint::VarScaleAroundCenter(transform) => { + let deltas = instance.var_deltas::<4>(transform.var_index_base()); + ResolvedPaint::Scale { + scale_x: transform.scale_x().apply_float_delta(deltas[0]), + scale_y: transform.scale_y().apply_float_delta(deltas[1]), + around_center: Some(Point::new( + transform.center_x().apply_float_delta(deltas[2]), + transform.center_y().apply_float_delta(deltas[3]), + )), + paint: transform.paint()?, + } + } + Paint::ScaleUniform(transform) => { + let scale = transform.scale().to_f32(); + ResolvedPaint::Scale { + scale_x: scale, + scale_y: scale, + around_center: None, + paint: transform.paint()?, + } + } + Paint::VarScaleUniform(transform) => { + let deltas = instance.var_deltas::<1>(transform.var_index_base()); + let scale = transform.scale().apply_float_delta(deltas[0]); + ResolvedPaint::Scale { + scale_x: scale, + scale_y: scale, + around_center: None, + paint: transform.paint()?, + } + } + Paint::ScaleUniformAroundCenter(transform) => { + let scale = transform.scale().to_f32(); + ResolvedPaint::Scale { + scale_x: scale, + scale_y: scale, + around_center: Some(Point::new( + transform.center_x().to_i16() as f32, + transform.center_y().to_i16() as f32, + )), + paint: transform.paint()?, + } + } + Paint::VarScaleUniformAroundCenter(transform) => { + let deltas = instance.var_deltas::<3>(transform.var_index_base()); + let scale = transform.scale().apply_float_delta(deltas[0]); + ResolvedPaint::Scale { + scale_x: scale, + scale_y: scale, + around_center: Some(Point::new( + transform.center_x().apply_float_delta(deltas[1]), + transform.center_y().apply_float_delta(deltas[2]), + )), + paint: transform.paint()?, + } + } + Paint::Rotate(transform) => ResolvedPaint::Rotate { + angle: transform.angle().to_f32(), + around_center: None, + paint: transform.paint()?, + }, + Paint::VarRotate(transform) => { + let deltas = instance.var_deltas::<1>(transform.var_index_base()); + ResolvedPaint::Rotate { + angle: transform.angle().apply_float_delta(deltas[0]), + around_center: None, + paint: transform.paint()?, + } + } + Paint::RotateAroundCenter(transform) => ResolvedPaint::Rotate { + angle: transform.angle().to_f32(), + around_center: Some(Point::new( + transform.center_x().to_i16() as f32, + transform.center_y().to_i16() as f32, + )), + paint: transform.paint()?, + }, + Paint::VarRotateAroundCenter(transform) => { + let deltas = instance.var_deltas::<3>(transform.var_index_base()); + ResolvedPaint::Rotate { + angle: transform.angle().apply_float_delta(deltas[0]), + around_center: Some(Point::new( + transform.center_x().apply_float_delta(deltas[1]), + transform.center_y().apply_float_delta(deltas[2]), + )), + paint: transform.paint()?, + } + } + Paint::Skew(transform) => ResolvedPaint::Skew { + x_skew_angle: transform.x_skew_angle().to_f32(), + y_skew_angle: transform.y_skew_angle().to_f32(), + around_center: None, + paint: transform.paint()?, + }, + Paint::VarSkew(transform) => { + let deltas = instance.var_deltas::<2>(transform.var_index_base()); + ResolvedPaint::Skew { + x_skew_angle: transform.x_skew_angle().apply_float_delta(deltas[0]), + y_skew_angle: transform.y_skew_angle().apply_float_delta(deltas[1]), + around_center: None, + paint: transform.paint()?, + } + } + Paint::SkewAroundCenter(transform) => ResolvedPaint::Skew { + x_skew_angle: transform.x_skew_angle().to_f32(), + y_skew_angle: transform.y_skew_angle().to_f32(), + around_center: Some(Point::new( + transform.center_x().to_i16() as f32, + transform.center_y().to_i16() as f32, + )), + paint: transform.paint()?, + }, + Paint::VarSkewAroundCenter(transform) => { + let deltas = instance.var_deltas::<4>(transform.var_index_base()); + ResolvedPaint::Skew { + x_skew_angle: transform.x_skew_angle().apply_float_delta(deltas[0]), + y_skew_angle: transform.y_skew_angle().apply_float_delta(deltas[1]), + around_center: Some(Point::new( + transform.center_x().apply_float_delta(deltas[2]), + transform.center_y().apply_float_delta(deltas[3]), + )), + paint: transform.paint()?, + } + } + Paint::Composite(composite) => ResolvedPaint::Composite { + source_paint: composite.source_paint()?, + mode: composite.composite_mode(), + backdrop_paint: composite.backdrop_paint()?, + }, + }) +} diff --git a/skrifa/src/color/mod.rs b/skrifa/src/color/mod.rs new file mode 100644 index 000000000..2ce4458d2 --- /dev/null +++ b/skrifa/src/color/mod.rs @@ -0,0 +1,6 @@ +//! Color outlines. + +// Temporary until remaining API is filled in. +#![allow(dead_code)] + +mod instance; diff --git a/skrifa/src/lib.rs b/skrifa/src/lib.rs index b555c5c26..6949cb617 100644 --- a/skrifa/src/lib.rs +++ b/skrifa/src/lib.rs @@ -23,6 +23,7 @@ pub mod scale; pub mod setting; pub mod string; +mod color; mod provider; mod small_array; mod variation;