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