1use std::f64;
5
6use crate::{Error, Stream};
7
8#[derive(Clone, Copy, PartialEq, Debug)]
12#[allow(missing_docs)]
13pub struct Transform {
14 pub a: f64,
15 pub b: f64,
16 pub c: f64,
17 pub d: f64,
18 pub e: f64,
19 pub f: f64,
20}
21
22impl Transform {
23 #[inline]
25 pub fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Self {
26 Transform { a, b, c, d, e, f }
27 }
28}
29
30impl Default for Transform {
31 #[inline]
32 fn default() -> Transform {
33 Transform::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
34 }
35}
36
37#[derive(Clone, Copy, PartialEq, Debug)]
39#[allow(missing_docs)]
40pub enum TransformListToken {
41 Matrix {
42 a: f64,
43 b: f64,
44 c: f64,
45 d: f64,
46 e: f64,
47 f: f64,
48 },
49 Translate {
50 tx: f64,
51 ty: f64,
52 },
53 Scale {
54 sx: f64,
55 sy: f64,
56 },
57 Rotate {
58 angle: f64,
59 },
60 SkewX {
61 angle: f64,
62 },
63 SkewY {
64 angle: f64,
65 },
66}
67
68#[derive(Clone, Copy, PartialEq, Debug)]
94pub struct TransformListParser<'a> {
95 stream: Stream<'a>,
96 rotate_ts: Option<(f64, f64)>,
97 last_angle: Option<f64>,
98}
99
100impl<'a> From<&'a str> for TransformListParser<'a> {
101 fn from(text: &'a str) -> Self {
102 TransformListParser {
103 stream: Stream::from(text),
104 rotate_ts: None,
105 last_angle: None,
106 }
107 }
108}
109
110impl Iterator for TransformListParser<'_> {
111 type Item = Result<TransformListToken, Error>;
112
113 fn next(&mut self) -> Option<Self::Item> {
114 if let Some(a) = self.last_angle {
115 self.last_angle = None;
116 return Some(Ok(TransformListToken::Rotate { angle: a }));
117 }
118
119 if let Some((x, y)) = self.rotate_ts {
120 self.rotate_ts = None;
121 return Some(Ok(TransformListToken::Translate { tx: -x, ty: -y }));
122 }
123
124 self.stream.skip_spaces();
125
126 if self.stream.at_end() {
127 return None;
129 }
130
131 let res = self.parse_next();
132 if res.is_err() {
133 self.stream.jump_to_end();
134 }
135
136 Some(res)
137 }
138}
139
140impl TransformListParser<'_> {
141 fn parse_next(&mut self) -> Result<TransformListToken, Error> {
142 let s = &mut self.stream;
143
144 let start = s.pos();
145 let name = s.consume_ascii_ident();
146 s.skip_spaces();
147 s.consume_byte(b'(')?;
148
149 let t = match name.as_bytes() {
150 b"matrix" => TransformListToken::Matrix {
151 a: s.parse_list_number()?,
152 b: s.parse_list_number()?,
153 c: s.parse_list_number()?,
154 d: s.parse_list_number()?,
155 e: s.parse_list_number()?,
156 f: s.parse_list_number()?,
157 },
158 b"translate" => {
159 let x = s.parse_list_number()?;
160 s.skip_spaces();
161
162 let y = if s.is_curr_byte_eq(b')') {
163 0.0
165 } else {
166 s.parse_list_number()?
167 };
168
169 TransformListToken::Translate { tx: x, ty: y }
170 }
171 b"scale" => {
172 let x = s.parse_list_number()?;
173 s.skip_spaces();
174
175 let y = if s.is_curr_byte_eq(b')') {
176 x
178 } else {
179 s.parse_list_number()?
180 };
181
182 TransformListToken::Scale { sx: x, sy: y }
183 }
184 b"rotate" => {
185 let a = s.parse_list_number()?;
186 s.skip_spaces();
187
188 if !s.is_curr_byte_eq(b')') {
189 let cx = s.parse_list_number()?;
194 let cy = s.parse_list_number()?;
195 self.rotate_ts = Some((cx, cy));
196 self.last_angle = Some(a);
197
198 TransformListToken::Translate { tx: cx, ty: cy }
199 } else {
200 TransformListToken::Rotate { angle: a }
201 }
202 }
203 b"skewX" => TransformListToken::SkewX {
204 angle: s.parse_list_number()?,
205 },
206 b"skewY" => TransformListToken::SkewY {
207 angle: s.parse_list_number()?,
208 },
209 _ => {
210 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
211 }
212 };
213
214 s.skip_spaces();
215 s.consume_byte(b')')?;
216 s.skip_spaces();
217
218 if s.is_curr_byte_eq(b',') {
219 s.advance(1);
220 }
221
222 Ok(t)
223 }
224}
225
226impl std::str::FromStr for Transform {
227 type Err = Error;
228
229 fn from_str(text: &str) -> Result<Self, Error> {
230 let tokens = TransformListParser::from(text);
231 let mut ts = Transform::default();
232
233 for token in tokens {
234 match token? {
235 TransformListToken::Matrix { a, b, c, d, e, f } => {
236 ts = multiply(&ts, &Transform::new(a, b, c, d, e, f))
237 }
238 TransformListToken::Translate { tx, ty } => {
239 ts = multiply(&ts, &Transform::new(1.0, 0.0, 0.0, 1.0, tx, ty))
240 }
241 TransformListToken::Scale { sx, sy } => {
242 ts = multiply(&ts, &Transform::new(sx, 0.0, 0.0, sy, 0.0, 0.0))
243 }
244 TransformListToken::Rotate { angle } => {
245 let v = angle.to_radians();
246 let a = v.cos();
247 let b = v.sin();
248 let c = -b;
249 let d = a;
250 ts = multiply(&ts, &Transform::new(a, b, c, d, 0.0, 0.0))
251 }
252 TransformListToken::SkewX { angle } => {
253 let c = angle.to_radians().tan();
254 ts = multiply(&ts, &Transform::new(1.0, 0.0, c, 1.0, 0.0, 0.0))
255 }
256 TransformListToken::SkewY { angle } => {
257 let b = angle.to_radians().tan();
258 ts = multiply(&ts, &Transform::new(1.0, b, 0.0, 1.0, 0.0, 0.0))
259 }
260 }
261 }
262
263 Ok(ts)
264 }
265}
266
267#[inline(never)]
268fn multiply(ts1: &Transform, ts2: &Transform) -> Transform {
269 Transform {
270 a: ts1.a * ts2.a + ts1.c * ts2.b,
271 b: ts1.b * ts2.a + ts1.d * ts2.b,
272 c: ts1.a * ts2.c + ts1.c * ts2.d,
273 d: ts1.b * ts2.c + ts1.d * ts2.d,
274 e: ts1.a * ts2.e + ts1.c * ts2.f + ts1.e,
275 f: ts1.b * ts2.e + ts1.d * ts2.f + ts1.f,
276 }
277}
278
279#[rustfmt::skip]
280#[cfg(test)]
281mod tests {
282 use std::str::FromStr;
283 use super::*;
284
285 macro_rules! test {
286 ($name:ident, $text:expr, $result:expr) => (
287 #[test]
288 fn $name() {
289 let ts = Transform::from_str($text).unwrap();
290 let s = format!("matrix({} {} {} {} {} {})", ts.a, ts.b, ts.c, ts.d, ts.e, ts.f);
291 assert_eq!(s, $result);
292 }
293 )
294 }
295
296 test!(parse_1,
297 "matrix(1 0 0 1 10 20)",
298 "matrix(1 0 0 1 10 20)"
299 );
300
301 test!(parse_2,
302 "translate(10 20)",
303 "matrix(1 0 0 1 10 20)"
304 );
305
306 test!(parse_3,
307 "scale(2 3)",
308 "matrix(2 0 0 3 0 0)"
309 );
310
311 test!(parse_4,
312 "rotate(30)",
313 "matrix(0.8660254037844387 0.49999999999999994 -0.49999999999999994 0.8660254037844387 0 0)"
314 );
315
316 test!(parse_5,
317 "rotate(30 10 20)",
318 "matrix(0.8660254037844387 0.49999999999999994 -0.49999999999999994 0.8660254037844387 11.339745962155611 -2.3205080756887746)"
319 );
320
321 test!(parse_6,
322 "translate(10 15) translate(0 5)",
323 "matrix(1 0 0 1 10 20)"
324 );
325
326 test!(parse_7,
327 "translate(10) scale(2)",
328 "matrix(2 0 0 2 10 0)"
329 );
330
331 test!(parse_8,
332 "translate(25 215) scale(2) skewX(45)",
333 "matrix(2 0 1.9999999999999998 2 25 215)"
334 );
335
336 test!(parse_9,
337 "skewX(45)",
338 "matrix(1 0 0.9999999999999999 1 0 0)"
339 );
340
341 macro_rules! test_err {
342 ($name:ident, $text:expr, $result:expr) => (
343 #[test]
344 fn $name() {
345 let ts = Transform::from_str($text);
346 assert_eq!(ts.unwrap_err().to_string(), $result);
347 }
348 )
349 }
350
351 test_err!(parse_err_1, "text", "unexpected end of stream");
352
353 #[test]
354 fn parse_err_2() {
355 let mut ts = TransformListParser::from("scale(2) text");
356 let _ = ts.next().unwrap();
357 assert_eq!(ts.next().unwrap().unwrap_err().to_string(),
358 "unexpected end of stream");
359 }
360
361 test_err!(parse_err_3, "???G", "expected '(' not '?' at position 1");
362
363 #[test]
364 fn parse_err_4() {
365 let mut ts = TransformListParser::from(" ");
366 assert!(ts.next().is_none());
367 }
368
369 #[test]
370 fn parse_err_5() {
371 let mut ts = TransformListParser::from("\x01");
372 assert!(ts.next().unwrap().is_err());
373 }
374
375 test_err!(parse_err_6, "rect()", "unexpected data at position 1");
376
377 test_err!(parse_err_7, "scale(2) rect()", "unexpected data at position 10");
378}