mod asset;
mod hit;
mod image;
pub(crate) mod interact;
mod paint;
pub(crate) mod pick;
mod protocol;
mod raster;
mod text;
mod walk;
use bevy::asset::Handle;
use bevy::ecs::component::Component;
use bevy::math::{UVec2, Vec2};
use bevy::ui::widget::ImageMeasure;
use bevy::ui::{ComputedNode, ContentSize, NodeMeasure, VisualBox};
pub use asset::{SvgAssetLoader, SvgDocument, SvgParseError, parse_svg_bytes};
pub(crate) use image::{ensure_svg_image, is_svg_src, warn_ignored_attrs};
pub use interact::SvgUserPos;
#[cfg(test)]
pub(crate) use protocol::st;
pub use protocol::{
FillRuleKind, LinecapKind, LinejoinKind, PathData, PathSeg, ShapeAttrs, ShapePaint,
ShapeTransform, ShapeTransitionSpec, ViewBox,
};
pub(crate) use protocol::{
NUMERIC_ATTR_COUNT, NUMERIC_ATTRS, de_view_box, numeric_attr, numeric_attr_mut,
};
pub use raster::{rasterize_document, stamp_svg_measures, update_svg_surfaces};
#[cfg(test)]
pub(crate) const CIRCLE_SVG: &str = r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#f00"/></svg>"##;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShapeKind {
Path,
Rect,
Circle,
Ellipse,
Line,
Polyline,
Polygon,
Group,
}
impl ShapeKind {
pub fn from_kind(kind: &str) -> Option<ShapeKind> {
Some(match kind {
"path" => Self::Path,
"rect" => Self::Rect,
"circle" => Self::Circle,
"ellipse" => Self::Ellipse,
"line" => Self::Line,
"polyline" => Self::Polyline,
"polygon" => Self::Polygon,
"g" => Self::Group,
_ => return None,
})
}
}
#[derive(Component, Debug, Clone, PartialEq)]
pub struct SvgShape {
pub kind: ShapeKind,
pub attrs: ShapeAttrs,
}
#[derive(Component)]
pub struct SvgSurface {
pub doc: Option<Handle<SvgDocument>>,
pub view_box: Option<ViewBox>,
pub last_size: UVec2,
pub dirty: bool,
}
impl SvgSurface {
pub fn new(doc: Handle<SvgDocument>) -> Self {
Self {
doc: Some(doc),
view_box: None,
last_size: UVec2::ZERO,
dirty: true,
}
}
pub fn jsx(view_box: Option<ViewBox>) -> Self {
Self {
doc: None,
view_box,
last_size: UVec2::ZERO,
dirty: true,
}
}
}
pub(crate) fn node_scale_factor(node: &ComputedNode) -> f32 {
if node.inverse_scale_factor > 0.0 {
node.inverse_scale_factor.recip()
} else {
1.0
}
}
pub(crate) fn stamp_intrinsic_measure(
content_size: &mut ContentSize,
doc_size: Vec2,
scale_factor: f32,
visual_box: VisualBox,
) {
content_size.set(NodeMeasure::Image(ImageMeasure {
size: doc_size * scale_factor,
visual_box,
}));
}
#[cfg(test)]
mod tests {
use super::CIRCLE_SVG;
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn usvg_tree_is_send_sync() {
assert_send_sync::<usvg::Tree>();
}
#[test]
fn smoke_rasters_a_circle() {
let tree =
usvg::Tree::from_str(CIRCLE_SVG, &usvg::Options::default()).expect("valid SVG parses");
let mut pixmap = tiny_skia::Pixmap::new(64, 64).expect("nonzero pixmap");
let transform = tiny_skia::Transform::from_scale(64.0 / 100.0, 64.0 / 100.0);
resvg::render(&tree, transform, &mut pixmap.as_mut());
let center = pixmap.pixel(32, 32).expect("in bounds");
assert_eq!(
(center.red(), center.green(), center.blue(), center.alpha()),
(255, 0, 0, 255),
"circle center must be opaque red"
);
let corner = pixmap.pixel(1, 1).expect("in bounds");
assert_eq!(
corner.alpha(),
0,
"corner outside the circle must be transparent"
);
}
#[test]
fn perf_datapoint_512() {
let tree =
usvg::Tree::from_str(CIRCLE_SVG, &usvg::Options::default()).expect("valid SVG parses");
let mut pixmap = tiny_skia::Pixmap::new(512, 512).expect("nonzero pixmap");
let transform = tiny_skia::Transform::from_scale(512.0 / 100.0, 512.0 / 100.0);
let start = std::time::Instant::now();
resvg::render(&tree, transform, &mut pixmap.as_mut());
eprintln!(
"svg perf datapoint: 512x512 circle raster took {:?}",
start.elapsed()
);
}
}