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#[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 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
42impl 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
122impl Extract for SvgItem {
124 type Target = VItemPrimitive;
125 fn extract(&self) -> Vec<Self::Target> {
126 self.0.extract()
127 }
128}
129
130fn 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 stack: Vec<(std::slice::Iter<'a, usvg::Node>, usvg::Transform)>,
141 }
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 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
179pub 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
185pub 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 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 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}