1 | // This Source Code Form is subject to the terms of the Mozilla Public |
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | |
5 | use std::sync::Arc; |
6 | |
7 | use strict_num::NonZeroPositiveF32; |
8 | use svgtypes::Length; |
9 | use tiny_skia_path::Point; |
10 | |
11 | use super::converter; |
12 | use super::svgtree::{AId, EId, SvgNode}; |
13 | use crate::{ |
14 | ApproxEqUlps, ApproxZeroUlps, ClipPath, Fill, Group, Node, NonZeroRect, Path, Size, Transform, |
15 | ViewBox, |
16 | }; |
17 | |
18 | // Similar to `tiny_skia_path::PathSegment`, but without the `QuadTo`. |
19 | #[derive (Copy, Clone, Debug)] |
20 | enum Segment { |
21 | MoveTo(Point), |
22 | LineTo(Point), |
23 | CubicTo(Point, Point, Point), |
24 | Close, |
25 | } |
26 | |
27 | pub(crate) fn is_valid(node: SvgNode) -> bool { |
28 | // `marker-*` attributes cannot be set on shapes inside a `clipPath`. |
29 | if nodeAncestors<'_, '_> |
30 | .ancestors() |
31 | .any(|n: SvgNode<'_, '_>| n.tag_name() == Some(EId::ClipPath)) |
32 | { |
33 | return false; |
34 | } |
35 | |
36 | let start: Option> = node.find_attribute::<SvgNode>(AId::MarkerStart); |
37 | let mid: Option> = node.find_attribute::<SvgNode>(AId::MarkerMid); |
38 | let end: Option> = node.find_attribute::<SvgNode>(AId::MarkerEnd); |
39 | start.is_some() || mid.is_some() || end.is_some() |
40 | } |
41 | |
42 | pub(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 | // TODO: move to svgtree |
65 | // Check for recursive marker. |
66 | 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)] |
77 | enum MarkerKind { |
78 | Start, |
79 | Middle, |
80 | End, |
81 | } |
82 | |
83 | enum MarkerOrientation { |
84 | Auto, |
85 | AutoStartReverse, |
86 | Angle(f32), |
87 | } |
88 | |
89 | fn 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 `hidden` by default. |
112 | 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 | // TODO: avoid allocation |
137 | 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 | // TODO: do not create a group when no clipPath |
196 | let mut g = Group { |
197 | transform: 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 | |