//! Append a [`usvg::Tree`] to a Vello [`SceneBuilder`] //! //! This currently lacks support for a [number of important](crate#unsupported-features) SVG features. //! This is because this integration was developed for examples, which only need to support enough SVG //! to demonstrate Vello. //! //! However, this is also intended to be the preferred integration between Vello and [usvg], so [consider //! contributing](https://github.com/linebender/vello) if you need a feature which is missing. //! //! [`render_tree_with`] is the primary entry point function, which supports choosing the behaviour //! when [unsupported features](crate#unsupported-features) are detected. In a future release where there are //! no unsupported features, this may be phased out //! //! [`render_tree`] is a convenience wrapper around [`render_tree_with`] which renders an indicator around not //! yet supported features //! //! This crate also re-exports [`usvg`], to make handling dependency versions easier //! //! # Unsupported features //! //! Missing features include: //! - embedded images //! - text //! - gradients //! - group opacity //! - mix-blend-modes //! - clipping //! - masking //! - filter effects //! - group background //! - path visibility //! - path paint order //! - path shape-rendering //! - patterns use std::convert::Infallible; use usvg::NodeExt; use vello::kurbo::{Affine, BezPath, Rect}; use vello::peniko::{Brush, Color, Fill, Stroke}; use vello::SceneBuilder; pub use usvg; /// Append a [`usvg::Tree`] into a Vello [`SceneBuilder`], with default error handling /// This will draw a red box over (some) unsupported elements /// /// Calls [`render_tree_with`] with an error handler implementing the above. /// /// See the [module level documentation](crate#unsupported-features) for a list of some unsupported svg features pub fn render_tree(sb: &mut SceneBuilder, svg: &usvg::Tree) { render_tree_with(sb, svg, default_error_handler).unwrap_or_else(|e| match e {}) } /// Append a [`usvg::Tree`] into a Vello [`SceneBuilder`]. /// /// Calls [`render_tree_with`] with [`default_error_handler`]. /// This will draw a red box over unsupported element types. /// /// See the [module level documentation](crate#unsupported-features) for a list of some unsupported svg features pub fn render_tree_with Result<(), E>, E>( sb: &mut SceneBuilder, svg: &usvg::Tree, mut on_err: F, ) -> Result<(), E> { 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(); // The semantics of SVG paths don't line up with `BezPath`; we must manually track initial points let mut just_closed = false; let mut most_recent_initial = (0., 0.); for elt in usvg::TransformedPath::new(&path.data, transform) { match elt { usvg::PathSegment::MoveTo { x, y } => { if std::mem::take(&mut just_closed) { local_path.move_to(most_recent_initial); } most_recent_initial = (x, y); local_path.move_to(most_recent_initial) } usvg::PathSegment::LineTo { x, y } => { if std::mem::take(&mut just_closed) { local_path.move_to(most_recent_initial); } local_path.line_to((x, y)) } usvg::PathSegment::CurveTo { x1, y1, x2, y2, x, y, } => { if std::mem::take(&mut just_closed) { local_path.move_to(most_recent_initial); } local_path.curve_to((x1, y1), (x2, y2), (x, y)) } usvg::PathSegment::ClosePath => { just_closed = true; 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 { on_err(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 { on_err(sb, &elt)?; } } } usvg::NodeKind::Image(_) => { on_err(sb, &elt)?; } usvg::NodeKind::Text(_) => { on_err(sb, &elt)?; } } } Ok(()) } /// Error handler function for [`render_tree_with`] which draws a transparent red box /// instead of unsupported SVG features pub fn default_error_handler(sb: &mut SceneBuilder, node: &usvg::Node) -> Result<(), Infallible> { 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.with_alpha_factor(0.5), None, &rect, ); } Ok(()) } fn paint_to_brush(paint: &usvg::Paint, opacity: usvg::Opacity) -> Option { 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, } }