diff options
author | Paolo Borelli <pborelli@gnome.org> | 2019-11-17 11:33:58 +0100 |
---|---|---|
committer | Paolo Borelli <pborelli@gnome.org> | 2020-01-06 11:08:51 +0100 |
commit | eeec41b46e65159aeaf88732046177948e0b360d (patch) | |
tree | b72b028f9143640962552c6847be92eb939fbb11 | |
parent | 9561d60a9daa74bef222928f007f92421b26b233 (diff) | |
download | librsvg-eeec41b46e65159aeaf88732046177948e0b360d.tar.gz |
transform: add a Tranform type
Use an alias for cairo::Matrix and add a trait to ease transition
to the euclid crate.
-rw-r--r-- | rsvg_internals/src/aspect_ratio.rs | 7 | ||||
-rw-r--r-- | rsvg_internals/src/bbox.rs | 25 | ||||
-rw-r--r-- | rsvg_internals/src/drawing_ctx.rs | 53 | ||||
-rw-r--r-- | rsvg_internals/src/filter.rs | 3 | ||||
-rw-r--r-- | rsvg_internals/src/filters/context.rs | 47 | ||||
-rw-r--r-- | rsvg_internals/src/gradient.rs | 9 | ||||
-rw-r--r-- | rsvg_internals/src/node.rs | 22 | ||||
-rw-r--r-- | rsvg_internals/src/pattern.rs | 10 | ||||
-rw-r--r-- | rsvg_internals/src/rect.rs | 46 | ||||
-rw-r--r-- | rsvg_internals/src/transform.rs | 201 |
10 files changed, 219 insertions, 204 deletions
diff --git a/rsvg_internals/src/aspect_ratio.rs b/rsvg_internals/src/aspect_ratio.rs index f51b7a23..e477a8f7 100644 --- a/rsvg_internals/src/aspect_ratio.rs +++ b/rsvg_internals/src/aspect_ratio.rs @@ -25,6 +25,7 @@ use std::ops::Deref; use crate::error::*; use crate::parsers::Parse; use crate::rect::Rect; +use crate::transform::Transform; use crate::viewbox::ViewBox; use cssparser::{BasicParseError, Parser}; @@ -155,7 +156,7 @@ impl AspectRatio { &self, vbox: Option<ViewBox>, viewport: &Rect, - ) -> Option<cairo::Matrix> { + ) -> Option<Transform> { // width or height set to 0 disables rendering of the element // https://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute // https://www.w3.org/TR/SVG/struct.html#UseElementWidthAttribute @@ -175,14 +176,14 @@ impl AspectRatio { None } else { let r = self.compute(&vbox, viewport); - let mut matrix = cairo::Matrix::identity(); + let mut matrix = Transform::identity(); matrix.translate(r.x0, r.y0); matrix.scale(r.width() / vbox.0.width(), r.height() / vbox.0.height()); matrix.translate(-vbox.0.x0, -vbox.0.y0); Some(matrix) } } else { - let mut matrix = cairo::Matrix::identity(); + let mut matrix = Transform::identity(); matrix.translate(viewport.x0, viewport.y0); Some(matrix) } diff --git a/rsvg_internals/src/bbox.rs b/rsvg_internals/src/bbox.rs index bb89186c..bb24e34a 100644 --- a/rsvg_internals/src/bbox.rs +++ b/rsvg_internals/src/bbox.rs @@ -1,10 +1,11 @@ //! Bounding boxes that know their coordinate space. -use crate::rect::{Rect, TransformRect}; +use crate::rect::Rect; +use crate::transform::{Transform, TransformExt}; #[derive(Debug, Copy, Clone)] pub struct BoundingBox { - pub affine: cairo::Matrix, + pub affine: Transform, pub rect: Option<Rect>, // without stroke pub ink_rect: Option<Rect>, // with stroke } @@ -12,13 +13,13 @@ pub struct BoundingBox { impl BoundingBox { pub fn new() -> BoundingBox { BoundingBox { - affine: cairo::Matrix::identity(), + affine: Transform::identity(), rect: None, ink_rect: None, } } - pub fn with_affine(self, affine: cairo::Matrix) -> BoundingBox { + pub fn with_affine(self, affine: Transform) -> BoundingBox { BoundingBox { affine, ..self } } @@ -46,14 +47,12 @@ impl BoundingBox { return; } - let mut affine = self.affine; + if let Some(inverse) = self.affine.inverse() { + let affine = inverse.pre_transform(&src.affine); - // this will panic!() if it's not invertible... should we check on our own? - affine.invert(); - affine = cairo::Matrix::multiply(&src.affine, &affine); - - self.rect = combine_rects(self.rect, src.rect, &affine, clip); - self.ink_rect = combine_rects(self.ink_rect, src.ink_rect, &affine, clip); + self.rect = combine_rects(self.rect, src.rect, &affine, clip); + self.ink_rect = combine_rects(self.ink_rect, src.ink_rect, &affine, clip); + } } pub fn insert(&mut self, src: &BoundingBox) { @@ -68,7 +67,7 @@ impl BoundingBox { fn combine_rects( r1: Option<Rect>, r2: Option<Rect>, - affine: &cairo::Matrix, + affine: &Transform, clip: bool, ) -> Option<Rect> { match (r1, r2, clip) { @@ -91,7 +90,7 @@ mod tests { let r1 = Rect::new(1.0, 2.0, 3.0, 4.0); let r2 = Rect::new(1.5, 2.5, 3.5, 4.5); let r3 = Rect::new(10.0, 11.0, 12.0, 13.0); - let affine = cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, 0.5, 0.5); + let affine = Transform::row_major(1.0, 0.0, 0.0, 1.0, 0.5, 0.5); let res = combine_rects(None, None, &affine, true); assert_eq!(res, None); diff --git a/rsvg_internals/src/drawing_ctx.rs b/rsvg_internals/src/drawing_ctx.rs index c3528646..854512a8 100644 --- a/rsvg_internals/src/drawing_ctx.rs +++ b/rsvg_internals/src/drawing_ctx.rs @@ -26,9 +26,10 @@ use crate::property_defs::{ ClipRule, FillRule, Opacity, Overflow, ShapeRendering, StrokeDasharray, StrokeLinecap, StrokeLinejoin, }; -use crate::rect::{Rect, TransformRect}; +use crate::rect::Rect; use crate::structure::{ClipPath, Mask, Symbol, Use}; use crate::surface_utils::{shared_surface::SharedImageSurface, shared_surface::SurfaceType}; +use crate::transform::{Transform, TransformExt}; use crate::unit_interval::UnitInterval; use crate::viewbox::ViewBox; @@ -81,7 +82,7 @@ pub enum ClipMode { pub struct DrawingCtx { document: Rc<Document>, - initial_affine: cairo::Matrix, + initial_affine: Transform, rect: Rect, dpi: Dpi, @@ -372,10 +373,10 @@ impl DrawingCtx { let cascaded = CascadedValues::new_from_node(node); - let matrix = if units == CoordUnits::ObjectBoundingBox { + let transform = if units == CoordUnits::ObjectBoundingBox { let bbox_rect = bbox.rect.as_ref().unwrap(); - Some(cairo::Matrix::new( + Some(Transform::row_major( bbox_rect.width(), 0.0, 0.0, @@ -387,7 +388,7 @@ impl DrawingCtx { None }; - self.with_saved_matrix(matrix, &mut |dc| { + self.with_saved_transform(transform, &mut |dc| { let cr = dc.get_cairo_context(); // here we don't push a layer because we are clipping @@ -411,7 +412,7 @@ impl DrawingCtx { &mut self, mask: &Mask, mask_node: &RsvgNode, - affine: cairo::Matrix, + affine: Transform, bbox: &BoundingBox, ) -> Result<Option<cairo::ImageSurface>, RenderingError> { if bbox.rect.is_none() { @@ -439,7 +440,7 @@ impl DrawingCtx { mask.get_rect(&values, ¶ms) }; - let mask_affine = cairo::Matrix::multiply(&mask_node.borrow().get_transform(), &affine); + let mask_affine = affine.pre_transform(&mask_node.borrow().get_transform()); let mask_content_surface = self.create_surface_for_toplevel_viewport()?; @@ -449,7 +450,7 @@ impl DrawingCtx { let mask_cr = cairo::Context::new(&mask_content_surface); mask_cr.set_matrix(mask_affine); - let bbtransform = cairo::Matrix::new(bb_w, 0.0, 0.0, bb_h, bb_x, bb_y); + let bbtransform = Transform::row_major(bb_w, 0.0, 0.0, bb_h, bb_x, bb_y); self.push_cairo_context(mask_cr); @@ -629,22 +630,22 @@ impl DrawingCtx { } } - fn initial_affine_with_offset(&self) -> cairo::Matrix { + fn initial_affine_with_offset(&self) -> Transform { let mut initial_with_offset = self.initial_affine; initial_with_offset.translate(self.rect.x0, self.rect.y0); initial_with_offset } - /// Saves the current Cairo matrix, applies a transform if specified, + /// Saves the current Transform, applies a transform if specified, /// runs the draw_fn, and restores the original matrix /// /// This is slightly cheaper than a `cr.save()` / `cr.restore()` /// pair, but more importantly, it does not reset the whole /// graphics state, i.e. it leaves a clipping path in place if it /// was set by the `draw_fn`. - pub fn with_saved_matrix( + pub fn with_saved_transform( &mut self, - transform: Option<cairo::Matrix>, + transform: Option<Transform>, draw_fn: &mut dyn FnMut(&mut DrawingCtx) -> Result<BoundingBox, RenderingError>, ) -> Result<BoundingBox, RenderingError> { let matrix = self.cr.get_matrix(); @@ -954,7 +955,7 @@ impl DrawingCtx { node: &RsvgNode, cascaded: &CascadedValues<'_>, surface: &cairo::ImageSurface, - affine: cairo::Matrix, + affine: Transform, width: f64, height: f64, ) -> Result<BoundingBox, RenderingError> { @@ -1108,19 +1109,15 @@ impl DrawingCtx { #[derive(Debug)] struct CompositingAffines { - pub outside_temporary_surface: cairo::Matrix, - pub initial: cairo::Matrix, - pub for_temporary_surface: cairo::Matrix, - pub compositing: cairo::Matrix, - pub for_snapshot: cairo::Matrix, + pub outside_temporary_surface: Transform, + pub initial: Transform, + pub for_temporary_surface: Transform, + pub compositing: Transform, + pub for_snapshot: Transform, } impl CompositingAffines { - fn new( - current: cairo::Matrix, - initial: cairo::Matrix, - cr_stack_depth: usize, - ) -> CompositingAffines { + fn new(current: Transform, initial: Transform, cr_stack_depth: usize) -> CompositingAffines { let is_topmost_temporary_surface = cr_stack_depth == 0; let initial_inverse = initial.try_invert().unwrap(); @@ -1128,15 +1125,15 @@ impl CompositingAffines { let outside_temporary_surface = if is_topmost_temporary_surface { current } else { - cairo::Matrix::multiply(¤t, &initial_inverse) + initial_inverse.pre_transform(¤t) }; let (scale_x, scale_y) = initial.transform_distance(1.0, 1.0); let for_temporary_surface = if is_topmost_temporary_surface { - let untransformed = cairo::Matrix::multiply(¤t, &initial_inverse); - let scale = cairo::Matrix::new(scale_x, 0.0, 0.0, scale_y, 0.0, 0.0); - cairo::Matrix::multiply(&untransformed, &scale) + let untransformed = initial_inverse.pre_transform(¤t); + let scale = Transform::new(scale_x, 0.0, 0.0, scale_y, 0.0, 0.0); + scale.pre_transform(&untransformed) } else { current }; @@ -1146,7 +1143,7 @@ impl CompositingAffines { scaled.scale(1.0 / scale_x, 1.0 / scale_y); scaled } else { - cairo::Matrix::identity() + Transform::identity() }; let for_snapshot = compositing.try_invert().unwrap(); diff --git a/rsvg_internals/src/filter.rs b/rsvg_internals/src/filter.rs index db80f012..7bcd2b27 100644 --- a/rsvg_internals/src/filter.rs +++ b/rsvg_internals/src/filter.rs @@ -12,6 +12,7 @@ use crate::parsers::{Parse, ParseValue}; use crate::properties::ComputedValues; use crate::property_bag::PropertyBag; use crate::rect::Rect; +use crate::transform::Transform; /// The <filter> node. pub struct Filter { @@ -51,7 +52,7 @@ impl Filter { &self, computed_from_target_node: &ComputedValues, draw_ctx: &mut DrawingCtx, - affine: cairo::Matrix, + affine: Transform, width: f64, height: f64, ) -> BoundingBox { diff --git a/rsvg_internals/src/filters/context.rs b/rsvg_internals/src/filters/context.rs index 2b47806f..8f630753 100644 --- a/rsvg_internals/src/filters/context.rs +++ b/rsvg_internals/src/filters/context.rs @@ -11,6 +11,7 @@ use crate::paint_server::PaintServer; use crate::properties::ComputedValues; use crate::rect::IRect; use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType}; +use crate::transform::{Transform, TransformExt}; use crate::unit_interval::UnitInterval; use super::error::FilterError; @@ -84,12 +85,12 @@ pub struct FilterContext { /// This is to be used in conjunction with setting the viewbox size to account for the scaling. /// For `filterUnits == userSpaceOnUse`, the viewbox will have the actual resolution size, and /// for `filterUnits == objectBoundingBox`, the viewbox will have the size of 1, 1. - _affine: cairo::Matrix, + _affine: Transform, /// The filter primitive affine matrix. /// /// See the comments for `_affine`, they largely apply here. - paffine: cairo::Matrix, + paffine: Transform, } impl FilterContext { @@ -112,32 +113,26 @@ impl FilterContext { let affine = match filter.get_filter_units() { CoordUnits::UserSpaceOnUse => cr_affine, - CoordUnits::ObjectBoundingBox => { - let affine = cairo::Matrix::new( - bbox_rect.width(), - 0.0, - 0.0, - bbox_rect.height(), - bbox_rect.x0, - bbox_rect.y0, - ); - cairo::Matrix::multiply(&affine, &cr_affine) - } + CoordUnits::ObjectBoundingBox => cr_affine.pre_transform(&Transform::row_major( + bbox_rect.width(), + 0.0, + 0.0, + bbox_rect.height(), + bbox_rect.x0, + bbox_rect.y0, + )), }; let paffine = match filter.get_primitive_units() { CoordUnits::UserSpaceOnUse => cr_affine, - CoordUnits::ObjectBoundingBox => { - let affine = cairo::Matrix::new( - bbox_rect.width(), - 0.0, - 0.0, - bbox_rect.height(), - bbox_rect.x0, - bbox_rect.y0, - ); - cairo::Matrix::multiply(&affine, &cr_affine) - } + CoordUnits::ObjectBoundingBox => cr_affine.pre_transform(&Transform::row_major( + bbox_rect.width(), + 0.0, + 0.0, + bbox_rect.height(), + bbox_rect.x0, + bbox_rect.y0, + )), }; let width = source_surface.width(); @@ -253,9 +248,9 @@ impl FilterContext { Ok(()) } - /// Returns the paffine matrix. + /// Returns the paffine transform. #[inline] - pub fn paffine(&self) -> cairo::Matrix { + pub fn paffine(&self) -> Transform { self.paffine } diff --git a/rsvg_internals/src/gradient.rs b/rsvg_internals/src/gradient.rs index deb44195..9ee58961 100644 --- a/rsvg_internals/src/gradient.rs +++ b/rsvg_internals/src/gradient.rs @@ -16,6 +16,7 @@ use crate::parsers::{Parse, ParseValue}; use crate::properties::ComputedValues; use crate::property_bag::PropertyBag; use crate::property_defs::StopColor; +use crate::transform::Transform; use crate::unit_interval::UnitInterval; /// Contents of a <stop> element for gradient color stops @@ -317,7 +318,7 @@ impl Variant { #[derive(Default)] struct Common { units: Option<GradientUnits>, - affine: Option<cairo::Matrix>, + affine: Option<Transform>, spread: Option<SpreadMethod>, fallback: Option<Fragment>, @@ -354,7 +355,7 @@ pub struct RadialGradient { /// field was specified. struct UnresolvedGradient { units: Option<GradientUnits>, - affine: Option<cairo::Matrix>, + affine: Option<Transform>, spread: Option<SpreadMethod>, stops: Option<Vec<ColorStop>>, @@ -365,7 +366,7 @@ struct UnresolvedGradient { #[derive(Clone)] pub struct Gradient { units: GradientUnits, - affine: cairo::Matrix, + affine: Transform, spread: SpreadMethod, stops: Vec<ColorStop>, @@ -496,7 +497,7 @@ impl UnresolvedGradient { fn resolve_from_defaults(&self) -> UnresolvedGradient { let units = self.units.or_else(|| Some(GradientUnits::default())); - let affine = self.affine.or_else(|| Some(cairo::Matrix::identity())); + let affine = self.affine.or_else(|| Some(Transform::identity())); let spread = self.spread.or_else(|| Some(SpreadMethod::default())); let stops = self.stops.clone().or_else(|| Some(Vec::<ColorStop>::new())); let variant = self.variant.resolve_from_defaults(); diff --git a/rsvg_internals/src/node.rs b/rsvg_internals/src/node.rs index 6dbce7fe..0ba4b2f9 100644 --- a/rsvg_internals/src/node.rs +++ b/rsvg_internals/src/node.rs @@ -13,6 +13,7 @@ //! [`create_node`]: ../create_node/index.html use downcast_rs::*; +use locale_config::Locale; use markup5ever::{expanded_name, local_name, namespace_url, ns, QualName}; use std::cell::Ref; use std::collections::HashSet; @@ -28,8 +29,7 @@ use crate::parsers::Parse; use crate::properties::{ComputedValues, SpecifiedValue, SpecifiedValues}; use crate::property_bag::PropertyBag; use crate::property_defs::Overflow; -use locale_config::Locale; -use rctree; +use crate::transform::Transform; /// Strong reference to an element in the SVG tree. /// @@ -50,7 +50,7 @@ pub struct NodeData { specified_values: SpecifiedValues, important_styles: HashSet<QualName>, result: NodeResult, - transform: cairo::Matrix, + transform: Transform, values: ComputedValues, cond: bool, style_attr: String, @@ -72,7 +72,7 @@ impl NodeData { class: class.map(str::to_string), specified_values: Default::default(), important_styles: Default::default(), - transform: cairo::Matrix::identity(), + transform: Transform::identity(), result: Ok(()), values: ComputedValues::default(), cond: true, @@ -113,7 +113,7 @@ impl NodeData { self.cond } - pub fn get_transform(&self) -> cairo::Matrix { + pub fn get_transform(&self) -> Transform { self.transform } @@ -154,12 +154,10 @@ impl NodeData { for (attr, value) in pbag.iter() { match attr.expanded() { expanded_name!(svg "transform") => { - return cairo::Matrix::parse_str(value) - .attribute(attr) - .and_then(|affine| { - self.transform = affine; - Ok(()) - }); + return Transform::parse_str(value).attribute(attr).and_then(|t| { + self.transform = t; + Ok(()) + }); } _ => (), } @@ -504,7 +502,7 @@ impl NodeDraw for RsvgNode { ) -> Result<BoundingBox, RenderingError> { if !self.borrow().is_in_error() { let transform = self.borrow().get_transform(); - draw_ctx.with_saved_matrix(Some(transform), &mut |dc| { + draw_ctx.with_saved_transform(Some(transform), &mut |dc| { self.borrow() .get_node_trait() .draw(self, cascaded, dc, clipping) diff --git a/rsvg_internals/src/pattern.rs b/rsvg_internals/src/pattern.rs index a628cd07..b1d274c7 100644 --- a/rsvg_internals/src/pattern.rs +++ b/rsvg_internals/src/pattern.rs @@ -18,6 +18,7 @@ use crate::parsers::ParseValue; use crate::properties::ComputedValues; use crate::property_bag::PropertyBag; use crate::rect::Rect; +use crate::transform::Transform; use crate::unit_interval::UnitInterval; use crate::viewbox::*; @@ -34,7 +35,7 @@ struct Common { // In that case, the fully resolved pattern will have a .vbox=Some(None) value. vbox: Option<Option<ViewBox>>, preserve_aspect_ratio: Option<AspectRatio>, - affine: Option<cairo::Matrix>, + affine: Option<Transform>, x: Option<Length<Horizontal>>, y: Option<Length<Vertical>>, width: Option<Length<Horizontal>>, @@ -98,7 +99,7 @@ pub struct ResolvedPattern { // In that case, the fully resolved pattern will have a .vbox=Some(None) value. vbox: Option<ViewBox>, preserve_aspect_ratio: AspectRatio, - affine: cairo::Matrix, + affine: Transform, x: Length<Horizontal>, y: Length<Vertical>, width: Length<Horizontal>, @@ -467,10 +468,7 @@ impl UnresolvedPattern { .common .preserve_aspect_ratio .or_else(|| Some(AspectRatio::default())); - let affine = self - .common - .affine - .or_else(|| Some(cairo::Matrix::identity())); + let affine = self.common.affine.or_else(|| Some(Transform::identity())); let x = self.common.x.or_else(|| Some(Default::default())); let y = self.common.y.or_else(|| Some(Default::default())); let width = self.common.width.or_else(|| Some(Default::default())); diff --git a/rsvg_internals/src/rect.rs b/rsvg_internals/src/rect.rs index ea6e7675..acb522d0 100644 --- a/rsvg_internals/src/rect.rs +++ b/rsvg_internals/src/rect.rs @@ -229,49 +229,3 @@ impl From<IRect> for cairo::Rectangle { } } } - -pub trait TransformRect { - fn transform_rect(&self, rect: &Rect) -> Rect; -} - -impl TransformRect for cairo::Matrix { - fn transform_rect(&self, rect: &Rect) -> Rect { - let points = vec![ - self.transform_point(rect.x0, rect.y0), - self.transform_point(rect.x1, rect.y0), - self.transform_point(rect.x0, rect.y1), - self.transform_point(rect.x1, rect.y1), - ]; - - let (mut xmin, mut ymin, mut xmax, mut ymax) = { - let (x, y) = points[0]; - - (x, y, x, y) - }; - - for &(x, y) in points.iter().take(4).skip(1) { - if x < xmin { - xmin = x; - } - - if x > xmax { - xmax = x; - } - - if y < ymin { - ymin = y; - } - - if y > ymax { - ymax = y; - } - } - - Rect { - x0: xmin, - y0: ymin, - x1: xmax, - y1: ymax, - } - } -} diff --git a/rsvg_internals/src/transform.rs b/rsvg_internals/src/transform.rs index ad9d6256..d18aa505 100644 --- a/rsvg_internals/src/transform.rs +++ b/rsvg_internals/src/transform.rs @@ -5,19 +5,97 @@ use std::f64::consts::*; use cssparser::{Parser, Token}; use crate::error::*; +use crate::rect::Rect; use crate::parsers::{optional_comma, Parse}; -impl Parse for cairo::Matrix { - fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, CssParseError<'i>> { +pub type Transform = cairo::Matrix; + +// Extension trait to prepare the switch from cairo::Matrix to euclid +pub trait TransformExt +where + Self: std::marker::Sized, +{ + fn row_major(m11: f64, m12: f64, m21: f64, m22: f64, m31: f64, m32: f64) -> Self; + + fn is_invertible(&self) -> bool; + + fn inverse(&self) -> Option<Self>; + + fn pre_transform(&self, mat: &Self) -> Self; + + fn transform_rect(&self, rect: &Rect) -> Rect; +} + +impl TransformExt for Transform { + fn row_major(m11: f64, m12: f64, m21: f64, m22: f64, m31: f64, m32: f64) -> Self { + cairo::Matrix::new(m11, m12, m21, m22, m31, m32) + } + + fn is_invertible(&self) -> bool { + self.try_invert().is_ok() + } + + fn inverse(&self) -> Option<Self> { + self.try_invert().ok() + } + + fn pre_transform(&self, mat: &Self) -> Self { + cairo::Matrix::multiply(mat, self) + } + fn transform_rect(&self, rect: &Rect) -> Rect { + let points = vec![ + self.transform_point(rect.x0, rect.y0), + self.transform_point(rect.x1, rect.y0), + self.transform_point(rect.x0, rect.y1), + self.transform_point(rect.x1, rect.y1), + ]; + + let (mut xmin, mut ymin, mut xmax, mut ymax) = { + let (x, y) = points[0]; + + (x, y, x, y) + }; + + for &(x, y) in points.iter().take(4).skip(1) { + if x < xmin { + xmin = x; + } + + if x > xmax { + xmax = x; + } + + if y < ymin { + ymin = y; + } + + if y > ymax { + ymax = y; + } + } + + Rect { + x0: xmin, + y0: ymin, + x1: xmax, + y1: ymax, + } + } +} + +impl Parse for Transform { + fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, CssParseError<'i>> { let loc = parser.current_source_location(); - let matrix = parse_transform_list(parser)?; + let t = parse_transform_list(parser)?; - matrix.try_invert().map(|_| matrix).map_err(|_| { - loc.new_custom_error(ValueErrorKind::Value( + if t.is_invertible() { + Ok(t) + } else { + Err(loc.new_custom_error(ValueErrorKind::Value( "invalid transformation matrix".to_string(), - )) - }) + ))) + } } } @@ -25,10 +103,8 @@ impl Parse for cairo::Matrix { // Its operataion and grammar are described here: // https://www.w3.org/TR/SVG/coords.html#TransformAttribute -fn parse_transform_list<'i>( - parser: &mut Parser<'i, '_>, -) -> Result<cairo::Matrix, CssParseError<'i>> { - let mut matrix = cairo::Matrix::identity(); +fn parse_transform_list<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, CssParseError<'i>> { + let mut t = Transform::identity(); loop { if parser.is_exhausted() { @@ -36,17 +112,17 @@ fn parse_transform_list<'i>( } let m = parse_transform_command(parser)?; - matrix = cairo::Matrix::multiply(&m, &matrix); + t = t.pre_transform(&m); optional_comma(parser); } - Ok(matrix) + Ok(t) } fn parse_transform_command<'i>( parser: &mut Parser<'i, '_>, -) -> Result<cairo::Matrix, CssParseError<'i>> { +) -> Result<Transform, CssParseError<'i>> { let loc = parser.current_source_location(); match parser.next()?.clone() { @@ -64,7 +140,7 @@ fn parse_transform_command<'i>( fn parse_transform_function<'i>( name: &str, parser: &mut Parser<'i, '_>, -) -> Result<cairo::Matrix, CssParseError<'i>> { +) -> Result<Transform, CssParseError<'i>> { let loc = parser.current_source_location(); match name { @@ -80,7 +156,7 @@ fn parse_transform_function<'i>( } } -fn parse_matrix_args<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, CssParseError<'i>> { +fn parse_matrix_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, CssParseError<'i>> { parser.parse_nested_block(|p| { let xx = f64::parse(p)?; optional_comma(p); @@ -99,13 +175,11 @@ fn parse_matrix_args<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, C let y0 = f64::parse(p)?; - Ok(cairo::Matrix::new(xx, yx, xy, yy, x0, y0)) + Ok(Transform::row_major(xx, yx, xy, yy, x0, y0)) }) } -fn parse_translate_args<'i>( - parser: &mut Parser<'i, '_>, -) -> Result<cairo::Matrix, CssParseError<'i>> { +fn parse_translate_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, CssParseError<'i>> { parser.parse_nested_block(|p| { let tx = f64::parse(p)?; @@ -116,11 +190,11 @@ fn parse_translate_args<'i>( }) .unwrap_or(0.0); - Ok(cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, tx, ty)) + Ok(Transform::row_major(1.0, 0.0, 0.0, 1.0, tx, ty)) }) } -fn parse_scale_args<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, CssParseError<'i>> { +fn parse_scale_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, CssParseError<'i>> { parser.parse_nested_block(|p| { let x = f64::parse(p)?; @@ -131,11 +205,11 @@ fn parse_scale_args<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, Cs }) .unwrap_or(x); - Ok(cairo::Matrix::new(x, 0.0, 0.0, y, 0.0, 0.0)) + Ok(Transform::row_major(x, 0.0, 0.0, y, 0.0, 0.0)) }) } -fn parse_rotate_args<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, CssParseError<'i>> { +fn parse_rotate_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, CssParseError<'i>> { parser.parse_nested_block(|p| { let angle = f64::parse(p)? * PI / 180.0; @@ -153,40 +227,37 @@ fn parse_rotate_args<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, C let (s, c) = angle.sin_cos(); - let mut m = cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, tx, ty); - - m = cairo::Matrix::multiply(&cairo::Matrix::new(c, s, -s, c, 0.0, 0.0), &m); - m = cairo::Matrix::multiply(&cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, -tx, -ty), &m); - Ok(m) + Ok(Transform::row_major(1.0, 0.0, 0.0, 1.0, tx, ty) + .pre_transform(&Transform::row_major(c, s, -s, c, 0.0, 0.0)) + .pre_transform(&Transform::row_major(1.0, 0.0, 0.0, 1.0, -tx, -ty))) }) } -fn parse_skewx_args<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, CssParseError<'i>> { +fn parse_skewx_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, CssParseError<'i>> { parser.parse_nested_block(|p| { let a = f64::parse(p)? * PI / 180.0; - Ok(cairo::Matrix::new(1.0, 0.0, a.tan(), 1.0, 0.0, 0.0)) + Ok(Transform::row_major(1.0, 0.0, a.tan(), 1.0, 0.0, 0.0)) }) } -fn parse_skewy_args<'i>(parser: &mut Parser<'i, '_>) -> Result<cairo::Matrix, CssParseError<'i>> { +fn parse_skewy_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, CssParseError<'i>> { parser.parse_nested_block(|p| { let a = f64::parse(p)? * PI / 180.0; - Ok(cairo::Matrix::new(1.0, a.tan(), 0.0, 1.0, 0.0, 0.0)) + Ok(Transform::row_major(1.0, a.tan(), 0.0, 1.0, 0.0, 0.0)) }) } #[cfg(test)] -fn make_rotation_matrix(angle_degrees: f64, tx: f64, ty: f64) -> cairo::Matrix { +fn make_rotation_matrix(angle_degrees: f64, tx: f64, ty: f64) -> Transform { let angle = angle_degrees * PI / 180.0; - let mut m = cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, tx, ty); + let mut t = Transform::row_major(1.0, 0.0, 0.0, 1.0, tx, ty); - let mut r = cairo::Matrix::identity(); + let mut r = Transform::identity(); r.rotate(angle); - m = cairo::Matrix::multiply(&r, &m); - m = cairo::Matrix::multiply(&cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, -tx, -ty), &m); - m + t = t.pre_transform(&r); + t.pre_transform(&Transform::row_major(1.0, 0.0, 0.0, 1.0, -tx, -ty)) } #[cfg(test)] @@ -195,11 +266,11 @@ mod tests { use float_cmp::ApproxEq; use std::f64; - fn parse_transform(s: &str) -> Result<cairo::Matrix, CssParseError> { - cairo::Matrix::parse_str(s) + fn parse_transform(s: &str) -> Result<Transform, CssParseError> { + Transform::parse_str(s) } - fn assert_matrix_eq(a: &cairo::Matrix, b: &cairo::Matrix) { + fn assert_matrix_eq(a: &Transform, b: &Transform) { let epsilon = 8.0 * f64::EPSILON; // kind of arbitrary, but allow for some sloppiness assert!(a.xx.approx_eq(b.xx, (epsilon, 1))); @@ -212,14 +283,14 @@ mod tests { #[test] fn parses_valid_transform() { - let t = cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, 20.0, 30.0); - let s = cairo::Matrix::new(10.0, 0.0, 0.0, 10.0, 0.0, 0.0); + let t = Transform::row_major(1.0, 0.0, 0.0, 1.0, 20.0, 30.0); + let s = Transform::row_major(10.0, 0.0, 0.0, 10.0, 0.0, 0.0); let r = make_rotation_matrix(30.0, 10.0, 10.0); - let a = cairo::Matrix::multiply(&s, &t); + let a = t.pre_transform(&s); assert_matrix_eq( &parse_transform("translate(20, 30), scale (10) rotate (30 10 10)").unwrap(), - &cairo::Matrix::multiply(&r, &a), + &a.pre_transform(&r), ); } @@ -250,17 +321,17 @@ mod tests { fn parses_matrix() { assert_matrix_eq( &parse_transform("matrix (1 2 3 4 5 6)").unwrap(), - &cairo::Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0), + &Transform::row_major(1.0, 2.0, 3.0, 4.0, 5.0, 6.0), ); assert_matrix_eq( &parse_transform("matrix(1,2,3,4 5 6)").unwrap(), - &cairo::Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0), + &Transform::row_major(1.0, 2.0, 3.0, 4.0, 5.0, 6.0), ); assert_matrix_eq( &parse_transform("matrix (1,2.25,-3.25e2,4 5 6)").unwrap(), - &cairo::Matrix::new(1.0, 2.25, -325.0, 4.0, 5.0, 6.0), + &Transform::row_major(1.0, 2.25, -325.0, 4.0, 5.0, 6.0), ); } @@ -268,17 +339,17 @@ mod tests { fn parses_translate() { assert_matrix_eq( &parse_transform("translate(-1 -2)").unwrap(), - &cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, -1.0, -2.0), + &Transform::row_major(1.0, 0.0, 0.0, 1.0, -1.0, -2.0), ); assert_matrix_eq( &parse_transform("translate(-1, -2)").unwrap(), - &cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, -1.0, -2.0), + &Transform::row_major(1.0, 0.0, 0.0, 1.0, -1.0, -2.0), ); assert_matrix_eq( &parse_transform("translate(-1)").unwrap(), - &cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, -1.0, 0.0), + &Transform::row_major(1.0, 0.0, 0.0, 1.0, -1.0, 0.0), ); } @@ -286,17 +357,17 @@ mod tests { fn parses_scale() { assert_matrix_eq( &parse_transform("scale (-1)").unwrap(), - &cairo::Matrix::new(-1.0, 0.0, 0.0, -1.0, 0.0, 0.0), + &Transform::row_major(-1.0, 0.0, 0.0, -1.0, 0.0, 0.0), ); assert_matrix_eq( &parse_transform("scale(-1 -2)").unwrap(), - &cairo::Matrix::new(-1.0, 0.0, 0.0, -2.0, 0.0, 0.0), + &Transform::row_major(-1.0, 0.0, 0.0, -2.0, 0.0, 0.0), ); assert_matrix_eq( &parse_transform("scale(-1, -2)").unwrap(), - &cairo::Matrix::new(-1.0, 0.0, 0.0, -2.0, 0.0, 0.0), + &Transform::row_major(-1.0, 0.0, 0.0, -2.0, 0.0, 0.0), ); } @@ -316,12 +387,12 @@ mod tests { ); } - fn make_skew_x_matrix(angle_degrees: f64) -> cairo::Matrix { + fn make_skew_x_matrix(angle_degrees: f64) -> Transform { let a = angle_degrees * PI / 180.0; - cairo::Matrix::new(1.0, 0.0, a.tan(), 1.0, 0.0, 0.0) + Transform::row_major(1.0, 0.0, a.tan(), 1.0, 0.0, 0.0) } - fn make_skew_y_matrix(angle_degrees: f64) -> cairo::Matrix { + fn make_skew_y_matrix(angle_degrees: f64) -> Transform { let mut m = make_skew_x_matrix(angle_degrees); m.yx = m.xy; m.xy = 0.0; @@ -346,29 +417,29 @@ mod tests { #[test] fn parses_transform_list() { - let t = cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, 20.0, 30.0); - let s = cairo::Matrix::new(10.0, 0.0, 0.0, 10.0, 0.0, 0.0); + let t = Transform::row_major(1.0, 0.0, 0.0, 1.0, 20.0, 30.0); + let s = Transform::row_major(10.0, 0.0, 0.0, 10.0, 0.0, 0.0); let r = make_rotation_matrix(30.0, 10.0, 10.0); assert_matrix_eq( &parse_transform("scale(10)rotate(30, 10, 10)").unwrap(), - &cairo::Matrix::multiply(&r, &s), + &s.pre_transform(&r), ); assert_matrix_eq( &parse_transform("translate(20, 30), scale (10)").unwrap(), - &cairo::Matrix::multiply(&s, &t), + &t.pre_transform(&s), ); - let a = cairo::Matrix::multiply(&s, &t); + let a = t.pre_transform(&s); assert_matrix_eq( &parse_transform("translate(20, 30), scale (10) rotate (30 10 10)").unwrap(), - &cairo::Matrix::multiply(&r, &a), + &a.pre_transform(&r), ); } #[test] fn parses_empty() { - assert_matrix_eq(&parse_transform("").unwrap(), &cairo::Matrix::identity()); + assert_matrix_eq(&parse_transform("").unwrap(), &Transform::identity()); } } |