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#[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 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
41impl 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
100impl 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
108fn 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 stack: Vec<(std::slice::Iter<'a, usvg::Node>, usvg::Transform)>,
119 }
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 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
157pub 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
163pub 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 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 .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 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}