Skip to content

Commit e7af975

Browse files
committed
Implement transform-origin
1 parent efed7c0 commit e7af975

5 files changed

Lines changed: 65 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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,34 @@ 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 =
334+
g_code::parse::file_parser(include_str!("../tests/transform_origin.gcode"))
335+
.unwrap()
336+
.iter_emit_tokens()
337+
.collect::<Vec<_>>();
338+
let actual = get_actual(svg, false, [None; 2]);
327339
assert_close(actual, expected)
328340
}
329341

342+
/// `transform-origin="5 5"` with `rotate(90)` should be identical to the
343+
/// manual SVG equivalent `translate(5,5) rotate(90) translate(-5,-5)`
344+
#[test]
345+
fn transform_origin_matches_manual_equivalent() {
346+
let with_origin = get_actual(include_str!("../tests/transform_origin.svg"), false, [None; 2]);
347+
let manual = get_actual(
348+
include_str!("../tests/transform_origin_equivalent.svg"),
349+
false,
350+
[None; 2],
351+
);
352+
assert_close(with_origin, manual)
353+
}
354+
330355
#[test]
331356
#[cfg(feature = "serde")]
332357
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)