| // Copyright 2025 the Vello Authors |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| //! SVG rendering example scene. |
| |
| use std::fmt; |
| use vello_common::kurbo::{Affine, Stroke}; |
| use vello_common::pico_svg::{Item, PicoSvg}; |
| |
| #[cfg(not(target_arch = "wasm32"))] |
| use std::path::{Path, PathBuf}; |
| |
| use crate::{ExampleScene, RenderingContext}; |
| |
| /// SVG scene that renders an SVG file |
| pub struct SvgScene { |
| transform: Affine, |
| svg: PicoSvg, |
| } |
| |
| impl fmt::Debug for SvgScene { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "SvgScene") |
| } |
| } |
| |
| impl ExampleScene for SvgScene { |
| fn render<T: RenderingContext>( |
| &mut self, |
| ctx: &mut T, |
| _resources: &mut T::Resources, |
| root_transform: Affine, |
| ) { |
| render_svg(ctx, &self.svg.items, root_transform * self.transform); |
| } |
| } |
| |
| impl SvgScene { |
| /// Create a new `SvgScene` with the Ghost Tiger SVG |
| pub fn tiger() -> Self { |
| // Load the ghost tiger SVG by default |
| #[cfg(target_arch = "wasm32")] |
| let svg_content = include_str!("../../../examples/assets/Ghostscript_Tiger.svg"); |
| #[cfg(not(target_arch = "wasm32"))] |
| let svg_content = { |
| let cargo_dir = Path::new(env!("CARGO_MANIFEST_DIR")) |
| .canonicalize() |
| .unwrap(); |
| &std::fs::read_to_string(cargo_dir.join("../../examples/assets/Ghostscript_Tiger.svg")) |
| .unwrap() |
| }; |
| |
| let svg = PicoSvg::load(svg_content, 1.0).expect("Failed to parse Ghost Tiger SVG"); |
| |
| Self { |
| transform: Affine::scale(3.0), |
| svg, |
| } |
| } |
| |
| /// Create a new `SvgScene` with the content from a given file |
| #[cfg(not(target_arch = "wasm32"))] |
| pub fn with_svg_file(path: PathBuf) -> Result<Self, Box<dyn std::error::Error>> { |
| let svg_content = std::fs::read_to_string(path)?; |
| let svg = PicoSvg::load(&svg_content, 1.0)?; |
| |
| Ok(Self { |
| transform: Affine::scale(3.0), |
| svg, |
| }) |
| } |
| } |
| |
| fn render_svg(ctx: &mut impl RenderingContext, items: &[Item], transform: Affine) { |
| ctx.set_transform(transform); |
| for item in items { |
| match item { |
| Item::Fill(fill_item) => { |
| ctx.set_paint(fill_item.color); |
| ctx.fill_path(&fill_item.path); |
| } |
| Item::Stroke(stroke_item) => { |
| let style = Stroke::new(stroke_item.width); |
| ctx.set_stroke(style); |
| ctx.set_paint(stroke_item.color); |
| ctx.stroke_path(&stroke_item.path); |
| } |
| Item::Group(group_item) => { |
| render_svg(ctx, &group_item.children, transform * group_item.affine); |
| ctx.set_transform(transform); |
| } |
| } |
| } |
| } |