1use std::{error::Error, io::Read};
2
3use ribir_algo::Resource;
4use ribir_geom::{Point, Rect, Size, Transform};
5use serde::{Deserialize, Serialize};
6use usvg::{Options, Stop, Tree, TreeParsing};
7
8use crate::{
9 color::{LinearGradient, RadialGradient},
10 Brush, Color, GradientStop, LineCap, LineJoin, PaintCommand, Path, StrokeOptions,
11};
12
13#[derive(Serialize, Deserialize, Clone)]
14pub struct Svg {
15 pub size: Size,
16 pub commands: Resource<Box<[PaintCommand]>>,
17}
18
19fn fit_view_box(size: usvg::Size, vb: &usvg::ViewBox) -> usvg::Size {
21 let s = vb.rect.size();
22
23 if vb.aspect.align == usvg::Align::None {
24 s
25 } else if vb.aspect.slice {
26 size.expand_to(s)
27 } else {
28 size.scale_to(s)
29 }
30}
31
32impl Svg {
34 pub fn parse_from_bytes(svg_data: &[u8]) -> Result<Self, Box<dyn Error>> {
35 let opt = Options { ..<_>::default() };
36 let tree = Tree::from_data(svg_data, &opt).unwrap();
37 let view_rect = tree.view_box.rect;
38 let size = tree.size;
39 let fit_size = fit_view_box(size, &tree.view_box);
40
41 let bound_rect = Rect::from_size(Size::new(f32::MAX, f32::MAX));
42 let mut painter = crate::Painter::new(bound_rect);
43 painter.apply_transform(
44 &Transform::translation(-view_rect.x(), -view_rect.y())
45 .then_scale(size.width() / fit_size.width(), size.height() / fit_size.height()),
46 );
47 tree.root.traverse().for_each(|edge| match edge {
48 rctree::NodeEdge::Start(node) => {
49 use usvg::NodeKind;
50 painter.save();
51 match &*node.borrow() {
52 NodeKind::Path(p) => {
53 painter.apply_transform(&matrix_convert(p.transform));
54 let path = usvg_path_to_path(p);
55 if let Some(ref fill) = p.fill {
56 let (brush, transform) = brush_from_usvg_paint(&fill.paint, fill.opacity, &size);
57 let mut painter = painter.save_guard();
58
59 let inverse_ts = transform.inverse().unwrap();
60 let path = Resource::new(path.clone().transform(&inverse_ts));
61 painter
62 .set_brush(brush.clone())
63 .apply_transform(&transform)
64 .fill_path(path);
65 }
67
68 if let Some(ref stroke) = p.stroke {
69 let cap = match stroke.linecap {
70 usvg::LineCap::Butt => LineCap::Butt,
71 usvg::LineCap::Square => LineCap::Square,
72 usvg::LineCap::Round => LineCap::Round,
73 };
74 let join = match stroke.linejoin {
75 usvg::LineJoin::Miter => LineJoin::Miter,
76 usvg::LineJoin::Bevel => LineJoin::Bevel,
77 usvg::LineJoin::Round => LineJoin::Round,
78 usvg::LineJoin::MiterClip => LineJoin::MiterClip,
79 };
80 let options = StrokeOptions {
81 width: stroke.width.get(),
82 line_cap: cap,
83 line_join: join,
84 miter_limit: stroke.miterlimit.get(),
85 };
86
87 let (brush, transform) = brush_from_usvg_paint(&stroke.paint, stroke.opacity, &size);
88 let mut painter = painter.save_guard();
89
90 painter
91 .set_brush(brush.clone())
92 .apply_transform(&transform);
93
94 let path = path
95 .transform(&transform.inverse().unwrap())
96 .stroke(&options, Some(painter.get_transform()));
97
98 if let Some(p) = path {
99 painter.fill_path(Resource::new(p));
100 }
101 };
102 }
103 NodeKind::Image(_) => {
104 log::warn!("[painter]: not support draw embed image in svg, ignored!");
106 }
107 NodeKind::Group(ref g) => {
108 painter.apply_transform(&matrix_convert(g.transform));
109 if g.opacity.get() != 1. {
111 log::warn!("[painter]: not support `opacity` in svg, ignored!");
112 }
113 if g.clip_path.is_some() {
114 log::warn!("[painter]: not support `clip path` in svg, ignored!");
115 }
116 if g.mask.is_some() {
117 log::warn!("[painter]: not support `mask` in svg, ignored!");
118 }
119 if !g.filters.is_empty() {
120 log::warn!("[painter]: not support `filters` in svg, ignored!");
121 }
122 }
123 NodeKind::Text(_) => {
124 todo!("Not support text in SVG temporarily, we'll add it after refactoring `painter`.")
125 }
126 }
127 }
128 rctree::NodeEdge::End(_) => {
129 painter.restore();
130 }
131 });
132
133 let paint_commands = painter.finish().to_owned().into_boxed_slice();
134
135 Ok(Svg {
136 size: Size::new(size.width(), size.height()),
137 commands: Resource::new(paint_commands),
138 })
139 }
140
141 pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Box<dyn Error>> {
142 let mut file = std::fs::File::open(path)?;
143 let mut bytes = vec![];
144 file.read_to_end(&mut bytes)?;
145 Self::parse_from_bytes(&bytes)
146 }
147
148 pub fn serialize(&self) -> Result<String, Box<dyn Error>> {
149 Ok(serde_json::to_string(self)?)
151 }
152
153 pub fn deserialize(str: &str) -> Result<Self, Box<dyn Error>> { Ok(serde_json::from_str(str)?) }
154}
155
156fn usvg_path_to_path(path: &usvg::Path) -> Path {
157 let mut builder = lyon_algorithms::path::Path::svg_builder();
158 path.data.segments().for_each(|seg| match seg {
159 usvg::tiny_skia_path::PathSegment::MoveTo(pt) => {
160 builder.move_to(point(pt.x, pt.y));
161 }
162 usvg::tiny_skia_path::PathSegment::LineTo(pt) => {
163 builder.line_to(point(pt.x, pt.y));
164 }
165 usvg::tiny_skia_path::PathSegment::CubicTo(pt1, pt2, pt3) => {
166 builder.cubic_bezier_to(point(pt1.x, pt1.y), point(pt2.x, pt2.y), point(pt3.x, pt3.y));
167 }
168 usvg::tiny_skia_path::PathSegment::QuadTo(pt1, pt2) => {
169 builder.quadratic_bezier_to(point(pt1.x, pt1.y), point(pt2.x, pt2.y));
170 }
171 usvg::tiny_skia_path::PathSegment::Close => builder.close(),
172 });
173
174 builder.build().into()
175}
176
177fn point(x: f32, y: f32) -> lyon_algorithms::math::Point { Point::new(x, y).to_untyped() }
178
179fn matrix_convert(t: usvg::Transform) -> Transform {
180 let usvg::Transform { sx, kx, ky, sy, tx, ty } = t;
181 Transform::new(sx, ky, kx, sy, tx, ty)
182}
183
184fn brush_from_usvg_paint(
185 paint: &usvg::Paint, opacity: usvg::Opacity, size: &usvg::Size,
186) -> (Brush, Transform) {
187 match paint {
188 usvg::Paint::Color(usvg::Color { red, green, blue }) => (
189 Color::from_rgb(*red, *green, *blue)
190 .with_alpha(opacity.get())
191 .into(),
192 Transform::identity(),
193 ),
194 usvg::Paint::LinearGradient(linear) => {
195 let stops = convert_to_gradient_stops(&linear.stops);
196 let size_scale = match linear.units {
197 usvg::Units::UserSpaceOnUse => (1., 1.),
198 usvg::Units::ObjectBoundingBox => (size.width(), size.height()),
199 };
200 let gradient = LinearGradient {
201 start: Point::new(linear.x1 * size_scale.0, linear.y1 * size_scale.1),
202 end: Point::new(linear.x2 * size_scale.0, linear.y2 * size_scale.1),
203 stops,
204 spread_method: linear.spread_method.into(),
205 };
206
207 (Brush::LinearGradient(gradient), matrix_convert(linear.transform))
208 }
209 usvg::Paint::RadialGradient(radial_gradient) => {
210 let stops = convert_to_gradient_stops(&radial_gradient.stops);
211 let size_scale = match radial_gradient.units {
212 usvg::Units::UserSpaceOnUse => (1., 1.),
213 usvg::Units::ObjectBoundingBox => (size.width(), size.height()),
214 };
215 let gradient = RadialGradient {
216 start_center: Point::new(
217 radial_gradient.fx * size_scale.0,
218 radial_gradient.fy * size_scale.1,
219 ),
220 start_radius: 0., end_center: Point::new(
222 radial_gradient.cx * size_scale.0,
223 radial_gradient.cy * size_scale.1,
224 ),
225 end_radius: radial_gradient.r.get() * size_scale.0,
226 stops,
227 spread_method: radial_gradient.spread_method.into(),
228 };
229
230 (Brush::RadialGradient(gradient), matrix_convert(radial_gradient.transform))
231 }
232 paint => {
233 log::warn!("[painter]: not support `{paint:?}` in svg, use black instead!");
234 (Color::BLACK.into(), Transform::identity())
235 }
236 }
237}
238
239fn convert_to_gradient_stops(stops: &[Stop]) -> Vec<GradientStop> {
240 assert!(!stops.is_empty());
241
242 let mut stops: Vec<_> = stops
243 .iter()
244 .map(|stop| {
245 let usvg::Color { red, green, blue } = stop.color;
246 GradientStop {
247 offset: stop.offset.get(),
248 color: Color::new(red, green, blue, stop.opacity.to_u8()),
249 }
250 })
251 .collect();
252
253 stops.sort_by(|s1, s2| s1.offset.partial_cmp(&s2.offset).unwrap());
254
255 if let Some(first) = stops.first() {
256 if first.offset != 0. {
257 stops.insert(0, GradientStop { offset: 0., color: first.color });
258 }
259 }
260 if let Some(last) = stops.last() {
261 if last.offset < 1. {
262 stops.push(GradientStop { offset: 1., color: last.color });
263 }
264 }
265 stops
266}