blob: 894f812d9b77509cf089cb9170d9462f5b4245ab [file] [log] [blame]
//! 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<F: FnMut(&mut SceneBuilder, &usvg::Node) -> Result<(), E>, E>(
sb: &mut SceneBuilder,
svg: &usvg::Tree,
mut on_err: F,
) -> Result<(), E> {
for elt in svg.root.descendants() {
let transform = {
let usvg::Transform { a, b, c, d, e, f } = elt.abs_transform();
Affine::new([a, b, c, d, e, f])
};
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 path.data.segments() {
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, transform, &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),
transform,
&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<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,
}
}