1use std::sync::Arc;
6
7use strict_num::NonZeroPositiveF32;
8use svgtypes::Length;
9use tiny_skia_path::Point;
10
11use super::converter;
12use super::svgtree::{AId, EId, SvgNode};
13use crate::{
14 ApproxEqUlps, ApproxZeroUlps, ClipPath, Fill, Group, Node, NonZeroRect, Path, Size, Transform,
15 ViewBox,
16};
17
18#[derive(Copy, Clone, Debug)]
20enum Segment {
21 MoveTo(Point),
22 LineTo(Point),
23 CubicTo(Point, Point, Point),
24 Close,
25}
26
27pub(crate) fn is_valid(node: SvgNode) -> bool {
28 if node
30 .ancestors()
31 .any(|n| n.tag_name() == Some(EId::ClipPath))
32 {
33 return false;
34 }
35
36 let start = node.find_attribute::<SvgNode>(AId::MarkerStart);
37 let mid = node.find_attribute::<SvgNode>(AId::MarkerMid);
38 let end = node.find_attribute::<SvgNode>(AId::MarkerEnd);
39 start.is_some() || mid.is_some() || end.is_some()
40}
41
42pub(crate) fn convert(
43 node: SvgNode,
44 path: &tiny_skia_path::Path,
45 state: &converter::State,
46 cache: &mut converter::Cache,
47 parent: &mut Group,
48) {
49 let list = [
50 (AId::MarkerStart, MarkerKind::Start),
51 (AId::MarkerMid, MarkerKind::Middle),
52 (AId::MarkerEnd, MarkerKind::End),
53 ];
54
55 for (aid, kind) in &list {
56 let mut marker = None;
57 if let Some(link) = node.find_attribute::<SvgNode>(*aid) {
58 if link.tag_name() == Some(EId::Marker) {
59 marker = Some(link);
60 }
61 }
62
63 if let Some(marker) = marker {
64 if state.parent_markers.contains(&marker) {
67 log::warn!("Recursive marker detected: {}", marker.element_id());
68 continue;
69 }
70
71 resolve(node, path, marker, *kind, state, cache, parent);
72 }
73 }
74}
75
76#[derive(Clone, Copy)]
77enum MarkerKind {
78 Start,
79 Middle,
80 End,
81}
82
83enum MarkerOrientation {
84 Auto,
85 AutoStartReverse,
86 Angle(f32),
87}
88
89fn resolve(
90 shape_node: SvgNode,
91 path: &tiny_skia_path::Path,
92 marker_node: SvgNode,
93 marker_kind: MarkerKind,
94 state: &converter::State,
95 cache: &mut converter::Cache,
96 parent: &mut Group,
97) -> Option<()> {
98 let stroke_scale = stroke_scale(shape_node, marker_node, state)?.get();
99
100 let r = convert_rect(marker_node, state)?;
101
102 let view_box = marker_node.parse_viewbox().map(|vb| ViewBox {
103 rect: vb,
104 aspect: marker_node
105 .attribute(AId::PreserveAspectRatio)
106 .unwrap_or_default(),
107 });
108
109 let has_overflow = {
110 let overflow = marker_node.attribute(AId::Overflow);
111 overflow.is_none() || overflow == Some("hidden") || overflow == Some("scroll")
113 };
114
115 let clip_path = if has_overflow {
116 let clip_rect = if let Some(vbox) = view_box {
117 vbox.rect
118 } else {
119 r.size().to_non_zero_rect(0.0, 0.0)
120 };
121
122 let mut clip_path = ClipPath::empty(cache.gen_clip_path_id());
123
124 let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect(
125 clip_rect.to_rect(),
126 )))?;
127 path.fill = Some(Fill::default());
128
129 clip_path.root.children.push(Node::Path(Box::new(path)));
130
131 Some(Arc::new(clip_path))
132 } else {
133 None
134 };
135
136 let mut segments: Vec<Segment> = Vec::with_capacity(path.len());
138 let mut prev = Point::zero();
139 let mut prev_move = Point::zero();
140 for seg in path.segments() {
141 match seg {
142 tiny_skia_path::PathSegment::MoveTo(p) => {
143 segments.push(Segment::MoveTo(p));
144 prev = p;
145 prev_move = p;
146 }
147 tiny_skia_path::PathSegment::LineTo(p) => {
148 segments.push(Segment::LineTo(p));
149 prev = p;
150 }
151 tiny_skia_path::PathSegment::QuadTo(p1, p) => {
152 let (p1, p2, p) = quad_to_curve(prev, p1, p);
153 segments.push(Segment::CubicTo(p1, p2, p));
154 prev = p;
155 }
156 tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
157 segments.push(Segment::CubicTo(p1, p2, p));
158 prev = p;
159 }
160 tiny_skia_path::PathSegment::Close => {
161 segments.push(Segment::Close);
162 prev = prev_move;
163 }
164 }
165 }
166
167 let draw_marker = |p: tiny_skia_path::Point, idx: usize| {
168 let mut ts = Transform::from_translate(p.x, p.y);
169
170 let angle = match convert_orientation(marker_node) {
171 MarkerOrientation::AutoStartReverse if idx == 0 => {
172 (calc_vertex_angle(&segments, idx) + 180.0) % 360.0
173 }
174 MarkerOrientation::Auto | MarkerOrientation::AutoStartReverse => {
175 calc_vertex_angle(&segments, idx)
176 }
177 MarkerOrientation::Angle(angle) => angle,
178 };
179
180 if !angle.approx_zero_ulps(4) {
181 ts = ts.pre_rotate(angle);
182 }
183
184 if let Some(vbox) = view_box {
185 let size = Size::from_wh(r.width() * stroke_scale, r.height() * stroke_scale).unwrap();
186 let vbox_ts = vbox.to_transform(size);
187 let (sx, sy) = vbox_ts.get_scale();
188 ts = ts.pre_scale(sx, sy);
189 } else {
190 ts = ts.pre_scale(stroke_scale, stroke_scale);
191 }
192
193 ts = ts.pre_translate(-r.x(), -r.y());
194
195 let mut g = Group {
197 transform: ts,
198 abs_transform: parent.abs_transform.pre_concat(ts),
199 clip_path: clip_path.clone(),
200 ..Group::empty()
201 };
202
203 let mut marker_state = state.clone();
204 marker_state.parent_markers.push(marker_node);
205 converter::convert_children(marker_node, &marker_state, cache, &mut g);
206 g.calculate_bounding_boxes();
207
208 if g.has_children() {
209 parent.children.push(Node::Group(Box::new(g)));
210 }
211 };
212
213 draw_markers(&segments, marker_kind, draw_marker);
214
215 Some(())
216}
217
218fn stroke_scale(
219 path_node: SvgNode,
220 marker_node: SvgNode,
221 state: &converter::State,
222) -> Option<NonZeroPositiveF32> {
223 match marker_node.attribute(AId::MarkerUnits) {
224 Some("userSpaceOnUse") => NonZeroPositiveF32::new(1.0),
225 _ => path_node.resolve_valid_length(AId::StrokeWidth, state, 1.0),
226 }
227}
228
229fn draw_markers<P>(path: &[Segment], kind: MarkerKind, mut draw_marker: P)
230where
231 P: FnMut(tiny_skia_path::Point, usize),
232{
233 match kind {
234 MarkerKind::Start => {
235 if let Some(Segment::MoveTo(p)) = path.first().cloned() {
236 draw_marker(p, 0);
237 }
238 }
239 MarkerKind::Middle => {
240 let total = path.len() - 1;
241 let mut i = 1;
242 while i < total {
243 let p = match path[i] {
244 Segment::MoveTo(p) => p,
245 Segment::LineTo(p) => p,
246 Segment::CubicTo(_, _, p) => p,
247 _ => {
248 i += 1;
249 continue;
250 }
251 };
252
253 draw_marker(p, i);
254
255 i += 1;
256 }
257 }
258 MarkerKind::End => {
259 let idx = path.len() - 1;
260 match path.last().cloned() {
261 Some(Segment::LineTo(p)) => {
262 draw_marker(p, idx);
263 }
264 Some(Segment::CubicTo(_, _, p)) => {
265 draw_marker(p, idx);
266 }
267 Some(Segment::Close) => {
268 let p = get_subpath_start(path, idx);
269 draw_marker(p, idx);
270 }
271 _ => {}
272 }
273 }
274 }
275}
276
277fn calc_vertex_angle(path: &[Segment], idx: usize) -> f32 {
278 if idx == 0 {
279 debug_assert!(path.len() > 1);
282
283 let seg1 = path[0];
284 let seg2 = path[1];
285
286 match (seg1, seg2) {
287 (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y),
288 (Segment::MoveTo(pm), Segment::CubicTo(p1, _, p)) => {
289 if pm.x.approx_eq_ulps(&p1.x, 4) && pm.y.approx_eq_ulps(&p1.y, 4) {
290 calc_line_angle(pm.x, pm.y, p.x, p.y)
291 } else {
292 calc_line_angle(pm.x, pm.y, p1.x, p1.y)
293 }
294 }
295 _ => 0.0,
296 }
297 } else if idx == path.len() - 1 {
298 let seg1 = path[idx - 1];
301 let seg2 = path[idx];
302
303 match (seg1, seg2) {
304 (_, Segment::MoveTo(_)) => 0.0, (_, Segment::LineTo(p)) => {
306 let prev = get_prev_vertex(path, idx);
307 calc_line_angle(prev.x, prev.y, p.x, p.y)
308 }
309 (_, Segment::CubicTo(p1, p2, p)) => {
310 if p2.x.approx_eq_ulps(&p.x, 4) && p2.y.approx_eq_ulps(&p.y, 4) {
311 calc_line_angle(p1.x, p1.y, p.x, p.y)
312 } else {
313 calc_line_angle(p2.x, p2.y, p.x, p.y)
314 }
315 }
316 (Segment::LineTo(p), Segment::Close) => {
317 let next = get_subpath_start(path, idx);
318 calc_line_angle(p.x, p.y, next.x, next.y)
319 }
320 (Segment::CubicTo(_, p2, p), Segment::Close) => {
321 let prev = get_prev_vertex(path, idx);
322 let next = get_subpath_start(path, idx);
323 calc_curves_angle(
324 prev.x, prev.y, p2.x, p2.y, p.x, p.y, next.x, next.y, next.x, next.y,
325 )
326 }
327 (_, Segment::Close) => 0.0,
328 }
329 } else {
330 let seg1 = path[idx];
333 let seg2 = path[idx + 1];
334
335 match (seg1, seg2) {
337 (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y),
338 (Segment::MoveTo(pm), Segment::CubicTo(p1, _, _)) => {
339 calc_line_angle(pm.x, pm.y, p1.x, p1.y)
340 }
341 (Segment::LineTo(p1), Segment::LineTo(p2)) => {
342 let prev = get_prev_vertex(path, idx);
343 calc_angle(prev.x, prev.y, p1.x, p1.y, p1.x, p1.y, p2.x, p2.y)
344 }
345 (Segment::CubicTo(_, c1_p2, c1_p), Segment::CubicTo(c2_p1, _, c2_p)) => {
346 let prev = get_prev_vertex(path, idx);
347 calc_curves_angle(
348 prev.x, prev.y, c1_p2.x, c1_p2.y, c1_p.x, c1_p.y, c2_p1.x, c2_p1.y, c2_p.x,
349 c2_p.y,
350 )
351 }
352 (Segment::LineTo(pl), Segment::CubicTo(p1, _, p)) => {
353 let prev = get_prev_vertex(path, idx);
354 calc_curves_angle(
355 prev.x, prev.y, prev.x, prev.y, pl.x, pl.y, p1.x, p1.y, p.x, p.y,
356 )
357 }
358 (Segment::CubicTo(_, p2, p), Segment::LineTo(pl)) => {
359 let prev = get_prev_vertex(path, idx);
360 calc_curves_angle(prev.x, prev.y, p2.x, p2.y, p.x, p.y, pl.x, pl.y, pl.x, pl.y)
361 }
362 (Segment::LineTo(p), Segment::MoveTo(_)) => {
363 let prev = get_prev_vertex(path, idx);
364 calc_line_angle(prev.x, prev.y, p.x, p.y)
365 }
366 (Segment::CubicTo(_, p2, p), Segment::MoveTo(_)) => {
367 if p.x.approx_eq_ulps(&p2.x, 4) && p.y.approx_eq_ulps(&p2.y, 4) {
368 let prev = get_prev_vertex(path, idx);
369 calc_line_angle(prev.x, prev.y, p.x, p.y)
370 } else {
371 calc_line_angle(p2.x, p2.y, p.x, p.y)
372 }
373 }
374 (Segment::LineTo(p), Segment::Close) => {
375 let prev = get_prev_vertex(path, idx);
376 let next = get_subpath_start(path, idx);
377 calc_angle(prev.x, prev.y, p.x, p.y, p.x, p.y, next.x, next.y)
378 }
379 (_, Segment::Close) => {
380 let prev = get_prev_vertex(path, idx);
381 let next = get_subpath_start(path, idx);
382 calc_line_angle(prev.x, prev.y, next.x, next.y)
383 }
384 (_, Segment::MoveTo(_)) | (Segment::Close, _) => 0.0,
385 }
386 }
387}
388
389fn calc_line_angle(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
390 calc_angle(x1, y1, x2, y2, x1, y1, x2, y2)
391}
392
393fn calc_curves_angle(
394 px: f32,
395 py: f32, cx1: f32,
397 cy1: f32, x: f32,
399 y: f32, cx2: f32,
401 cy2: f32, nx: f32,
403 ny: f32, ) -> f32 {
405 if cx1.approx_eq_ulps(&x, 4) && cy1.approx_eq_ulps(&y, 4) {
406 calc_angle(px, py, x, y, x, y, cx2, cy2)
407 } else if x.approx_eq_ulps(&cx2, 4) && y.approx_eq_ulps(&cy2, 4) {
408 calc_angle(cx1, cy1, x, y, x, y, nx, ny)
409 } else {
410 calc_angle(cx1, cy1, x, y, x, y, cx2, cy2)
411 }
412}
413
414fn calc_angle(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32) -> f32 {
415 use std::f32::consts::*;
416
417 fn normalize(rad: f32) -> f32 {
418 let v = rad % (PI * 2.0);
419 if v < 0.0 {
420 v + PI * 2.0
421 } else {
422 v
423 }
424 }
425
426 fn vector_angle(vx: f32, vy: f32) -> f32 {
427 let rad = vy.atan2(vx);
428 if rad.is_nan() {
429 0.0
430 } else {
431 normalize(rad)
432 }
433 }
434
435 let in_a = vector_angle(x2 - x1, y2 - y1);
436 let out_a = vector_angle(x4 - x3, y4 - y3);
437 let d = (out_a - in_a) * 0.5;
438
439 let mut angle = in_a + d;
440 if FRAC_PI_2 < d.abs() {
441 angle -= PI;
442 }
443
444 normalize(angle).to_degrees()
445}
446
447fn get_subpath_start(segments: &[Segment], idx: usize) -> tiny_skia_path::Point {
448 let offset = segments.len() - idx;
449 for seg in segments.iter().rev().skip(offset) {
450 if let Segment::MoveTo(p) = *seg {
451 return p;
452 }
453 }
454
455 tiny_skia_path::Point::zero()
456}
457
458fn get_prev_vertex(segments: &[Segment], idx: usize) -> tiny_skia_path::Point {
459 match segments[idx - 1] {
460 Segment::MoveTo(p) => p,
461 Segment::LineTo(p) => p,
462 Segment::CubicTo(_, _, p) => p,
463 Segment::Close => get_subpath_start(segments, idx),
464 }
465}
466
467fn convert_rect(node: SvgNode, state: &converter::State) -> Option<NonZeroRect> {
468 NonZeroRect::from_xywh(
469 node.convert_user_length(AId::RefX, state, Length::zero()),
470 node.convert_user_length(AId::RefY, state, Length::zero()),
471 node.convert_user_length(AId::MarkerWidth, state, Length::new_number(3.0)),
472 node.convert_user_length(AId::MarkerHeight, state, Length::new_number(3.0)),
473 )
474}
475
476fn convert_orientation(node: SvgNode) -> MarkerOrientation {
477 match node.attribute(AId::Orient) {
478 Some("auto") => MarkerOrientation::Auto,
479 Some("auto-start-reverse") => MarkerOrientation::AutoStartReverse,
480 _ => match node.attribute::<svgtypes::Angle>(AId::Orient) {
481 Some(angle) => MarkerOrientation::Angle(angle.to_degrees() as f32),
482 None => MarkerOrientation::Angle(0.0),
483 },
484 }
485}
486
487fn quad_to_curve(prev: Point, p1: Point, p: Point) -> (Point, Point, Point) {
488 #[inline]
489 fn calc(n1: f32, n2: f32) -> f32 {
490 (n1 + n2 * 2.0) / 3.0
491 }
492
493 (
494 Point::from_xy(calc(prev.x, p1.x), calc(prev.y, p1.y)),
495 Point::from_xy(calc(p.x, p1.x), calc(p.y, p1.y)),
496 p,
497 )
498}