rsvg/
transform.rs

1//! Handling of transform values.
2//!
3//! This module contains the following:
4//!
5//! * [`Transform`] to represent 2D transforms in general; it's just a matrix.
6//!
7//! * [`TransformProperty`] for the [`transform` property][prop] in SVG2/CSS3.
8//!
9//! * [`Transform`] also handles the [`transform` attribute][attr] in SVG1.1, which has a different
10//!   grammar than the `transform` property from SVG2.
11//!
12//! [prop]: https://www.w3.org/TR/css-transforms-1/#transform-property
13//! [attr]: https://www.w3.org/TR/SVG11/coords.html#TransformAttribute
14
15use cssparser::{Parser, Token};
16use std::ops::Deref;
17
18use crate::angle::Angle;
19use crate::error::*;
20use crate::length::*;
21use crate::parsers::{Parse, optional_comma};
22use crate::properties::ComputedValues;
23use crate::property_macros::Property;
24use crate::rect::Rect;
25
26/// A transform that has been checked to be invertible.
27///
28/// We need to validate user-supplied transforms before setting them on Cairo,
29/// so we use this type for that.
30#[derive(Debug, Default, Copy, Clone, PartialEq)]
31pub struct ValidTransform(Transform);
32
33impl TryFrom<Transform> for ValidTransform {
34    type Error = InvalidTransform;
35
36    /// Validates a [`Transform`] before converting it to a [`ValidTransform`].
37    ///
38    /// A transform is valid if it is invertible.  For example, a
39    /// matrix with all-zeros is not invertible, and it is invalid.
40    fn try_from(t: Transform) -> Result<ValidTransform, InvalidTransform> {
41        if t.is_invertible() {
42            Ok(ValidTransform(t))
43        } else {
44            Err(InvalidTransform)
45        }
46    }
47}
48
49impl Deref for ValidTransform {
50    type Target = Transform;
51
52    fn deref(&self) -> &Transform {
53        &self.0
54    }
55}
56
57/// A 2D transformation matrix.
58#[derive(Debug, Copy, Clone, PartialEq)]
59pub struct Transform {
60    pub xx: f64,
61    pub yx: f64,
62    pub xy: f64,
63    pub yy: f64,
64    pub x0: f64,
65    pub y0: f64,
66}
67
68/// The `transform` property from the CSS Transforms Module Level 1.
69///
70/// CSS Transforms 1: <https://www.w3.org/TR/css-transforms-1/#transform-property>
71#[derive(Debug, Default, Clone, PartialEq)]
72pub enum TransformProperty {
73    #[default]
74    None,
75    List(Vec<TransformFunction>),
76}
77
78/// The `transform` attribute from SVG1.1
79///
80/// SVG1.1: <https://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
81#[derive(Copy, Clone, Default, Debug, PartialEq)]
82pub struct TransformAttribute(Transform);
83
84impl Property for TransformProperty {
85    fn inherits_automatically() -> bool {
86        false
87    }
88
89    fn compute(&self, _v: &ComputedValues) -> Self {
90        self.clone()
91    }
92}
93
94impl TransformProperty {
95    pub fn to_transform(&self) -> Transform {
96        // From the spec (https://www.w3.org/TR/css-transforms-1/#current-transformation-matrix):
97        // Start with the identity matrix.
98        // TODO: implement (#685) - Translate by the computed X and Y of transform-origin
99        // Multiply by each of the transform functions in transform property from left to right
100        // TODO: implement - Translate by the negated computed X and Y values of transform-origin
101
102        match self {
103            TransformProperty::None => Transform::identity(),
104
105            TransformProperty::List(l) => {
106                let mut final_transform = Transform::identity();
107
108                for f in l.iter() {
109                    use TransformFunction::*;
110
111                    let transform_matrix = match f {
112                        Matrix(trans_matrix) => *trans_matrix,
113                        Translate(h, v) => Transform::new_translate(h.length, v.length),
114                        TranslateX(h) => Transform::new_translate(h.length, 0.0),
115                        TranslateY(v) => Transform::new_translate(0.0, v.length),
116                        Scale(x, y) => Transform::new_scale(*x, *y),
117                        ScaleX(x) => Transform::new_scale(*x, 1.0),
118                        ScaleY(y) => Transform::new_scale(1.0, *y),
119                        Rotate(a) => Transform::new_rotate(*a),
120                        Skew(ax, ay) => Transform::new_skew(*ax, *ay),
121                        SkewX(ax) => Transform::new_skew(*ax, Angle::new(0.0)),
122                        SkewY(ay) => Transform::new_skew(Angle::new(0.0), *ay),
123                    };
124                    final_transform = transform_matrix.post_transform(&final_transform);
125                }
126
127                final_transform
128            }
129        }
130    }
131}
132
133// https://www.w3.org/TR/css-transforms-1/#typedef-transform-function
134#[derive(Debug, Clone, PartialEq)]
135pub enum TransformFunction {
136    Matrix(Transform),
137    Translate(Length<Horizontal>, Length<Vertical>),
138    TranslateX(Length<Horizontal>),
139    TranslateY(Length<Vertical>),
140    Scale(f64, f64),
141    ScaleX(f64),
142    ScaleY(f64),
143    Rotate(Angle),
144    Skew(Angle, Angle),
145    SkewX(Angle),
146    SkewY(Angle),
147}
148
149impl Parse for TransformProperty {
150    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<TransformProperty, ParseError<'i>> {
151        if parser
152            .try_parse(|p| p.expect_ident_matching("none"))
153            .is_ok()
154        {
155            Ok(TransformProperty::None)
156        } else {
157            let t = parse_transform_prop_function_list(parser)?;
158
159            Ok(TransformProperty::List(t))
160        }
161    }
162}
163
164fn parse_transform_prop_function_list<'i>(
165    parser: &mut Parser<'i, '_>,
166) -> Result<Vec<TransformFunction>, ParseError<'i>> {
167    let mut v = Vec::<TransformFunction>::new();
168
169    loop {
170        v.push(parse_transform_prop_function_command(parser)?);
171
172        if parser.is_exhausted() {
173            break;
174        }
175    }
176
177    Ok(v)
178}
179
180fn parse_transform_prop_function_command<'i>(
181    parser: &mut Parser<'i, '_>,
182) -> Result<TransformFunction, ParseError<'i>> {
183    let loc = parser.current_source_location();
184
185    match parser.next()?.clone() {
186        Token::Function(ref name) => parse_transform_prop_function_internal(name, parser),
187        tok => Err(loc.new_unexpected_token_error(tok.clone())),
188    }
189}
190
191fn parse_transform_prop_function_internal<'i>(
192    name: &str,
193    parser: &mut Parser<'i, '_>,
194) -> Result<TransformFunction, ParseError<'i>> {
195    let loc = parser.current_source_location();
196
197    match name {
198        "matrix" => parse_prop_matrix_args(parser),
199        "translate" => parse_prop_translate_args(parser),
200        "translateX" => parse_prop_translate_x_args(parser),
201        "translateY" => parse_prop_translate_y_args(parser),
202        "scale" => parse_prop_scale_args(parser),
203        "scaleX" => parse_prop_scale_x_args(parser),
204        "scaleY" => parse_prop_scale_y_args(parser),
205        "rotate" => parse_prop_rotate_args(parser),
206        "skew" => parse_prop_skew_args(parser),
207        "skewX" => parse_prop_skew_x_args(parser),
208        "skewY" => parse_prop_skew_y_args(parser),
209        _ => Err(loc.new_custom_error(ValueErrorKind::parse_error(
210            "expected matrix|translate|translateX|translateY|scale|scaleX|scaleY|rotate|skewX|skewY",
211        ))),
212    }
213}
214
215fn parse_prop_matrix_args<'i>(
216    parser: &mut Parser<'i, '_>,
217) -> Result<TransformFunction, ParseError<'i>> {
218    parser.parse_nested_block(|p| {
219        let xx = f64::parse(p)?;
220        p.expect_comma()?;
221        let yx = f64::parse(p)?;
222        p.expect_comma()?;
223        let xy = f64::parse(p)?;
224        p.expect_comma()?;
225        let yy = f64::parse(p)?;
226        p.expect_comma()?;
227        let x0 = f64::parse(p)?;
228        p.expect_comma()?;
229        let y0 = f64::parse(p)?;
230
231        Ok(TransformFunction::Matrix(Transform::new_unchecked(
232            xx, yx, xy, yy, x0, y0,
233        )))
234    })
235}
236
237fn length_is_in_pixels<N: Normalize>(l: &Length<N>) -> bool {
238    l.unit == LengthUnit::Px
239}
240
241fn only_pixels_error<'i>(loc: cssparser::SourceLocation) -> ParseError<'i> {
242    loc.new_custom_error(ValueErrorKind::parse_error(
243        "only translations in pixels are supported for now",
244    ))
245}
246
247fn parse_prop_translate_args<'i>(
248    parser: &mut Parser<'i, '_>,
249) -> Result<TransformFunction, ParseError<'i>> {
250    parser.parse_nested_block(|p| {
251        let loc = p.current_source_location();
252
253        let tx: Length<Horizontal> = Length::parse(p)?;
254
255        let ty: Length<Vertical> = if p.try_parse(|p| p.expect_comma()).is_ok() {
256            Length::parse(p)?
257        } else {
258            Length::new(0.0, LengthUnit::Px)
259        };
260
261        if !(length_is_in_pixels(&tx) && length_is_in_pixels(&ty)) {
262            return Err(only_pixels_error(loc));
263        }
264
265        Ok(TransformFunction::Translate(tx, ty))
266    })
267}
268
269fn parse_prop_translate_x_args<'i>(
270    parser: &mut Parser<'i, '_>,
271) -> Result<TransformFunction, ParseError<'i>> {
272    parser.parse_nested_block(|p| {
273        let loc = p.current_source_location();
274
275        let tx: Length<Horizontal> = Length::parse(p)?;
276
277        if !length_is_in_pixels(&tx) {
278            return Err(only_pixels_error(loc));
279        }
280
281        Ok(TransformFunction::TranslateX(tx))
282    })
283}
284
285fn parse_prop_translate_y_args<'i>(
286    parser: &mut Parser<'i, '_>,
287) -> Result<TransformFunction, ParseError<'i>> {
288    parser.parse_nested_block(|p| {
289        let loc = p.current_source_location();
290
291        let ty: Length<Vertical> = Length::parse(p)?;
292
293        if !length_is_in_pixels(&ty) {
294            return Err(only_pixels_error(loc));
295        }
296
297        Ok(TransformFunction::TranslateY(ty))
298    })
299}
300
301fn parse_prop_scale_args<'i>(
302    parser: &mut Parser<'i, '_>,
303) -> Result<TransformFunction, ParseError<'i>> {
304    parser.parse_nested_block(|p| {
305        let x = f64::parse(p)?;
306
307        let y = if p.try_parse(|p| p.expect_comma()).is_ok() {
308            f64::parse(p)?
309        } else {
310            x
311        };
312
313        Ok(TransformFunction::Scale(x, y))
314    })
315}
316
317fn parse_prop_scale_x_args<'i>(
318    parser: &mut Parser<'i, '_>,
319) -> Result<TransformFunction, ParseError<'i>> {
320    parser.parse_nested_block(|p| {
321        let x = f64::parse(p)?;
322
323        Ok(TransformFunction::ScaleX(x))
324    })
325}
326
327fn parse_prop_scale_y_args<'i>(
328    parser: &mut Parser<'i, '_>,
329) -> Result<TransformFunction, ParseError<'i>> {
330    parser.parse_nested_block(|p| {
331        let y = f64::parse(p)?;
332
333        Ok(TransformFunction::ScaleY(y))
334    })
335}
336
337fn parse_prop_rotate_args<'i>(
338    parser: &mut Parser<'i, '_>,
339) -> Result<TransformFunction, ParseError<'i>> {
340    parser.parse_nested_block(|p| {
341        let angle = Angle::parse(p)?;
342
343        Ok(TransformFunction::Rotate(angle))
344    })
345}
346
347fn parse_prop_skew_args<'i>(
348    parser: &mut Parser<'i, '_>,
349) -> Result<TransformFunction, ParseError<'i>> {
350    parser.parse_nested_block(|p| {
351        let ax = Angle::parse(p)?;
352
353        let ay = if p.try_parse(|p| p.expect_comma()).is_ok() {
354            Angle::parse(p)?
355        } else {
356            Angle::from_degrees(0.0)
357        };
358
359        Ok(TransformFunction::Skew(ax, ay))
360    })
361}
362
363fn parse_prop_skew_x_args<'i>(
364    parser: &mut Parser<'i, '_>,
365) -> Result<TransformFunction, ParseError<'i>> {
366    parser.parse_nested_block(|p| {
367        let angle = Angle::parse(p)?;
368        Ok(TransformFunction::SkewX(angle))
369    })
370}
371
372fn parse_prop_skew_y_args<'i>(
373    parser: &mut Parser<'i, '_>,
374) -> Result<TransformFunction, ParseError<'i>> {
375    parser.parse_nested_block(|p| {
376        let angle = Angle::parse(p)?;
377        Ok(TransformFunction::SkewY(angle))
378    })
379}
380
381impl Transform {
382    #[inline]
383    pub fn new_unchecked(xx: f64, yx: f64, xy: f64, yy: f64, x0: f64, y0: f64) -> Self {
384        Self {
385            xx,
386            yx,
387            xy,
388            yy,
389            x0,
390            y0,
391        }
392    }
393
394    #[inline]
395    pub fn identity() -> Self {
396        Self::new_unchecked(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
397    }
398
399    #[inline]
400    pub fn new_translate(tx: f64, ty: f64) -> Self {
401        Self::new_unchecked(1.0, 0.0, 0.0, 1.0, tx, ty)
402    }
403
404    #[inline]
405    pub fn new_scale(sx: f64, sy: f64) -> Self {
406        Self::new_unchecked(sx, 0.0, 0.0, sy, 0.0, 0.0)
407    }
408
409    #[inline]
410    pub fn new_rotate(a: Angle) -> Self {
411        let (s, c) = a.radians().sin_cos();
412        Self::new_unchecked(c, s, -s, c, 0.0, 0.0)
413    }
414
415    #[inline]
416    pub fn new_skew(ax: Angle, ay: Angle) -> Self {
417        Self::new_unchecked(1.0, ay.radians().tan(), ax.radians().tan(), 1.0, 0.0, 0.0)
418    }
419
420    #[must_use]
421    pub fn multiply(t1: &Transform, t2: &Transform) -> Self {
422        #[allow(clippy::suspicious_operation_groupings)]
423        Transform {
424            xx: t1.xx * t2.xx + t1.yx * t2.xy,
425            yx: t1.xx * t2.yx + t1.yx * t2.yy,
426            xy: t1.xy * t2.xx + t1.yy * t2.xy,
427            yy: t1.xy * t2.yx + t1.yy * t2.yy,
428            x0: t1.x0 * t2.xx + t1.y0 * t2.xy + t2.x0,
429            y0: t1.x0 * t2.yx + t1.y0 * t2.yy + t2.y0,
430        }
431    }
432
433    #[inline]
434    pub fn pre_transform(&self, t: &Transform) -> Self {
435        Self::multiply(t, self)
436    }
437
438    #[inline]
439    pub fn post_transform(&self, t: &Transform) -> Self {
440        Self::multiply(self, t)
441    }
442
443    #[inline]
444    pub fn pre_translate(&self, x: f64, y: f64) -> Self {
445        self.pre_transform(&Transform::new_translate(x, y))
446    }
447
448    #[inline]
449    pub fn pre_scale(&self, sx: f64, sy: f64) -> Self {
450        self.pre_transform(&Transform::new_scale(sx, sy))
451    }
452
453    #[inline]
454    pub fn pre_rotate(&self, angle: Angle) -> Self {
455        self.pre_transform(&Transform::new_rotate(angle))
456    }
457
458    #[inline]
459    pub fn post_translate(&self, x: f64, y: f64) -> Self {
460        self.post_transform(&Transform::new_translate(x, y))
461    }
462
463    #[inline]
464    pub fn post_scale(&self, sx: f64, sy: f64) -> Self {
465        self.post_transform(&Transform::new_scale(sx, sy))
466    }
467
468    #[inline]
469    pub fn post_rotate(&self, angle: Angle) -> Self {
470        self.post_transform(&Transform::new_rotate(angle))
471    }
472
473    #[inline]
474    fn determinant(&self) -> f64 {
475        self.xx * self.yy - self.xy * self.yx
476    }
477
478    #[inline]
479    pub fn is_invertible(&self) -> bool {
480        let det = self.determinant();
481
482        det != 0.0 && det.is_finite()
483    }
484
485    #[must_use]
486    pub fn invert(&self) -> Option<Self> {
487        let det = self.determinant();
488
489        if det == 0.0 || !det.is_finite() {
490            return None;
491        }
492
493        let inv_det = 1.0 / det;
494
495        Some(Transform::new_unchecked(
496            inv_det * self.yy,
497            inv_det * (-self.yx),
498            inv_det * (-self.xy),
499            inv_det * self.xx,
500            inv_det * (self.xy * self.y0 - self.yy * self.x0),
501            inv_det * (self.yx * self.x0 - self.xx * self.y0),
502        ))
503    }
504
505    #[inline]
506    pub fn transform_distance(&self, dx: f64, dy: f64) -> (f64, f64) {
507        (dx * self.xx + dy * self.xy, dx * self.yx + dy * self.yy)
508    }
509
510    #[inline]
511    pub fn transform_point(&self, px: f64, py: f64) -> (f64, f64) {
512        let (x, y) = self.transform_distance(px, py);
513        (x + self.x0, y + self.y0)
514    }
515
516    pub fn transform_rect(&self, rect: &Rect) -> Rect {
517        let points = [
518            self.transform_point(rect.x0, rect.y0),
519            self.transform_point(rect.x1, rect.y0),
520            self.transform_point(rect.x0, rect.y1),
521            self.transform_point(rect.x1, rect.y1),
522        ];
523
524        let (mut xmin, mut ymin, mut xmax, mut ymax) = {
525            let (x, y) = points[0];
526
527            (x, y, x, y)
528        };
529
530        for &(x, y) in points.iter().skip(1) {
531            if x < xmin {
532                xmin = x;
533            }
534
535            if x > xmax {
536                xmax = x;
537            }
538
539            if y < ymin {
540                ymin = y;
541            }
542
543            if y > ymax {
544                ymax = y;
545            }
546        }
547
548        Rect {
549            x0: xmin,
550            y0: ymin,
551            x1: xmax,
552            y1: ymax,
553        }
554    }
555}
556
557impl Default for Transform {
558    #[inline]
559    fn default() -> Transform {
560        Transform::identity()
561    }
562}
563
564impl TransformAttribute {
565    pub fn to_transform(self) -> Transform {
566        self.0
567    }
568}
569
570impl Parse for TransformAttribute {
571    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<TransformAttribute, ParseError<'i>> {
572        Ok(TransformAttribute(parse_transform_list(parser)?))
573    }
574}
575
576fn parse_transform_list<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, ParseError<'i>> {
577    let mut t = Transform::identity();
578
579    loop {
580        if parser.is_exhausted() {
581            break;
582        }
583
584        t = parse_transform_command(parser)?.post_transform(&t);
585        optional_comma(parser);
586    }
587
588    Ok(t)
589}
590
591fn parse_transform_command<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, ParseError<'i>> {
592    let loc = parser.current_source_location();
593
594    match parser.next()?.clone() {
595        Token::Function(ref name) => parse_transform_function(name, parser),
596
597        Token::Ident(ref name) => {
598            parser.expect_parenthesis_block()?;
599            parse_transform_function(name, parser)
600        }
601
602        tok => Err(loc.new_unexpected_token_error(tok.clone())),
603    }
604}
605
606fn parse_transform_function<'i>(
607    name: &str,
608    parser: &mut Parser<'i, '_>,
609) -> Result<Transform, ParseError<'i>> {
610    let loc = parser.current_source_location();
611
612    match name {
613        "matrix" => parse_matrix_args(parser),
614        "translate" => parse_translate_args(parser),
615        "scale" => parse_scale_args(parser),
616        "rotate" => parse_rotate_args(parser),
617        "skewX" => parse_skew_x_args(parser),
618        "skewY" => parse_skew_y_args(parser),
619        _ => Err(loc.new_custom_error(ValueErrorKind::parse_error(
620            "expected matrix|translate|scale|rotate|skewX|skewY",
621        ))),
622    }
623}
624
625fn parse_matrix_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, ParseError<'i>> {
626    parser.parse_nested_block(|p| {
627        let xx = f64::parse(p)?;
628        optional_comma(p);
629
630        let yx = f64::parse(p)?;
631        optional_comma(p);
632
633        let xy = f64::parse(p)?;
634        optional_comma(p);
635
636        let yy = f64::parse(p)?;
637        optional_comma(p);
638
639        let x0 = f64::parse(p)?;
640        optional_comma(p);
641
642        let y0 = f64::parse(p)?;
643
644        Ok(Transform::new_unchecked(xx, yx, xy, yy, x0, y0))
645    })
646}
647
648fn parse_translate_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, ParseError<'i>> {
649    parser.parse_nested_block(|p| {
650        let tx = f64::parse(p)?;
651
652        let ty = p
653            .try_parse(|p| {
654                optional_comma(p);
655                f64::parse(p)
656            })
657            .unwrap_or(0.0);
658
659        Ok(Transform::new_translate(tx, ty))
660    })
661}
662
663fn parse_scale_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, ParseError<'i>> {
664    parser.parse_nested_block(|p| {
665        let x = f64::parse(p)?;
666
667        let y = p
668            .try_parse(|p| {
669                optional_comma(p);
670                f64::parse(p)
671            })
672            .unwrap_or(x);
673
674        Ok(Transform::new_scale(x, y))
675    })
676}
677
678fn parse_rotate_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, ParseError<'i>> {
679    parser.parse_nested_block(|p| {
680        let angle = Angle::from_degrees(f64::parse(p)?);
681
682        let (tx, ty) = p
683            .try_parse(|p| -> Result<_, ParseError<'_>> {
684                optional_comma(p);
685                let tx = f64::parse(p)?;
686
687                optional_comma(p);
688                let ty = f64::parse(p)?;
689
690                Ok((tx, ty))
691            })
692            .unwrap_or((0.0, 0.0));
693
694        Ok(Transform::new_translate(tx, ty)
695            .pre_rotate(angle)
696            .pre_translate(-tx, -ty))
697    })
698}
699
700fn parse_skew_x_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, ParseError<'i>> {
701    parser.parse_nested_block(|p| {
702        let angle = Angle::from_degrees(f64::parse(p)?);
703        Ok(Transform::new_skew(angle, Angle::new(0.0)))
704    })
705}
706
707fn parse_skew_y_args<'i>(parser: &mut Parser<'i, '_>) -> Result<Transform, ParseError<'i>> {
708    parser.parse_nested_block(|p| {
709        let angle = Angle::from_degrees(f64::parse(p)?);
710        Ok(Transform::new_skew(Angle::new(0.0), angle))
711    })
712}
713
714#[cfg(test)]
715mod tests {
716    use super::*;
717    use float_cmp::ApproxEq;
718    use std::f64;
719
720    fn rotation_transform(deg: f64, tx: f64, ty: f64) -> Transform {
721        Transform::new_translate(tx, ty)
722            .pre_rotate(Angle::from_degrees(deg))
723            .pre_translate(-tx, -ty)
724    }
725
726    fn parse_transform_attr(s: &str) -> Result<Transform, ParseError<'_>> {
727        let transform_attr = TransformAttribute::parse_str(s)?;
728        Ok(transform_attr.to_transform())
729    }
730
731    fn parse_transform_prop(s: &str) -> Result<TransformProperty, ParseError<'_>> {
732        TransformProperty::parse_str(s)
733    }
734
735    fn assert_transform_eq(t1: &Transform, t2: &Transform) {
736        let epsilon = 8.0 * f64::EPSILON; // kind of arbitrary, but allow for some sloppiness
737
738        assert!(t1.xx.approx_eq(t2.xx, (epsilon, 1)));
739        assert!(t1.yx.approx_eq(t2.yx, (epsilon, 1)));
740        assert!(t1.xy.approx_eq(t2.xy, (epsilon, 1)));
741        assert!(t1.yy.approx_eq(t2.yy, (epsilon, 1)));
742        assert!(t1.x0.approx_eq(t2.x0, (epsilon, 1)));
743        assert!(t1.y0.approx_eq(t2.y0, (epsilon, 1)));
744    }
745
746    #[test]
747    fn test_multiply() {
748        let t1 = Transform::identity();
749        let t2 = Transform::new_unchecked(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
750        assert_transform_eq(&Transform::multiply(&t1, &t2), &t2);
751        assert_transform_eq(&Transform::multiply(&t2, &t1), &t2);
752
753        let t1 = Transform::new_unchecked(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
754        let t2 = Transform::new_unchecked(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
755        let r = Transform::new_unchecked(0.0, 0.0, 0.0, 0.0, 5.0, 6.0);
756        assert_transform_eq(&Transform::multiply(&t1, &t2), &t2);
757        assert_transform_eq(&Transform::multiply(&t2, &t1), &r);
758
759        let t1 = Transform::new_unchecked(0.5, 0.0, 0.0, 0.5, 10.0, 10.0);
760        let t2 = Transform::new_unchecked(1.0, 0.0, 0.0, 1.0, -10.0, -10.0);
761        let r1 = Transform::new_unchecked(0.5, 0.0, 0.0, 0.5, 0.0, 0.0);
762        let r2 = Transform::new_unchecked(0.5, 0.0, 0.0, 0.5, 5.0, 5.0);
763        assert_transform_eq(&Transform::multiply(&t1, &t2), &r1);
764        assert_transform_eq(&Transform::multiply(&t2, &t1), &r2);
765    }
766
767    #[test]
768    fn test_invert() {
769        let t = Transform::new_unchecked(2.0, 0.0, 0.0, 0.0, 0.0, 0.0);
770        assert!(!t.is_invertible());
771        assert!(t.invert().is_none());
772
773        let t = Transform::identity();
774        assert!(t.is_invertible());
775        assert!(t.invert().is_some());
776        let i = t.invert().unwrap();
777        assert_transform_eq(&i, &Transform::identity());
778
779        let t = Transform::new_unchecked(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
780        assert!(t.is_invertible());
781        assert!(t.invert().is_some());
782        let i = t.invert().unwrap();
783        assert_transform_eq(&t.pre_transform(&i), &Transform::identity());
784        assert_transform_eq(&t.post_transform(&i), &Transform::identity());
785    }
786
787    #[test]
788    pub fn test_transform_point() {
789        let t = Transform::new_translate(10.0, 10.0);
790        assert_eq!((11.0, 11.0), t.transform_point(1.0, 1.0));
791    }
792
793    #[test]
794    pub fn test_transform_distance() {
795        let t = Transform::new_translate(10.0, 10.0).pre_scale(2.0, 1.0);
796        assert_eq!((2.0, 1.0), t.transform_distance(1.0, 1.0));
797    }
798
799    #[test]
800    fn parses_valid_transform() {
801        let t = Transform::new_unchecked(1.0, 0.0, 0.0, 1.0, 20.0, 30.0);
802        let s = Transform::new_unchecked(10.0, 0.0, 0.0, 10.0, 0.0, 0.0);
803        let r = rotation_transform(30.0, 10.0, 10.0);
804
805        let a = Transform::multiply(&s, &t);
806        assert_transform_eq(
807            &parse_transform_attr("translate(20, 30), scale (10) rotate (30 10 10)").unwrap(),
808            &Transform::multiply(&r, &a),
809        );
810    }
811
812    fn assert_parse_error(s: &str) {
813        assert!(parse_transform_attr(s).is_err());
814    }
815
816    #[test]
817    fn syntax_error_yields_parse_error() {
818        assert_parse_error("foo");
819        assert_parse_error("matrix (1 2 3 4 5)");
820        assert_parse_error("translate(1 2 3 4 5)");
821        assert_parse_error("translate (1,)");
822        assert_parse_error("scale (1,)");
823        assert_parse_error("skewX (1,2)");
824        assert_parse_error("skewY ()");
825        assert_parse_error("skewY");
826    }
827
828    #[test]
829    fn parses_matrix() {
830        assert_transform_eq(
831            &parse_transform_attr("matrix (1 2 3 4 5 6)").unwrap(),
832            &Transform::new_unchecked(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
833        );
834
835        assert_transform_eq(
836            &parse_transform_attr("matrix(1,2,3,4 5 6)").unwrap(),
837            &Transform::new_unchecked(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
838        );
839
840        assert_transform_eq(
841            &parse_transform_attr("matrix (1,2.25,-3.25e2,4 5 6)").unwrap(),
842            &Transform::new_unchecked(1.0, 2.25, -325.0, 4.0, 5.0, 6.0),
843        );
844    }
845
846    #[test]
847    fn parses_translate() {
848        assert_transform_eq(
849            &parse_transform_attr("translate(-1 -2)").unwrap(),
850            &Transform::new_unchecked(1.0, 0.0, 0.0, 1.0, -1.0, -2.0),
851        );
852
853        assert_transform_eq(
854            &parse_transform_attr("translate(-1, -2)").unwrap(),
855            &Transform::new_unchecked(1.0, 0.0, 0.0, 1.0, -1.0, -2.0),
856        );
857
858        assert_transform_eq(
859            &parse_transform_attr("translate(-1)").unwrap(),
860            &Transform::new_unchecked(1.0, 0.0, 0.0, 1.0, -1.0, 0.0),
861        );
862    }
863
864    #[test]
865    fn parses_scale() {
866        assert_transform_eq(
867            &parse_transform_attr("scale (-1)").unwrap(),
868            &Transform::new_unchecked(-1.0, 0.0, 0.0, -1.0, 0.0, 0.0),
869        );
870
871        assert_transform_eq(
872            &parse_transform_attr("scale(-1 -2)").unwrap(),
873            &Transform::new_unchecked(-1.0, 0.0, 0.0, -2.0, 0.0, 0.0),
874        );
875
876        assert_transform_eq(
877            &parse_transform_attr("scale(-1, -2)").unwrap(),
878            &Transform::new_unchecked(-1.0, 0.0, 0.0, -2.0, 0.0, 0.0),
879        );
880    }
881
882    #[test]
883    fn parses_rotate() {
884        assert_transform_eq(
885            &parse_transform_attr("rotate (30)").unwrap(),
886            &rotation_transform(30.0, 0.0, 0.0),
887        );
888        assert_transform_eq(
889            &parse_transform_attr("rotate (30,-1,-2)").unwrap(),
890            &rotation_transform(30.0, -1.0, -2.0),
891        );
892        assert_transform_eq(
893            &parse_transform_attr("rotate(30, -1, -2)").unwrap(),
894            &rotation_transform(30.0, -1.0, -2.0),
895        );
896    }
897
898    #[test]
899    fn parses_skew_x() {
900        assert_transform_eq(
901            &parse_transform_attr("skewX (30)").unwrap(),
902            &Transform::new_skew(Angle::from_degrees(30.0), Angle::new(0.0)),
903        );
904    }
905
906    #[test]
907    fn parses_skew_y() {
908        assert_transform_eq(
909            &parse_transform_attr("skewY (30)").unwrap(),
910            &Transform::new_skew(Angle::new(0.0), Angle::from_degrees(30.0)),
911        );
912    }
913
914    #[test]
915    fn parses_transform_list() {
916        let t = Transform::new_unchecked(1.0, 0.0, 0.0, 1.0, 20.0, 30.0);
917        let s = Transform::new_unchecked(10.0, 0.0, 0.0, 10.0, 0.0, 0.0);
918        let r = rotation_transform(30.0, 10.0, 10.0);
919
920        assert_transform_eq(
921            &parse_transform_attr("scale(10)rotate(30, 10, 10)").unwrap(),
922            &Transform::multiply(&r, &s),
923        );
924
925        assert_transform_eq(
926            &parse_transform_attr("translate(20, 30), scale (10)").unwrap(),
927            &Transform::multiply(&s, &t),
928        );
929
930        let a = Transform::multiply(&s, &t);
931        assert_transform_eq(
932            &parse_transform_attr("translate(20, 30), scale (10) rotate (30 10 10)").unwrap(),
933            &Transform::multiply(&r, &a),
934        );
935    }
936
937    #[test]
938    fn parses_empty() {
939        assert_transform_eq(&parse_transform_attr("").unwrap(), &Transform::identity());
940    }
941
942    #[test]
943    fn garbage_transform_attr_is_error() {
944        assert!(parse_transform_attr("123").is_err());
945        assert!(parse_transform_attr("translate").is_err());
946        assert!(parse_transform_attr("&").is_err());
947        assert!(parse_transform_attr("plonk()").is_err());
948        assert!(parse_transform_attr("plonk ()").is_err());
949    }
950
951    #[test]
952    fn test_parse_transform_property_none() {
953        assert_eq!(
954            parse_transform_prop("none").unwrap(),
955            TransformProperty::None
956        );
957    }
958
959    #[test]
960    fn none_transform_is_identity() {
961        assert_eq!(
962            parse_transform_prop("none").unwrap().to_transform(),
963            Transform::identity()
964        );
965    }
966
967    #[test]
968    fn empty_transform_property_is_error() {
969        // https://www.w3.org/TR/css-transforms-1/#transform-property
970        //
971        // <transform-list> = <transform-function>+
972        //                                        ^ one or more required
973        assert!(parse_transform_prop("").is_err());
974    }
975
976    #[test]
977    fn garbage_transform_property_is_error() {
978        assert!(parse_transform_prop("123").is_err());
979        assert!(parse_transform_prop("translate").is_err());
980        assert!(parse_transform_prop("&").is_err());
981        assert!(parse_transform_prop("plonk()").is_err());
982    }
983
984    #[test]
985    fn test_parse_transform_property_matrix() {
986        let tp = TransformProperty::List(vec![TransformFunction::Matrix(
987            Transform::new_unchecked(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
988        )]);
989
990        assert_eq!(&tp, &parse_transform_prop("matrix(1,2,3,4,5,6)").unwrap());
991        assert_eq!(
992            tp.to_transform(),
993            Transform::new_unchecked(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
994        );
995
996        assert!(parse_transform_prop("matrix(1 2 3 4 5 6)").is_err());
997        assert!(parse_transform_prop("Matrix(1,2,3,4,5,6)").is_err());
998    }
999
1000    #[test]
1001    fn test_parse_transform_property_translate() {
1002        let tpt = TransformProperty::List(vec![TransformFunction::Translate(
1003            Length::<Horizontal>::new(100.0, LengthUnit::Px),
1004            Length::<Vertical>::new(200.0, LengthUnit::Px),
1005        )]);
1006
1007        assert_eq!(
1008            &tpt,
1009            &parse_transform_prop("translate(100px,200px)").unwrap()
1010        );
1011
1012        assert_eq!(
1013            parse_transform_prop("translate(1)").unwrap(),
1014            parse_transform_prop("translate(1, 0)").unwrap()
1015        );
1016
1017        assert_eq!(tpt.to_transform(), Transform::new_translate(100.0, 200.0));
1018
1019        assert!(parse_transform_prop("translate(100, foo)").is_err());
1020        assert!(parse_transform_prop("translate(100, )").is_err());
1021        assert!(parse_transform_prop("translate(100 200)").is_err());
1022        assert!(parse_transform_prop("translate(1px,2px,3px,4px)").is_err());
1023    }
1024
1025    #[test]
1026    fn test_parse_transform_property_translate_x() {
1027        let tptx = TransformProperty::List(vec![TransformFunction::TranslateX(
1028            Length::<Horizontal>::new(100.0, LengthUnit::Px),
1029        )]);
1030
1031        assert_eq!(&tptx, &parse_transform_prop("translateX(100px)").unwrap());
1032
1033        assert_eq!(tptx.to_transform(), Transform::new_translate(100.0, 0.0));
1034
1035        assert!(parse_transform_prop("translateX(1)").is_ok());
1036        assert!(parse_transform_prop("translateX(100 100)").is_err());
1037        assert!(parse_transform_prop("translatex(1px)").is_err());
1038        assert!(parse_transform_prop("translatex(1rad)").is_err());
1039    }
1040
1041    #[test]
1042    fn test_parse_transform_property_translate_y() {
1043        let tpty = TransformProperty::List(vec![TransformFunction::TranslateY(
1044            Length::<Vertical>::new(100.0, LengthUnit::Px),
1045        )]);
1046
1047        assert_eq!(&tpty, &parse_transform_prop("translateY(100px)").unwrap());
1048
1049        assert_eq!(tpty.to_transform(), Transform::new_translate(0.0, 100.0));
1050
1051        assert!(parse_transform_prop("translateY(1)").is_ok());
1052        assert!(parse_transform_prop("translateY(100 100)").is_err());
1053        assert!(parse_transform_prop("translatey(1px)").is_err());
1054    }
1055
1056    #[test]
1057    fn test_translate_only_supports_pixel_units() {
1058        assert!(parse_transform_prop("translate(1in, 2)").is_err());
1059        assert!(parse_transform_prop("translate(1, 2in)").is_err());
1060        assert!(parse_transform_prop("translateX(1cm)").is_err());
1061        assert!(parse_transform_prop("translateY(1cm)").is_err());
1062    }
1063
1064    #[test]
1065    fn test_parse_transform_property_scale() {
1066        let tps = TransformProperty::List(vec![TransformFunction::Scale(1.0, 10.0)]);
1067
1068        assert_eq!(&tps, &parse_transform_prop("scale(1,10)").unwrap());
1069
1070        assert_eq!(
1071            parse_transform_prop("scale(2)").unwrap(),
1072            parse_transform_prop("scale(2, 2)").unwrap()
1073        );
1074
1075        assert_eq!(tps.to_transform(), Transform::new_scale(1.0, 10.0));
1076
1077        assert!(parse_transform_prop("scale(100, foo)").is_err());
1078        assert!(parse_transform_prop("scale(100, )").is_err());
1079        assert!(parse_transform_prop("scale(1 10)").is_err());
1080        assert!(parse_transform_prop("scale(1px,10px)").is_err());
1081        assert!(parse_transform_prop("scale(1%)").is_err());
1082    }
1083
1084    #[test]
1085    fn test_parse_transform_property_scale_x() {
1086        let tpsx = TransformProperty::List(vec![TransformFunction::ScaleX(10.0)]);
1087
1088        assert_eq!(&tpsx, &parse_transform_prop("scaleX(10)").unwrap());
1089
1090        assert_eq!(tpsx.to_transform(), Transform::new_scale(10.0, 1.0));
1091
1092        assert!(parse_transform_prop("scaleX(100 100)").is_err());
1093        assert!(parse_transform_prop("scalex(10)").is_err());
1094        assert!(parse_transform_prop("scaleX(10px)").is_err());
1095    }
1096
1097    #[test]
1098    fn test_parse_transform_property_scale_y() {
1099        let tpsy = TransformProperty::List(vec![TransformFunction::ScaleY(10.0)]);
1100
1101        assert_eq!(&tpsy, &parse_transform_prop("scaleY(10)").unwrap());
1102
1103        assert_eq!(tpsy.to_transform(), Transform::new_scale(1.0, 10.0));
1104
1105        assert!(parse_transform_prop("scaleY(10 1)").is_err());
1106        assert!(parse_transform_prop("scaleY(1px)").is_err());
1107    }
1108
1109    #[test]
1110    fn test_parse_transform_property_rotate() {
1111        let tpr =
1112            TransformProperty::List(vec![TransformFunction::Rotate(Angle::from_degrees(100.0))]);
1113
1114        assert_eq!(&tpr, &parse_transform_prop("rotate(100deg)").unwrap());
1115
1116        assert_eq!(
1117            tpr.to_transform(),
1118            Transform::new_rotate(Angle::from_degrees(100.0))
1119        );
1120
1121        assert!(parse_transform_prop("rotate(100deg 100)").is_err());
1122        assert!(parse_transform_prop("rotate(3px)").is_err());
1123    }
1124
1125    #[test]
1126    fn test_parse_transform_property_skew() {
1127        let tpsk = TransformProperty::List(vec![TransformFunction::Skew(
1128            Angle::from_degrees(90.0),
1129            Angle::from_degrees(120.0),
1130        )]);
1131
1132        assert_eq!(&tpsk, &parse_transform_prop("skew(90deg,120deg)").unwrap());
1133
1134        assert_eq!(
1135            parse_transform_prop("skew(45deg)").unwrap(),
1136            parse_transform_prop("skew(45deg, 0)").unwrap()
1137        );
1138
1139        assert!(parse_transform_prop("skew(1.0,1.0)").is_ok());
1140        assert!(parse_transform_prop("skew(1rad,1rad)").is_ok());
1141
1142        assert_eq!(
1143            tpsk.to_transform(),
1144            Transform::new_skew(Angle::from_degrees(90.0), Angle::from_degrees(120.0))
1145        );
1146
1147        assert!(parse_transform_prop("skew(100, foo)").is_err());
1148        assert!(parse_transform_prop("skew(100, )").is_err());
1149        assert!(parse_transform_prop("skew(1.0px)").is_err());
1150        assert!(parse_transform_prop("skew(1.0,1.0,1deg)").is_err());
1151    }
1152
1153    #[test]
1154    fn test_parse_transform_property_skew_x() {
1155        let tpskx =
1156            TransformProperty::List(vec![TransformFunction::SkewX(Angle::from_degrees(90.0))]);
1157
1158        assert_eq!(&tpskx, &parse_transform_prop("skewX(90deg)").unwrap());
1159        assert!(parse_transform_prop("skewX(1.0)").is_ok());
1160        assert!(parse_transform_prop("skewX(1rad)").is_ok());
1161
1162        assert_eq!(
1163            tpskx.to_transform(),
1164            Transform::new_skew(Angle::from_degrees(90.0), Angle::from_degrees(0.0))
1165        );
1166
1167        assert!(parse_transform_prop("skewx(1.0)").is_err());
1168        assert!(parse_transform_prop("skewX(1.0,1.0)").is_err());
1169    }
1170
1171    #[test]
1172    fn test_parse_transform_property_skew_y() {
1173        let tpsky =
1174            TransformProperty::List(vec![TransformFunction::SkewY(Angle::from_degrees(90.0))]);
1175
1176        assert_eq!(&tpsky, &parse_transform_prop("skewY(90deg)").unwrap());
1177        assert!(parse_transform_prop("skewY(1.0)").is_ok());
1178        assert!(parse_transform_prop("skewY(1rad)").is_ok());
1179
1180        assert_eq!(
1181            tpsky.to_transform(),
1182            Transform::new_skew(Angle::from_degrees(0.0), Angle::from_degrees(90.0))
1183        );
1184
1185        assert!(parse_transform_prop("skewy(1.0)").is_err());
1186        assert!(parse_transform_prop("skewY(1.0,1.0)").is_err());
1187    }
1188}