From 415f9c4dc80650087473f5a327cfe288d79ccc32 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Wed, 8 Nov 2023 22:26:00 -0500 Subject: [PATCH] [skrifa] floating point COLR Internal interface for color outline loading in full floating point. --- read-fonts/src/tables/variations.rs | 84 ++++ skrifa/src/scale/colr/instance.rs | 653 ++++++++++++++++++++++++++++ skrifa/src/scale/colr/mod.rs | 4 + skrifa/src/scale/colr/print.rs | 149 +++++++ skrifa/src/scale/mod.rs | 3 + 5 files changed, 893 insertions(+) create mode 100644 skrifa/src/scale/colr/instance.rs create mode 100644 skrifa/src/scale/colr/mod.rs create mode 100644 skrifa/src/scale/colr/print.rs diff --git a/read-fonts/src/tables/variations.rs b/read-fonts/src/tables/variations.rs index ec2403abd..7ed90cec6 100644 --- a/read-fonts/src/tables/variations.rs +++ b/read-fonts/src/tables/variations.rs @@ -577,6 +577,66 @@ 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. + /// + /// This requires a type parameter specifying the type of target value for the + /// delta. + pub fn compute_delta_f32( + &self, + index: DeltaSetIndex, + coords: &[F2Dot14], + ) -> Result { + let data = match self.item_variation_data().get(index.outer as usize) { + Some(data) => data?, + None => return Ok(0.0), + }; + let regions = self.variation_region_list()?.variation_regions(); + let region_indices = data.region_indexes(); + // Compute deltas in 32-bit floating point. + let mut accum = 0f32; + 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 += T::delta_to_f32(region_delta) * scalar; + } + Ok(accum) + } +} + +pub trait ItemDeltaTarget { + fn delta_to_f32(delta: i32) -> f32; +} + +impl ItemDeltaTarget for Fixed { + fn delta_to_f32(delta: i32) -> f32 { + Fixed::from_bits(delta).to_f32() + } +} + +impl ItemDeltaTarget for FWord { + fn delta_to_f32(delta: i32) -> f32 { + delta as f32 + } +} + +impl ItemDeltaTarget for UfWord { + fn delta_to_f32(delta: i32) -> f32 { + delta as f32 + } +} + +impl ItemDeltaTarget for F2Dot14 { + fn delta_to_f32(delta: i32) -> f32 { + F2Dot14::from_bits(delta as i16).to_f32() + } } impl<'a> VariationRegion<'a> { @@ -604,6 +664,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> { diff --git a/skrifa/src/scale/colr/instance.rs b/skrifa/src/scale/colr/instance.rs new file mode 100644 index 000000000..e78bd3ce5 --- /dev/null +++ b/skrifa/src/scale/colr/instance.rs @@ -0,0 +1,653 @@ +//! COLR table instance. + +use read_fonts::{ + tables::{ + colr::*, + variations::{DeltaSetIndex, DeltaSetIndexMap, ItemVariationStore}, + }, + types::{BoundingBox, F2Dot14, FWord, Fixed, GlyphId, Point, UfWord}, + ReadError, +}; + +use core::ops::{Deref, Range}; + +/// Identifier used for representing a paint on the recursion blacklist. +pub type PaintId = usize; + +/// 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, tys: [DeltaTy; N]) -> [f32; 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.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(); + let compute_delta = |ty: &DeltaTy, delta_ix| { + match ty { + DeltaTy::FWord => var_store.compute_delta_f32::(delta_ix, self.coords), + DeltaTy::UfWord => var_store.compute_delta_f32::(delta_ix, self.coords), + DeltaTy::Fixed => var_store.compute_delta_f32::(delta_ix, self.coords), + DeltaTy::F2Dot14 => var_store.compute_delta_f32::(delta_ix, self.coords), + } + .unwrap_or_default() + }; + if let Some(index_map) = self.index_map.as_ref() { + for (i, (ty, delta)) in tys.iter().zip(deltas.iter_mut()).enumerate() { + let var_index = var_index_base + i as u32; + if let Ok(delta_ix) = index_map.get(var_index) { + *delta = compute_delta(ty, delta_ix); + } + } + } else { + for (i, (ty, delta)) in tys.iter().zip(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 = compute_delta(ty, delta_ix); + } + } + deltas + } +} + +#[derive(Copy, Clone)] +enum DeltaTy { + FWord, + UfWord, + Fixed, + F2Dot14, +} + +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(cbox.var_index_base(), [DeltaTy::FWord; 4]); + 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: 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. +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_f32(), + palette_index: stop.palette_index(), + alpha: stop.alpha().to_f32(), + }) + .chain(self.var_stops.iter().map(|stop| { + let deltas = instance.var_deltas(stop.var_index_base(), [DeltaTy::F2Dot14; 2]); + 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 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 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_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(solid.var_index_base(), [DeltaTy::F2Dot14]); + ResolvedPaint::Solid { + palette_index: solid.palette_index(), + alpha: solid.alpha().apply_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(gradient.var_index_base(), [DeltaTy::FWord; 6]); + 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, + } + } + 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(gradient.var_index_base(), [DeltaTy::FWord; 6]); + 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, + } + } + 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( + gradient.var_index_base(), + [ + DeltaTy::FWord, + DeltaTy::FWord, + DeltaTy::F2Dot14, + DeltaTy::F2Dot14, + ], + ); + 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, + } + } + 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(affine.var_index_base(), [DeltaTy::Fixed; 6]); + 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, + } + } + 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(transform.var_index_base(), [DeltaTy::FWord; 2]); + ResolvedPaint::Translate { + dx: transform.dx().apply_delta(deltas[0]), + dy: transform.dy().apply_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(transform.var_index_base(), [DeltaTy::F2Dot14; 2]); + 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()?, + } + } + 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( + transform.var_index_base(), + [ + DeltaTy::F2Dot14, + DeltaTy::F2Dot14, + DeltaTy::FWord, + DeltaTy::FWord, + ], + ); + 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()?, + } + } + 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(transform.var_index_base(), [DeltaTy::F2Dot14]); + let scale = transform.scale().apply_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( + transform.var_index_base(), + [DeltaTy::F2Dot14, DeltaTy::FWord, DeltaTy::FWord], + ); + 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()?, + } + } + Paint::Rotate(transform) => ResolvedPaint::Rotate { + angle: transform.angle().to_f32(), + around_center: None, + paint: transform.paint()?, + }, + Paint::VarRotate(transform) => { + let deltas = instance.var_deltas(transform.var_index_base(), [DeltaTy::F2Dot14]); + ResolvedPaint::Rotate { + angle: transform.angle().apply_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( + transform.var_index_base(), + [DeltaTy::F2Dot14, DeltaTy::FWord, DeltaTy::FWord], + ); + 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()?, + } + } + 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(transform.var_index_base(), [DeltaTy::F2Dot14; 2]); + 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()?, + } + } + 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( + transform.var_index_base(), + [ + DeltaTy::F2Dot14, + DeltaTy::F2Dot14, + DeltaTy::FWord, + DeltaTy::FWord, + ], + ); + 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()?, + } + } + Paint::Composite(composite) => ResolvedPaint::Composite { + source_paint: composite.source_paint()?, + mode: composite.composite_mode(), + backdrop_paint: composite.backdrop_paint()?, + }, + }) +} + +trait ApplyDelta { + fn apply_delta(self, delta: f32) -> f32; +} + +impl ApplyDelta for Fixed { + fn apply_delta(self, delta: f32) -> f32 { + self.to_f32() + delta + } +} + +impl ApplyDelta for F2Dot14 { + fn apply_delta(self, delta: f32) -> f32 { + self.to_f32() + delta + } +} + +impl ApplyDelta for FWord { + fn apply_delta(self, delta: f32) -> f32 { + self.to_i16() as f32 + delta + } +} + +impl ApplyDelta for UfWord { + fn apply_delta(self, delta: f32) -> f32 { + self.to_u16() as f32 + delta + } +} diff --git a/skrifa/src/scale/colr/mod.rs b/skrifa/src/scale/colr/mod.rs new file mode 100644 index 000000000..1a847e3b1 --- /dev/null +++ b/skrifa/src/scale/colr/mod.rs @@ -0,0 +1,4 @@ +mod instance; +mod print; + +pub use print::print_color_glyph; diff --git a/skrifa/src/scale/colr/print.rs b/skrifa/src/scale/colr/print.rs new file mode 100644 index 000000000..32aa3d44f --- /dev/null +++ b/skrifa/src/scale/colr/print.rs @@ -0,0 +1,149 @@ +use super::instance::{resolve_paint, ColorStops, ColrInstance, ResolvedPaint}; +use crate::prelude::{GlyphId, LocationRef}; +use read_fonts::tables::colr::{Colr, Paint}; + +pub fn print_color_glyph(colr: &Colr, glyph_id: GlyphId, location: LocationRef) { + let instance = ColrInstance::new(colr.clone(), location.coords()); + let paint = instance.v1_base_glyph(glyph_id).unwrap().unwrap().0; + print_paint(&instance, &paint, 0); +} + +fn print_paint(instance: &ColrInstance, paint: &Paint, depth: usize) { + let paint = resolve_paint(instance, paint).unwrap(); + for _ in 0..depth { + print!(" "); + } + match paint { + ResolvedPaint::Glyph { glyph_id, paint } => { + println!("Glyph {glyph_id}"); + print_paint(instance, &paint, depth + 1); + } + ResolvedPaint::ColrGlyph { glyph_id } => { + println!("ColrGlyph {glyph_id}"); + } + ResolvedPaint::ColrLayers { range } => { + println!("ColrLayers {:?}", range.clone()); + for i in range { + let paint = instance.v1_layer(i).unwrap().0; + print_paint(instance, &paint, depth + 1); + } + } + ResolvedPaint::Composite { + source_paint, + mode, + backdrop_paint, + } => { + println!("Composite {:?}", mode); + print_paint(instance, &backdrop_paint, depth + 1); + print_paint(instance, &source_paint, depth + 1); + } + ResolvedPaint::LinearGradient { + x0, + y0, + x1, + y1, + x2, + y2, + color_stops, + extend, + } => { + print!( + "LinearGradient p0: ({x0},{y0}) p1: ({x1},{y1}) p2: ({x2},{y2}) extend: {:?} ", + extend + ); + print_color_stops(instance, color_stops); + } + ResolvedPaint::RadialGradient { + x0, + y0, + radius0, + x1, + y1, + radius1, + color_stops, + extend, + } => { + print!("RadialGradient p0: ({x0},{y0}) r0: {radius0} p1: ({x1},{y1}) r1: {radius1} extend: {:?} ", extend); + print_color_stops(instance, color_stops); + } + ResolvedPaint::SweepGradient { + center_x, + center_y, + start_angle, + end_angle, + color_stops, + extend, + } => { + print!("SweepGradient center: ({center_x},{center_y}) angle: {start_angle}->{end_angle} extend: {:?} ", extend); + print_color_stops(instance, color_stops); + } + ResolvedPaint::Rotate { + angle, + around_center, + paint, + } => { + let center = around_center.unwrap_or_default(); + println!("Rotate angle: {angle} center: ({}, {})", center.x, center.y); + print_paint(instance, &paint, depth + 1); + } + ResolvedPaint::Scale { + scale_x, + scale_y, + around_center, + paint, + } => { + let center = around_center.unwrap_or_default(); + println!( + "Scale x: {scale_x} y: {scale_y} center: ({}, {})", + center.x, center.y + ); + print_paint(instance, &paint, depth + 1); + } + ResolvedPaint::Skew { + x_skew_angle, + y_skew_angle, + around_center, + paint, + } => { + let center = around_center.unwrap_or_default(); + println!( + "Skew x: {x_skew_angle} y: {y_skew_angle} center: ({}, {})", + center.x, center.y + ); + print_paint(instance, &paint, depth + 1); + } + ResolvedPaint::Solid { + palette_index, + alpha, + } => { + println!("Solid {palette_index} {alpha}"); + } + ResolvedPaint::Transform { + xx, + yx, + xy, + yy, + dx, + dy, + paint, + } => { + println!("Transform {xx} {yx} {xy} {yy} {dx} {dy}"); + print_paint(instance, &paint, depth + 1); + } + ResolvedPaint::Translate { dx, dy, paint } => { + println!("Translate x: {dx} y: {dy}"); + print_paint(instance, &paint, depth + 1); + } + } +} + +fn print_color_stops(instance: &ColrInstance, stops: ColorStops) { + print!("stops: "); + for (i, stop) in stops.resolve(instance).enumerate() { + if i > 0 { + print!(", "); + } + print!("({} {} {})", stop.offset, stop.palette_index, stop.alpha); + } + println!(); +} diff --git a/skrifa/src/scale/mod.rs b/skrifa/src/scale/mod.rs index 18ffb828d..5c7bebcd5 100644 --- a/skrifa/src/scale/mod.rs +++ b/skrifa/src/scale/mod.rs @@ -148,10 +148,13 @@ #![allow(dead_code)] mod cff; +mod colr; mod error; mod glyf; mod scaler; +pub use colr::print_color_glyph; + pub use read_fonts::types::Pen; pub use error::{Error, Result};