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