diff --git a/lib/src/converter/visit.rs b/lib/src/converter/visit.rs index 9a84ffe..4c7c8a0 100644 --- a/lib/src/converter/visit.rs +++ b/lib/src/converter/visit.rs @@ -3,7 +3,10 @@ use std::str::FromStr; use euclid::default::Transform2D; use log::{debug, warn}; use roxmltree::{Document, Node}; -use svgtypes::{AspectRatio, PathParser, PathSegment, PointsParser, TransformListParser, ViewBox}; +use svgtypes::{ + AspectRatio, LengthListParser, PathParser, PathSegment, PointsParser, TransformListParser, + ViewBox, +}; use super::{ ConversionVisitor, @@ -108,12 +111,6 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> { warn!("Clip paths are not supported: {:?}", node); } - // TODO: https://www.w3.org/TR/css-transforms-1/#transform-origin-property - if let Some(mut origin) = node.attribute("transform-origin").map(PointsParser::from) { - let _origin = origin.next(); - warn!("transform-origin not supported yet"); - } - let mut flattened_transform = if let Some(transform) = node.attribute("transform") { // https://stackoverflow.com/questions/18582935/the-applying-order-of-svg-transforms TransformListParser::from(transform) @@ -124,6 +121,28 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> { Transform2D::identity() }; + // https://www.w3.org/TR/css-transforms-1/#transform-origin-property + if let Some(to_str) = node.attribute("transform-origin") { + let mut parser = LengthListParser::from(to_str); + let ox = parser + .next() + .and_then(|r| r.ok()) + .map(|l| self.length_to_user_units(l, DimensionHint::Horizontal)) + .unwrap_or(0.); + let oy = parser + .next() + .and_then(|r| r.ok()) + .map(|l| self.length_to_user_units(l, DimensionHint::Vertical)) + .unwrap_or(0.); + if ox != 0. || oy != 0. { + // https://www.w3.org/TR/css-transforms-1/#transformation-matrix-computation + // Steps 2 & 4 + flattened_transform = Transform2D::translation(-ox, -oy) + .then(&flattened_transform) + .then(&Transform2D::translation(ox, oy)); + } + } + // https://www.w3.org/TR/SVG/coords.html#EstablishingANewSVGViewport if node.has_tag_name(SVG_TAG_NAME) { let view_box = node diff --git a/lib/src/lib.rs b/lib/src/lib.rs index d420170..6e7778e 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -324,9 +324,34 @@ mod test { .collect::>(); let actual = get_actual(svg, false, [None; 2]); + assert_close(actual, expected); + } + + #[test] + fn transform_origin_produces_expected_gcode() { + let svg = include_str!("../tests/transform_origin.svg"); + let expected = + g_code::parse::file_parser(include_str!("../tests/transform_origin.gcode")) + .unwrap() + .iter_emit_tokens() + .collect::>(); + let actual = get_actual(svg, false, [None; 2]); assert_close(actual, expected) } + /// `transform-origin="5 5"` with `rotate(90)` should be identical to the + /// manual SVG equivalent `translate(5,5) rotate(90) translate(-5,-5)` + #[test] + fn transform_origin_matches_manual_equivalent() { + let with_origin = get_actual(include_str!("../tests/transform_origin.svg"), false, [None; 2]); + let manual = get_actual( + include_str!("../tests/transform_origin_equivalent.svg"), + false, + [None; 2], + ); + assert_close(with_origin, manual) + } + #[test] #[cfg(feature = "serde")] fn deserialize_v1_config_succeeds() { diff --git a/lib/tests/transform_origin.gcode b/lib/tests/transform_origin.gcode new file mode 100644 index 0000000..0ce8e6f --- /dev/null +++ b/lib/tests/transform_origin.gcode @@ -0,0 +1,4 @@ +G21 +G90;svg > g > path +G0 X9 Y5 +G1 X5 Y5 F300 diff --git a/lib/tests/transform_origin.svg b/lib/tests/transform_origin.svg new file mode 100644 index 0000000..2a81764 --- /dev/null +++ b/lib/tests/transform_origin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/tests/transform_origin_equivalent.svg b/lib/tests/transform_origin_equivalent.svg new file mode 100644 index 0000000..1c9a984 --- /dev/null +++ b/lib/tests/transform_origin_equivalent.svg @@ -0,0 +1,5 @@ + + + + +