ranim_items/vitem/
svg.rs

1use color::{AlphaColor, Srgb, palette::css, rgb8, rgba};
2use glam::DVec3;
3use glam::{DAffine2, dvec3};
4use ranim_core::traits::PointsFunc;
5use ranim_core::{
6    Extract, components::width::Width, primitives::vitem::VItemPrimitive, traits::Anchor,
7    utils::bezier::PathBuilder,
8};
9use ranim_core::{color, glam};
10use tracing::warn;
11
12use crate::vitem::Group;
13use ranim_core::traits::{
14    BoundingBox, FillColor, Opacity, Rotate, Scale, Shift, StrokeColor, StrokeWidth,
15};
16
17use super::VItem;
18
19// MARK: ### SvgItem ###
20/// An Svg Item
21///
22/// Its inner is a `Vec<VItem>`
23#[derive(Clone)]
24pub struct SvgItem(Group<VItem>);
25
26impl From<SvgItem> for Group<VItem> {
27    fn from(value: SvgItem) -> Self {
28        value.0
29    }
30}
31
32impl SvgItem {
33    /// Creates a new SvgItem from a SVG string
34    pub fn new(svg: impl AsRef<str>) -> Self {
35        let mut vitem_group = Self(Group(vitems_from_svg(svg.as_ref())));
36        vitem_group.put_center_on(DVec3::ZERO);
37        vitem_group.rotate(std::f64::consts::PI, DVec3::X);
38        vitem_group
39    }
40}
41
42// MARK: Trait impls
43impl BoundingBox for SvgItem {
44    fn get_bounding_box(&self) -> [glam::DVec3; 3] {
45        self.0.get_bounding_box()
46    }
47}
48
49impl Shift for SvgItem {
50    fn shift(&mut self, shift: glam::DVec3) -> &mut Self {
51        self.0.shift(shift);
52        self
53    }
54}
55
56impl Rotate for SvgItem {
57    fn rotate_by_anchor(&mut self, angle: f64, axis: glam::DVec3, anchor: Anchor) -> &mut Self {
58        self.0.rotate_by_anchor(angle, axis, anchor);
59        self
60    }
61}
62
63impl Scale for SvgItem {
64    fn scale_by_anchor(&mut self, scale: glam::DVec3, anchor: Anchor) -> &mut Self {
65        self.0.scale_by_anchor(scale, anchor);
66        self
67    }
68}
69
70impl FillColor for SvgItem {
71    fn fill_color(&self) -> AlphaColor<Srgb> {
72        self.0[0].fill_color()
73    }
74    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
75        self.0.set_fill_color(color);
76        self
77    }
78    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
79        self.0.set_fill_opacity(opacity);
80        self
81    }
82}
83
84impl StrokeColor for SvgItem {
85    fn stroke_color(&self) -> AlphaColor<Srgb> {
86        self.0[0].fill_color()
87    }
88    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
89        self.0.set_stroke_color(color);
90        self
91    }
92    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
93        self.0.set_stroke_opacity(opacity);
94        self
95    }
96}
97
98impl Opacity for SvgItem {
99    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
100        self.0.set_fill_opacity(opacity);
101        self.0.set_stroke_opacity(opacity);
102        self
103    }
104}
105
106impl StrokeWidth for SvgItem {
107    fn stroke_width(&self) -> f32 {
108        self.0.stroke_width()
109    }
110    fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
111        self.0.iter_mut().for_each(|vitem| {
112            vitem.apply_stroke_func(&f);
113        });
114        self
115    }
116    fn set_stroke_width(&mut self, width: f32) -> &mut Self {
117        self.0.set_stroke_width(width);
118        self
119    }
120}
121
122// MARK: Conversions
123impl Extract for SvgItem {
124    type Target = VItemPrimitive;
125    fn extract(&self) -> Vec<Self::Target> {
126        self.0.extract()
127    }
128}
129
130// MARK: misc
131fn parse_paint(paint: &usvg::Paint) -> AlphaColor<Srgb> {
132    match paint {
133        usvg::Paint::Color(color) => rgb8(color.red, color.green, color.blue),
134        _ => css::GREEN,
135    }
136}
137
138struct SvgElementIterator<'a> {
139    // Group children iter and its transform
140    stack: Vec<(std::slice::Iter<'a, usvg::Node>, usvg::Transform)>,
141    // transform_stack: Vec<usvg::Transform>,
142}
143
144impl<'a> Iterator for SvgElementIterator<'a> {
145    type Item = (&'a usvg::Path, usvg::Transform);
146    fn next(&mut self) -> Option<Self::Item> {
147        #[allow(clippy::never_loop)]
148        while !self.stack.is_empty() {
149            let (group, transform) = self.stack.last_mut().unwrap();
150            match group.next() {
151                Some(node) => match node {
152                    usvg::Node::Group(group) => {
153                        // trace!("group {:?}", group.abs_transform());
154                        self.stack
155                            .push((group.children().iter(), group.abs_transform()));
156                    }
157                    usvg::Node::Path(path) => {
158                        return Some((path, *transform));
159                    }
160                    usvg::Node::Image(_image) => {}
161                    usvg::Node::Text(_text) => {}
162                },
163                None => {
164                    self.stack.pop();
165                }
166            }
167            return self.next();
168        }
169        None
170    }
171}
172
173fn walk_svg_group(group: &usvg::Group) -> impl Iterator<Item = (&usvg::Path, usvg::Transform)> {
174    SvgElementIterator {
175        stack: vec![(group.children().iter(), usvg::Transform::identity())],
176    }
177}
178
179/// Construct a `Vec<VItem` from `&str` of a SVG
180pub fn vitems_from_svg(svg: &str) -> Vec<VItem> {
181    let tree = usvg::Tree::from_str(svg, &usvg::Options::default()).unwrap();
182    vitems_from_tree(&tree)
183}
184
185/// Construct a `Vec<VItem>` from `&usvg::Tree`
186pub fn vitems_from_tree(tree: &usvg::Tree) -> Vec<VItem> {
187    let mut vitems = vec![];
188    for (path, transform) in walk_svg_group(tree.root()) {
189        // let transform = path.abs_transform();
190
191        let mut builder = PathBuilder::new();
192        for segment in path.data().segments() {
193            match segment {
194                usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
195                    builder.move_to(dvec3(p.x as f64, p.y as f64, 0.0))
196                }
197                usvg::tiny_skia_path::PathSegment::LineTo(p) => {
198                    builder.line_to(dvec3(p.x as f64, p.y as f64, 0.0))
199                }
200                usvg::tiny_skia_path::PathSegment::QuadTo(p1, p2) => builder.quad_to(
201                    dvec3(p1.x as f64, p1.y as f64, 0.0),
202                    dvec3(p2.x as f64, p2.y as f64, 0.0),
203                ),
204                usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p3) => builder.cubic_to(
205                    dvec3(p1.x as f64, p1.y as f64, 0.0),
206                    dvec3(p2.x as f64, p2.y as f64, 0.0),
207                    dvec3(p3.x as f64, p3.y as f64, 0.0),
208                ),
209                usvg::tiny_skia_path::PathSegment::Close => builder.close_path(),
210            };
211        }
212        if builder.is_empty() {
213            warn!("empty path");
214            continue;
215        }
216
217        let mut vitem = VItem::from_vpoints(builder.vpoints().to_vec());
218        let affine = DAffine2::from_cols_array(&[
219            transform.sx as f64,
220            transform.kx as f64,
221            transform.kx as f64,
222            transform.sy as f64,
223            transform.tx as f64,
224            transform.ty as f64,
225        ]);
226        vitem.apply_affine(affine);
227        let fill_color = if let Some(fill) = path.fill() {
228            parse_paint(fill.paint()).with_alpha(fill.opacity().get())
229        } else {
230            rgba(0.0, 0.0, 0.0, 0.0)
231        };
232        vitem.set_fill_color(fill_color);
233        if let Some(stroke) = path.stroke() {
234            let color = parse_paint(stroke.paint()).with_alpha(stroke.opacity().get());
235            vitem.set_stroke_color(color);
236            vitem.set_stroke_width(stroke.width().get());
237        } else {
238            vitem.set_stroke_color(fill_color.with_alpha(0.0));
239            vitem.set_stroke_width(0.0);
240        }
241        vitems.push(vitem);
242    }
243    vitems
244}
245
246#[cfg(test)]
247mod tests {
248    use std::f64::consts::PI;
249
250    use glam::dvec3;
251
252    use crate::vitem::{geometry::Arc, typst::typst_svg};
253    use ranim_core::traits::{ScaleHint, ScaleStrokeExt, With};
254
255    use super::*;
256
257    fn print_typst_vitem(points: Vec<DVec3>) {
258        let colors = ["blue.darken(40%)", "yellow.darken(50%)"];
259        let mut last_anchor = None;
260        let mut subpath_cnt = 0;
261        let segs = points
262            .iter()
263            .step_by(2)
264            .cloned()
265            .zip(points.iter().skip(1).step_by(2).cloned())
266            .zip(points.iter().skip(2).step_by(2).cloned())
267            .collect::<Vec<_>>();
268
269        segs.iter().enumerate().for_each(|(i, ((a, b), c))| {
270            if last_anchor.is_none() {
271                last_anchor = Some(a);
272                println!(
273                    "circle(({}, {}), radius: 2pt, fill: green.transparentize(50%))",
274                    a.x, a.y
275                );
276            } else if a.distance(*b) < 0.00001 {
277                last_anchor = None;
278                subpath_cnt += 1;
279                println!(
280                    "circle(({}, {}), radius: 4pt, fill: red.transparentize(50%))",
281                    a.x, a.y
282                );
283            } else {
284                println!("circle(({}, {}), radius: 2pt, fill: none)", a.x, a.y);
285            }
286            println!(
287                "circle(({}, {}), radius: 1pt, fill: gray, stroke: none)",
288                b.x, b.y
289            );
290
291            if i == segs.len() - 1 {
292                println!(
293                    "circle(({}, {}), radius: 4pt, fill: red.transparentize(50%))",
294                    c.x, c.y
295                );
296            }
297
298            if a.distance(*b) > 0.00001 {
299                println!(
300                    "bezier(({}, {}), ({}, {}), ({}, {}), stroke: {})",
301                    a.x, a.y, c.x, c.y, b.x, b.y, colors[subpath_cnt]
302                );
303            }
304        });
305    }
306
307    #[test]
308    fn test_foo() {
309        let svg = SvgItem::new(typst_svg("R")).with(|svg| {
310            svg.scale_to_with_stroke(ScaleHint::PorportionalY(4.0))
311                .put_center_on(dvec3(2.0, 2.0, 0.0));
312        });
313        // println!("{:?}", svg.0[0].vpoints);
314        let points = (*svg.0[0].vpoints.0).clone();
315
316        print_typst_vitem(points);
317    }
318
319    #[test]
320    fn test_foo2() {
321        let angle = PI / 3.0 * 2.0;
322        let arc = Arc::new(angle, 2.0).with(|arc| {
323            arc.rotate(PI / 2.0 - angle / 2.0, DVec3::Z)
324                .put_center_on(dvec3(2.0, 2.0, 0.0));
325        });
326        let arc = VItem::from(arc);
327        let points = (**arc.vpoints).clone();
328        println!("{points:?}");
329
330        print_typst_vitem(points);
331    }
332}