blob: ff0503bcd6f409bd2e9a7ffa1a97449ddbec2055 [file] [log] [blame] [edit]
use std::path::PathBuf;
use usvg::NodeExt;
use vello::kurbo::{Affine, BezPath, Rect};
use vello::peniko::{Brush, Color, Fill, Stroke};
use vello::{SceneBuilder, SceneFragment};
pub fn render_svg(
sb: &mut SceneBuilder,
scene: &mut Option<SceneFragment>,
xform: Affine,
path: &PathBuf,
) {
let scene_frag = scene.get_or_insert_with(|| {
let contents = std::fs::read_to_string(path).expect("failed to read svg file");
let svg = usvg::Tree::from_str(&contents, &usvg::Options::default())
.expect("failed to parse svg file");
let mut new_scene = SceneFragment::new();
let mut builder = SceneBuilder::for_fragment(&mut new_scene);
render_tree(&mut builder, svg);
new_scene
});
sb.append(scene_frag, Some(xform));
}
fn render_tree(sb: &mut SceneBuilder, svg: usvg::Tree) {
for elt in svg.root.descendants() {
let transform = elt.abs_transform();
match &*elt.borrow() {
usvg::NodeKind::Group(_) => {}
usvg::NodeKind::Path(path) => {
let mut local_path = BezPath::new();
for elt in usvg::TransformedPath::new(&path.data, transform) {
match elt {
usvg::PathSegment::MoveTo { x, y } => local_path.move_to((x, y)),
usvg::PathSegment::LineTo { x, y } => local_path.line_to((x, y)),
usvg::PathSegment::CurveTo {
x1,
y1,
x2,
y2,
x,
y,
} => local_path.curve_to((x1, y1), (x2, y2), (x, y)),
usvg::PathSegment::ClosePath => local_path.close_path(),
}
}
// FIXME: let path.paint_order determine the fill/stroke order.
if let Some(fill) = &path.fill {
if let Some(brush) = paint_to_brush(&fill.paint, fill.opacity) {
// FIXME: Set the fill rule
sb.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &local_path);
} else {
unimplemented(sb, &elt);
}
}
if let Some(stroke) = &path.stroke {
if let Some(brush) = paint_to_brush(&stroke.paint, stroke.opacity) {
// FIXME: handle stroke options such as linecap, linejoin, etc.
sb.stroke(
&Stroke::new(stroke.width.get() as f32),
Affine::IDENTITY,
&brush,
None,
&local_path,
);
} else {
unimplemented(sb, &elt);
}
}
}
usvg::NodeKind::Image(_) => {
unimplemented(sb, &elt);
}
usvg::NodeKind::Text(_) => {
unimplemented(sb, &elt);
}
}
}
}
// Draw a red box over unsupported SVG features.
fn unimplemented(sb: &mut SceneBuilder, node: &usvg::Node) {
if let Some(bb) = node.calculate_bbox() {
let rect = Rect {
x0: bb.left(),
y0: bb.top(),
x1: bb.right(),
y1: bb.bottom(),
};
sb.fill(Fill::NonZero, Affine::IDENTITY, Color::RED, None, &rect);
}
}
fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option<Brush> {
match paint {
usvg::Paint::Color(color) => Some(Brush::Solid(Color::rgba8(
color.red,
color.green,
color.blue,
opacity.to_u8(),
))),
usvg::Paint::LinearGradient(_) => None,
usvg::Paint::RadialGradient(_) => None,
usvg::Paint::Pattern(_) => None,
}
}