use usvg::tiny_skia_path::PathSegment;
use super::super::polyline::{Polyline, classify, halfway_along};
use super::super::{Connector, Outline, Rect};
pub(super) fn collect_geometry(
group: &usvg::Group,
outlines: &mut Vec<Outline>,
connectors: &mut Vec<Connector>,
) -> bool {
collect_geometry_at(group, outlines, connectors, 0)
}
fn collect_geometry_at(
group: &usvg::Group,
outlines: &mut Vec<Outline>,
connectors: &mut Vec<Connector>,
depth: usize,
) -> bool {
if depth > super::MAX_NESTING_DEPTH {
return true;
}
let mut truncated = false;
for child in group.children() {
match child {
usvg::Node::Group(inner) => {
truncated |= collect_geometry_at(inner, outlines, connectors, depth + 1);
}
usvg::Node::Path(path) => {
if !path.is_visible() {
continue;
}
let Some(absolute) = path.data().clone().transform(path.abs_transform()) else {
continue;
};
let flattened = flatten(&absolute);
if flattened.len() < 2 {
continue;
}
let stroke = path.stroke();
let stroke_color = stroke.and_then(|s| paint_color(s.paint()));
let dashed = stroke.is_some_and(|s| s.dasharray().is_some());
let closed =
absolute.segments().any(|s| matches!(s, PathSegment::Close)) || flattened.ends_where_it_started();
if closed {
let Some(bounds) = absolute.compute_tight_bounds() else {
continue;
};
let bbox = Rect {
x0: bounds.left(),
y0: bounds.top(),
x1: bounds.right(),
y1: bounds.bottom(),
};
outlines.push(Outline {
shape: classify(flattened.points(), &bbox),
bbox,
fill: path.fill().and_then(|f| paint_color(f.paint())),
stroke: stroke_color,
stroke_width: stroke.map(|s| s.width().get()),
dashed,
});
} else {
let points = flattened.into_points();
connectors.push(Connector {
start: points[0],
end: points[points.len() - 1],
midpoint: halfway_along(&points),
stroke: stroke_color,
dashed,
});
}
}
_ => {}
}
}
truncated
}
fn paint_color(paint: &usvg::Paint) -> Option<String> {
match paint {
usvg::Paint::Color(c) => Some(format!("#{:02x}{:02x}{:02x}", c.red, c.green, c.blue)),
_ => None,
}
}
fn flatten(path: &usvg::tiny_skia_path::Path) -> Polyline {
let mut line = Polyline::default();
let mut cursor = (0.0f32, 0.0f32);
let mut subpath_start = (0.0f32, 0.0f32);
for segment in path.segments() {
match segment {
PathSegment::MoveTo(p) => {
cursor = (p.x, p.y);
subpath_start = cursor;
line.push(cursor);
}
PathSegment::LineTo(p) => {
cursor = (p.x, p.y);
line.push(cursor);
}
PathSegment::QuadTo(c, p) => {
line.push_quad(cursor, (c.x, c.y), (p.x, p.y));
cursor = (p.x, p.y);
}
PathSegment::CubicTo(c1, c2, p) => {
line.push_cubic(cursor, (c1.x, c1.y), (c2.x, c2.y), (p.x, p.y));
cursor = (p.x, p.y);
}
PathSegment::Close => {
cursor = subpath_start;
line.push(cursor);
}
}
}
line
}