use std::f64;
use crate::{Error, Stream};
#[derive(Clone, Copy, PartialEq, Debug)]
#[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 {
#[inline]
pub fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Self {
Transform { a, b, c, d, e, f }
}
}
impl Default for Transform {
#[inline]
fn default() -> Transform {
Transform::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub enum TransformListToken {
Matrix {
a: f64,
b: f64,
c: f64,
d: f64,
e: f64,
f: f64,
},
Translate {
tx: f64,
ty: f64,
},
Scale {
sx: f64,
sy: f64,
},
Rotate {
angle: f64,
},
SkewX {
angle: f64,
},
SkewY {
angle: f64,
},
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct TransformListParser<'a> {
stream: Stream<'a>,
rotate_ts: Option<(f64, f64)>,
last_angle: Option<f64>,
}
impl<'a> From<&'a str> for TransformListParser<'a> {
fn from(text: &'a str) -> Self {
TransformListParser {
stream: Stream::from(text),
rotate_ts: None,
last_angle: None,
}
}
}
impl<'a> Iterator for TransformListParser<'a> {
type Item = Result<TransformListToken, Error>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(a) = self.last_angle {
self.last_angle = None;
return Some(Ok(TransformListToken::Rotate { angle: a }));
}
if let Some((x, y)) = self.rotate_ts {
self.rotate_ts = None;
return Some(Ok(TransformListToken::Translate { tx: -x, ty: -y }));
}
self.stream.skip_spaces();
if self.stream.at_end() {
return None;
}
let res = self.parse_next();
if res.is_err() {
self.stream.jump_to_end();
}
Some(res)
}
}
impl<'a> TransformListParser<'a> {
fn parse_next(&mut self) -> Result<TransformListToken, Error> {
let s = &mut self.stream;
let start = s.pos();
let name = s.consume_ascii_ident();
s.skip_spaces();
s.consume_byte(b'(')?;
let t = match name.as_bytes() {
b"matrix" => TransformListToken::Matrix {
a: s.parse_list_number()?,
b: s.parse_list_number()?,
c: s.parse_list_number()?,
d: s.parse_list_number()?,
e: s.parse_list_number()?,
f: s.parse_list_number()?,
},
b"translate" => {
let x = s.parse_list_number()?;
s.skip_spaces();
let y = if s.is_curr_byte_eq(b')') {
0.0
} else {
s.parse_list_number()?
};
TransformListToken::Translate { tx: x, ty: y }
}
b"scale" => {
let x = s.parse_list_number()?;
s.skip_spaces();
let y = if s.is_curr_byte_eq(b')') {
x
} else {
s.parse_list_number()?
};
TransformListToken::Scale { sx: x, sy: y }
}
b"rotate" => {
let a = s.parse_list_number()?;
s.skip_spaces();
if !s.is_curr_byte_eq(b')') {
let cx = s.parse_list_number()?;
let cy = s.parse_list_number()?;
self.rotate_ts = Some((cx, cy));
self.last_angle = Some(a);
TransformListToken::Translate { tx: cx, ty: cy }
} else {
TransformListToken::Rotate { angle: a }
}
}
b"skewX" => TransformListToken::SkewX {
angle: s.parse_list_number()?,
},
b"skewY" => TransformListToken::SkewY {
angle: s.parse_list_number()?,
},
_ => {
return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
}
};
s.skip_spaces();
s.consume_byte(b')')?;
s.skip_spaces();
if s.is_curr_byte_eq(b',') {
s.advance(1);
}
Ok(t)
}
}
impl std::str::FromStr for Transform {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Error> {
let tokens = TransformListParser::from(text);
let mut ts = Transform::default();
for token in tokens {
match token? {
TransformListToken::Matrix { a, b, c, d, e, f } => {
ts = multiply(&ts, &Transform::new(a, b, c, d, e, f))
}
TransformListToken::Translate { tx, ty } => {
ts = multiply(&ts, &Transform::new(1.0, 0.0, 0.0, 1.0, tx, ty))
}
TransformListToken::Scale { sx, sy } => {
ts = multiply(&ts, &Transform::new(sx, 0.0, 0.0, sy, 0.0, 0.0))
}
TransformListToken::Rotate { angle } => {
let v = angle.to_radians();
let a = v.cos();
let b = v.sin();
let c = -b;
let d = a;
ts = multiply(&ts, &Transform::new(a, b, c, d, 0.0, 0.0))
}
TransformListToken::SkewX { angle } => {
let c = angle.to_radians().tan();
ts = multiply(&ts, &Transform::new(1.0, 0.0, c, 1.0, 0.0, 0.0))
}
TransformListToken::SkewY { angle } => {
let b = angle.to_radians().tan();
ts = multiply(&ts, &Transform::new(1.0, b, 0.0, 1.0, 0.0, 0.0))
}
}
}
Ok(ts)
}
}
#[inline(never)]
fn multiply(ts1: &Transform, ts2: &Transform) -> Transform {
Transform {
a: ts1.a * ts2.a + ts1.c * ts2.b,
b: ts1.b * ts2.a + ts1.d * ts2.b,
c: ts1.a * ts2.c + ts1.c * ts2.d,
d: ts1.b * ts2.c + ts1.d * ts2.d,
e: ts1.a * ts2.e + ts1.c * ts2.f + ts1.e,
f: ts1.b * ts2.e + ts1.d * ts2.f + ts1.f,
}
}
#[rustfmt::skip]
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
macro_rules! test {
($name:ident, $text:expr, $result:expr) => (
#[test]
fn $name() {
let ts = Transform::from_str($text).unwrap();
let s = format!("matrix({} {} {} {} {} {})", ts.a, ts.b, ts.c, ts.d, ts.e, ts.f);
assert_eq!(s, $result);
}
)
}
test!(parse_1,
"matrix(1 0 0 1 10 20)",
"matrix(1 0 0 1 10 20)"
);
test!(parse_2,
"translate(10 20)",
"matrix(1 0 0 1 10 20)"
);
test!(parse_3,
"scale(2 3)",
"matrix(2 0 0 3 0 0)"
);
test!(parse_4,
"rotate(30)",
"matrix(0.8660254037844387 0.49999999999999994 -0.49999999999999994 0.8660254037844387 0 0)"
);
test!(parse_5,
"rotate(30 10 20)",
"matrix(0.8660254037844387 0.49999999999999994 -0.49999999999999994 0.8660254037844387 11.339745962155611 -2.3205080756887746)"
);
test!(parse_6,
"translate(10 15) translate(0 5)",
"matrix(1 0 0 1 10 20)"
);
test!(parse_7,
"translate(10) scale(2)",
"matrix(2 0 0 2 10 0)"
);
test!(parse_8,
"translate(25 215) scale(2) skewX(45)",
"matrix(2 0 1.9999999999999998 2 25 215)"
);
test!(parse_9,
"skewX(45)",
"matrix(1 0 0.9999999999999999 1 0 0)"
);
macro_rules! test_err {
($name:ident, $text:expr, $result:expr) => (
#[test]
fn $name() {
let ts = Transform::from_str($text);
assert_eq!(ts.unwrap_err().to_string(), $result);
}
)
}
test_err!(parse_err_1, "text", "unexpected end of stream");
#[test]
fn parse_err_2() {
let mut ts = TransformListParser::from("scale(2) text");
let _ = ts.next().unwrap();
assert_eq!(ts.next().unwrap().unwrap_err().to_string(),
"unexpected end of stream");
}
test_err!(parse_err_3, "???G", "expected '(' not '?' at position 1");
#[test]
fn parse_err_4() {
let mut ts = TransformListParser::from(" ");
assert_eq!(ts.next().is_none(), true);
}
#[test]
fn parse_err_5() {
let mut ts = TransformListParser::from("\x01");
assert_eq!(ts.next().unwrap().is_err(), true);
}
test_err!(parse_err_6, "rect()", "unexpected data at position 1");
test_err!(parse_err_7, "scale(2) rect()", "unexpected data at position 10");
}