ranim_items/vitem/
svg.rs

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