157 lines
4.9 KiB
Rust
157 lines
4.9 KiB
Rust
use crate::{Layer, LayerData, LayerType, VectorShape};
|
|
use usvg;
|
|
|
|
pub fn import_svg(svg_data: &[u8]) -> Result<Vec<Layer>, String> {
|
|
let opts = usvg::Options::default();
|
|
let tree = usvg::Tree::from_data(svg_data, &opts).map_err(|e| e.to_string())?;
|
|
let svg_size = tree.size();
|
|
|
|
let canvas_w = svg_size.width().ceil() as u32;
|
|
let canvas_h = svg_size.height().ceil() as u32;
|
|
|
|
let mut shapes: Vec<VectorShape> = Vec::new();
|
|
|
|
collect_nodes(tree.root(), &mut shapes);
|
|
|
|
if shapes.is_empty() {
|
|
return Err("SVG contains no convertible path elements".to_string());
|
|
}
|
|
|
|
let mut layer = Layer::new_transparent("SVG Import", canvas_w, canvas_h);
|
|
layer.layer_type = LayerType::Vector;
|
|
layer.data = LayerData::Vector { shapes };
|
|
layer.id = rand::random::<u64>();
|
|
|
|
Ok(vec![layer])
|
|
}
|
|
|
|
fn collect_nodes(group: &usvg::Group, shapes: &mut Vec<VectorShape>) {
|
|
for node in group.children() {
|
|
match node {
|
|
usvg::Node::Path(ref path) => {
|
|
if let Some(shape) = convert_path(path) {
|
|
shapes.push(shape);
|
|
}
|
|
}
|
|
usvg::Node::Group(ref g) => {
|
|
collect_nodes(g, shapes);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn convert_path(path: &usvg::Path) -> Option<VectorShape> {
|
|
let data = path.data();
|
|
if data.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let transform = path.abs_transform();
|
|
let mut raw_pts: Vec<(f32, f32)> = Vec::new();
|
|
let mut is_closed = false;
|
|
|
|
for seg in data.segments() {
|
|
match seg {
|
|
usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
|
|
raw_pts.push(apply_transform(transform, p.x, p.y));
|
|
}
|
|
usvg::tiny_skia_path::PathSegment::LineTo(p) => {
|
|
raw_pts.push(apply_transform(transform, p.x, p.y));
|
|
}
|
|
usvg::tiny_skia_path::PathSegment::QuadTo(p1, p) => {
|
|
if let Some(&(sx, sy)) = raw_pts.last() {
|
|
let steps: u32 = 8;
|
|
for i in 1..=steps {
|
|
let t = i as f32 / steps as f32;
|
|
let bx = quad_bezier(sx, p1.x, p.x, t);
|
|
let by = quad_bezier(sy, p1.y, p.y, t);
|
|
raw_pts.push(apply_transform(transform, bx, by));
|
|
}
|
|
} else {
|
|
raw_pts.push(apply_transform(transform, p.x, p.y));
|
|
}
|
|
}
|
|
usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
|
|
if let Some(&(sx, sy)) = raw_pts.last() {
|
|
let steps: u32 = 8;
|
|
for i in 1..=steps {
|
|
let t = i as f32 / steps as f32;
|
|
let bx = cubic_bezier(sx, p1.x, p2.x, p.x, t);
|
|
let by = cubic_bezier(sy, p1.y, p2.y, p.y, t);
|
|
raw_pts.push(apply_transform(transform, bx, by));
|
|
}
|
|
} else {
|
|
raw_pts.push(apply_transform(transform, p.x, p.y));
|
|
}
|
|
}
|
|
usvg::tiny_skia_path::PathSegment::Close => {
|
|
if let Some(&first) = raw_pts.first() {
|
|
raw_pts.push(first);
|
|
}
|
|
is_closed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if raw_pts.len() < 2 {
|
|
return None;
|
|
}
|
|
|
|
let has_fill = path.fill().is_some();
|
|
let stroke_paint = path.stroke().map(|s| s.paint().clone());
|
|
let stroke_width = path.stroke().map(|s| s.width().get()).unwrap_or(1.0) as f32;
|
|
|
|
let stroke_color = match &stroke_paint {
|
|
Some(paint) => convert_paint(paint, 255),
|
|
None => [0, 0, 0, 255],
|
|
};
|
|
|
|
let fill_color = if has_fill {
|
|
convert_paint(path.fill().unwrap().paint(), 255)
|
|
} else {
|
|
[0, 0, 0, 64]
|
|
};
|
|
|
|
let pts: Vec<[f32; 2]> = raw_pts.into_iter().map(|(x, y)| [x, y]).collect();
|
|
|
|
Some(VectorShape::FreePath {
|
|
pts,
|
|
closed: is_closed,
|
|
stroke: stroke_width,
|
|
color: stroke_color,
|
|
fill: has_fill,
|
|
fill_color,
|
|
opacity: 1.0,
|
|
hardness: 1.0,
|
|
})
|
|
}
|
|
|
|
fn apply_transform(transform: usvg::Transform, x: f32, y: f32) -> (f32, f32) {
|
|
let tx = (transform.sx * x + transform.kx * y + transform.tx) as f32;
|
|
let ty = (transform.ky * x + transform.sy * y + transform.ty) as f32;
|
|
(tx, ty)
|
|
}
|
|
|
|
fn cubic_bezier(p0: f32, p1: f32, p2: f32, p3: f32, t: f32) -> f32 {
|
|
let u = 1.0 - t;
|
|
u * u * u * p0 + 3.0 * u * u * t * p1 + 3.0 * u * t * t * p2 + t * t * t * p3
|
|
}
|
|
|
|
fn quad_bezier(p0: f32, p1: f32, p2: f32, t: f32) -> f32 {
|
|
let u = 1.0 - t;
|
|
u * u * p0 + 2.0 * u * t * p1 + t * t * p2
|
|
}
|
|
|
|
fn convert_paint(paint: &usvg::Paint, default_alpha: u8) -> [u8; 4] {
|
|
match paint {
|
|
usvg::Paint::Color(ref col) => [
|
|
col.red,
|
|
col.green,
|
|
col.blue,
|
|
default_alpha,
|
|
],
|
|
_ => [0, 0, 0, default_alpha],
|
|
}
|
|
}
|