[librsvg/wip/euclid: 189/190] transform: add a Tranform type



commit eeec41b46e65159aeaf88732046177948e0b360d
Author: Paolo Borelli <pborelli gnome org>
Date:   Sun Nov 17 11:33:58 2019 +0100

    transform: add a Tranform type
    
    Use an alias for cairo::Matrix and add a trait to ease transition
    to the euclid crate.

 rsvg_internals/src/aspect_ratio.rs    |   7 +-
 rsvg_internals/src/bbox.rs            |  25 ++---
 rsvg_internals/src/drawing_ctx.rs     |  53 +++++----
 rsvg_internals/src/filter.rs          |   3 +-
 rsvg_internals/src/filters/context.rs |  47 ++++----
 rsvg_internals/src/gradient.rs        |   9 +-
 rsvg_internals/src/node.rs            |  22 ++--
 rsvg_internals/src/pattern.rs         |  10 +-
 rsvg_internals/src/rect.rs            |  46 --------
 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, &params)
         };
 
-        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(&current, &initial_inverse)
+            initial_inverse.pre_transform(&current)
         };
 
         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(&current, &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(&current);
+            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());
     }
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]