diff --git a/Cargo.lock b/Cargo.lock index ebf954e0..257d0435 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1764,6 +1764,7 @@ dependencies = [ "pathfinder_content 0.5.0", "pathfinder_geometry 0.5.1", "pathfinder_renderer 0.5.0", + "pathfinder_simd 0.5.0", "usvg 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/content/src/gradient.rs b/content/src/gradient.rs index ecae5ac2..11155970 100644 --- a/content/src/gradient.rs +++ b/content/src/gradient.rs @@ -12,6 +12,7 @@ use crate::sorted_vector::SortedVector; use crate::util; use pathfinder_color::ColorU; use pathfinder_geometry::line_segment::LineSegment2F; +use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::util as geometry_util; use pathfinder_simd::default::F32x2; @@ -22,14 +23,7 @@ use std::mem; #[derive(Clone, PartialEq, Debug)] pub struct Gradient { - /// The line this gradient runs along. - /// - /// If this is a radial gradient, this is the line that connects the two circles. It may have - /// zero-length in the case of simple radial gradients. - pub line: LineSegment2F, - /// For radial gradients, the radii of the start and endpoints respectively. If this is a - /// linear gradient, this is `None`. - pub radii: Option, + pub geometry: GradientGeometry, stops: SortedVector, } @@ -39,17 +33,43 @@ pub struct ColorStop { pub color: ColorU, } +#[derive(Clone, PartialEq, Debug)] +pub enum GradientGeometry { + Linear(LineSegment2F), + Radial { + /// The line that connects the two circles. It may have zero length for simple radial + /// gradients. + line: LineSegment2F, + /// The radii of the two circles. The first value may be zero. + radii: F32x2, + /// Transform from radial gradient space into screen space. + /// + /// Like `gradientTransform` in SVG. Note that this is the inverse of Cairo's gradient + /// transform. + transform: Transform2F, + } +} + impl Eq for Gradient {} impl Hash for Gradient { fn hash(&self, state: &mut H) where H: Hasher { - util::hash_line_segment(self.line, state); - match self.radii { - None => (0).hash(state), - Some(radii) => { + match self.geometry { + GradientGeometry::Linear(line) => { + (0).hash(state); + util::hash_line_segment(line, state); + } + GradientGeometry::Radial { line, radii, transform } => { (1).hash(state); + util::hash_line_segment(line, state); util::hash_f32(radii.x(), state); util::hash_f32(radii.y(), state); + util::hash_f32(transform.m11(), state); + util::hash_f32(transform.m12(), state); + util::hash_f32(transform.m13(), state); + util::hash_f32(transform.m21(), state); + util::hash_f32(transform.m22(), state); + util::hash_f32(transform.m23(), state); } } self.stops.hash(state); @@ -71,7 +91,7 @@ impl Hash for ColorStop { impl Gradient { #[inline] pub fn linear(line: LineSegment2F) -> Gradient { - Gradient { line, radii: None, stops: SortedVector::new() } + Gradient { geometry: GradientGeometry::Linear(line), stops: SortedVector::new() } } #[inline] @@ -81,7 +101,11 @@ impl Gradient { #[inline] pub fn radial(line: L, radii: F32x2) -> Gradient where L: RadialGradientLine { - Gradient { line: line.to_line(), radii: Some(radii), stops: SortedVector::new() } + let transform = Transform2F::default(); + Gradient { + geometry: GradientGeometry::Radial { line: line.to_line(), radii, transform }, + stops: SortedVector::new(), + } } #[inline] @@ -95,26 +119,6 @@ impl Gradient { self.add(ColorStop::new(color, offset)) } - #[inline] - pub fn line(&self) -> LineSegment2F { - self.line - } - - #[inline] - pub fn set_line(&mut self, line: LineSegment2F) { - self.line = line - } - - #[inline] - pub fn radii(&self) -> Option { - self.radii - } - - #[inline] - pub fn set_radii(&mut self, radii: Option) { - self.radii = radii - } - #[inline] pub fn stops(&self) -> &[ColorStop] { &self.stops.array @@ -160,6 +164,19 @@ impl Gradient { pub fn is_fully_transparent(&self) -> bool { self.stops.array.iter().all(|stop| stop.color.is_fully_transparent()) } + + pub fn apply_transform(&mut self, new_transform: Transform2F) { + if new_transform.is_identity() { + return; + } + + match self.geometry { + GradientGeometry::Linear(ref mut line) => *line = new_transform * *line, + GradientGeometry::Radial { ref mut transform, .. } => { + *transform = new_transform * *transform + } + } + } } impl ColorStop { diff --git a/geometry/src/line_segment.rs b/geometry/src/line_segment.rs index 2cc06be3..56707a4c 100644 --- a/geometry/src/line_segment.rs +++ b/geometry/src/line_segment.rs @@ -205,6 +205,11 @@ impl LineSegment2F { dx * dx + dy * dy } + #[inline] + pub fn length(self) -> f32 { + self.square_length().sqrt() + } + #[inline] pub fn vector(self) -> Vector2F { self.to() - self.from() diff --git a/geometry/src/transform2d.rs b/geometry/src/transform2d.rs index 8b139852..c29ae7b9 100644 --- a/geometry/src/transform2d.rs +++ b/geometry/src/transform2d.rs @@ -184,10 +184,10 @@ impl Transform2F { } #[inline] - pub fn row_major(m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32) -> Transform2F { + pub fn row_major(m11: f32, m12: f32, m13: f32, m21: f32, m22: f32, m23: f32) -> Transform2F { Transform2F { matrix: Matrix2x2F::row_major(m11, m12, m21, m22), - vector: Vector2F::new(m31, m32), + vector: Vector2F::new(m13, m23), } } @@ -242,11 +242,11 @@ impl Transform2F { self.matrix.m22() } #[inline] - pub fn m31(&self) -> f32 { + pub fn m13(&self) -> f32 { self.vector.x() } #[inline] - pub fn m32(&self) -> f32 { + pub fn m23(&self) -> f32 { self.vector.y() } diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 9346aeff..29ebd585 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -504,8 +504,8 @@ where f16::from_f32(entry.color_0_transform.m21()), f16::from_f32(entry.color_0_transform.m12()), f16::from_f32(entry.color_0_transform.m22()), - f16::from_f32(entry.color_0_transform.m31()), - f16::from_f32(entry.color_0_transform.m32()), + f16::from_f32(entry.color_0_transform.m13()), + f16::from_f32(entry.color_0_transform.m23()), f16::default(), f16::default(), f16::from_f32(base_color.r()), diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index 5166b4b4..8d876f3c 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -15,15 +15,16 @@ use crate::scene::{RenderTarget, SceneId}; use hashbrown::HashMap; use pathfinder_color::ColorU; use pathfinder_content::effects::{Filter, PatternFilter}; -use pathfinder_content::gradient::Gradient; +use pathfinder_content::gradient::{Gradient, GradientGeometry}; use pathfinder_content::pattern::{Pattern, PatternSource}; use pathfinder_content::render_target::RenderTargetId; use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::{RectF, RectI}; -use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F}; +use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i}; use pathfinder_gpu::TextureSamplingFlags; -use pathfinder_simd::default::F32x2; +use pathfinder_simd::default::{F32x2, F32x4}; +use std::f32; use std::fmt::{self, Debug, Formatter}; use std::sync::Arc; @@ -81,10 +82,7 @@ pub enum PaintCompositeOp { impl Debug for PaintContents { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { match *self { - PaintContents::Gradient(_) => { - // TODO(pcwalton) - write!(formatter, "(gradient)") - } + PaintContents::Gradient(ref gradient) => gradient.fmt(formatter), PaintContents::Pattern(ref pattern) => pattern.fmt(formatter), } } @@ -185,12 +183,7 @@ impl Paint { if let Some(ref mut overlay) = self.overlay { match overlay.contents { - PaintContents::Gradient(ref mut gradient) => { - gradient.set_line(*transform * gradient.line()); - if let Some(radii) = gradient.radii() { - gradient.set_radii(Some(radii * transform.extract_scale().0)); - } - } + PaintContents::Gradient(ref mut gradient) => gradient.apply_transform(*transform), PaintContents::Pattern(ref mut pattern) => pattern.apply_transform(*transform), } } @@ -300,6 +293,8 @@ pub struct PaintMetadata { pub struct PaintColorTextureMetadata { /// The location of the paint. pub location: TextureLocation, + /// The scale for the page this paint is on. + pub page_scale: Vector2F, /// The transform to apply to screen coordinates to translate them into UVs. pub transform: Transform2F, /// The sampling mode for the texture. @@ -373,13 +368,15 @@ impl Palette { match overlay.contents { PaintContents::Gradient(ref gradient) => { // FIXME(pcwalton): The gradient size might not be big enough. Detect this. + let location = gradient_tile_builder.allocate(allocator, gradient); PaintColorTextureMetadata { - location: gradient_tile_builder.allocate(allocator, gradient), + location, + page_scale: allocator.page_scale(location.page), sampling_flags: TextureSamplingFlags::empty(), - filter: match gradient.radii() { - None => PaintFilter::None, - Some(radii) => { - PaintFilter::RadialGradient { line: gradient.line(), radii } + filter: match gradient.geometry { + GradientGeometry::Linear(_) => PaintFilter::None, + GradientGeometry::Radial { line, radii, .. } => { + PaintFilter::RadialGradient { line, radii } } }, transform: Transform2F::default(), @@ -424,6 +421,7 @@ impl Palette { PaintColorTextureMetadata { location, + page_scale: allocator.page_scale(location.page), sampling_flags, filter, transform: Transform2F::default(), @@ -455,36 +453,27 @@ impl Palette { metadata but no overlay?") .contents { PaintContents::Gradient(Gradient { - line: gradient_line, - radii: None, + geometry: GradientGeometry::Linear(gradient_line), .. }) => { + // Project gradient line onto (0.0-1.0, v0). let v0 = texture_rect.to_f32().center().y() * texture_scale.y(); - let length_inv = 1.0 / gradient_line.square_length(); - let (p0, d) = (gradient_line.from(), gradient_line.vector()); - Transform2F { - matrix: Matrix2x2F::row_major( - d.x(), d.y(), 0.0, 0.0).scale(length_inv), - vector: Vector2F::new(-p0.dot(d) * length_inv, v0), - } * render_transform - } - PaintContents::Gradient(Gradient { radii: Some(_), .. }) => { - let texture_origin_uv = rect_to_inset_uv(texture_rect, texture_scale).origin(); - let gradient_tile_scale = texture_scale * (GRADIENT_TILE_LENGTH - 1) as - f32; - Transform2F { - matrix: Matrix2x2F::from_scale(gradient_tile_scale), - vector: texture_origin_uv, - } * render_transform + let dp = gradient_line.vector(); + let m0 = dp.0.concat_xy_xy(dp.0) / F32x4::splat(gradient_line.square_length()); + let m13 = m0.zw() * -gradient_line.from().0; + Transform2F::row_major(m0.x(), m0.y(), m13.x() + m13.y(), 0.0, 0.0, v0) } + PaintContents::Gradient(Gradient { + geometry: GradientGeometry::Radial { ref transform, .. }, + .. + }) => transform.inverse(), PaintContents::Pattern(ref pattern) => { match pattern.source() { PatternSource::Image(_) => { let texture_origin_uv = rect_to_uv(texture_rect, texture_scale).origin(); - Transform2F::from_translation(texture_origin_uv) * - Transform2F::from_scale(texture_scale) * - pattern.transform().inverse() * render_transform + Transform2F::from_scale(texture_scale).translate(texture_origin_uv) * + pattern.transform().inverse() } PatternSource::RenderTarget { .. } => { // FIXME(pcwalton): Only do this in GL, not Metal! @@ -492,11 +481,12 @@ impl Palette { rect_to_uv(texture_rect, texture_scale).lower_left(); Transform2F::from_translation(texture_origin_uv) * Transform2F::from_scale(texture_scale * vec2f(1.0, -1.0)) * - pattern.transform().inverse() * render_transform + pattern.transform().inverse() } } } - } + }; + color_texture_metadata.transform *= render_transform; } // Create texture metadata. @@ -610,8 +600,10 @@ impl PaintMetadata { match color_metadata.filter { PaintFilter::None => Filter::None, PaintFilter::RadialGradient { line, radii } => { - let uv_origin = color_metadata.transform.vector; - Filter::RadialGradient { line, radii, uv_origin } + let uv_rect = rect_to_uv(color_metadata.location.rect, + color_metadata.page_scale).contract( + vec2f(0.0, color_metadata.page_scale.y() * 0.5)); + Filter::RadialGradient { line, radii, uv_origin: uv_rect.origin() } } PaintFilter::PatternFilter(pattern_filter) => { Filter::PatternFilter(pattern_filter) @@ -630,10 +622,6 @@ fn rect_to_uv(rect: RectI, texture_scale: Vector2F) -> RectF { rect.to_f32() * texture_scale } -fn rect_to_inset_uv(rect: RectI, texture_scale: Vector2F) -> RectF { - rect_to_uv(rect, texture_scale).contract(texture_scale * 0.5) -} - // Gradient allocation struct GradientTileBuilder { diff --git a/resources/shaders/gl3/tile.fs.glsl b/resources/shaders/gl3/tile.fs.glsl index 49904eb6..e5202a04 100644 --- a/resources/shaders/gl3/tile.fs.glsl +++ b/resources/shaders/gl3/tile.fs.glsl @@ -310,11 +310,7 @@ vec4 filterRadialGradient(vec2 colorTexCoord, vec2 lineFrom = filterParams0 . xy, lineVector = filterParams0 . zw; vec2 radii = filterParams1 . xy, uvOrigin = filterParams1 . zw; - - fragCoord . y = framebufferSize . y - fragCoord . y; - - - vec2 dP = fragCoord - lineFrom, dC = lineVector; + vec2 dP = colorTexCoord - lineFrom, dC = lineVector; float dR = radii . y - radii . x; float a = dot(dC, dC)- dR * dR; diff --git a/resources/shaders/metal/tile.fs.metal b/resources/shaders/metal/tile.fs.metal index 670c9e28..b422004a 100644 --- a/resources/shaders/metal/tile.fs.metal +++ b/resources/shaders/metal/tile.fs.metal @@ -26,6 +26,8 @@ struct spvDescriptorSetBuffer0 constant int* uCtrl [[id(15)]]; }; +constant float3 _1040 = {}; + struct main0_out { float4 oFragColor [[color(0)]]; @@ -41,12 +43,11 @@ struct main0_in // Implementation of the GLSL mod() function, which is slightly different than Metal fmod() template -inline Tx mod(Tx x, Ty y) +Tx mod(Tx x, Ty y) { return x - y * floor(x / y); } -static inline __attribute__((always_inline)) float sampleMask(thread const float& maskAlpha, thread const texture2d maskTexture, thread const sampler maskTextureSmplr, thread const float3& maskTexCoord, thread const int& maskCtrl) { if (maskCtrl == 0) @@ -65,14 +66,13 @@ float sampleMask(thread const float& maskAlpha, thread const texture2d ma return fast::min(maskAlpha, coverage); } -static inline __attribute__((always_inline)) float4 filterRadialGradient(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTextureSize, thread const float2& fragCoord, thread const float2& framebufferSize, thread const float4& filterParams0, thread const float4& filterParams1) { float2 lineFrom = filterParams0.xy; float2 lineVector = filterParams0.zw; float2 radii = filterParams1.xy; float2 uvOrigin = filterParams1.zw; - float2 dP = fragCoord - lineFrom; + float2 dP = colorTexCoord - lineFrom; float2 dC = lineVector; float dR = radii.y - radii.x; float a = dot(dC, dC) - (dR * dR); @@ -102,7 +102,6 @@ float4 filterRadialGradient(thread const float2& colorTexCoord, thread const tex return color; } -static inline __attribute__((always_inline)) float4 filterBlur(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTextureSize, thread const float4& filterParams0, thread const float4& filterParams1) { float2 srcOffsetScale = filterParams0.xy / colorTextureSize; @@ -127,13 +126,11 @@ float4 filterBlur(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTexCoord) { return colorTexture.sample(colorTextureSmplr, (colorTexCoord + float2(offset, 0.0))).x; } -static inline __attribute__((always_inline)) void filterTextSample9Tap(thread float4& outAlphaLeft, thread float& outAlphaCenter, thread float4& outAlphaRight, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTexCoord, thread const float4& kernel0, thread const float& onePixel) { bool wide = kernel0.x > 0.0; @@ -178,19 +175,16 @@ void filterTextSample9Tap(thread float4& outAlphaLeft, thread float& outAlphaCen outAlphaRight = float4(filterTextSample1Tap(param_10, colorTexture, colorTextureSmplr, param_11), filterTextSample1Tap(param_12, colorTexture, colorTextureSmplr, param_13), filterTextSample1Tap(param_14, colorTexture, colorTextureSmplr, param_15), _294); } -static inline __attribute__((always_inline)) float filterTextConvolve7Tap(thread const float4& alpha0, thread const float3& alpha1, thread const float4& kernel0) { return dot(alpha0, kernel0) + dot(alpha1, kernel0.zyx); } -static inline __attribute__((always_inline)) float filterTextGammaCorrectChannel(thread const float& bgColor, thread const float& fgColor, thread const texture2d gammaLUT, thread const sampler gammaLUTSmplr) { return gammaLUT.sample(gammaLUTSmplr, float2(fgColor, 1.0 - bgColor)).x; } -static inline __attribute__((always_inline)) float3 filterTextGammaCorrect(thread const float3& bgColor, thread const float3& fgColor, thread const texture2d gammaLUT, thread const sampler gammaLUTSmplr) { float param = bgColor.x; @@ -202,7 +196,6 @@ float3 filterTextGammaCorrect(thread const float3& bgColor, thread const float3& return float3(filterTextGammaCorrectChannel(param, param_1, gammaLUT, gammaLUTSmplr), filterTextGammaCorrectChannel(param_2, param_3, gammaLUT, gammaLUTSmplr), filterTextGammaCorrectChannel(param_4, param_5, gammaLUT, gammaLUTSmplr)); } -static inline __attribute__((always_inline)) float4 filterText(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const texture2d gammaLUT, thread const sampler gammaLUTSmplr, thread const float2& colorTextureSize, thread const float4& filterParams0, thread const float4& filterParams1, thread const float4& filterParams2) { float4 kernel0 = filterParams0; @@ -249,20 +242,17 @@ float4 filterText(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTexCoord) { return colorTexture.sample(colorTextureSmplr, colorTexCoord); } -static inline __attribute__((always_inline)) float4 filterNone(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr) { float2 param = colorTexCoord; return sampleColor(colorTexture, colorTextureSmplr, param); } -static inline __attribute__((always_inline)) float4 filterColor(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const texture2d gammaLUT, thread const sampler gammaLUTSmplr, thread const float2& colorTextureSize, thread const float2& fragCoord, thread const float2& framebufferSize, thread const float4& filterParams0, thread const float4& filterParams1, thread const float4& filterParams2, thread const int& colorFilter) { switch (colorFilter) @@ -299,7 +289,6 @@ float4 filterColor(thread const float2& colorTexCoord, thread const texture2d destTexture, thread const sampler destTextureSmplr, thread const float2& destTextureSize, thread const float2& fragCoord, thread const int& op) { if (op == 0) @@ -570,7 +548,6 @@ float4 composite(thread const float4& srcColor, thread const texture2d de return float4(((srcColor.xyz * (srcColor.w * (1.0 - destColor.w))) + (blendedRGB * (srcColor.w * destColor.w))) + (destColor.xyz * (1.0 - srcColor.w)), 1.0); } -static inline __attribute__((always_inline)) void calculateColor(thread const int& ctrl, thread texture2d uMaskTexture0, thread const sampler uMaskTexture0Smplr, thread float3& vMaskTexCoord0, thread texture2d uMaskTexture1, thread const sampler uMaskTexture1Smplr, thread float3& vMaskTexCoord1, thread float4& vBaseColor, thread float2& vColorTexCoord0, thread texture2d uColorTexture0, thread const sampler uColorTexture0Smplr, thread texture2d uGammaLUT, thread const sampler uGammaLUTSmplr, thread float2 uColorTexture0Size, thread float4& gl_FragCoord, thread float2 uFramebufferSize, thread float4 uFilterParams0, thread float4 uFilterParams1, thread float4 uFilterParams2, thread texture2d uDestTexture, thread const sampler uDestTextureSmplr, thread float4& oFragColor) { int maskCtrl0 = (ctrl >> 0) & 3; diff --git a/shaders/tile.fs.glsl b/shaders/tile.fs.glsl index 7eb2a10e..e795cb5c 100644 --- a/shaders/tile.fs.glsl +++ b/shaders/tile.fs.glsl @@ -294,7 +294,7 @@ vec4 filterText(vec2 colorTexCoord, // opposite sign // // | x y z w -// --------------+------------------------------------------- +// --------------+----------------------------------------------------- // filterParams0 | lineFrom.x lineFrom.y lineVector.x lineVector.y // filterParams1 | radii.x radii.y uvOrigin.x uvOrigin.y // filterParams2 | - - - - @@ -308,11 +308,7 @@ vec4 filterRadialGradient(vec2 colorTexCoord, vec2 lineFrom = filterParams0.xy, lineVector = filterParams0.zw; vec2 radii = filterParams1.xy, uvOrigin = filterParams1.zw; -#ifndef PF_ORIGIN_UPPER_LEFT - fragCoord.y = framebufferSize.y - fragCoord.y; -#endif - - vec2 dP = fragCoord - lineFrom, dC = lineVector; + vec2 dP = colorTexCoord - lineFrom, dC = lineVector; float dR = radii.y - radii.x; float a = dot(dC, dC) - dR * dR; diff --git a/svg/Cargo.toml b/svg/Cargo.toml index ff3fe4a6..1842c81d 100644 --- a/svg/Cargo.toml +++ b/svg/Cargo.toml @@ -29,3 +29,7 @@ version = "0.5" [dependencies.pathfinder_renderer] path = "../renderer" version = "0.5" + +[dependencies.pathfinder_simd] +path = "../simd" +version = "0.5" diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 6255b8b3..397501bc 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -16,6 +16,7 @@ extern crate bitflags; use hashbrown::HashMap; use pathfinder_color::ColorU; use pathfinder_content::fill::FillRule; +use pathfinder_content::gradient::{ColorStop, Gradient}; use pathfinder_content::outline::Outline; use pathfinder_content::segment::{Segment, SegmentFlags}; use pathfinder_content::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle}; @@ -26,11 +27,12 @@ use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::{Vector2F, vec2f}; use pathfinder_renderer::paint::Paint; use pathfinder_renderer::scene::{ClipPath, ClipPathId, DrawPath, Scene}; +use pathfinder_simd::default::F32x2; use std::fmt::{Display, Formatter, Result as FormatResult}; -use usvg::{Color as SvgColor, FillRule as UsvgFillRule, LineCap as UsvgLineCap}; +use usvg::{BaseGradient, Color as SvgColor, FillRule as UsvgFillRule, LineCap as UsvgLineCap}; use usvg::{LineJoin as UsvgLineJoin, Node, NodeExt, NodeKind, Opacity, Paint as UsvgPaint}; -use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform}; -use usvg::{Tree, Visibility}; +use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, SpreadMethod, Stop}; +use usvg::{Transform as UsvgTransform, Tree, Visibility}; const HAIRLINE_STROKE_WIDTH: f32 = 0.0333; @@ -38,24 +40,26 @@ pub struct BuiltSVG { pub scene: Scene, pub result_flags: BuildResultFlags, pub clip_paths: HashMap, + gradients: HashMap, } bitflags! { // NB: If you change this, make sure to update the `Display` // implementation as well. pub struct BuildResultFlags: u16 { - const UNSUPPORTED_FILTER_NODE = 0x0001; - const UNSUPPORTED_IMAGE_NODE = 0x0002; - const UNSUPPORTED_LINEAR_GRADIENT_NODE = 0x0004; - const UNSUPPORTED_MASK_NODE = 0x0008; - const UNSUPPORTED_PATTERN_NODE = 0x0010; - const UNSUPPORTED_RADIAL_GRADIENT_NODE = 0x0020; - const UNSUPPORTED_NESTED_SVG_NODE = 0x0040; - const UNSUPPORTED_MULTIPLE_CLIP_PATHS = 0x0080; - const UNSUPPORTED_LINK_PAINT = 0x0100; - const UNSUPPORTED_FILTER_ATTR = 0x0200; - const UNSUPPORTED_MASK_ATTR = 0x0400; - const UNSUPPORTED_STROKE_DASH = 0x0800; + const UNSUPPORTED_FILTER_NODE = 0x0001; + const UNSUPPORTED_IMAGE_NODE = 0x0002; + const UNSUPPORTED_LINEAR_GRADIENT_NODE = 0x0004; + const UNSUPPORTED_MASK_NODE = 0x0008; + const UNSUPPORTED_PATTERN_NODE = 0x0010; + const UNSUPPORTED_RADIAL_GRADIENT_NODE = 0x0020; + const UNSUPPORTED_NESTED_SVG_NODE = 0x0040; + const UNSUPPORTED_MULTIPLE_CLIP_PATHS = 0x0080; + const UNSUPPORTED_LINK_PAINT = 0x0100; + const UNSUPPORTED_FILTER_ATTR = 0x0200; + const UNSUPPORTED_MASK_ATTR = 0x0400; + const UNSUPPORTED_STROKE_DASH = 0x0800; + const UNSUPPORTED_GRADIENT_SPREAD_METHOD = 0x1000; } } @@ -74,6 +78,7 @@ impl BuiltSVG { scene, result_flags: BuildResultFlags::empty(), clip_paths: HashMap::new(), + gradients: HashMap::new(), }; let root = &tree.root(); @@ -197,6 +202,23 @@ impl BuiltSVG { self.process_node(&kid, &state, clip_outline); } } + NodeKind::LinearGradient(ref svg_linear_gradient) => { + let from = vec2f(svg_linear_gradient.x1 as f32, svg_linear_gradient.y1 as f32); + let to = vec2f(svg_linear_gradient.x2 as f32, svg_linear_gradient.y2 as f32); + let gradient = Gradient::linear_from_points(from, to); + self.add_gradient(gradient, + svg_linear_gradient.id.clone(), + &svg_linear_gradient.base) + } + NodeKind::RadialGradient(ref svg_radial_gradient) => { + let from = vec2f(svg_radial_gradient.fx as f32, svg_radial_gradient.fy as f32); + let to = vec2f(svg_radial_gradient.cx as f32, svg_radial_gradient.cy as f32); + let radii = F32x2::new(0.0, svg_radial_gradient.r.value() as f32); + let gradient = Gradient::radial(LineSegment2F::new(from, to), radii); + self.add_gradient(gradient, + svg_radial_gradient.id.clone(), + &svg_radial_gradient.base) + } NodeKind::Filter(..) => { self.result_flags .insert(BuildResultFlags::UNSUPPORTED_FILTER_NODE); @@ -205,10 +227,6 @@ impl BuiltSVG { self.result_flags .insert(BuildResultFlags::UNSUPPORTED_IMAGE_NODE); } - NodeKind::LinearGradient(..) => { - self.result_flags - .insert(BuildResultFlags::UNSUPPORTED_LINEAR_GRADIENT_NODE); - } NodeKind::Mask(..) => { self.result_flags .insert(BuildResultFlags::UNSUPPORTED_MASK_NODE); @@ -217,10 +235,6 @@ impl BuiltSVG { self.result_flags .insert(BuildResultFlags::UNSUPPORTED_PATTERN_NODE); } - NodeKind::RadialGradient(..) => { - self.result_flags - .insert(BuildResultFlags::UNSUPPORTED_RADIAL_GRADIENT_NODE); - } NodeKind::Svg(..) => { self.result_flags .insert(BuildResultFlags::UNSUPPORTED_NESTED_SVG_NODE); @@ -228,6 +242,24 @@ impl BuiltSVG { } } + fn add_gradient(&mut self, + mut gradient: Gradient, + id: String, + usvg_base_gradient: &BaseGradient) { + for stop in &usvg_base_gradient.stops { + gradient.add(ColorStop::from_usvg_stop(stop)); + } + + if usvg_base_gradient.spread_method != SpreadMethod::Pad { + self.result_flags.insert(BuildResultFlags::UNSUPPORTED_GRADIENT_SPREAD_METHOD); + } + + let transform = usvg_transform_to_transform_2d(&usvg_base_gradient.transform); + + // TODO(pcwalton): What should we do with `gradientUnits`? + self.gradients.insert(id, GradientInfo { gradient, transform }); + } + fn push_draw_path(&mut self, mut outline: Outline, name: String, @@ -239,6 +271,7 @@ impl BuiltSVG { let paint = Paint::from_svg_paint(paint, &state.transform, opacity, + &self.gradients, &mut self.result_flags); let style = self.scene.push_paint(&paint); let fill_rule = FillRule::from_usvg_fill_rule(fill_rule); @@ -293,6 +326,7 @@ trait PaintExt { fn from_svg_paint(svg_paint: &UsvgPaint, transform: &Transform2F, opacity: Opacity, + gradients: &HashMap, result_flags: &mut BuildResultFlags) -> Self; } @@ -302,18 +336,26 @@ impl PaintExt for Paint { fn from_svg_paint(svg_paint: &UsvgPaint, transform: &Transform2F, opacity: Opacity, + gradients: &HashMap, result_flags: &mut BuildResultFlags) -> Paint { - // TODO(pcwalton): Support gradients. - let mut paint = Paint::from_color(match *svg_paint { - UsvgPaint::Color(color) => ColorU::from_svg_color(color), - UsvgPaint::Link(_) => { - // TODO(pcwalton) - result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT); - ColorU::black() + let mut paint; + match *svg_paint { + UsvgPaint::Color(color) => paint = Paint::from_color(ColorU::from_svg_color(color)), + UsvgPaint::Link(ref id) => { + match gradients.get(id) { + Some(ref gradient_info) => { + paint = Paint::from_gradient(gradient_info.gradient.clone()); + paint.apply_transform(&(*transform * gradient_info.transform)); + } + None => { + // TODO(pcwalton) + result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT); + paint = Paint::from_color(ColorU::black()); + } + } } - }); - paint.apply_transform(transform); + } let mut base_color = paint.base_color().to_f32(); base_color.set_a(base_color.a() * opacity.value() as f32); @@ -329,14 +371,8 @@ fn usvg_rect_to_euclid_rect(rect: &UsvgRect) -> RectF { } fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2F { - Transform2F::row_major( - transform.a as f32, - transform.b as f32, - transform.c as f32, - transform.d as f32, - transform.e as f32, - transform.f as f32, - ) + Transform2F::row_major(transform.a as f32, transform.c as f32, transform.e as f32, + transform.b as f32, transform.d as f32, transform.f as f32) } struct UsvgPathToSegments @@ -477,6 +513,18 @@ impl FillRuleExt for FillRule { } } +trait ColorStopExt { + fn from_usvg_stop(usvg_stop: &Stop) -> Self; +} + +impl ColorStopExt for ColorStop { + fn from_usvg_stop(usvg_stop: &Stop) -> ColorStop { + let mut color = ColorU::from_svg_color(usvg_stop.color); + color.a = (usvg_stop.opacity.value() * 255.0) as u8; + ColorStop::new(color, usvg_stop.offset.value() as f32) + } +} + #[derive(Clone)] struct State { // Where paths are being appended to. @@ -503,3 +551,8 @@ enum PathDestination { Defs, Clip, } + +struct GradientInfo { + gradient: Gradient, + transform: Transform2F, +}