use crate::parser::dom::ElementNode;
#[derive(Debug, Clone)]
pub struct SvgTree {
pub width: f32,
pub height: f32,
pub view_box: Option<ViewBox>,
pub children: Vec<SvgNode>,
}
#[derive(Debug, Clone)]
pub struct ViewBox {
pub min_x: f32,
pub min_y: f32,
pub width: f32,
pub height: f32,
}
#[derive(Debug, Clone)]
pub enum SvgNode {
Group {
transform: Option<SvgTransform>,
children: Vec<SvgNode>,
style: SvgStyle,
},
Rect {
x: f32,
y: f32,
width: f32,
height: f32,
rx: f32,
ry: f32,
style: SvgStyle,
},
Circle {
cx: f32,
cy: f32,
r: f32,
style: SvgStyle,
},
Ellipse {
cx: f32,
cy: f32,
rx: f32,
ry: f32,
style: SvgStyle,
},
Line {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
style: SvgStyle,
},
Polyline {
points: Vec<(f32, f32)>,
style: SvgStyle,
},
Polygon {
points: Vec<(f32, f32)>,
style: SvgStyle,
},
Path {
commands: Vec<PathCommand>,
style: SvgStyle,
},
}
#[derive(Debug, Clone, Default)]
pub struct SvgStyle {
pub fill: Option<(f32, f32, f32)>, pub stroke: Option<(f32, f32, f32)>, pub stroke_width: f32,
pub opacity: f32,
}
#[derive(Debug, Clone)]
pub enum SvgTransform {
Matrix(f32, f32, f32, f32, f32, f32),
}
#[derive(Debug, Clone, PartialEq)]
pub enum PathCommand {
MoveTo(f32, f32),
LineTo(f32, f32),
CubicTo(f32, f32, f32, f32, f32, f32), QuadTo(f32, f32, f32, f32), ClosePath,
}
pub fn parse_svg_from_element(el: &ElementNode) -> Option<SvgTree> {
let width = el
.attributes
.get("width")
.and_then(|v| parse_length(v))
.unwrap_or(300.0);
let height = el
.attributes
.get("height")
.and_then(|v| parse_length(v))
.unwrap_or(150.0);
let view_box = el.attributes.get("viewBox").and_then(|v| parse_viewbox(v));
let mut children = Vec::new();
for child in &el.children {
if let crate::parser::dom::DomNode::Element(child_el) = child {
if let Some(node) = parse_svg_node(child_el) {
children.push(node);
}
}
}
Some(SvgTree {
width,
height,
view_box,
children,
})
}
fn parse_svg_node(el: &ElementNode) -> Option<SvgNode> {
let tag = el.raw_tag_name.as_str();
match tag {
"g" | "svg" => {
let transform = el
.attributes
.get("transform")
.and_then(|v| parse_transform(v));
let style = parse_svg_style(el);
let mut children = Vec::new();
for child in &el.children {
if let crate::parser::dom::DomNode::Element(child_el) = child {
if let Some(node) = parse_svg_node(child_el) {
children.push(node);
}
}
}
Some(SvgNode::Group {
transform,
children,
style,
})
}
"rect" => {
let x = attr_f32(el, "x");
let y = attr_f32(el, "y");
let width = attr_f32(el, "width");
let height = attr_f32(el, "height");
let rx = attr_f32(el, "rx");
let ry = attr_f32(el, "ry");
let style = parse_svg_style(el);
Some(SvgNode::Rect {
x,
y,
width,
height,
rx,
ry,
style,
})
}
"circle" => {
let cx = attr_f32(el, "cx");
let cy = attr_f32(el, "cy");
let r = attr_f32(el, "r");
let style = parse_svg_style(el);
Some(SvgNode::Circle { cx, cy, r, style })
}
"ellipse" => {
let cx = attr_f32(el, "cx");
let cy = attr_f32(el, "cy");
let rx = attr_f32(el, "rx");
let ry = attr_f32(el, "ry");
let style = parse_svg_style(el);
Some(SvgNode::Ellipse {
cx,
cy,
rx,
ry,
style,
})
}
"line" => {
let x1 = attr_f32(el, "x1");
let y1 = attr_f32(el, "y1");
let x2 = attr_f32(el, "x2");
let y2 = attr_f32(el, "y2");
let style = parse_svg_style(el);
Some(SvgNode::Line {
x1,
y1,
x2,
y2,
style,
})
}
"polyline" => {
let points = el
.attributes
.get("points")
.map(|v| parse_points(v))
.unwrap_or_default();
let style = parse_svg_style(el);
Some(SvgNode::Polyline { points, style })
}
"polygon" => {
let points = el
.attributes
.get("points")
.map(|v| parse_points(v))
.unwrap_or_default();
let style = parse_svg_style(el);
Some(SvgNode::Polygon { points, style })
}
"path" => {
let commands = el
.attributes
.get("d")
.map(|v| parse_path_data(v))
.unwrap_or_default();
let style = parse_svg_style(el);
Some(SvgNode::Path { commands, style })
}
_ => None,
}
}
fn attr_f32(el: &ElementNode, name: &str) -> f32 {
el.attributes
.get(name)
.and_then(|v| parse_length(v))
.unwrap_or(0.0)
}
fn parse_length(val: &str) -> Option<f32> {
let trimmed = val.trim();
let num_str = trimmed.trim_end_matches(|c: char| c.is_ascii_alphabetic() || c == '%');
num_str.trim().parse::<f32>().ok()
}
fn parse_viewbox(val: &str) -> Option<ViewBox> {
let parts: Vec<f32> = val
.split(|c: char| c == ',' || c.is_whitespace())
.filter(|s| !s.is_empty())
.filter_map(|s| s.parse().ok())
.collect();
if parts.len() == 4 {
Some(ViewBox {
min_x: parts[0],
min_y: parts[1],
width: parts[2],
height: parts[3],
})
} else {
None
}
}
fn parse_svg_style(el: &ElementNode) -> SvgStyle {
let fill = el.attributes.get("fill").and_then(|v| parse_svg_color(v));
let stroke = el.attributes.get("stroke").and_then(|v| parse_svg_color(v));
let stroke_width = el
.attributes
.get("stroke-width")
.and_then(|v| v.parse().ok())
.unwrap_or(1.0);
let opacity = el
.attributes
.get("opacity")
.and_then(|v| v.parse().ok())
.unwrap_or(1.0);
SvgStyle {
fill,
stroke,
stroke_width,
opacity,
}
}
pub fn parse_svg_color(val: &str) -> Option<(f32, f32, f32)> {
let val = val.trim();
if val.eq_ignore_ascii_case("none") {
return None;
}
match val.to_ascii_lowercase().as_str() {
"black" => return Some((0.0, 0.0, 0.0)),
"white" => return Some((1.0, 1.0, 1.0)),
"red" => return Some((1.0, 0.0, 0.0)),
"green" => return Some((0.0, 128.0 / 255.0, 0.0)),
"blue" => return Some((0.0, 0.0, 1.0)),
"yellow" => return Some((1.0, 1.0, 0.0)),
"cyan" => return Some((0.0, 1.0, 1.0)),
"magenta" => return Some((1.0, 0.0, 1.0)),
"gray" | "grey" => return Some((128.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0)),
"orange" => return Some((1.0, 165.0 / 255.0, 0.0)),
_ => {}
}
if let Some(hex) = val.strip_prefix('#') {
return parse_hex_color(hex);
}
if let Some(inner) = val.strip_prefix("rgb(").and_then(|s| s.strip_suffix(')')) {
let parts: Vec<&str> = inner.split(',').collect();
if parts.len() == 3 {
let r = parts[0].trim().parse::<f32>().ok()?;
let g = parts[1].trim().parse::<f32>().ok()?;
let b = parts[2].trim().parse::<f32>().ok()?;
return Some((r / 255.0, g / 255.0, b / 255.0));
}
}
None
}
fn parse_hex_color(hex: &str) -> Option<(f32, f32, f32)> {
match hex.len() {
3 => {
let r = u8::from_str_radix(&hex[0..1], 16).ok()?;
let g = u8::from_str_radix(&hex[1..2], 16).ok()?;
let b = u8::from_str_radix(&hex[2..3], 16).ok()?;
Some((
(r * 17) as f32 / 255.0,
(g * 17) as f32 / 255.0,
(b * 17) as f32 / 255.0,
))
}
6 => {
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some((r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0))
}
_ => None,
}
}
pub fn parse_path_data(d: &str) -> Vec<PathCommand> {
let mut commands = Vec::new();
let mut cur_x: f32 = 0.0;
let mut cur_y: f32 = 0.0;
let mut last_ctrl_x: f32 = 0.0;
let mut last_ctrl_y: f32 = 0.0;
let mut last_cmd: char = ' ';
let tokens = tokenize_path(d);
let mut i = 0;
while i < tokens.len() {
let token = &tokens[i];
let cmd_char = if token.len() == 1 && token.as_bytes()[0].is_ascii_alphabetic() {
let c = token.chars().next().unwrap();
i += 1;
c
} else {
match last_cmd {
'M' => 'L',
'm' => 'l',
c => c,
}
};
match cmd_char {
'M' => {
if let Some((x, y)) = read_pair(&tokens, &mut i) {
cur_x = x;
cur_y = y;
commands.push(PathCommand::MoveTo(cur_x, cur_y));
last_cmd = 'M';
last_ctrl_x = cur_x;
last_ctrl_y = cur_y;
}
}
'm' => {
if let Some((dx, dy)) = read_pair(&tokens, &mut i) {
cur_x += dx;
cur_y += dy;
commands.push(PathCommand::MoveTo(cur_x, cur_y));
last_cmd = 'm';
last_ctrl_x = cur_x;
last_ctrl_y = cur_y;
}
}
'L' => {
if let Some((x, y)) = read_pair(&tokens, &mut i) {
cur_x = x;
cur_y = y;
commands.push(PathCommand::LineTo(cur_x, cur_y));
last_cmd = 'L';
last_ctrl_x = cur_x;
last_ctrl_y = cur_y;
}
}
'l' => {
if let Some((dx, dy)) = read_pair(&tokens, &mut i) {
cur_x += dx;
cur_y += dy;
commands.push(PathCommand::LineTo(cur_x, cur_y));
last_cmd = 'l';
last_ctrl_x = cur_x;
last_ctrl_y = cur_y;
}
}
'H' => {
if let Some(x) = read_number(&tokens, &mut i) {
cur_x = x;
commands.push(PathCommand::LineTo(cur_x, cur_y));
last_cmd = 'H';
last_ctrl_x = cur_x;
last_ctrl_y = cur_y;
}
}
'h' => {
if let Some(dx) = read_number(&tokens, &mut i) {
cur_x += dx;
commands.push(PathCommand::LineTo(cur_x, cur_y));
last_cmd = 'h';
last_ctrl_x = cur_x;
last_ctrl_y = cur_y;
}
}
'V' => {
if let Some(y) = read_number(&tokens, &mut i) {
cur_y = y;
commands.push(PathCommand::LineTo(cur_x, cur_y));
last_cmd = 'V';
last_ctrl_x = cur_x;
last_ctrl_y = cur_y;
}
}
'v' => {
if let Some(dy) = read_number(&tokens, &mut i) {
cur_y += dy;
commands.push(PathCommand::LineTo(cur_x, cur_y));
last_cmd = 'v';
last_ctrl_x = cur_x;
last_ctrl_y = cur_y;
}
}
'C' => {
if let Some((x1, y1, x2, y2, x, y)) = read_six(&tokens, &mut i) {
commands.push(PathCommand::CubicTo(x1, y1, x2, y2, x, y));
last_ctrl_x = x2;
last_ctrl_y = y2;
cur_x = x;
cur_y = y;
last_cmd = 'C';
}
}
'c' => {
if let Some((dx1, dy1, dx2, dy2, dx, dy)) = read_six(&tokens, &mut i) {
let x1 = cur_x + dx1;
let y1 = cur_y + dy1;
let x2 = cur_x + dx2;
let y2 = cur_y + dy2;
let x = cur_x + dx;
let y = cur_y + dy;
commands.push(PathCommand::CubicTo(x1, y1, x2, y2, x, y));
last_ctrl_x = x2;
last_ctrl_y = y2;
cur_x = x;
cur_y = y;
last_cmd = 'c';
}
}
'S' => {
if let Some((x2, y2, x, y)) = read_four(&tokens, &mut i) {
let x1 = 2.0 * cur_x - last_ctrl_x;
let y1 = 2.0 * cur_y - last_ctrl_y;
commands.push(PathCommand::CubicTo(x1, y1, x2, y2, x, y));
last_ctrl_x = x2;
last_ctrl_y = y2;
cur_x = x;
cur_y = y;
last_cmd = 'S';
}
}
's' => {
if let Some((dx2, dy2, dx, dy)) = read_four(&tokens, &mut i) {
let x1 = 2.0 * cur_x - last_ctrl_x;
let y1 = 2.0 * cur_y - last_ctrl_y;
let x2 = cur_x + dx2;
let y2 = cur_y + dy2;
let x = cur_x + dx;
let y = cur_y + dy;
commands.push(PathCommand::CubicTo(x1, y1, x2, y2, x, y));
last_ctrl_x = x2;
last_ctrl_y = y2;
cur_x = x;
cur_y = y;
last_cmd = 's';
}
}
'Q' => {
if let Some((x1, y1, x, y)) = read_four(&tokens, &mut i) {
commands.push(PathCommand::QuadTo(x1, y1, x, y));
last_ctrl_x = x1;
last_ctrl_y = y1;
cur_x = x;
cur_y = y;
last_cmd = 'Q';
}
}
'q' => {
if let Some((dx1, dy1, dx, dy)) = read_four(&tokens, &mut i) {
let x1 = cur_x + dx1;
let y1 = cur_y + dy1;
let x = cur_x + dx;
let y = cur_y + dy;
commands.push(PathCommand::QuadTo(x1, y1, x, y));
last_ctrl_x = x1;
last_ctrl_y = y1;
cur_x = x;
cur_y = y;
last_cmd = 'q';
}
}
'T' => {
if let Some((x, y)) = read_pair(&tokens, &mut i) {
let x1 = 2.0 * cur_x - last_ctrl_x;
let y1 = 2.0 * cur_y - last_ctrl_y;
commands.push(PathCommand::QuadTo(x1, y1, x, y));
last_ctrl_x = x1;
last_ctrl_y = y1;
cur_x = x;
cur_y = y;
last_cmd = 'T';
}
}
't' => {
if let Some((dx, dy)) = read_pair(&tokens, &mut i) {
let x1 = 2.0 * cur_x - last_ctrl_x;
let y1 = 2.0 * cur_y - last_ctrl_y;
let x = cur_x + dx;
let y = cur_y + dy;
commands.push(PathCommand::QuadTo(x1, y1, x, y));
last_ctrl_x = x1;
last_ctrl_y = y1;
cur_x = x;
cur_y = y;
last_cmd = 't';
}
}
'Z' | 'z' => {
commands.push(PathCommand::ClosePath);
last_cmd = 'Z';
}
_ => {
i += 1;
}
}
}
commands
}
fn tokenize_path(d: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current = String::new();
let chars: Vec<char> = d.chars().collect();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
if c.is_ascii_alphabetic() {
if !current.is_empty() {
tokens.push(current.clone());
current.clear();
}
tokens.push(c.to_string());
i += 1;
} else if c == '-' {
if !current.is_empty() {
tokens.push(current.clone());
current.clear();
}
current.push(c);
i += 1;
} else if c == '.' {
if current.contains('.') {
tokens.push(current.clone());
current.clear();
}
current.push(c);
i += 1;
} else if c.is_ascii_digit() {
current.push(c);
i += 1;
} else {
if !current.is_empty() {
tokens.push(current.clone());
current.clear();
}
i += 1;
}
}
if !current.is_empty() {
tokens.push(current);
}
tokens
}
fn read_number(tokens: &[String], i: &mut usize) -> Option<f32> {
if *i < tokens.len() {
let val = tokens[*i].parse::<f32>().ok()?;
*i += 1;
Some(val)
} else {
None
}
}
fn read_pair(tokens: &[String], i: &mut usize) -> Option<(f32, f32)> {
let x = read_number(tokens, i)?;
let y = read_number(tokens, i)?;
Some((x, y))
}
fn read_four(tokens: &[String], i: &mut usize) -> Option<(f32, f32, f32, f32)> {
let a = read_number(tokens, i)?;
let b = read_number(tokens, i)?;
let c = read_number(tokens, i)?;
let d = read_number(tokens, i)?;
Some((a, b, c, d))
}
fn read_six(tokens: &[String], i: &mut usize) -> Option<(f32, f32, f32, f32, f32, f32)> {
let a = read_number(tokens, i)?;
let b = read_number(tokens, i)?;
let c = read_number(tokens, i)?;
let d = read_number(tokens, i)?;
let e = read_number(tokens, i)?;
let f = read_number(tokens, i)?;
Some((a, b, c, d, e, f))
}
pub fn parse_points(val: &str) -> Vec<(f32, f32)> {
let mut points = Vec::new();
let numbers: Vec<f32> = val
.split(|c: char| c == ',' || c.is_whitespace())
.filter(|s| !s.is_empty())
.filter_map(|s| s.parse().ok())
.collect();
let mut i = 0;
while i + 1 < numbers.len() {
points.push((numbers[i], numbers[i + 1]));
i += 2;
}
points
}
pub fn parse_transform(val: &str) -> Option<SvgTransform> {
let val = val.trim();
if let Some(inner) = extract_func_args(val, "matrix") {
let nums = parse_num_list(&inner);
if nums.len() == 6 {
return Some(SvgTransform::Matrix(
nums[0], nums[1], nums[2], nums[3], nums[4], nums[5],
));
}
}
if let Some(inner) = extract_func_args(val, "translate") {
let nums = parse_num_list(&inner);
let tx = nums.first().copied().unwrap_or(0.0);
let ty = nums.get(1).copied().unwrap_or(0.0);
return Some(SvgTransform::Matrix(1.0, 0.0, 0.0, 1.0, tx, ty));
}
if let Some(inner) = extract_func_args(val, "scale") {
let nums = parse_num_list(&inner);
let sx = nums.first().copied().unwrap_or(1.0);
let sy = nums.get(1).copied().unwrap_or(sx);
return Some(SvgTransform::Matrix(sx, 0.0, 0.0, sy, 0.0, 0.0));
}
if let Some(inner) = extract_func_args(val, "rotate") {
let nums = parse_num_list(&inner);
let angle_deg = nums.first().copied().unwrap_or(0.0);
let angle = angle_deg.to_radians();
let cos_a = angle.cos();
let sin_a = angle.sin();
if nums.len() >= 3 {
let cx = nums[1];
let cy = nums[2];
let tx = cx - cos_a * cx + sin_a * cy;
let ty = cy - sin_a * cx - cos_a * cy;
return Some(SvgTransform::Matrix(cos_a, sin_a, -sin_a, cos_a, tx, ty));
}
return Some(SvgTransform::Matrix(cos_a, sin_a, -sin_a, cos_a, 0.0, 0.0));
}
None
}
fn extract_func_args(val: &str, func_name: &str) -> Option<String> {
let lower = val.to_ascii_lowercase();
if let Some(start) = lower.find(func_name) {
let after = &val[start + func_name.len()..];
if let Some(open) = after.find('(') {
if let Some(close) = after.find(')') {
return Some(after[open + 1..close].to_string());
}
}
}
None
}
fn parse_num_list(s: &str) -> Vec<f32> {
s.split(|c: char| c == ',' || c.is_whitespace())
.filter(|s| !s.is_empty())
.filter_map(|s| s.parse().ok())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_path_data_move_and_line() {
let cmds = parse_path_data("M 0 0 L 10 10");
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0], PathCommand::MoveTo(0.0, 0.0));
assert_eq!(cmds[1], PathCommand::LineTo(10.0, 10.0));
}
#[test]
fn parse_path_data_cubic() {
let cmds = parse_path_data("M 0 0 C 10 0 10 10 0 10");
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0], PathCommand::MoveTo(0.0, 0.0));
assert_eq!(
cmds[1],
PathCommand::CubicTo(10.0, 0.0, 10.0, 10.0, 0.0, 10.0)
);
}
#[test]
fn parse_path_data_close() {
let cmds = parse_path_data("M 0 0 L 10 0 L 10 10 Z");
assert_eq!(cmds.len(), 4);
assert_eq!(cmds[0], PathCommand::MoveTo(0.0, 0.0));
assert_eq!(cmds[1], PathCommand::LineTo(10.0, 0.0));
assert_eq!(cmds[2], PathCommand::LineTo(10.0, 10.0));
assert_eq!(cmds[3], PathCommand::ClosePath);
}
#[test]
fn parse_path_data_relative() {
let cmds = parse_path_data("M 0 0 l 10 10");
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0], PathCommand::MoveTo(0.0, 0.0));
assert_eq!(cmds[1], PathCommand::LineTo(10.0, 10.0));
}
#[test]
fn parse_path_data_horizontal_vertical() {
let cmds = parse_path_data("M 0 0 H 10 V 10");
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[0], PathCommand::MoveTo(0.0, 0.0));
assert_eq!(cmds[1], PathCommand::LineTo(10.0, 0.0));
assert_eq!(cmds[2], PathCommand::LineTo(10.0, 10.0));
}
#[test]
fn parse_svg_color_hex() {
let color = parse_svg_color("#ff0000");
assert_eq!(color, Some((1.0, 0.0, 0.0)));
}
#[test]
fn parse_svg_color_named() {
let color = parse_svg_color("red");
assert_eq!(color, Some((1.0, 0.0, 0.0)));
}
#[test]
fn parse_svg_color_none() {
let color = parse_svg_color("none");
assert_eq!(color, None);
}
#[test]
fn parse_points_basic() {
let pts = parse_points("10,20 30,40");
assert_eq!(pts, vec![(10.0, 20.0), (30.0, 40.0)]);
}
#[test]
fn parse_transform_translate() {
let t = parse_transform("translate(10, 20)").unwrap();
match t {
SvgTransform::Matrix(a, b, c, d, e, f) => {
assert!((a - 1.0).abs() < 0.001);
assert!((b - 0.0).abs() < 0.001);
assert!((c - 0.0).abs() < 0.001);
assert!((d - 1.0).abs() < 0.001);
assert!((e - 10.0).abs() < 0.001);
assert!((f - 20.0).abs() < 0.001);
}
}
}
#[test]
fn parse_transform_scale() {
let t = parse_transform("scale(2)").unwrap();
match t {
SvgTransform::Matrix(a, b, c, d, e, f) => {
assert!((a - 2.0).abs() < 0.001);
assert!((b - 0.0).abs() < 0.001);
assert!((c - 0.0).abs() < 0.001);
assert!((d - 2.0).abs() < 0.001);
assert!((e - 0.0).abs() < 0.001);
assert!((f - 0.0).abs() < 0.001);
}
}
}
#[test]
fn parse_transform_rotate() {
let t = parse_transform("rotate(45)").unwrap();
match t {
SvgTransform::Matrix(a, b, c, d, e, f) => {
let cos45 = 45.0_f32.to_radians().cos();
let sin45 = 45.0_f32.to_radians().sin();
assert!((a - cos45).abs() < 0.001);
assert!((b - sin45).abs() < 0.001);
assert!((c - (-sin45)).abs() < 0.001);
assert!((d - cos45).abs() < 0.001);
assert!((e - 0.0).abs() < 0.001);
assert!((f - 0.0).abs() < 0.001);
}
}
}
#[test]
fn parse_transform_matrix() {
let t = parse_transform("matrix(1,0,0,1,10,20)").unwrap();
match t {
SvgTransform::Matrix(a, b, c, d, e, f) => {
assert!((a - 1.0).abs() < 0.001);
assert!((b - 0.0).abs() < 0.001);
assert!((c - 0.0).abs() < 0.001);
assert!((d - 1.0).abs() < 0.001);
assert!((e - 10.0).abs() < 0.001);
assert!((f - 20.0).abs() < 0.001);
}
}
}
use crate::parser::dom::{DomNode, HtmlTag};
use std::collections::HashMap;
fn make_el(raw_tag: &str, attrs: Vec<(&str, &str)>) -> ElementNode {
let mut attributes = HashMap::new();
for (k, v) in attrs {
attributes.insert(k.to_string(), v.to_string());
}
ElementNode {
tag: HtmlTag::Unknown,
raw_tag_name: raw_tag.to_string(),
attributes,
children: Vec::new(),
}
}
fn make_svg_el(attrs: Vec<(&str, &str)>, children: Vec<ElementNode>) -> ElementNode {
let mut attributes = HashMap::new();
for (k, v) in attrs {
attributes.insert(k.to_string(), v.to_string());
}
ElementNode {
tag: HtmlTag::Svg,
raw_tag_name: "svg".to_string(),
attributes,
children: children.into_iter().map(DomNode::Element).collect(),
}
}
#[test]
fn parse_length_plain_number() {
assert_eq!(parse_length("42"), Some(42.0));
}
#[test]
fn parse_length_with_px_suffix() {
assert_eq!(parse_length("100px"), Some(100.0));
}
#[test]
fn parse_length_with_em_suffix() {
assert_eq!(parse_length("1.5em"), Some(1.5));
}
#[test]
fn parse_length_with_percent() {
assert_eq!(parse_length("50%"), Some(50.0));
}
#[test]
fn parse_length_with_whitespace() {
assert_eq!(parse_length(" 200 "), Some(200.0));
}
#[test]
fn parse_length_invalid() {
assert_eq!(parse_length("abc"), None);
}
#[test]
fn parse_length_empty() {
assert_eq!(parse_length(""), None);
}
#[test]
fn parse_viewbox_comma_separated() {
let vb = parse_viewbox("0,0,100,200").unwrap();
assert_eq!(
(vb.min_x, vb.min_y, vb.width, vb.height),
(0.0, 0.0, 100.0, 200.0)
);
}
#[test]
fn parse_viewbox_space_separated() {
let vb = parse_viewbox("10 20 300 400").unwrap();
assert_eq!(
(vb.min_x, vb.min_y, vb.width, vb.height),
(10.0, 20.0, 300.0, 400.0)
);
}
#[test]
fn parse_viewbox_mixed_separators() {
let vb = parse_viewbox("5, 10 200, 300").unwrap();
assert_eq!(
(vb.min_x, vb.min_y, vb.width, vb.height),
(5.0, 10.0, 200.0, 300.0)
);
}
#[test]
fn parse_viewbox_too_few_values() {
assert!(parse_viewbox("0 0 100").is_none());
}
#[test]
fn parse_viewbox_too_many_values() {
assert!(parse_viewbox("0 0 100 200 300").is_none());
}
#[test]
fn parse_viewbox_invalid_number() {
assert!(parse_viewbox("0 abc 100 200").is_none());
}
#[test]
fn parse_svg_color_hex_3_char() {
let c = parse_svg_color("#f00").unwrap();
assert_eq!(c, (1.0, 0.0, 0.0));
}
#[test]
fn parse_svg_color_hex_3_char_white() {
let c = parse_svg_color("#fff").unwrap();
assert_eq!(c, (1.0, 1.0, 1.0));
}
#[test]
fn parse_svg_color_hex_invalid_length() {
assert!(parse_svg_color("#abcd").is_none());
}
#[test]
fn parse_svg_color_rgb_func() {
let c = parse_svg_color("rgb(255, 0, 128)").unwrap();
assert!((c.0 - 1.0).abs() < 0.01);
assert!((c.1 - 0.0).abs() < 0.01);
assert!((c.2 - 128.0 / 255.0).abs() < 0.01);
}
#[test]
fn parse_svg_color_rgb_func_with_spaces() {
let c = parse_svg_color("rgb( 0 , 128 , 255 )").unwrap();
assert!((c.0 - 0.0).abs() < 0.01);
assert!((c.1 - 128.0 / 255.0).abs() < 0.01);
assert!((c.2 - 1.0).abs() < 0.01);
}
#[test]
fn parse_svg_color_rgb_invalid_components() {
assert!(parse_svg_color("rgb(255, 0)").is_none());
}
#[test]
fn parse_svg_color_rgb_non_numeric() {
assert!(parse_svg_color("rgb(a, b, c)").is_none());
}
#[test]
fn parse_svg_color_named_black() {
assert_eq!(parse_svg_color("black"), Some((0.0, 0.0, 0.0)));
}
#[test]
fn parse_svg_color_named_white() {
assert_eq!(parse_svg_color("white"), Some((1.0, 1.0, 1.0)));
}
#[test]
fn parse_svg_color_named_green() {
assert_eq!(parse_svg_color("green"), Some((0.0, 128.0 / 255.0, 0.0)));
}
#[test]
fn parse_svg_color_named_blue() {
assert_eq!(parse_svg_color("blue"), Some((0.0, 0.0, 1.0)));
}
#[test]
fn parse_svg_color_named_yellow() {
assert_eq!(parse_svg_color("yellow"), Some((1.0, 1.0, 0.0)));
}
#[test]
fn parse_svg_color_named_cyan() {
assert_eq!(parse_svg_color("cyan"), Some((0.0, 1.0, 1.0)));
}
#[test]
fn parse_svg_color_named_magenta() {
assert_eq!(parse_svg_color("magenta"), Some((1.0, 0.0, 1.0)));
}
#[test]
fn parse_svg_color_named_gray() {
let expected = (128.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0);
assert_eq!(parse_svg_color("gray"), Some(expected));
assert_eq!(parse_svg_color("grey"), Some(expected));
}
#[test]
fn parse_svg_color_named_orange() {
assert_eq!(parse_svg_color("orange"), Some((1.0, 165.0 / 255.0, 0.0)));
}
#[test]
fn parse_svg_color_unknown_name() {
assert!(parse_svg_color("papayawhip").is_none());
}
#[test]
fn parse_svg_color_none_case_insensitive() {
assert_eq!(parse_svg_color("None"), None);
assert_eq!(parse_svg_color("NONE"), None);
}
#[test]
fn parse_svg_color_with_leading_trailing_spaces() {
assert_eq!(parse_svg_color(" red "), Some((1.0, 0.0, 0.0)));
}
#[test]
fn parse_points_space_only() {
let pts = parse_points("10 20 30 40");
assert_eq!(pts, vec![(10.0, 20.0), (30.0, 40.0)]);
}
#[test]
fn parse_points_odd_count() {
let pts = parse_points("10,20,30");
assert_eq!(pts, vec![(10.0, 20.0)]);
}
#[test]
fn parse_points_empty() {
let pts = parse_points("");
assert!(pts.is_empty());
}
#[test]
fn parse_points_extra_whitespace() {
let pts = parse_points(" 1 , 2 , 3 , 4 ");
assert_eq!(pts, vec![(1.0, 2.0), (3.0, 4.0)]);
}
#[test]
fn parse_path_relative_move() {
let cmds = parse_path_data("m 5 10 l 3 4");
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0], PathCommand::MoveTo(5.0, 10.0));
assert_eq!(cmds[1], PathCommand::LineTo(8.0, 14.0));
}
#[test]
fn parse_path_relative_h_v() {
let cmds = parse_path_data("M 10 20 h 5 v 10");
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[0], PathCommand::MoveTo(10.0, 20.0));
assert_eq!(cmds[1], PathCommand::LineTo(15.0, 20.0));
assert_eq!(cmds[2], PathCommand::LineTo(15.0, 30.0));
}
#[test]
fn parse_path_relative_cubic() {
let cmds = parse_path_data("M 10 10 c 5 0 5 5 0 5");
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0], PathCommand::MoveTo(10.0, 10.0));
assert_eq!(
cmds[1],
PathCommand::CubicTo(15.0, 10.0, 15.0, 15.0, 10.0, 15.0)
);
}
#[test]
fn parse_path_smooth_cubic_s() {
let cmds = parse_path_data("M 0 0 C 10 0 20 10 20 20 S 30 40 20 40");
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[0], PathCommand::MoveTo(0.0, 0.0));
assert_eq!(
cmds[1],
PathCommand::CubicTo(10.0, 0.0, 20.0, 10.0, 20.0, 20.0)
);
assert_eq!(
cmds[2],
PathCommand::CubicTo(20.0, 30.0, 30.0, 40.0, 20.0, 40.0)
);
}
#[test]
fn parse_path_smooth_cubic_s_relative() {
let cmds = parse_path_data("M 10 10 C 15 10 20 15 20 20 s 5 10 0 10");
assert_eq!(cmds.len(), 3);
assert_eq!(
cmds[2],
PathCommand::CubicTo(20.0, 25.0, 25.0, 30.0, 20.0, 30.0)
);
}
#[test]
fn parse_path_quad_q() {
let cmds = parse_path_data("M 0 0 Q 10 20 30 40");
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[1], PathCommand::QuadTo(10.0, 20.0, 30.0, 40.0));
}
#[test]
fn parse_path_quad_relative_q() {
let cmds = parse_path_data("M 10 10 q 5 10 15 20");
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[1], PathCommand::QuadTo(15.0, 20.0, 25.0, 30.0));
}
#[test]
fn parse_path_smooth_quad_t() {
let cmds = parse_path_data("M 0 0 Q 10 20 20 20 T 40 0");
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[2], PathCommand::QuadTo(30.0, 20.0, 40.0, 0.0));
}
#[test]
fn parse_path_smooth_quad_t_relative() {
let cmds = parse_path_data("M 0 0 Q 5 10 10 10 t 10 0");
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[2], PathCommand::QuadTo(15.0, 10.0, 20.0, 10.0));
}
#[test]
fn parse_path_lowercase_z() {
let cmds = parse_path_data("M 0 0 L 10 0 z");
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[2], PathCommand::ClosePath);
}
#[test]
fn parse_path_implicit_lineto_after_move() {
let cmds = parse_path_data("M 0 0 10 10 20 20");
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[0], PathCommand::MoveTo(0.0, 0.0));
assert_eq!(cmds[1], PathCommand::LineTo(10.0, 10.0));
assert_eq!(cmds[2], PathCommand::LineTo(20.0, 20.0));
}
#[test]
fn parse_path_implicit_lineto_after_relative_move() {
let cmds = parse_path_data("m 0 0 10 10");
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0], PathCommand::MoveTo(0.0, 0.0));
assert_eq!(cmds[1], PathCommand::LineTo(10.0, 10.0));
}
#[test]
fn parse_path_negative_numbers() {
let cmds = parse_path_data("M -5 -10 L -20 -30");
assert_eq!(cmds[0], PathCommand::MoveTo(-5.0, -10.0));
assert_eq!(cmds[1], PathCommand::LineTo(-20.0, -30.0));
}
#[test]
fn parse_path_numbers_without_space() {
let cmds = parse_path_data("M10-20L30-40");
assert_eq!(cmds[0], PathCommand::MoveTo(10.0, -20.0));
assert_eq!(cmds[1], PathCommand::LineTo(30.0, -40.0));
}
#[test]
fn parse_path_decimal_without_leading_zero() {
let cmds = parse_path_data("M .5 .5 L 1.5 1.5");
assert_eq!(cmds[0], PathCommand::MoveTo(0.5, 0.5));
assert_eq!(cmds[1], PathCommand::LineTo(1.5, 1.5));
}
#[test]
fn parse_path_consecutive_decimals() {
let cmds = parse_path_data("M 0.5.5 1.5.5");
assert_eq!(cmds[0], PathCommand::MoveTo(0.5, 0.5));
assert_eq!(cmds[1], PathCommand::LineTo(1.5, 0.5));
}
#[test]
fn parse_path_empty() {
let cmds = parse_path_data("");
assert!(cmds.is_empty());
}
#[test]
fn parse_path_unknown_command_skipped() {
let cmds = parse_path_data("M 0 0 A 1 1 0 0 1 10 10 L 20 20");
assert!(cmds.iter().any(|c| *c == PathCommand::MoveTo(0.0, 0.0)));
}
#[test]
fn parse_transform_rotate_with_center() {
let t = parse_transform("rotate(90, 50, 50)").unwrap();
match t {
SvgTransform::Matrix(a, b, c, d, e, f) => {
let cos90 = 90.0_f32.to_radians().cos();
let sin90 = 90.0_f32.to_radians().sin();
assert!((a - cos90).abs() < 0.01);
assert!((b - sin90).abs() < 0.01);
assert!((c - (-sin90)).abs() < 0.01);
assert!((d - cos90).abs() < 0.01);
let tx = 50.0 - cos90 * 50.0 + sin90 * 50.0;
let ty = 50.0 - sin90 * 50.0 - cos90 * 50.0;
assert!((e - tx).abs() < 0.01);
assert!((f - ty).abs() < 0.01);
}
}
}
#[test]
fn parse_transform_scale_xy() {
let t = parse_transform("scale(2, 3)").unwrap();
match t {
SvgTransform::Matrix(a, _b, _c, d, _e, _f) => {
assert!((a - 2.0).abs() < 0.001);
assert!((d - 3.0).abs() < 0.001);
}
}
}
#[test]
fn parse_transform_translate_single_value() {
let t = parse_transform("translate(10)").unwrap();
match t {
SvgTransform::Matrix(_a, _b, _c, _d, e, f) => {
assert!((e - 10.0).abs() < 0.001);
assert!((f - 0.0).abs() < 0.001);
}
}
}
#[test]
fn parse_transform_unknown() {
assert!(parse_transform("skewX(30)").is_none());
}
#[test]
fn parse_transform_empty() {
assert!(parse_transform("").is_none());
}
#[test]
fn parse_node_rect() {
let el = make_el(
"rect",
vec![
("x", "10"),
("y", "20"),
("width", "100"),
("height", "50"),
("rx", "5"),
("ry", "3"),
],
);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Rect {
x,
y,
width,
height,
rx,
ry,
..
} => {
assert_eq!(
(x, y, width, height, rx, ry),
(10.0, 20.0, 100.0, 50.0, 5.0, 3.0)
);
}
_ => panic!("Expected Rect"),
}
}
#[test]
fn parse_node_circle() {
let el = make_el("circle", vec![("cx", "50"), ("cy", "50"), ("r", "25")]);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Circle { cx, cy, r, .. } => {
assert_eq!((cx, cy, r), (50.0, 50.0, 25.0));
}
_ => panic!("Expected Circle"),
}
}
#[test]
fn parse_node_ellipse() {
let el = make_el(
"ellipse",
vec![("cx", "50"), ("cy", "50"), ("rx", "30"), ("ry", "20")],
);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Ellipse { cx, cy, rx, ry, .. } => {
assert_eq!((cx, cy, rx, ry), (50.0, 50.0, 30.0, 20.0));
}
_ => panic!("Expected Ellipse"),
}
}
#[test]
fn parse_node_line() {
let el = make_el(
"line",
vec![("x1", "0"), ("y1", "0"), ("x2", "100"), ("y2", "100")],
);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Line { x1, y1, x2, y2, .. } => {
assert_eq!((x1, y1, x2, y2), (0.0, 0.0, 100.0, 100.0));
}
_ => panic!("Expected Line"),
}
}
#[test]
fn parse_node_polyline() {
let el = make_el("polyline", vec![("points", "0,0 10,20 30,40")]);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Polyline { points, .. } => {
assert_eq!(points, vec![(0.0, 0.0), (10.0, 20.0), (30.0, 40.0)]);
}
_ => panic!("Expected Polyline"),
}
}
#[test]
fn parse_node_polyline_no_points() {
let el = make_el("polyline", vec![]);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Polyline { points, .. } => {
assert!(points.is_empty());
}
_ => panic!("Expected Polyline"),
}
}
#[test]
fn parse_node_polygon() {
let el = make_el("polygon", vec![("points", "0,0 50,0 50,50 0,50")]);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Polygon { points, .. } => {
assert_eq!(points.len(), 4);
}
_ => panic!("Expected Polygon"),
}
}
#[test]
fn parse_node_path() {
let el = make_el("path", vec![("d", "M 0 0 L 10 10 Z")]);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Path { commands, .. } => {
assert_eq!(commands.len(), 3);
}
_ => panic!("Expected Path"),
}
}
#[test]
fn parse_node_path_no_d_attr() {
let el = make_el("path", vec![]);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Path { commands, .. } => {
assert!(commands.is_empty());
}
_ => panic!("Expected Path"),
}
}
#[test]
fn parse_node_group() {
let child = make_el("rect", vec![("width", "10"), ("height", "10")]);
let mut group = make_el("g", vec![("transform", "translate(5,5)")]);
group.children.push(DomNode::Element(child));
let node = parse_svg_node(&group).unwrap();
match node {
SvgNode::Group {
transform,
children,
..
} => {
assert!(transform.is_some());
assert_eq!(children.len(), 1);
}
_ => panic!("Expected Group"),
}
}
#[test]
fn parse_node_group_with_text_child_ignored() {
let mut group = make_el("g", vec![]);
group.children.push(DomNode::Text("hello".to_string()));
let node = parse_svg_node(&group).unwrap();
match node {
SvgNode::Group { children, .. } => {
assert!(children.is_empty());
}
_ => panic!("Expected Group"),
}
}
#[test]
fn parse_node_unknown_tag_returns_none() {
let el = make_el("text", vec![]);
assert!(parse_svg_node(&el).is_none());
}
#[test]
fn parse_style_defaults() {
let el = make_el("rect", vec![]);
let style = parse_svg_style(&el);
assert!(style.fill.is_none());
assert!(style.stroke.is_none());
assert_eq!(style.stroke_width, 1.0);
assert_eq!(style.opacity, 1.0);
}
#[test]
fn parse_style_with_fill_stroke() {
let el = make_el(
"rect",
vec![
("fill", "#ff0000"),
("stroke", "blue"),
("stroke-width", "2.5"),
("opacity", "0.5"),
],
);
let style = parse_svg_style(&el);
assert_eq!(style.fill, Some((1.0, 0.0, 0.0)));
assert_eq!(style.stroke, Some((0.0, 0.0, 1.0)));
assert!((style.stroke_width - 2.5).abs() < 0.001);
assert!((style.opacity - 0.5).abs() < 0.001);
}
#[test]
fn parse_style_fill_none() {
let el = make_el("rect", vec![("fill", "none")]);
let style = parse_svg_style(&el);
assert!(style.fill.is_none());
}
#[test]
fn parse_style_stroke_none() {
let el = make_el("rect", vec![("stroke", "none")]);
let style = parse_svg_style(&el);
assert!(style.stroke.is_none());
}
#[test]
fn parse_svg_from_element_basic() {
let rect = make_el("rect", vec![("width", "50"), ("height", "30")]);
let svg = make_svg_el(
vec![
("width", "200"),
("height", "100"),
("viewBox", "0 0 200 100"),
],
vec![rect],
);
let tree = parse_svg_from_element(&svg).unwrap();
assert_eq!(tree.width, 200.0);
assert_eq!(tree.height, 100.0);
assert!(tree.view_box.is_some());
assert_eq!(tree.children.len(), 1);
}
#[test]
fn parse_svg_from_element_defaults() {
let svg = make_svg_el(vec![], vec![]);
let tree = parse_svg_from_element(&svg).unwrap();
assert_eq!(tree.width, 300.0);
assert_eq!(tree.height, 150.0);
assert!(tree.view_box.is_none());
assert!(tree.children.is_empty());
}
#[test]
fn parse_svg_from_element_text_children_ignored() {
let mut svg = make_svg_el(vec![("width", "100"), ("height", "100")], vec![]);
svg.children.push(DomNode::Text("some text".to_string()));
let tree = parse_svg_from_element(&svg).unwrap();
assert!(tree.children.is_empty());
}
#[test]
fn parse_svg_from_element_unknown_child_skipped() {
let text_el = make_el("text", vec![]);
let svg = make_svg_el(vec![("width", "100"), ("height", "100")], vec![text_el]);
let tree = parse_svg_from_element(&svg).unwrap();
assert!(tree.children.is_empty());
}
#[test]
fn attr_f32_present() {
let el = make_el("rect", vec![("x", "42px")]);
assert_eq!(attr_f32(&el, "x"), 42.0);
}
#[test]
fn attr_f32_missing() {
let el = make_el("rect", vec![]);
assert_eq!(attr_f32(&el, "x"), 0.0);
}
#[test]
fn tokenize_path_commas_and_spaces() {
let tokens = tokenize_path("M10,20 L30,40");
assert_eq!(
tokens,
vec!["M", "10", "20", "L", "30", "40"]
.into_iter()
.map(String::from)
.collect::<Vec<_>>()
);
}
#[test]
fn tokenize_path_negative_after_number() {
let tokens = tokenize_path("M10-20");
assert_eq!(
tokens,
vec!["M", "10", "-20"]
.into_iter()
.map(String::from)
.collect::<Vec<_>>()
);
}
#[test]
fn tokenize_path_double_dot() {
let tokens = tokenize_path("0.5.5");
assert_eq!(
tokens,
vec!["0.5", ".5"]
.into_iter()
.map(String::from)
.collect::<Vec<_>>()
);
}
#[test]
fn read_number_past_end() {
let tokens: Vec<String> = vec![];
let mut i = 0;
assert!(read_number(&tokens, &mut i).is_none());
}
#[test]
fn read_number_non_numeric() {
let tokens = vec!["abc".to_string()];
let mut i = 0;
assert!(read_number(&tokens, &mut i).is_none());
}
#[test]
fn read_pair_insufficient_tokens() {
let tokens = vec!["5".to_string()];
let mut i = 0;
assert!(read_pair(&tokens, &mut i).is_none());
}
#[test]
fn read_four_insufficient_tokens() {
let tokens = vec!["1".to_string(), "2".to_string(), "3".to_string()];
let mut i = 0;
assert!(read_four(&tokens, &mut i).is_none());
}
#[test]
fn read_six_insufficient_tokens() {
let tokens = vec![
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string(),
"5".to_string(),
];
let mut i = 0;
assert!(read_six(&tokens, &mut i).is_none());
}
#[test]
fn extract_func_args_basic() {
assert_eq!(
extract_func_args("translate(10, 20)", "translate"),
Some("10, 20".to_string())
);
}
#[test]
fn extract_func_args_not_found() {
assert_eq!(extract_func_args("translate(10, 20)", "rotate"), None);
}
#[test]
fn extract_func_args_no_parens() {
assert_eq!(extract_func_args("translate", "translate"), None);
}
#[test]
fn parse_num_list_basic() {
let nums = parse_num_list("1, 2.5, 3");
assert_eq!(nums, vec![1.0, 2.5, 3.0]);
}
#[test]
fn parse_num_list_empty() {
let nums = parse_num_list("");
assert!(nums.is_empty());
}
#[test]
fn parse_num_list_with_invalid() {
let nums = parse_num_list("1, abc, 3");
assert_eq!(nums, vec![1.0, 3.0]);
}
#[test]
fn parse_node_nested_svg_acts_as_group() {
let inner = make_el("rect", vec![("width", "10"), ("height", "10")]);
let mut svg_inner = make_el("svg", vec![]);
svg_inner.children.push(DomNode::Element(inner));
let node = parse_svg_node(&svg_inner).unwrap();
match node {
SvgNode::Group { children, .. } => {
assert_eq!(children.len(), 1);
}
_ => panic!("Expected Group for inner svg"),
}
}
#[test]
fn parse_node_polygon_no_points() {
let el = make_el("polygon", vec![]);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Polygon { points, .. } => {
assert!(points.is_empty());
}
_ => panic!("Expected Polygon"),
}
}
#[test]
fn parse_node_rect_defaults() {
let el = make_el("rect", vec![]);
let node = parse_svg_node(&el).unwrap();
match node {
SvgNode::Rect {
x,
y,
width,
height,
rx,
ry,
..
} => {
assert_eq!(
(x, y, width, height, rx, ry),
(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
);
}
_ => panic!("Expected Rect"),
}
}
#[test]
fn parse_node_group_no_transform() {
let group = make_el("g", vec![]);
let node = parse_svg_node(&group).unwrap();
match node {
SvgNode::Group { transform, .. } => {
assert!(transform.is_none());
}
_ => panic!("Expected Group"),
}
}
}