mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-10 04:31:30 +11:00
180 lines
6.7 KiB
Rust
180 lines
6.7 KiB
Rust
//! 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 = 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<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,
|
|
}
|
|
}
|