Files
hcie-rust-v3.05/hcie-io/src/svg_import.rs
T
2026-07-09 02:59:53 +03:00

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],
}
}