Transform as an html attribute not parsed correctly
Because transform="..." tends to be unit-less and space-separated they aren't parsed correctly by lightning-css despite being a presentation attribute.
For example, the following is a valid transform in HTML, but not CSS
/* eg <g transform="translate(0 10)"></g> */
translate(0 10)
While the following is valid in both CSS and HTML
/* eg <g transform="translate(0, 10px)></g> */
/* eg transform: translate(0, 10px); */
translate(0, 10px);
Yet, they are functionally equivalent.
Since in a HTML context the expected syntax is slightly difference, it might be nice to specify in the parser options whether a property being parsed is a presentation attribute
let options = ParserOptions { flags: ParserFlags::HTML, ..ParserOptions::default() }
let transform = properties::Property::parse_string(PropertyId::Transform(VendorPrefix::None), "translate(0 10)", options);
Yeah the syntax for SVG transform attributes is completely different from CSS. For example rotate takes additional arguments for the center point instead of using transform-origin. I'd probably recommend using a different parser and value type for the presentation attributes vs CSS.
Yep, absolutely -- SVG transform seems to be a kind of superset of CSS transforms. Thought it'd be worth making this issue for awareness anyway. For what it's worth, I do have a workaround using some (yucky) regex.
use lazy_static::lazy_static;
fn svg_transform_to_css_transform(css_string: String) -> Cow<'_, str> {
// transform `rotate(r, x, y)` -> `matrix(a, b, c, d, e, f)`
// see https://github.com/svg/svgo/blob/a8472bc45fe1d92d5f848a08cf4d5c8f4a531ad9/plugins/_transforms.js#L511
let v = ROTATE_LONG.replace_all(&value, |caps: ®ex::Captures| {
let original = format!("rotate({} {} {})", &caps["r"], &caps["x"], &caps["y"]);
let Ok(deg) = caps["r"].parse::<f64>() else {
log::debug!("r failed: {}", &caps["r"]);
return original;
};
let Ok(x) = caps["x"].parse::<f64>() else {
log::debug!("x failed: {}", &caps["x"]);
return original;
};
let Ok(y) = caps["y"].parse::<f64>() else {
log::debug!("y failed: {}", &caps["y"]);
return original;
};
let rad = deg.to_radians();
let cos = rad.cos();
let sin = rad.sin();
format!(
"matrix({cos} {sin} {} {cos} {} {})",
-sin,
(1.0 - cos) * x + sin * y,
(1.0 - cos) * y - sin * x
)
});
// transform `f(a b ...)` -> `f(a, b, ...)`
let v = LIST_SEP_SPACE.replace_all(&v, "$a, ");
// transform `rotate(r)` -> `rotate(rdeg)`
value = ROTATE
.replace_all(&v, |caps: ®ex::Captures| {
format!("{}({}deg", &caps["f"], &caps["v"])
})
}
lazy_static! {
static ref LIST_SEP_SPACE: regex::Regex = regex::Regex::new(r"(?<a>\d)\s+").unwrap();
static ref ROTATE: regex::Regex =
regex::Regex::new(r"(?<f>rotate|skewX|skewY)\((?<v>\s*[^\s\),]+)").unwrap();
static ref ROTATE_LONG: regex::Regex = regex::Regex::new(
r"rotate\((?<r>[\d\.e-]+)[^\d\)]+?(?<x>[\d\.e-]+)[^\d\)]+?(?<y>[\d\.e-]+)\)"
)
.unwrap();
}
In terms of lightningcss, having a separate parser makes sense to me.
Would a separate parser mean having something similar to Property but for HTML presentation attributes instead of CSS properties?
https://github.com/noahbald/oxvg/blob/9cbed0f2d3d70c1b3847f29cf7cc8e12b6ddc1cc/crates/oxvg_ast/src/style.rs#L53-L1431
I've implemented PresentationAttr and SVGTransform in my project which are analogous to Property and Transform.
Would it be worth porting this from my project to lightningcss?