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