use std::fmt;
use std::ops::Mul;
use std::f64;
use {WriteOptions, WriteBuffer, WriteToString};
use super::number::{write_num, FuzzyEq};
#[cfg(feature = "parsing")]
use FromStream;
#[cfg(feature = "parsing")]
use svgparser::{TextFrame, Tokenize, Error as ParseError};
#[derive(Debug,Clone,Copy,PartialEq)]
#[allow(missing_docs)]
pub struct Transform {
pub a: f64,
pub b: f64,
pub c: f64,
pub d: f64,
pub e: f64,
pub f: f64,
}
impl Transform {
pub fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Transform {
Transform {
a: a,
b: b,
c: c,
d: d,
e: e,
f: f,
}
}
pub fn translate(&mut self, x: f64, y: f64) {
self.append(&Transform::new(1.0, 0.0, 0.0, 1.0, x, y));
}
pub fn scale(&mut self, sx: f64, sy: f64) {
self.append(&Transform::new(sx, 0.0, 0.0, sy, 0.0, 0.0));
}
pub fn rotate(&mut self, angle: f64) {
let v = (angle / 180.0) * f64::consts::PI;
let a = v.cos();
let b = v.sin();
let c = -b;
let d = a;
self.append(&Transform::new(a, b, c, d, 0.0, 0.0));
}
pub fn skew_x(&mut self, angle: f64) {
let c = ((angle / 180.0) * f64::consts::PI).tan();
self.append(&Transform::new(1.0, 0.0, c, 1.0, 0.0, 0.0));
}
pub fn skew_y(&mut self, angle: f64) {
let b = ((angle / 180.0) * f64::consts::PI).tan();
self.append(&Transform::new(1.0, b, 0.0, 1.0, 0.0, 0.0));
}
pub fn append(&mut self, t: &Transform) {
let tm = self.to_matrix() * t.to_matrix();
self.a = tm.d[0][0];
self.c = tm.d[1][0];
self.e = tm.d[2][0];
self.b = tm.d[0][1];
self.d = tm.d[1][1];
self.f = tm.d[2][1];
}
fn to_matrix(&self) -> TransformMatrix {
let mut tm = TransformMatrix::default();
tm.d[0][0] = self.a;
tm.d[1][0] = self.c;
tm.d[2][0] = self.e;
tm.d[0][1] = self.b;
tm.d[1][1] = self.d;
tm.d[2][1] = self.f;
tm
}
pub fn is_default(&self) -> bool {
self.a.fuzzy_eq(&1.0)
&& self.b.fuzzy_eq(&0.0)
&& self.c.fuzzy_eq(&0.0)
&& self.d.fuzzy_eq(&1.0)
&& self.e.fuzzy_eq(&0.0)
&& self.f.fuzzy_eq(&0.0)
}
pub fn is_translate(&self) -> bool {
self.a.fuzzy_eq(&1.0)
&& self.b.fuzzy_eq(&0.0)
&& self.c.fuzzy_eq(&0.0)
&& self.d.fuzzy_eq(&1.0)
&& (self.e.fuzzy_ne(&0.0) || self.f.fuzzy_ne(&0.0))
}
pub fn is_scale(&self) -> bool {
(self.a.fuzzy_ne(&1.0) || self.d.fuzzy_ne(&1.0))
&& self.b.fuzzy_eq(&0.0)
&& self.c.fuzzy_eq(&0.0)
&& self.e.fuzzy_eq(&0.0)
&& self.f.fuzzy_eq(&0.0)
}
pub fn has_translate(&self) -> bool {
self.e.fuzzy_ne(&0.0) || self.f.fuzzy_ne(&0.0)
}
pub fn has_scale(&self) -> bool {
let (sx, sy) = self.get_scale();
sx.fuzzy_ne(&1.0) || sy.fuzzy_ne(&1.0)
}
pub fn has_proportional_scale(&self) -> bool {
let (sx, sy) = self.get_scale();
sx.fuzzy_eq(&sy)
}
pub fn has_skew(&self) -> bool {
let (skew_x, skew_y) = self.get_skew();
skew_x.fuzzy_ne(&0.0) || skew_y.fuzzy_ne(&0.0)
}
pub fn has_rotate(&self) -> bool {
self.get_rotate().fuzzy_ne(&0.0)
}
pub fn get_translate(&self) -> (f64, f64) {
(self.e, self.f)
}
pub fn get_scale(&self) -> (f64, f64) {
let x_scale = (self.a * self.a + self.c * self.c).sqrt();
let y_scale = (self.b * self.b + self.d * self.d).sqrt();
(x_scale, y_scale)
}
pub fn get_skew(&self) -> (f64, f64) {
let rad = 180.0 / f64::consts::PI;
let skew_x = rad * (self.d).atan2(self.c) - 90.0;
let skew_y = rad * (self.b).atan2(self.a);
(skew_x, skew_y)
}
pub fn get_rotate(&self) -> f64 {
let rad = 180.0 / f64::consts::PI;
let mut angle = (-self.b/self.a).atan() * rad;
if self.b < self.c || self.b > self.c {
angle = -angle;
}
angle
}
pub fn apply(&self, x: f64, y: f64) -> (f64,f64) {
let new_x = self.a * x + self.c * y + self.e;
let new_y = self.b * x + self.d * y + self.f;
(new_x, new_y)
}
pub fn apply_ref(&self, x: &mut f64, y: &mut f64) {
let tx = *x;
let ty = *y;
*x = self.a * tx + self.c * ty + self.e;
*y = self.b * tx + self.d * ty + self.f;
}
pub fn fuzzy_eq(&self, other: &Transform) -> bool {
self.a.fuzzy_eq(&other.a)
&& self.b.fuzzy_eq(&other.b)
&& self.c.fuzzy_eq(&other.c)
&& self.d.fuzzy_eq(&other.d)
&& self.e.fuzzy_eq(&other.e)
&& self.f.fuzzy_eq(&other.f)
}
}
impl Default for Transform {
fn default() -> Transform {
Transform::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
}
}
#[cfg(feature = "parsing")]
impl FromStream for Transform {
type Err = ParseError;
fn from_stream(s: TextFrame) -> Result<Transform, ParseError> {
use svgparser::transform::Tokenizer;
use svgparser::transform::Token;
let mut ts = Tokenizer::from_frame(s);
let mut transform = Transform::default();
loop {
match ts.parse_next()? {
Token::Matrix { a, b, c, d, e, f } =>
{ transform.append(&Transform::new(a, b, c, d, e, f)); }
Token::Translate { tx, ty } => { transform.translate(tx, ty); }
Token::Scale { sx, sy } => { transform.scale(sx, sy); }
Token::Rotate { angle } => { transform.rotate(angle); }
Token::SkewX { angle } => { transform.skew_x(angle); }
Token::SkewY { angle } => { transform.skew_y(angle); }
Token::EndOfStream => break,
}
}
Ok(transform)
}
}
impl WriteBuffer for Transform {
fn write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>) {
if self.is_default() {
return;
}
if opt.simplify_transform_matrices {
write_simplified_transform(self, opt, buf);
} else {
write_matrix_transform(self, opt, buf);
}
}
}
fn write_matrix_transform(ts: &Transform, opt: &WriteOptions, out: &mut Vec<u8>) {
let rm = opt.remove_leading_zero;
out.extend_from_slice(b"matrix(");
write_num(&ts.a, rm, out);
out.push(b' ');
write_num(&ts.b, rm, out);
out.push(b' ');
write_num(&ts.c, rm, out);
out.push(b' ');
write_num(&ts.d, rm, out);
out.push(b' ');
write_num(&ts.e, rm, out);
out.push(b' ');
write_num(&ts.f, rm, out);
out.push(b')');
}
fn write_simplified_transform(ts: &Transform, opt: &WriteOptions, out: &mut Vec<u8>) {
let rm = opt.remove_leading_zero;
if ts.is_translate() {
out.extend_from_slice(b"translate(");
write_num(&ts.e, rm, out);
if ts.f.fuzzy_ne(&0.0) {
out.push(b' ');
write_num(&ts.f, rm, out);
}
out.push(b')');
} else if ts.is_scale() {
out.extend_from_slice(b"scale(");
write_num(&ts.a, rm, out);
if ts.a.fuzzy_ne(&ts.d) {
out.push(b' ');
write_num(&ts.d, rm, out);
}
out.push(b')');
} else if !ts.has_translate() {
let a = ts.get_rotate();
let (sx, sy) = ts.get_scale();
let (skx, sky) = ts.get_skew();
if a.fuzzy_eq(&skx) && a.fuzzy_eq(&sky) && sx.fuzzy_eq(&1.0) && sy.fuzzy_eq(&1.0) {
out.extend_from_slice(b"rotate(");
write_num(&a, rm, out);
out.push(b')');
} else {
write_matrix_transform(ts, opt, out);
}
} else {
write_matrix_transform(ts, opt, out);
}
}
impl_display!(Transform);
struct TransformMatrix {
d: [[f64; 3]; 3]
}
impl Default for TransformMatrix {
fn default() -> TransformMatrix {
TransformMatrix {
d: [[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]]
}
}
}
impl Mul for TransformMatrix {
type Output = TransformMatrix;
fn mul(self, other: TransformMatrix) -> TransformMatrix {
let mut res = TransformMatrix::default();
for row in 0..3 {
for col in 0..3 {
let mut sum = 0.0;
for j in 0..3 {
sum += self.d[j][row] * other.d[col][j];
}
res.d[col][row] = sum;
}
}
res
}
}
#[cfg(test)]
mod tests {
use super::*;
use {
Document,
FromStream,
WriteOptions,
WriteBuffer,
AttributeId as AId,
AttributeValue,
};
macro_rules! test_transform {
($name:ident, $text:expr, $result:expr) => (
#[test]
fn $name() {
let doc = Document::from_str($text).unwrap();
let svg = doc.root().first_child().unwrap();
match svg.attribute_value(AId::Transform).unwrap() {
AttributeValue::Transform(v) => assert_eq!(v.to_string(), $result),
_ => unreachable!(),
}
}
)
}
test_transform!(parse_transform_1,
"<svg transform='matrix(1 0 0 1 10 20)'/>",
"matrix(1 0 0 1 10 20)"
);
test_transform!(parse_transform_2,
"<svg transform='translate(10 20)'/>",
"matrix(1 0 0 1 10 20)"
);
test_transform!(parse_transform_3,
"<svg transform='scale(2 3)'/>",
"matrix(2 0 0 3 0 0)"
);
test_transform!(parse_transform_4,
"<svg transform='rotate(30)'/>",
"matrix(0.866025403784 0.5 -0.5 0.866025403784 0 0)"
);
test_transform!(parse_transform_5,
"<svg transform='rotate(30 10 20)'/>",
"matrix(0.866025403784 0.5 -0.5 0.866025403784 11.339745962156 -2.320508075689)"
);
test_transform!(parse_transform_6,
"<svg transform='translate(10 15) translate(0 5)'/>",
"matrix(1 0 0 1 10 20)"
);
test_transform!(parse_transform_7,
"<svg transform='translate(10) scale(2)'/>",
"matrix(2 0 0 2 10 0)"
);
test_transform!(parse_transform_8,
"<svg transform='translate(25 215) scale(2) skewX(45)'/>",
"matrix(2 0 2 2 25 215)"
);
test_transform!(parse_transform_9,
"<svg transform='skewX(45)'/>",
"matrix(1 0 1 1 0 0)"
);
#[test]
fn api_1() {
let mut ts = Transform::default();
ts.translate(10.0, 20.0);
assert_eq!(Transform::from_str("translate(10 20)").unwrap(), ts);
}
#[test]
fn api_2() {
let mut ts = Transform::default();
ts.scale(2.0, 3.0);
assert_eq!(Transform::from_str("scale(2 3)").unwrap(), ts);
}
#[test]
fn api_3() {
let mut ts = Transform::default();
ts.skew_x(20.0);
assert_eq!(Transform::from_str("skewX(20)").unwrap(), ts);
}
#[test]
fn api_4() {
let mut ts = Transform::default();
ts.skew_y(20.0);
assert_eq!(Transform::from_str("skewY(20)").unwrap(), ts);
}
#[test]
fn api_5() {
let mut ts = Transform::default();
ts.rotate(20.0);
assert_eq!(Transform::from_str("rotate(20)").unwrap(), ts);
}
macro_rules! test_ts {
($name:ident, $ts:expr, $simplify:expr, $result:expr) => (
#[test]
fn $name() {
let mut opt = WriteOptions::default();
opt.simplify_transform_matrices = $simplify;
let mut out = Vec::new();
$ts.write_buf_opt(&opt, &mut out);
assert_eq!(String::from_utf8(out).unwrap(), $result);
}
)
}
test_ts!(write_buf_1, Transform::default(), false, "");
test_ts!(write_buf_2,
Transform::new(2.0, 0.0, 0.0, 3.0, 20.0, 30.0), false,
"matrix(2 0 0 3 20 30)"
);
test_ts!(write_buf_3,
Transform::new(1.0, 0.0, 0.0, 1.0, 20.0, 30.0), true,
"translate(20 30)"
);
test_ts!(write_buf_4,
Transform::new(1.0, 0.0, 0.0, 1.0, 20.0, 0.0), true,
"translate(20)"
);
test_ts!(write_buf_5,
Transform::new(2.0, 0.0, 0.0, 3.0, 0.0, 0.0), true,
"scale(2 3)"
);
test_ts!(write_buf_6,
Transform::new(2.0, 0.0, 0.0, 2.0, 0.0, 0.0), true,
"scale(2)"
);
test_ts!(write_buf_7,
Transform::from_str("rotate(30)").unwrap(), true,
"rotate(30)"
);
test_ts!(write_buf_8,
Transform::from_str("rotate(-45)").unwrap(), true,
"rotate(-45)"
);
test_ts!(write_buf_9,
Transform::from_str("rotate(33)").unwrap(), true,
"rotate(33)"
);
test_ts!(write_buf_10,
Transform::from_str("scale(-1)").unwrap(), true,
"scale(-1)"
);
test_ts!(write_buf_11,
Transform::from_str("scale(-1 1)").unwrap(), true,
"scale(-1 1)"
);
test_ts!(write_buf_12,
Transform::from_str("scale(1 -1)").unwrap(), true,
"scale(1 -1)"
);
}