Skip to content

Commit 7a70e09

Browse files
committed
Implement transform-origin
1 parent efed7c0 commit 7a70e09

5 files changed

Lines changed: 68 additions & 7 deletions

File tree

lib/src/converter/visit.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use std::str::FromStr;
33
use euclid::default::Transform2D;
44
use log::{debug, warn};
55
use roxmltree::{Document, Node};
6-
use svgtypes::{AspectRatio, PathParser, PathSegment, PointsParser, TransformListParser, ViewBox};
6+
use svgtypes::{
7+
AspectRatio, LengthListParser, PathParser, PathSegment, PointsParser, TransformListParser,
8+
ViewBox,
9+
};
710

811
use super::{
912
ConversionVisitor,
@@ -108,12 +111,6 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
108111
warn!("Clip paths are not supported: {:?}", node);
109112
}
110113

111-
// TODO: https://www.w3.org/TR/css-transforms-1/#transform-origin-property
112-
if let Some(mut origin) = node.attribute("transform-origin").map(PointsParser::from) {
113-
let _origin = origin.next();
114-
warn!("transform-origin not supported yet");
115-
}
116-
117114
let mut flattened_transform = if let Some(transform) = node.attribute("transform") {
118115
// https://stackoverflow.com/questions/18582935/the-applying-order-of-svg-transforms
119116
TransformListParser::from(transform)
@@ -124,6 +121,28 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
124121
Transform2D::identity()
125122
};
126123

124+
// https://www.w3.org/TR/css-transforms-1/#transform-origin-property
125+
if let Some(to_str) = node.attribute("transform-origin") {
126+
let mut parser = LengthListParser::from(to_str);
127+
let ox = parser
128+
.next()
129+
.and_then(|r| r.ok())
130+
.map(|l| self.length_to_user_units(l, DimensionHint::Horizontal))
131+
.unwrap_or(0.);
132+
let oy = parser
133+
.next()
134+
.and_then(|r| r.ok())
135+
.map(|l| self.length_to_user_units(l, DimensionHint::Vertical))
136+
.unwrap_or(0.);
137+
if ox != 0. || oy != 0. {
138+
// https://www.w3.org/TR/css-transforms-1/#transformation-matrix-computation
139+
// Steps 2 & 4
140+
flattened_transform = Transform2D::translation(-ox, -oy)
141+
.then(&flattened_transform)
142+
.then(&Transform2D::translation(ox, oy));
143+
}
144+
}
145+
127146
// https://www.w3.org/TR/SVG/coords.html#EstablishingANewSVGViewport
128147
if node.has_tag_name(SVG_TAG_NAME) {
129148
let view_box = node

lib/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,37 @@ mod test {
324324
.collect::<Vec<_>>();
325325
let actual = get_actual(svg, false, [None; 2]);
326326

327+
assert_close(actual, expected);
328+
}
329+
330+
#[test]
331+
fn transform_origin_produces_expected_gcode() {
332+
let svg = include_str!("../tests/transform_origin.svg");
333+
let expected = g_code::parse::file_parser(include_str!("../tests/transform_origin.gcode"))
334+
.unwrap()
335+
.iter_emit_tokens()
336+
.collect::<Vec<_>>();
337+
let actual = get_actual(svg, false, [None; 2]);
327338
assert_close(actual, expected)
328339
}
329340

341+
/// `transform-origin="5 5"` with `rotate(90)` should be identical to the
342+
/// manual SVG equivalent `translate(5,5) rotate(90) translate(-5,-5)`
343+
#[test]
344+
fn transform_origin_matches_manual_equivalent() {
345+
let with_origin = get_actual(
346+
include_str!("../tests/transform_origin.svg"),
347+
false,
348+
[None; 2],
349+
);
350+
let manual = get_actual(
351+
include_str!("../tests/transform_origin_equivalent.svg"),
352+
false,
353+
[None; 2],
354+
);
355+
assert_close(with_origin, manual)
356+
}
357+
330358
#[test]
331359
#[cfg(feature = "serde")]
332360
fn deserialize_v1_config_succeeds() {

lib/tests/transform_origin.gcode

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
G21
2+
G90;svg > g > path
3+
G0 X9 Y5
4+
G1 X5 Y5 F300

lib/tests/transform_origin.svg

Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)