This commit is contained in:
kieran 2024-10-30 14:04:30 +00:00
commit b3f3b3f958
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
6 changed files with 3991 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/.idea

3815
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "egui-svg"
version = "0.1.0"
edition = "2021"
[dependencies]
eframe = "0.29.1"
egui = "0.29.1"
itertools = "0.13.0"
usvg = "0.44.0"

32
examples/main.rs Normal file
View File

@ -0,0 +1,32 @@
use eframe::{Frame, NativeOptions};
use egui::Context;
use egui_svg::SVG;
fn main() -> eframe::Result {
let options = NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([320.0, 240.0]),
..Default::default()
};
eframe::run_native(
"SVG Example",
options,
Box::new(|cc| {
Ok(Box::new(MyApp))
}),
)
}
struct MyApp;
impl eframe::App for MyApp {
fn update(&mut self, ctx: &Context, frame: &mut Frame) {
ctx.set_debug_on_hover(true);
let test = include_bytes!("./test.svg");
egui::CentralPanel::default().show(ctx, |ui| {
let svg = SVG::new(test).expect("failed to load test SVG");
ui.add(svg.with_size(ui.available_size()));
});
}
}

13
examples/test.svg Normal file
View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg"
width="350" height="100" viewBox="0 0 350 100">
<defs>
<linearGradient id="OrangeYellow" gradientUnits="objectBoundingBox">
<stop offset="0%" stop-color="#F60"/>
<stop offset="100%" stop-color="#FF6"/>
</linearGradient>
</defs>
<g stroke="black" stroke-width="2px" fill="url(#OrangeYellow)">
<rect x="50" y="25" width="100" height="50"/>
<rect x="200" y="25" width="100" height="50"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 459 B

119
src/lib.rs Normal file
View File

@ -0,0 +1,119 @@
use egui::emath::TSTransform;
use egui::epaint::{PathShape, PathStroke};
use egui::{pos2, vec2, Color32, Painter, Response, Sense, Shape, Ui, Vec2, Widget};
use itertools::Itertools;
use usvg::{Options, Paint, Tree};
pub struct SVG {
tree: Tree,
scale: Option<f32>,
}
impl SVG {
pub fn new(bytes: &'static [u8]) -> Result<Self, usvg::Error> {
Ok(Self {
tree: Tree::from_data(bytes, &Options::default())?,
scale: None,
})
}
pub fn with_size(mut self, size: Vec2) -> Self {
// scale x/y until fits inside size
let bbox = self.tree.root().abs_bounding_box();
let scale_x = size.x / bbox.width();
let scale_y = size.y / bbox.height();
self.scale = Some(scale_x.min(scale_y));
self
}
pub fn with_scale(mut self, scale: f32) -> Self {
self.scale = Some(scale);
self
}
fn render_nodes(&self, group: &usvg::Group, painter: &Painter) {
for node in group.children() {
self.render_node(node, painter);
}
}
fn render_group(&self, group: &usvg::Group, painter: &Painter) {
if !group.should_isolate() {
self.render_nodes(group, painter);
return;
}
}
fn render_node(&self, node: &usvg::Node, painter: &Painter) {
match node {
usvg::Node::Group(ref group) => {
self.render_group(group, painter);
}
usvg::Node::Text(ref text) => {
self.render_group(text.flattened(), painter);
}
usvg::Node::Path(ref path) => {
self.render_path(path, painter);
}
usvg::Node::Image(ref image) => {
todo!()
}
}
}
fn render_path(&self, path: &usvg::Path, painter: &Painter) {
if !path.is_visible() {
return;
}
// convert to PathShape
let points = path.data().points().iter()
.map(|p| pos2(p.x, p.y)).collect();
let fill = if let Some(f) = path.fill() {
Self::paint_to_color(f.paint())
} else {
Color32::default()
};
let stroke = if let Some(s) = path.stroke() {
PathStroke::new(s.width().get(), Self::paint_to_color(s.paint()))
} else {
PathStroke::default()
};
let mut shape: Shape = PathShape::convex_polygon(points, fill, stroke).into();
let transform = path.abs_transform();
if transform.has_translate() {
shape.translate(vec2(transform.tx, transform.ty));
}
if transform.has_scale() {
shape.scale(transform.sx.min(transform.sy));
}
if let Some(scale) = self.scale {
shape.transform(TSTransform::from_scaling(scale));
}
painter.add(shape);
}
fn paint_to_color(paint: &Paint) -> Color32 {
match paint {
Paint::Color(ref c) => Color32::from_rgb(c.red, c.green, c.blue),
Paint::LinearGradient(_) => todo!(),
Paint::RadialGradient(_) => todo!(),
Paint::Pattern(_) => todo!(),
}
}
}
impl Widget for SVG {
fn ui(self, ui: &mut Ui) -> Response {
let bbox = self.tree.root().abs_bounding_box();
let size = if let Some(s) = self.scale {
vec2(bbox.width() * s, bbox.height() * s)
} else {
vec2(bbox.width(), bbox.height())
};
let (response, painter) = ui.allocate_painter(size, Sense::click());
self.render_group(self.tree.root(), &painter);
response
}
}