summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Borelli <pborelli@gnome.org>2019-11-17 11:33:58 +0100
committerPaolo Borelli <pborelli@gnome.org>2020-01-06 11:08:51 +0100
commiteeec41b46e65159aeaf88732046177948e0b360d (patch)
treeb72b028f9143640962552c6847be92eb939fbb11
parent9561d60a9daa74bef222928f007f92421b26b233 (diff)
downloadlibrsvg-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.rs7
-rw-r--r--rsvg_internals/src/bbox.rs25
-rw-r--r--rsvg_internals/src/drawing_ctx.rs53
-rw-r--r--rsvg_internals/src/filter.rs3
-rw-r--r--rsvg_internals/src/filters/context.rs47
-rw-r--r--rsvg_internals/src/gradient.rs9
-rw-r--r--rsvg_internals/src/node.rs22
-rw-r--r--rsvg_internals/src/pattern.rs10
-rw-r--r--rsvg_internals/src/rect.rs46
-rw-r--r--rsvg_internals/src/transform.rs201
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());
}
}