Init
This commit is contained in:
commit
b3f3b3f958
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/.idea
|
3815
Cargo.lock
generated
Normal file
3815
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
Normal file
10
Cargo.toml
Normal 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
32
examples/main.rs
Normal 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
13
examples/test.svg
Normal 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
119
src/lib.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user