1use alloc::boxed::Box;
14use core::fmt;
15
16#[cfg(not(feature = "svg"))]
17pub use azul_core::svg::*;
18#[cfg(feature = "svg")]
20pub use azul_core::svg::{
21 c_void,
22 FontDatabase,
23 ImageRendering,
24 Indent,
25 OptionSvgDashPattern,
26 ResultSvgSvgParseError,
27 ResultSvgXmlNodeSvgParseError,
28 ShapeRendering,
29 SvgCircle,
30 SvgColoredVertex,
31 SvgColoredVertexVec,
32 SvgColoredVertexVecDestructor,
33 SvgDashPattern,
34 SvgFillRule,
35 SvgFillStyle,
36 SvgFitTo,
37 SvgLine,
38 SvgLineCap,
39 SvgLineJoin,
40 SvgMultiPolygon,
41 SvgMultiPolygonVec,
42 SvgMultiPolygonVecDestructor,
43 SvgNode,
44 SvgParseError,
45 SvgParseOptions,
46 SvgPath,
47 SvgPathElement,
48 SvgPathElementVec,
49 SvgPathElementVecDestructor,
50 SvgPathVec,
51 SvgPathVecDestructor,
52 SvgRenderOptions,
53 SvgRenderTransform,
54
55 SvgSimpleNode,
56 SvgSimpleNodeVec,
57 SvgSimpleNodeVecDestructor,
58 SvgSize,
59 SvgStrokeStyle,
60 SvgStyle,
61 SvgStyledNode,
62 SvgTransform,
63 SvgVertex,
64 SvgVertexVec,
65 SvgVertexVecDestructor,
66 SvgXmlOptions,
67 TessellatedColoredSvgNode,
68 TessellatedColoredSvgNodeVec,
69 TessellatedColoredSvgNodeVecDestructor,
70 TessellatedGPUSvgNode,
72 TessellatedSvgNode,
73 TessellatedSvgNodeVec,
74 TessellatedSvgNodeVecDestructor,
75 TessellatedSvgNodeVecRef,
76 TextRendering,
77};
78use azul_core::{
79 geom::PhysicalSizeU32,
80 gl::{GlContextPtr, Texture},
81 resources::{RawImage, RawImageFormat},
82};
83#[cfg(feature = "svg")]
84pub use azul_css::props::basic::animation::{
85 SvgCubicCurve, SvgPoint, SvgQuadraticCurve, SvgRect, SvgVector,
86};
87use azul_css::{
88 impl_result, impl_result_inner,
89 props::basic::{ColorU, LayoutSize, OptionColorU, OptionLayoutSize},
90 AzString, OptionI16, OptionString, OptionU16, StringVec, U8Vec,
91};
92#[cfg(feature = "svg")]
93use lyon::{
94 geom::euclid::{Point2D, Rect, Size2D, UnknownUnit},
95 math::Point,
96 path::Path,
97 tessellation::{
98 BuffersBuilder, FillOptions, FillTessellator, FillVertex, StrokeOptions, StrokeTessellator,
99 StrokeVertex, VertexBuffers,
100 },
101};
102
103use crate::xml::XmlError;
104
105#[cfg(feature = "svg")]
106extern crate agg_rust;
107
108use azul_core::gl::GL_RESTART_INDEX;
109
110const CIRCLE_BEZIER_KAPPA: f64 = 0.552_284_749_8;
112
113const DEFAULT_SVG_RENDER_SIZE: (u32, u32) = (800, 600);
115
116#[cfg(feature = "svg")]
117const fn translate_svg_line_join(e: SvgLineJoin) -> lyon::tessellation::LineJoin {
118 use azul_core::svg::SvgLineJoin::{Miter, MiterClip, Round, Bevel};
119 match e {
120 Miter => lyon::tessellation::LineJoin::Miter,
121 MiterClip => lyon::tessellation::LineJoin::MiterClip,
122 Round => lyon::tessellation::LineJoin::Round,
123 Bevel => lyon::tessellation::LineJoin::Bevel,
124 }
125}
126
127#[cfg(feature = "svg")]
128const fn translate_svg_line_cap(e: SvgLineCap) -> lyon::tessellation::LineCap {
129 use azul_core::svg::SvgLineCap::{Butt, Square, Round};
130 match e {
131 Butt => lyon::tessellation::LineCap::Butt,
132 Square => lyon::tessellation::LineCap::Square,
133 Round => lyon::tessellation::LineCap::Round,
134 }
135}
136
137#[cfg(feature = "svg")]
138fn translate_svg_stroke_style(e: SvgStrokeStyle) -> StrokeOptions {
139 StrokeOptions::tolerance(e.tolerance)
140 .with_start_cap(translate_svg_line_cap(e.start_cap))
141 .with_end_cap(translate_svg_line_cap(e.end_cap))
142 .with_line_join(translate_svg_line_join(e.line_join))
143 .with_line_width(e.line_width)
144 .with_miter_limit(e.miter_limit)
145 }
147
148#[cfg(feature = "svg")]
149fn svg_multipolygon_to_lyon_path(polygon: &SvgMultiPolygon) -> Path {
150 let mut builder = Path::builder();
151
152 for p in polygon.rings.as_ref() {
153 if p.items.as_ref().is_empty() {
154 continue;
155 }
156
157 let start_item = p.items.as_ref()[0];
158 let first_point = Point2D::new(start_item.get_start().x, start_item.get_start().y);
159
160 builder.begin(first_point);
161
162 for q in p.items.as_ref().iter().rev()
163 {
165 match q {
166 SvgPathElement::Line(l) => {
167 builder.line_to(Point2D::new(l.end.x, l.end.y));
168 }
169 SvgPathElement::QuadraticCurve(qc) => {
170 builder.quadratic_bezier_to(
171 Point2D::new(qc.ctrl.x, qc.ctrl.y),
172 Point2D::new(qc.end.x, qc.end.y),
173 );
174 }
175 SvgPathElement::CubicCurve(cc) => {
176 builder.cubic_bezier_to(
177 Point2D::new(cc.ctrl_1.x, cc.ctrl_1.y),
178 Point2D::new(cc.ctrl_2.x, cc.ctrl_2.y),
179 Point2D::new(cc.end.x, cc.end.y),
180 );
181 }
182 }
183 }
184
185 builder.end(p.is_closed());
186 }
187
188 builder.build()
189}
190
191#[cfg(feature = "svg")]
192fn svg_multi_shape_to_lyon_path(polygon: &[SvgSimpleNode]) -> Path {
193 use lyon::{
194 geom::Box2D,
195 path::{traits::PathBuilder, Winding},
196 };
197
198 let mut builder = Path::builder();
199
200 for p in polygon {
201 match p {
202 SvgSimpleNode::Path(p) => {
203 if p.items.as_ref().is_empty() {
204 continue;
205 }
206
207 let start_item = p.items.as_ref()[0];
208 let first_point = Point2D::new(start_item.get_start().x, start_item.get_start().y);
209
210 builder.begin(first_point);
211
212 for q in p.items.as_ref().iter().rev()
213 {
215 match q {
216 SvgPathElement::Line(l) => {
217 builder.line_to(Point2D::new(l.end.x, l.end.y));
218 }
219 SvgPathElement::QuadraticCurve(qc) => {
220 builder.quadratic_bezier_to(
221 Point2D::new(qc.ctrl.x, qc.ctrl.y),
222 Point2D::new(qc.end.x, qc.end.y),
223 );
224 }
225 SvgPathElement::CubicCurve(cc) => {
226 builder.cubic_bezier_to(
227 Point2D::new(cc.ctrl_1.x, cc.ctrl_1.y),
228 Point2D::new(cc.ctrl_2.x, cc.ctrl_2.y),
229 Point2D::new(cc.end.x, cc.end.y),
230 );
231 }
232 }
233 }
234
235 builder.end(p.is_closed());
236 }
237 SvgSimpleNode::Circle(c) => {
238 builder.add_circle(
239 Point::new(c.center_x, c.center_y),
240 c.radius,
241 Winding::Positive,
242 );
243 }
244 SvgSimpleNode::CircleHole(c) => {
245 builder.add_circle(
246 Point::new(c.center_x, c.center_y),
247 c.radius,
248 Winding::Negative,
249 );
250 }
251 SvgSimpleNode::Rect(c) => {
252 builder.add_rectangle(
253 &Box2D::from_origin_and_size(
254 Point::new(c.x, c.y),
255 Size2D::new(c.width, c.height),
256 ),
257 Winding::Positive,
258 );
259 }
260 SvgSimpleNode::RectHole(c) => {
261 builder.add_rectangle(
262 &Box2D::from_origin_and_size(
263 Point::new(c.x, c.y),
264 Size2D::new(c.width, c.height),
265 ),
266 Winding::Negative,
267 );
268 }
269 }
270 }
271
272 builder.build()
273}
274
275#[allow(clippy::suboptimal_flops)] #[allow(clippy::similar_names)] #[must_use] pub fn raw_line_intersection(p: &SvgLine, q: &SvgLine) -> Option<SvgPoint> {
278 let p_min_x = p.start.x.min(p.end.x);
279 let p_min_y = p.start.y.min(p.end.y);
280 let p_max_x = p.start.x.max(p.end.x);
281 let p_max_y = p.start.y.max(p.end.y);
282
283 let q_min_x = q.start.x.min(q.end.x);
284 let q_min_y = q.start.y.min(q.end.y);
285 let q_max_x = q.start.x.max(q.end.x);
286 let q_max_y = q.start.y.max(q.end.y);
287
288 let int_min_x = p_min_x.max(q_min_x);
289 let int_max_x = p_max_x.min(q_max_x);
290 let int_min_y = p_min_y.max(q_min_y);
291 let int_max_y = p_max_y.min(q_max_y);
292
293 let two = 2.0;
294 let mid_x = (int_min_x + int_max_x) / two;
295 let mid_y = (int_min_y + int_max_y) / two;
296
297 let p1x = p.start.x - mid_x;
299 let p1y = p.start.y - mid_y;
300 let p2x = p.end.x - mid_x;
301 let p2y = p.end.y - mid_y;
302 let q1x = q.start.x - mid_x;
303 let q1y = q.start.y - mid_y;
304 let q2x = q.end.x - mid_x;
305 let q2y = q.end.y - mid_y;
306
307 let px = p1y - p2y;
309 let py = p2x - p1x;
310 let pw = p1x * p2y - p2x * p1y;
311
312 let qx = q1y - q2y;
313 let qy = q2x - q1x;
314 let qw = q1x * q2y - q2x * q1y;
315
316 let xw = py * qw - qy * pw;
317 let yw = qx * pw - px * qw;
318 let w = px * qy - qx * py;
319
320 let x_int = xw / w;
321 let y_int = yw / w;
322
323 if (x_int.is_nan() || x_int.is_infinite()) || (y_int.is_nan() || y_int.is_infinite()) {
325 None
326 } else {
327 Some(SvgPoint {
329 x: x_int + mid_x,
330 y: y_int + mid_y,
331 })
332 }
333}
334
335#[must_use] pub fn raw_line_intersection_byval(p: &SvgLine, q: SvgLine) -> Option<SvgPoint> {
337 raw_line_intersection(p, &q)
338}
339
340#[allow(clippy::too_many_lines)] #[must_use] pub fn svg_path_offset(p: &SvgPath, distance: f32, join: SvgLineJoin, cap: SvgLineCap) -> SvgPath {
342 if distance == 0.0 {
343 return p.clone();
344 }
345
346 let mut items = p.items.as_slice().to_vec();
347 if let Some(mut first) = items.first() {
348 items.push(*first);
349 }
350
351 let mut items = items
352 .iter()
353 .map(|l| match l {
354 SvgPathElement::Line(q) => {
355 let normal = match q.outwards_normal() {
356 Some(s) => SvgPoint {
357 x: s.x * distance,
358 y: s.y * distance,
359 },
360 None => return *l,
361 };
362
363 SvgPathElement::Line(SvgLine {
364 start: SvgPoint {
365 x: q.start.x + normal.x,
366 y: q.start.y + normal.y,
367 },
368 end: SvgPoint {
369 x: q.end.x + normal.x,
370 y: q.end.y + normal.y,
371 },
372 })
373 }
374 SvgPathElement::QuadraticCurve(q) => {
375 let n1 = match (SvgLine {
376 start: q.start,
377 end: q.ctrl,
378 }
379 .outwards_normal())
380 {
381 Some(s) => SvgPoint {
382 x: s.x * distance,
383 y: s.y * distance,
384 },
385 None => return *l,
386 };
387
388 let n2 = match (SvgLine {
389 start: q.ctrl,
390 end: q.end,
391 }
392 .outwards_normal())
393 {
394 Some(s) => SvgPoint {
395 x: s.x * distance,
396 y: s.y * distance,
397 },
398 None => return *l,
399 };
400
401 let nl1 = SvgLine {
402 start: SvgPoint {
403 x: q.start.x + n1.x,
404 y: q.start.y + n1.y,
405 },
406 end: SvgPoint {
407 x: q.ctrl.x + n1.x,
408 y: q.ctrl.y + n1.y,
409 },
410 };
411
412 let nl2 = SvgLine {
413 start: SvgPoint {
414 x: q.ctrl.x + n2.x,
415 y: q.ctrl.y + n2.y,
416 },
417 end: SvgPoint {
418 x: q.end.x + n2.x,
419 y: q.end.y + n2.y,
420 },
421 };
422
423 let Some(nctrl) = raw_line_intersection(&nl1, &nl2) else {
424 return *l;
425 };
426
427 SvgPathElement::QuadraticCurve(SvgQuadraticCurve {
428 start: nl1.start,
429 ctrl: nctrl,
430 end: nl2.end,
431 })
432 }
433 SvgPathElement::CubicCurve(q) => {
434 let n1 = match (SvgLine {
435 start: q.start,
436 end: q.ctrl_1,
437 }
438 .outwards_normal())
439 {
440 Some(s) => SvgPoint {
441 x: s.x * distance,
442 y: s.y * distance,
443 },
444 None => return *l,
445 };
446
447 let n2 = match (SvgLine {
448 start: q.ctrl_1,
449 end: q.ctrl_2,
450 }
451 .outwards_normal())
452 {
453 Some(s) => SvgPoint {
454 x: s.x * distance,
455 y: s.y * distance,
456 },
457 None => return *l,
458 };
459
460 let n3 = match (SvgLine {
461 start: q.ctrl_2,
462 end: q.end,
463 }
464 .outwards_normal())
465 {
466 Some(s) => SvgPoint {
467 x: s.x * distance,
468 y: s.y * distance,
469 },
470 None => return *l,
471 };
472
473 let nl1 = SvgLine {
474 start: SvgPoint {
475 x: q.start.x + n1.x,
476 y: q.start.y + n1.y,
477 },
478 end: SvgPoint {
479 x: q.ctrl_1.x + n1.x,
480 y: q.ctrl_1.y + n1.y,
481 },
482 };
483
484 let nl2 = SvgLine {
485 start: SvgPoint {
486 x: q.ctrl_1.x + n2.x,
487 y: q.ctrl_1.y + n2.y,
488 },
489 end: SvgPoint {
490 x: q.ctrl_2.x + n2.x,
491 y: q.ctrl_2.y + n2.y,
492 },
493 };
494
495 let nl3 = SvgLine {
496 start: SvgPoint {
497 x: q.ctrl_2.x + n3.x,
498 y: q.ctrl_2.y + n3.y,
499 },
500 end: SvgPoint {
501 x: q.end.x + n3.x,
502 y: q.end.y + n3.y,
503 },
504 };
505
506 let Some(nctrl_1) = raw_line_intersection(&nl1, &nl2) else {
507 return *l;
508 };
509
510 let Some(nctrl_2) = raw_line_intersection(&nl2, &nl3) else {
511 return *l;
512 };
513
514 SvgPathElement::CubicCurve(SvgCubicCurve {
515 start: nl1.start,
516 ctrl_1: nctrl_1,
517 ctrl_2: nctrl_2,
518 end: nl3.end,
519 })
520 }
521 })
522 .collect::<Vec<_>>();
523
524 for i in 0..items.len().saturating_sub(2) {
525 let a_end_line = match items[i] {
526 SvgPathElement::Line(q) => q,
527 SvgPathElement::QuadraticCurve(q) => SvgLine {
528 start: q.ctrl,
529 end: q.end,
530 },
531 SvgPathElement::CubicCurve(q) => SvgLine {
532 start: q.ctrl_2,
533 end: q.end,
534 },
535 };
536
537 let b_start_line = match items[i + 1] {
538 SvgPathElement::Line(q) => q,
539 SvgPathElement::QuadraticCurve(q) => SvgLine {
540 start: q.ctrl,
541 end: q.start,
542 },
543 SvgPathElement::CubicCurve(q) => SvgLine {
544 start: q.ctrl_1,
545 end: q.start,
546 },
547 };
548
549 if let Some(intersect_pt) = raw_line_intersection(&a_end_line, &b_start_line) {
550 items[i].set_last(intersect_pt);
551 items[i + 1].set_first(intersect_pt);
552 }
553 }
554
555 items.pop();
556
557 SvgPath {
558 items: items.into(),
559 }
560}
561
562#[allow(clippy::suboptimal_flops)] fn shorten_line_end_by(line: SvgLine, distance: f32) -> SvgLine {
564 let dx = line.end.x - line.start.x;
565 let dy = line.end.y - line.start.y;
566 let dt = dx.hypot(dy);
567 let dt_short = dt - distance;
568
569 SvgLine {
570 start: line.start,
571 end: SvgPoint {
572 x: line.start.x + (dt_short / dt) * dx,
573 y: line.start.y + (dt_short / dt) * dy,
574 },
575 }
576}
577
578#[allow(clippy::suboptimal_flops)] fn shorten_line_start_by(line: SvgLine, distance: f32) -> SvgLine {
580 let dx = line.end.x - line.start.x;
581 let dy = line.end.y - line.start.y;
582 let dt = dx.hypot(dy);
583 let dt_short = dt - distance;
584
585 SvgLine {
586 start: SvgPoint {
587 x: line.start.x + (1.0 - dt_short / dt) * dx,
588 y: line.start.y + (1.0 - dt_short / dt) * dy,
589 },
590 end: line.end,
591 }
592}
593
594#[must_use] pub fn svg_path_bevel(p: &SvgPath, distance: f32) -> SvgPath {
596 let mut items = p.items.as_slice().to_vec();
597
598 let first = items.first().copied();
600 let last = items.last().copied();
601 if let Some(first) = first {
602 items.push(first);
603 }
604 items.reverse();
605 if let Some(last) = last {
606 items.push(last);
607 }
608 items.reverse();
609
610 let mut final_items = Vec::new();
611 for i in 0..items.len().saturating_sub(1) {
612 let a = items[i];
613 let b = items[i + 1];
614 match (a, b) {
615 (SvgPathElement::Line(a), SvgPathElement::Line(b)) => {
616 let a_short = shorten_line_end_by(a, distance);
617 let b_short = shorten_line_start_by(b, distance);
618 final_items.push(SvgPathElement::Line(a_short));
619 final_items.push(SvgPathElement::CubicCurve(SvgCubicCurve {
620 start: a_short.end,
621 ctrl_1: a.end,
622 ctrl_2: b.start,
623 end: b_short.start,
624 }));
625 final_items.push(SvgPathElement::Line(b_short));
626 }
627 (other_a, other_b) => {
628 final_items.push(other_a);
629 final_items.push(other_b);
630 }
631 }
632 }
633
634 final_items.pop();
636 final_items.reverse();
637 final_items.pop();
638 final_items.reverse();
639
640 SvgPath {
641 items: final_items.into(),
642 }
643}
644
645#[cfg(feature = "svg")]
646fn svg_path_to_lyon_path_events(path: &SvgPath) -> Path {
647 let mut builder = Path::builder();
648
649 if !path.items.as_ref().is_empty() {
650 let start_item = path.items.as_ref()[0];
651 let first_point = Point2D::new(start_item.get_start().x, start_item.get_start().y);
652
653 builder.begin(first_point);
654
655 for p in path.items.as_ref() {
656 match p {
657 SvgPathElement::Line(l) => {
658 builder.line_to(Point2D::new(l.end.x, l.end.y));
659 }
660 SvgPathElement::QuadraticCurve(qc) => {
661 builder.quadratic_bezier_to(
662 Point2D::new(qc.ctrl.x, qc.ctrl.y),
663 Point2D::new(qc.end.x, qc.end.y),
664 );
665 }
666 SvgPathElement::CubicCurve(cc) => {
667 builder.cubic_bezier_to(
668 Point2D::new(cc.ctrl_1.x, cc.ctrl_1.y),
669 Point2D::new(cc.ctrl_2.x, cc.ctrl_2.y),
670 Point2D::new(cc.end.x, cc.end.y),
671 );
672 }
673 }
674 }
675
676 builder.end(path.is_closed());
677 }
678
679 builder.build()
680}
681
682#[cfg(feature = "svg")]
683#[inline]
684fn vertex_buffers_to_tessellated_cpu_node(v: VertexBuffers<SvgVertex, u32>) -> TessellatedSvgNode {
685 TessellatedSvgNode {
686 vertices: v.vertices.into(),
687 indices: v.indices.into(),
688 }
689}
690
691#[cfg(feature = "svg")]
692#[must_use] pub fn tessellate_multi_polygon_fill(
693 polygon: &SvgMultiPolygon,
694 fill_style: SvgFillStyle,
695) -> TessellatedSvgNode {
696 let polygon = svg_multipolygon_to_lyon_path(polygon);
697
698 let mut geometry = VertexBuffers::new();
699 let mut tessellator = FillTessellator::new();
700
701 let tess_result = tessellator.tessellate_path(
702 &polygon,
703 &FillOptions::tolerance(fill_style.tolerance),
704 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex<'_>| {
705 let xy_arr = vertex.position();
706 SvgVertex {
707 x: xy_arr.x,
708 y: xy_arr.y,
709 }
710 }),
711 );
712
713 if tess_result.is_err() {
714 TessellatedSvgNode::empty()
715 } else {
716 vertex_buffers_to_tessellated_cpu_node(geometry)
717 }
718}
719
720#[cfg(not(feature = "svg"))]
721pub fn tessellate_multi_polygon_fill(
722 polygon: &SvgMultiPolygon,
723 fill_style: SvgFillStyle,
724) -> TessellatedSvgNode {
725 TessellatedSvgNode::default()
726}
727
728#[cfg(feature = "svg")]
729#[must_use] pub fn tessellate_multi_shape_fill(
730 ms: &[SvgSimpleNode],
731 fill_style: SvgFillStyle,
732) -> TessellatedSvgNode {
733 let polygon = svg_multi_shape_to_lyon_path(ms);
734
735 let mut geometry = VertexBuffers::new();
736 let mut tessellator = FillTessellator::new();
737
738 let tess_result = tessellator.tessellate_path(
739 &polygon,
740 &FillOptions::tolerance(fill_style.tolerance),
741 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex<'_>| {
742 let xy_arr = vertex.position();
743 SvgVertex {
744 x: xy_arr.x,
745 y: xy_arr.y,
746 }
747 }),
748 );
749
750 if tess_result.is_err() {
751 TessellatedSvgNode::empty()
752 } else {
753 vertex_buffers_to_tessellated_cpu_node(geometry)
754 }
755}
756
757#[cfg(not(feature = "svg"))]
758pub fn tessellate_multi_shape_fill(
759 ms: &[SvgSimpleNode],
760 fill_style: SvgFillStyle,
761) -> TessellatedSvgNode {
762 TessellatedSvgNode::default()
763}
764
765#[must_use] pub fn svg_node_contains_point(
766 node: &SvgNode,
767 point: SvgPoint,
768 fill_rule: SvgFillRule,
769 tolerance: f32,
770) -> bool {
771 match node {
772 SvgNode::MultiPolygonCollection(a) => a
773 .as_ref()
774 .iter()
775 .any(|e| polygon_contains_point(e, point, fill_rule, tolerance)),
776 SvgNode::MultiPolygon(a) => polygon_contains_point(a, point, fill_rule, tolerance),
777 SvgNode::Path(a) => {
778 if !a.is_closed() {
779 return false;
780 }
781 path_contains_point(a, point, fill_rule, tolerance)
782 }
783 SvgNode::Circle(a) => a.contains_point(point.x, point.y),
784 SvgNode::Rect(a) => a.contains_point(point),
785 SvgNode::MultiShape(a) => a.as_ref().iter().any(|e| match e {
786 SvgSimpleNode::Path(a) => {
787 if !a.is_closed() {
788 return false;
789 }
790 path_contains_point(a, point, fill_rule, tolerance)
791 }
792 SvgSimpleNode::Circle(a) => a.contains_point(point.x, point.y),
793 SvgSimpleNode::Rect(a) => a.contains_point(point),
794 SvgSimpleNode::CircleHole(a) => !a.contains_point(point.x, point.y),
795 SvgSimpleNode::RectHole(a) => !a.contains_point(point),
796 }),
797 }
798}
799
800#[cfg(feature = "svg")]
801#[must_use] pub fn path_contains_point(
802 path: &SvgPath,
803 point: SvgPoint,
804 fill_rule: SvgFillRule,
805 tolerance: f32,
806) -> bool {
807 use lyon::{
808 algorithms::hit_test::hit_test_path, math::Point as LyonPoint,
809 path::FillRule as LyonFillRule,
810 };
811 let path = svg_path_to_lyon_path_events(path);
812 let fill_rule = match fill_rule {
813 SvgFillRule::Winding => LyonFillRule::NonZero,
814 SvgFillRule::EvenOdd => LyonFillRule::EvenOdd,
815 };
816 let point = LyonPoint::new(point.x, point.y);
817 hit_test_path(&point, path.iter(), fill_rule, tolerance)
818}
819
820#[cfg(not(feature = "svg"))]
821pub fn path_contains_point(
822 path: &SvgPath,
823 point: SvgPoint,
824 fill_rule: SvgFillRule,
825 tolerance: f32,
826) -> bool {
827 false
828}
829
830#[cfg(feature = "svg")]
831#[must_use] pub fn polygon_contains_point(
832 polygon: &SvgMultiPolygon,
833 point: SvgPoint,
834 fill_rule: SvgFillRule,
835 tolerance: f32,
836) -> bool {
837 use lyon::{
838 algorithms::hit_test::hit_test_path, math::Point as LyonPoint,
839 path::FillRule as LyonFillRule,
840 };
841 polygon.rings.iter().any(|path| {
842 let path = svg_path_to_lyon_path_events(path);
843 let fill_rule = match fill_rule {
844 SvgFillRule::Winding => LyonFillRule::NonZero,
845 SvgFillRule::EvenOdd => LyonFillRule::EvenOdd,
846 };
847 let point = LyonPoint::new(point.x, point.y);
848 hit_test_path(&point, path.iter(), fill_rule, tolerance)
849 })
850}
851
852#[cfg(not(feature = "svg"))]
853pub fn polygon_contains_point(
854 polygon: &SvgMultiPolygon,
855 point: SvgPoint,
856 fill_rule: SvgFillRule,
857 tolerance: f32,
858) -> bool {
859 false
860}
861
862#[cfg(feature = "svg")]
863#[must_use] pub fn tessellate_multi_shape_stroke(
864 ms: &[SvgSimpleNode],
865 stroke_style: SvgStrokeStyle,
866) -> TessellatedSvgNode {
867 let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
868 let polygon = svg_multi_shape_to_lyon_path(ms);
869
870 let mut stroke_geometry = VertexBuffers::new();
871 let mut stroke_tess = StrokeTessellator::new();
872
873 let tess_result = stroke_tess.tessellate_path(
874 &polygon,
875 &stroke_options,
876 &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex<'_, '_>| {
877 let xy_arr = vertex.position();
878 SvgVertex {
879 x: xy_arr.x,
880 y: xy_arr.y,
881 }
882 }),
883 );
884
885 if tess_result.is_err() {
886 TessellatedSvgNode::empty()
887 } else {
888 vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
889 }
890}
891
892#[cfg(not(feature = "svg"))]
893pub fn tessellate_multi_shape_stroke(
894 polygon: &[SvgSimpleNode],
895 stroke_style: SvgStrokeStyle,
896) -> TessellatedSvgNode {
897 TessellatedSvgNode::default()
898}
899
900#[cfg(feature = "svg")]
901#[must_use] pub fn tessellate_multi_polygon_stroke(
902 polygon: &SvgMultiPolygon,
903 stroke_style: SvgStrokeStyle,
904) -> TessellatedSvgNode {
905 let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
906 let polygon = svg_multipolygon_to_lyon_path(polygon);
907
908 let mut stroke_geometry = VertexBuffers::new();
909 let mut stroke_tess = StrokeTessellator::new();
910
911 let tess_result = stroke_tess.tessellate_path(
912 &polygon,
913 &stroke_options,
914 &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex<'_, '_>| {
915 let xy_arr = vertex.position();
916 SvgVertex {
917 x: xy_arr.x,
918 y: xy_arr.y,
919 }
920 }),
921 );
922
923 if tess_result.is_err() {
924 TessellatedSvgNode::empty()
925 } else {
926 vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
927 }
928}
929
930#[cfg(not(feature = "svg"))]
931pub fn tessellate_multi_polygon_stroke(
932 polygon: &SvgMultiPolygon,
933 stroke_style: SvgStrokeStyle,
934) -> TessellatedSvgNode {
935 TessellatedSvgNode::default()
936}
937
938#[cfg(feature = "svg")]
939#[must_use] pub fn tessellate_path_fill(path: &SvgPath, fill_style: SvgFillStyle) -> TessellatedSvgNode {
940 let polygon = svg_path_to_lyon_path_events(path);
941
942 let mut geometry = VertexBuffers::new();
943 let mut tessellator = FillTessellator::new();
944
945 let tess_result = tessellator.tessellate_path(
946 &polygon,
947 &FillOptions::tolerance(fill_style.tolerance),
948 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex<'_>| {
949 let xy_arr = vertex.position();
950 SvgVertex {
951 x: xy_arr.x,
952 y: xy_arr.y,
953 }
954 }),
955 );
956
957 if tess_result.is_err() {
958 TessellatedSvgNode::empty()
959 } else {
960 vertex_buffers_to_tessellated_cpu_node(geometry)
961 }
962}
963
964#[cfg(not(feature = "svg"))]
965pub fn tessellate_path_fill(path: &SvgPath, fill_style: SvgFillStyle) -> TessellatedSvgNode {
966 TessellatedSvgNode::default()
967}
968
969#[cfg(feature = "svg")]
970#[must_use] pub fn tessellate_path_stroke(path: &SvgPath, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
971 let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
972 let polygon = svg_path_to_lyon_path_events(path);
973
974 let mut stroke_geometry = VertexBuffers::new();
975 let mut stroke_tess = StrokeTessellator::new();
976
977 let tess_result = stroke_tess.tessellate_path(
978 &polygon,
979 &stroke_options,
980 &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex<'_, '_>| {
981 let xy_arr = vertex.position();
982 SvgVertex {
983 x: xy_arr.x,
984 y: xy_arr.y,
985 }
986 }),
987 );
988
989 if tess_result.is_err() {
990 TessellatedSvgNode::empty()
991 } else {
992 vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
993 }
994}
995
996#[cfg(not(feature = "svg"))]
997pub fn tessellate_path_stroke(path: &SvgPath, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
998 TessellatedSvgNode::default()
999}
1000
1001#[cfg(feature = "svg")]
1002#[must_use] pub fn tessellate_circle_fill(c: &SvgCircle, fill_style: SvgFillStyle) -> TessellatedSvgNode {
1003 let center = Point2D::new(c.center_x, c.center_y);
1004
1005 let mut geometry = VertexBuffers::new();
1006 let mut tesselator = FillTessellator::new();
1007 let tess_result = tesselator.tessellate_circle(
1008 center,
1009 c.radius,
1010 &FillOptions::tolerance(fill_style.tolerance),
1011 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex<'_>| {
1012 let xy_arr = vertex.position();
1013 SvgVertex {
1014 x: xy_arr.x,
1015 y: xy_arr.y,
1016 }
1017 }),
1018 );
1019
1020 if tess_result.is_err() {
1021 TessellatedSvgNode::empty()
1022 } else {
1023 vertex_buffers_to_tessellated_cpu_node(geometry)
1024 }
1025}
1026
1027#[cfg(not(feature = "svg"))]
1028pub fn tessellate_circle_fill(c: &SvgCircle, fill_style: SvgFillStyle) -> TessellatedSvgNode {
1029 TessellatedSvgNode::default()
1030}
1031
1032#[cfg(feature = "svg")]
1033#[must_use] pub fn tessellate_circle_stroke(c: &SvgCircle, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
1034 let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1035 let center = Point2D::new(c.center_x, c.center_y);
1036
1037 let mut stroke_geometry = VertexBuffers::new();
1038 let mut tesselator = StrokeTessellator::new();
1039
1040 let tess_result = tesselator.tessellate_circle(
1041 center,
1042 c.radius,
1043 &stroke_options,
1044 &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex<'_, '_>| {
1045 let xy_arr = vertex.position();
1046 SvgVertex {
1047 x: xy_arr.x,
1048 y: xy_arr.y,
1049 }
1050 }),
1051 );
1052
1053 if tess_result.is_err() {
1054 TessellatedSvgNode::empty()
1055 } else {
1056 vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1057 }
1058}
1059
1060#[cfg(not(feature = "svg"))]
1061pub fn tessellate_circle_stroke(c: &SvgCircle, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
1062 TessellatedSvgNode::default()
1063}
1064
1065#[cfg(feature = "svg")]
1067fn get_radii(r: &SvgRect) -> lyon::geom::Box2D<f32> {
1068
1069 lyon::geom::Box2D::from_origin_and_size(
1077 Point2D::new(r.x, r.y),
1078 Size2D::new(r.width, r.height),
1079 )
1080}
1081
1082#[cfg(feature = "svg")]
1083#[must_use] pub fn tessellate_rect_fill(r: &SvgRect, fill_style: SvgFillStyle) -> TessellatedSvgNode {
1084 let rect = get_radii(r);
1085 let mut geometry = VertexBuffers::new();
1086 let mut tesselator = FillTessellator::new();
1087
1088 let tess_result = tesselator.tessellate_rectangle(
1089 &rect,
1090 &FillOptions::tolerance(fill_style.tolerance),
1091 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex<'_>| {
1092 let xy_arr = vertex.position();
1093 SvgVertex {
1094 x: xy_arr.x,
1095 y: xy_arr.y,
1096 }
1097 }),
1098 );
1099
1100 if tess_result.is_err() {
1101 TessellatedSvgNode::empty()
1102 } else {
1103 vertex_buffers_to_tessellated_cpu_node(geometry)
1104 }
1105}
1106
1107#[cfg(not(feature = "svg"))]
1108pub fn tessellate_rect_fill(r: &SvgRect, fill_style: SvgFillStyle) -> TessellatedSvgNode {
1109 TessellatedSvgNode::default()
1110}
1111
1112#[cfg(feature = "svg")]
1113#[must_use] pub fn tessellate_rect_stroke(r: &SvgRect, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
1114 let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1115 let rect = get_radii(r);
1116
1117 let mut stroke_geometry = VertexBuffers::new();
1118 let mut tesselator = StrokeTessellator::new();
1119
1120 let tess_result = tesselator.tessellate_rectangle(
1121 &rect,
1122 &stroke_options,
1123 &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex<'_, '_>| {
1124 let xy_arr = vertex.position();
1125 SvgVertex {
1126 x: xy_arr.x,
1127 y: xy_arr.y,
1128 }
1129 }),
1130 );
1131
1132 if tess_result.is_err() {
1133 TessellatedSvgNode::empty()
1134 } else {
1135 vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1136 }
1137}
1138
1139#[cfg(not(feature = "svg"))]
1140pub fn tessellate_rect_stroke(r: &SvgRect, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
1141 TessellatedSvgNode::default()
1142}
1143
1144#[cfg(feature = "svg")]
1146#[must_use] pub fn tessellate_styled_node(node: &SvgStyledNode) -> TessellatedSvgNode {
1147 match node.style {
1148 SvgStyle::Fill(fs) => tessellate_node_fill(&node.geometry, fs),
1149 SvgStyle::Stroke(ss) => tessellate_node_stroke(&node.geometry, ss),
1150 }
1151}
1152
1153#[cfg(not(feature = "svg"))]
1154pub fn tessellate_styled_node(node: &SvgStyledNode) -> TessellatedSvgNode {
1155 TessellatedSvgNode::default()
1156}
1157
1158#[cfg(feature = "svg")]
1159#[must_use] pub fn tessellate_line_stroke(
1160 svgline: &SvgLine,
1161 stroke_style: SvgStrokeStyle,
1162) -> TessellatedSvgNode {
1163 let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1164
1165 let mut builder = Path::builder();
1166 builder.begin(Point2D::new(svgline.start.x, svgline.start.y));
1167 builder.line_to(Point2D::new(svgline.end.x, svgline.end.y));
1168 builder.end(false);
1169 let path = builder.build();
1170
1171 let mut stroke_geometry = VertexBuffers::new();
1172 let mut stroke_tess = StrokeTessellator::new();
1173
1174 let tess_result = stroke_tess.tessellate_path(
1175 &path,
1176 &stroke_options,
1177 &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex<'_, '_>| {
1178 let xy_arr = vertex.position();
1179 SvgVertex {
1180 x: xy_arr.x,
1181 y: xy_arr.y,
1182 }
1183 }),
1184 );
1185
1186 if tess_result.is_err() {
1187 TessellatedSvgNode::empty()
1188 } else {
1189 vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1190 }
1191}
1192
1193#[cfg(not(feature = "svg"))]
1194pub fn tessellate_line_stroke(
1195 svgline: &SvgLine,
1196 stroke_style: SvgStrokeStyle,
1197) -> TessellatedSvgNode {
1198 TessellatedSvgNode::default()
1199}
1200
1201#[cfg(feature = "svg")]
1202#[must_use] pub fn tessellate_cubiccurve_stroke(
1203 svgcubiccurve: &SvgCubicCurve,
1204 stroke_style: SvgStrokeStyle,
1205) -> TessellatedSvgNode {
1206 let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1207
1208 let mut builder = Path::builder();
1209 builder.begin(Point2D::new(svgcubiccurve.start.x, svgcubiccurve.start.y));
1210 builder.cubic_bezier_to(
1211 Point2D::new(svgcubiccurve.ctrl_1.x, svgcubiccurve.ctrl_1.y),
1212 Point2D::new(svgcubiccurve.ctrl_2.x, svgcubiccurve.ctrl_2.y),
1213 Point2D::new(svgcubiccurve.end.x, svgcubiccurve.end.y),
1214 );
1215 builder.end(false);
1216 let path = builder.build();
1217
1218 let mut stroke_geometry = VertexBuffers::new();
1219 let mut stroke_tess = StrokeTessellator::new();
1220
1221 let tess_result = stroke_tess.tessellate_path(
1222 &path,
1223 &stroke_options,
1224 &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex<'_, '_>| {
1225 let xy_arr = vertex.position();
1226 SvgVertex {
1227 x: xy_arr.x,
1228 y: xy_arr.y,
1229 }
1230 }),
1231 );
1232
1233 if tess_result.is_err() {
1234 TessellatedSvgNode::empty()
1235 } else {
1236 vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1237 }
1238}
1239
1240#[cfg(not(feature = "svg"))]
1241pub fn tessellate_cubiccurve_stroke(
1242 svgline: &SvgCubicCurve,
1243 stroke_style: SvgStrokeStyle,
1244) -> TessellatedSvgNode {
1245 TessellatedSvgNode::default()
1246}
1247
1248#[cfg(feature = "svg")]
1249#[must_use] pub fn tessellate_quadraticcurve_stroke(
1250 svgquadraticcurve: &SvgQuadraticCurve,
1251 stroke_style: SvgStrokeStyle,
1252) -> TessellatedSvgNode {
1253 let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1254
1255 let mut builder = Path::builder();
1256 builder.begin(Point2D::new(
1257 svgquadraticcurve.start.x,
1258 svgquadraticcurve.start.y,
1259 ));
1260 builder.quadratic_bezier_to(
1261 Point2D::new(svgquadraticcurve.ctrl.x, svgquadraticcurve.ctrl.y),
1262 Point2D::new(svgquadraticcurve.end.x, svgquadraticcurve.end.y),
1263 );
1264 builder.end(false);
1265 let path = builder.build();
1266
1267 let mut stroke_geometry = VertexBuffers::new();
1268 let mut stroke_tess = StrokeTessellator::new();
1269
1270 let tess_result = stroke_tess.tessellate_path(
1271 &path,
1272 &stroke_options,
1273 &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex<'_, '_>| {
1274 let xy_arr = vertex.position();
1275 SvgVertex {
1276 x: xy_arr.x,
1277 y: xy_arr.y,
1278 }
1279 }),
1280 );
1281
1282 if tess_result.is_err() {
1283 TessellatedSvgNode::empty()
1284 } else {
1285 vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1286 }
1287}
1288
1289#[cfg(not(feature = "svg"))]
1290pub fn tessellate_quadraticcurve_stroke(
1291 svgquadraticcurve: &SvgQuadraticCurve,
1292 stroke_style: SvgStrokeStyle,
1293) -> TessellatedSvgNode {
1294 TessellatedSvgNode::default()
1295}
1296
1297#[cfg(feature = "svg")]
1298#[must_use] pub fn tessellate_svgpathelement_stroke(
1299 svgpathelement: &SvgPathElement,
1300 stroke_style: SvgStrokeStyle,
1301) -> TessellatedSvgNode {
1302 match svgpathelement {
1303 SvgPathElement::Line(l) => tessellate_line_stroke(l, stroke_style),
1304 SvgPathElement::QuadraticCurve(l) => tessellate_quadraticcurve_stroke(l, stroke_style),
1305 SvgPathElement::CubicCurve(l) => tessellate_cubiccurve_stroke(l, stroke_style),
1306 }
1307}
1308
1309#[cfg(not(feature = "svg"))]
1310pub fn tessellate_svgpathelement_stroke(
1311 svgpathelement: &SvgPathElement,
1312 stroke_style: SvgStrokeStyle,
1313) -> TessellatedSvgNode {
1314 TessellatedSvgNode::default()
1315}
1316
1317#[cfg(feature = "svg")]
1318#[allow(clippy::cast_possible_truncation)] #[must_use] pub fn join_tessellated_nodes(nodes: &[TessellatedSvgNode]) -> TessellatedSvgNode {
1320 let mut index_offset = 0;
1321
1322 let all_index_offsets = nodes
1324 .as_ref()
1325 .iter()
1326 .map(|t| {
1327 let i = index_offset;
1328 index_offset += t.vertices.len();
1329 i
1330 })
1331 .collect::<Vec<_>>();
1332
1333 let all_vertices = nodes
1334 .as_ref()
1335 .iter()
1336 .flat_map(|t| t.vertices.clone().into_library_owned_vec())
1337 .collect::<Vec<_>>();
1338
1339 let all_indices = nodes
1340 .as_ref()
1341 .iter()
1342 .enumerate()
1343 .flat_map(|(buffer_index, t)| {
1344 let vertex_buffer_offset: u32 = all_index_offsets
1348 .get(buffer_index)
1349 .copied()
1350 .unwrap_or(0)
1351 .min(core::u32::MAX as usize) as u32;
1352
1353 let mut indices = t.indices.clone().into_library_owned_vec();
1354 if vertex_buffer_offset != 0 {
1355 for i in &mut indices {
1356 if *i != GL_RESTART_INDEX {
1357 *i += vertex_buffer_offset;
1358 }
1359 }
1360 }
1361
1362 indices.push(GL_RESTART_INDEX);
1363
1364 indices
1365 })
1366 .collect::<Vec<_>>();
1367
1368 TessellatedSvgNode {
1369 vertices: all_vertices.into(),
1370 indices: all_indices.into(),
1371 }
1372}
1373
1374#[cfg(feature = "svg")]
1375#[allow(clippy::cast_possible_truncation)] #[must_use] pub fn join_tessellated_colored_nodes(
1377 nodes: &[TessellatedColoredSvgNode],
1378) -> TessellatedColoredSvgNode {
1379 let mut index_offset = 0;
1380
1381 let all_index_offsets = nodes
1383 .as_ref()
1384 .iter()
1385 .map(|t| {
1386 let i = index_offset;
1387 index_offset += t.vertices.len();
1388 i
1389 })
1390 .collect::<Vec<_>>();
1391
1392 let all_vertices = nodes
1393 .as_ref()
1394 .iter()
1395 .flat_map(|t| t.vertices.clone().into_library_owned_vec())
1396 .collect::<Vec<_>>();
1397
1398 let all_indices = nodes
1399 .as_ref()
1400 .iter()
1401 .enumerate()
1402 .flat_map(|(buffer_index, t)| {
1403 let vertex_buffer_offset: u32 = all_index_offsets
1407 .get(buffer_index)
1408 .copied()
1409 .unwrap_or(0)
1410 .min(core::u32::MAX as usize) as u32;
1411
1412 let mut indices = t.indices.clone().into_library_owned_vec();
1413 if vertex_buffer_offset != 0 {
1414 for i in &mut indices {
1415 if *i != GL_RESTART_INDEX {
1416 *i += vertex_buffer_offset;
1417 }
1418 }
1419 }
1420
1421 indices.push(GL_RESTART_INDEX);
1422
1423 indices
1424 })
1425 .collect::<Vec<_>>();
1426
1427 TessellatedColoredSvgNode {
1428 vertices: all_vertices.into(),
1429 indices: all_indices.into(),
1430 }
1431}
1432
1433#[cfg(not(feature = "svg"))]
1434pub fn join_tessellated_nodes(nodes: &[TessellatedSvgNode]) -> TessellatedSvgNode {
1435 TessellatedSvgNode::default()
1436}
1437
1438#[cfg(not(feature = "svg"))]
1439pub fn join_tessellated_colored_nodes(
1440 nodes: &[TessellatedColoredSvgNode],
1441) -> TessellatedColoredSvgNode {
1442 TessellatedColoredSvgNode::default()
1443}
1444
1445#[cfg(feature = "svg")]
1446#[must_use] pub fn tessellate_node_fill(node: &SvgNode, fs: SvgFillStyle) -> TessellatedSvgNode {
1447 match &node {
1448 SvgNode::MultiPolygonCollection(ref mpc) => {
1449 let tessellated_multipolygons = mpc
1450 .as_ref()
1451 .iter()
1452 .map(|mp| tessellate_multi_polygon_fill(mp, fs))
1453 .collect::<Vec<_>>();
1454 join_tessellated_nodes(&tessellated_multipolygons)
1455 }
1456 SvgNode::MultiPolygon(ref mp) => tessellate_multi_polygon_fill(mp, fs),
1457 SvgNode::Path(ref p) => tessellate_path_fill(p, fs),
1458 SvgNode::Circle(ref c) => tessellate_circle_fill(c, fs),
1459 SvgNode::Rect(ref r) => tessellate_rect_fill(r, fs),
1460 SvgNode::MultiShape(ref r) => tessellate_multi_shape_fill(r.as_ref(), fs),
1461 }
1462}
1463
1464#[cfg(not(feature = "svg"))]
1465pub fn tessellate_node_fill(node: &SvgNode, fs: SvgFillStyle) -> TessellatedSvgNode {
1466 TessellatedSvgNode::default()
1467}
1468
1469#[cfg(feature = "svg")]
1470#[must_use] pub fn tessellate_node_stroke(node: &SvgNode, ss: SvgStrokeStyle) -> TessellatedSvgNode {
1471 match &node {
1472 SvgNode::MultiPolygonCollection(ref mpc) => {
1473 let tessellated_multipolygons = mpc
1474 .as_ref()
1475 .iter()
1476 .map(|mp| tessellate_multi_polygon_stroke(mp, ss))
1477 .collect::<Vec<_>>();
1478 join_tessellated_nodes(&tessellated_multipolygons)
1479 }
1480 SvgNode::MultiPolygon(ref mp) => tessellate_multi_polygon_stroke(mp, ss),
1481 SvgNode::Path(ref p) => tessellate_path_stroke(p, ss),
1482 SvgNode::Circle(ref c) => tessellate_circle_stroke(c, ss),
1483 SvgNode::Rect(ref r) => tessellate_rect_stroke(r, ss),
1484 SvgNode::MultiShape(ms) => tessellate_multi_shape_stroke(ms.as_ref(), ss),
1485 }
1486}
1487
1488#[cfg(not(feature = "svg"))]
1489pub fn tessellate_node_stroke(node: &SvgNode, ss: SvgStrokeStyle) -> TessellatedSvgNode {
1490 TessellatedSvgNode::default()
1491}
1492
1493#[must_use] pub fn allocate_clipmask_texture(
1499 gl_context: GlContextPtr,
1500 size: PhysicalSizeU32,
1501 _background: ColorU,
1502) -> Texture {
1503 use azul_core::gl::TextureFlags;
1504
1505 let textures = gl_context.gen_textures(1);
1506 let texture_id = textures.get(0).unwrap();
1507
1508 Texture::create(
1509 *texture_id,
1510 TextureFlags {
1511 is_opaque: true,
1512 is_video_texture: false,
1513 },
1514 size,
1515 ColorU::TRANSPARENT,
1516 gl_context,
1517 RawImageFormat::R8,
1518 )
1519}
1520
1521pub fn apply_fxaa(texture: &mut Texture) -> Option<()> {
1527 apply_fxaa_with_config(texture, &azul_core::gl_fxaa::FxaaConfig::enabled())
1528}
1529
1530#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_precision_loss, clippy::cast_sign_loss)] #[allow(clippy::similar_names)] #[allow(clippy::too_many_lines)] pub fn apply_fxaa_with_config(
1535 texture: &mut Texture,
1536 config: &azul_core::gl_fxaa::FxaaConfig,
1537) -> Option<()> {
1538 use std::mem;
1539
1540 use azul_core::gl::{GLuint, GlVoidPtrConst, VertexAttributeType};
1541 use gl_context_loader::gl;
1542
1543 if !config.enabled || texture.size.width == 0 || texture.size.height == 0 {
1544 return Some(());
1545 }
1546
1547 if texture.format != RawImageFormat::RGBA8 {
1549 return Some(());
1550 }
1551
1552 let texture_size = texture.size;
1553 let gl_context = &texture.gl_context;
1554 let fxaa_shader = gl_context.get_fxaa_shader();
1555 let w = texture_size.width as f32;
1556 let h = texture_size.height as f32;
1557
1558 let mut current_program = [0_i32];
1560 let mut current_framebuffers = [0_i32];
1561 let mut current_texture_2d = [0_i32];
1562 let mut current_vertex_array_object = [0_i32];
1563 let mut current_vertex_buffer = [0_i32];
1564 let mut current_index_buffer = [0_i32];
1565 let mut current_active_texture = [0_i32];
1566 let mut current_blend_enabled = [0_u8];
1567 let mut current_viewport = [0_i32; 4];
1568
1569 gl_context.get_integer_v(gl::CURRENT_PROGRAM, (&mut current_program[..]).into());
1570 gl_context.get_integer_v(gl::FRAMEBUFFER, (&mut current_framebuffers[..]).into());
1571 gl_context.get_integer_v(gl::TEXTURE_2D, (&mut current_texture_2d[..]).into());
1572 gl_context.get_integer_v(
1573 gl::VERTEX_ARRAY_BINDING,
1574 (&mut current_vertex_array_object[..]).into(),
1575 );
1576 gl_context.get_integer_v(
1577 gl::ARRAY_BUFFER_BINDING,
1578 (&mut current_vertex_buffer[..]).into(),
1579 );
1580 gl_context.get_integer_v(
1581 gl::ELEMENT_ARRAY_BUFFER_BINDING,
1582 (&mut current_index_buffer[..]).into(),
1583 );
1584 gl_context.get_integer_v(
1585 gl::ACTIVE_TEXTURE,
1586 (&mut current_active_texture[..]).into(),
1587 );
1588 gl_context.get_boolean_v(gl::BLEND, (&mut current_blend_enabled[..]).into());
1589 gl_context.get_integer_v(gl::VIEWPORT, (&mut current_viewport[..]).into());
1590
1591 let temp_textures = gl_context.gen_textures(1);
1593 let temp_tex_id = *temp_textures.get(0)?;
1594 gl_context.bind_texture(gl::TEXTURE_2D, temp_tex_id);
1595 gl_context.tex_image_2d(
1596 gl::TEXTURE_2D,
1597 0,
1598 gl::RGBA as i32,
1599 texture_size.width as i32,
1600 texture_size.height as i32,
1601 0,
1602 gl::RGBA,
1603 gl::UNSIGNED_BYTE,
1604 None.into(),
1605 );
1606 gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
1607 gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
1608 gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
1609 gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
1610
1611 let fbos = gl_context.gen_framebuffers(1);
1613 let fbo_id = *fbos.get(0)?;
1614 gl_context.bind_framebuffer(gl::FRAMEBUFFER, fbo_id);
1615 gl_context.framebuffer_texture_2d(
1616 gl::FRAMEBUFFER,
1617 gl::COLOR_ATTACHMENT0,
1618 gl::TEXTURE_2D,
1619 temp_tex_id,
1620 0,
1621 );
1622 gl_context.draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
1623
1624 debug_assert!(
1625 gl_context.check_frame_buffer_status(gl::FRAMEBUFFER) == gl::FRAMEBUFFER_COMPLETE
1626 );
1627
1628 let quad_vertices: [f32; 8] = [
1631 -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, ];
1636 let quad_indices: [u32; 6] = [0, 1, 2, 0, 2, 3];
1637
1638 let vaos = gl_context.gen_vertex_arrays(1);
1639 let vao_id = *vaos.get(0)?;
1640 gl_context.bind_vertex_array(vao_id);
1641
1642 let vbos = gl_context.gen_buffers(1);
1643 let vbo_id = *vbos.get(0)?;
1644 gl_context.bind_buffer(gl::ARRAY_BUFFER, vbo_id);
1645 gl_context.buffer_data_untyped(
1646 gl::ARRAY_BUFFER,
1647 (size_of::<f32>() * quad_vertices.len()) as isize,
1648 GlVoidPtrConst {
1649 ptr: quad_vertices.as_ptr().cast::<c_void>(),
1650 run_destructor: true,
1651 },
1652 gl::STATIC_DRAW,
1653 );
1654
1655 let ibos = gl_context.gen_buffers(1);
1656 let ibo_id = *ibos.get(0)?;
1657 gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, ibo_id);
1658 gl_context.buffer_data_untyped(
1659 gl::ELEMENT_ARRAY_BUFFER,
1660 (size_of::<u32>() * quad_indices.len()) as isize,
1661 GlVoidPtrConst {
1662 ptr: quad_indices.as_ptr().cast::<c_void>(),
1663 run_destructor: true,
1664 },
1665 gl::STATIC_DRAW,
1666 );
1667
1668 let vertex_type = VertexAttributeType::Float;
1670 let stride = vertex_type.get_mem_size() * 2; gl_context.vertex_attrib_pointer(0, 2, vertex_type.get_gl_id(), false, stride as i32, 0);
1672 gl_context.enable_vertex_attrib_array(0);
1673
1674 gl_context.use_program(fxaa_shader);
1676 gl_context.viewport(0, 0, texture_size.width as i32, texture_size.height as i32);
1677 gl_context.disable(gl::BLEND); gl_context.active_texture(gl::TEXTURE0);
1681 gl_context.bind_texture(gl::TEXTURE_2D, texture.texture_id);
1682
1683 let u_texture = gl_context.get_uniform_location(fxaa_shader, "uTexture");
1685 gl_context.uniform_1i(u_texture, 0);
1686
1687 let u_texel_size = gl_context.get_uniform_location(fxaa_shader, "uTexelSize");
1688 gl_context.uniform_2f(u_texel_size, 1.0 / w, 1.0 / h);
1689
1690 let u_edge_threshold =
1691 gl_context.get_uniform_location(fxaa_shader, "uEdgeThreshold");
1692 gl_context.uniform_1f(u_edge_threshold, config.edge_threshold);
1693
1694 let u_edge_threshold_min =
1695 gl_context.get_uniform_location(fxaa_shader, "uEdgeThresholdMin");
1696 gl_context.uniform_1f(u_edge_threshold_min, config.edge_threshold_min);
1697
1698 gl_context.draw_elements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, 0);
1700
1701 let old_texture_id = texture.texture_id;
1705 texture.texture_id = temp_tex_id;
1706 gl_context.delete_textures((&[old_texture_id])[..].into());
1708
1709 gl_context.delete_framebuffers((&[fbo_id])[..].into());
1711 gl_context.disable_vertex_attrib_array(0);
1712 gl_context.delete_vertex_arrays((&[vao_id])[..].into());
1713 gl_context.delete_buffers((&[vbo_id, ibo_id])[..].into());
1714
1715 gl_context.bind_framebuffer(gl::FRAMEBUFFER, current_framebuffers[0] as u32);
1717 gl_context.bind_texture(gl::TEXTURE_2D, current_texture_2d[0] as u32);
1718 gl_context.bind_vertex_array(current_vertex_array_object[0] as u32);
1719 gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, current_index_buffer[0] as u32);
1720 gl_context.bind_buffer(gl::ARRAY_BUFFER, current_vertex_buffer[0] as u32);
1721 gl_context.use_program(current_program[0] as u32);
1722 gl_context.active_texture(current_active_texture[0] as u32);
1723 gl_context.viewport(
1724 current_viewport[0],
1725 current_viewport[1],
1726 current_viewport[2],
1727 current_viewport[3],
1728 );
1729 if u32::from(current_blend_enabled[0]) == gl::TRUE {
1730 gl_context.enable(gl::BLEND);
1731 }
1732
1733 Some(())
1734}
1735
1736#[cfg(feature = "svg")]
1737#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] #[allow(clippy::too_many_lines)] pub fn render_node_clipmask_cpu(
1740 image: &mut RawImage,
1741 node: &SvgNode,
1742 style: SvgStyle,
1743) -> Option<()> {
1744 use azul_core::resources::RawImageData;
1745 use agg_rust::{
1746 basics::{FillingRule, VertexSource, PATH_FLAGS_NONE},
1747 path_storage::PathStorage,
1748 color::Rgba8,
1749 conv_stroke::ConvStroke,
1750 conv_transform::ConvTransform,
1751 math_stroke::{LineCap, LineJoin},
1752 pixfmt_rgba::{PixfmtRgba32, PixelFormat},
1753 rasterizer_scanline_aa::RasterizerScanlineAa,
1754 renderer_base::RendererBase,
1755 renderer_scanline::render_scanlines_aa_solid,
1756 rendering_buffer::RowAccessor,
1757 scanline_u::ScanlineU8,
1758 trans_affine::TransAffine,
1759 };
1760
1761 #[allow(clippy::many_single_char_names)] #[allow(clippy::match_same_arms)] #[allow(clippy::too_many_lines)] fn agg_translate_node(node: &SvgNode) -> Option<PathStorage> {
1765 macro_rules! build_path {
1766 ($path:expr, $p:expr) => {{
1767 if $p.items.as_ref().is_empty() {
1768 return None;
1769 }
1770
1771 let start = $p.items.as_ref()[0].get_start();
1772 $path.move_to(f64::from(start.x), f64::from(start.y));
1773
1774 for path_element in $p.items.as_ref() {
1775 match path_element {
1776 SvgPathElement::Line(l) => {
1777 $path.line_to(f64::from(l.end.x), f64::from(l.end.y));
1778 }
1779 SvgPathElement::QuadraticCurve(qc) => {
1780 $path.curve3(
1781 f64::from(qc.ctrl.x), f64::from(qc.ctrl.y),
1782 f64::from(qc.end.x), f64::from(qc.end.y),
1783 );
1784 }
1785 SvgPathElement::CubicCurve(cc) => {
1786 $path.curve4(
1787 f64::from(cc.ctrl_1.x), f64::from(cc.ctrl_1.y),
1788 f64::from(cc.ctrl_2.x), f64::from(cc.ctrl_2.y),
1789 f64::from(cc.end.x), f64::from(cc.end.y),
1790 );
1791 }
1792 }
1793 }
1794
1795 if $p.is_closed() {
1796 $path.close_polygon(PATH_FLAGS_NONE);
1797 }
1798 }};
1799 }
1800
1801 let mut path = PathStorage::new();
1802 match node {
1803 SvgNode::MultiPolygonCollection(mpc) => {
1804 for mp in mpc {
1805 for p in &mp.rings {
1806 build_path!(path, p);
1807 }
1808 }
1809 }
1810 SvgNode::MultiPolygon(mp) => {
1811 for p in &mp.rings {
1812 build_path!(path, p);
1813 }
1814 }
1815 SvgNode::Path(p) => {
1816 build_path!(path, p);
1817 }
1818 SvgNode::Circle(c) => {
1819 let cx = f64::from(c.center_x);
1821 let cy = f64::from(c.center_y);
1822 let r = f64::from(c.radius);
1823 let k = CIRCLE_BEZIER_KAPPA;
1824 let kr = k * r;
1825 path.move_to(cx + r, cy);
1826 path.curve4(cx + r, cy + kr, cx + kr, cy + r, cx, cy + r);
1827 path.curve4(cx - kr, cy + r, cx - r, cy + kr, cx - r, cy);
1828 path.curve4(cx - r, cy - kr, cx - kr, cy - r, cx, cy - r);
1829 path.curve4(cx + kr, cy - r, cx + r, cy - kr, cx + r, cy);
1830 path.close_polygon(PATH_FLAGS_NONE);
1831 }
1832 SvgNode::Rect(r) => {
1833 let x = f64::from(r.x);
1834 let y = f64::from(r.y);
1835 let w = f64::from(r.width);
1836 let h = f64::from(r.height);
1837 path.move_to(x, y);
1838 path.line_to(x + w, y);
1839 path.line_to(x + w, y + h);
1840 path.line_to(x, y + h);
1841 path.close_polygon(PATH_FLAGS_NONE);
1842 }
1843 SvgNode::MultiShape(ms) => {
1844 for p in ms.as_ref() {
1845 match p {
1846 SvgSimpleNode::Path(p) => {
1847 build_path!(path, p);
1848 }
1849 SvgSimpleNode::Rect(r) => {
1850 let x = f64::from(r.x);
1851 let y = f64::from(r.y);
1852 let w = f64::from(r.width);
1853 let h = f64::from(r.height);
1854 path.move_to(x, y);
1855 path.line_to(x + w, y);
1856 path.line_to(x + w, y + h);
1857 path.line_to(x, y + h);
1858 path.close_polygon(PATH_FLAGS_NONE);
1859 }
1860 SvgSimpleNode::Circle(c) | SvgSimpleNode::CircleHole(c) => {
1861 let cx = f64::from(c.center_x);
1862 let cy = f64::from(c.center_y);
1863 let r = f64::from(c.radius);
1864 let k = CIRCLE_BEZIER_KAPPA;
1865 let kr = k * r;
1866 path.move_to(cx + r, cy);
1867 path.curve4(cx + r, cy + kr, cx + kr, cy + r, cx, cy + r);
1868 path.curve4(cx - kr, cy + r, cx - r, cy + kr, cx - r, cy);
1869 path.curve4(cx - r, cy - kr, cx - kr, cy - r, cx, cy - r);
1870 path.curve4(cx + kr, cy - r, cx + r, cy - kr, cx + r, cy);
1871 path.close_polygon(PATH_FLAGS_NONE);
1872 }
1873 SvgSimpleNode::RectHole(r) => {
1874 let x = f64::from(r.x);
1875 let y = f64::from(r.y);
1876 let w = f64::from(r.width);
1877 let h = f64::from(r.height);
1878 path.move_to(x, y);
1879 path.line_to(x + w, y);
1880 path.line_to(x + w, y + h);
1881 path.line_to(x, y + h);
1882 path.close_polygon(PATH_FLAGS_NONE);
1883 }
1884 }
1885 }
1886 }
1887 }
1888 if path.total_vertices() == 0 {
1889 return None;
1890 }
1891 Some(path)
1892 }
1893
1894 let w = image.width as u32;
1895 let h = image.height as u32;
1896 if w == 0 || h == 0 {
1897 return None;
1898 }
1899
1900 let transform_data = style.get_transform();
1901 let transform = TransAffine::new_custom(
1902 f64::from(transform_data.sx),
1903 f64::from(transform_data.ky),
1904 f64::from(transform_data.kx),
1905 f64::from(transform_data.sy),
1906 f64::from(transform_data.tx),
1907 f64::from(transform_data.ty),
1908 );
1909
1910 let mut agg_path = agg_translate_node(node)?;
1911 let white = Rgba8::new(255, 255, 255, 255);
1912
1913 let mut buf = vec![0u8; (w as usize) * (h as usize) * 4];
1915 let stride = (w * 4) as i32;
1916 let mut ra = unsafe { RowAccessor::new_with_buf(buf.as_mut_ptr(), w, h, stride) };
1917 let mut pf = PixfmtRgba32::new(&mut ra);
1918 let mut rb = RendererBase::new(pf);
1919 let mut ras = RasterizerScanlineAa::new();
1920 let mut sl = ScanlineU8::new();
1921
1922 match style {
1923 SvgStyle::Fill(fs) => {
1924 ras.filling_rule(match fs.fill_rule {
1925 SvgFillRule::Winding => FillingRule::NonZero,
1926 SvgFillRule::EvenOdd => FillingRule::EvenOdd,
1927 });
1928 if transform.is_identity(0.0001) {
1929 ras.add_path(&mut agg_path, 0);
1930 } else {
1931 let mut transformed = ConvTransform::new(&mut agg_path, transform);
1932 ras.add_path(&mut transformed, 0);
1933 }
1934 render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &white);
1935 }
1936 SvgStyle::Stroke(ss) => {
1937 let mut stroke = ConvStroke::new(agg_path);
1938 stroke.set_width(f64::from(ss.line_width));
1939 stroke.set_miter_limit(f64::from(ss.miter_limit));
1940 stroke.set_line_cap(match ss.start_cap {
1941 SvgLineCap::Butt => LineCap::Butt,
1942 SvgLineCap::Square => LineCap::Square,
1943 SvgLineCap::Round => LineCap::Round,
1944 });
1945 stroke.set_line_join(match ss.line_join {
1946 SvgLineJoin::Miter | SvgLineJoin::MiterClip => LineJoin::Miter,
1947 SvgLineJoin::Round => LineJoin::Round,
1948 SvgLineJoin::Bevel => LineJoin::Bevel,
1949 });
1950 if transform.is_identity(0.0001) {
1951 ras.add_path(&mut stroke, 0);
1952 } else {
1953 let mut transformed = ConvTransform::new(&mut stroke, transform);
1954 ras.add_path(&mut transformed, 0);
1955 }
1956 render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &white);
1957 }
1958 }
1959
1960 let red_channel = buf
1962 .chunks_exact(4)
1963 .map(|r| r[0])
1964 .collect::<Vec<_>>();
1965
1966 image.premultiplied_alpha = true;
1967 image.pixels = RawImageData::U8(red_channel.into());
1968 image.data_format = RawImageFormat::R8;
1969
1970 Some(())
1971}
1972
1973#[cfg(not(feature = "svg"))]
1974pub fn render_node_clipmask_cpu(
1975 image: &mut RawImage,
1976 node: &SvgNode,
1977 style: SvgStyle,
1978) -> Option<()> {
1979 None
1980}
1981
1982fn rasterize_multi_polygon(mp: &SvgMultiPolygon) -> agg_rust::rasterizer_scanline_aa::RasterizerScanlineAa {
1988 use agg_rust::{
1989 basics::{FillingRule, PATH_FLAGS_NONE},
1990 path_storage::PathStorage,
1991 rasterizer_scanline_aa::RasterizerScanlineAa,
1992 };
1993
1994 let mut ras = RasterizerScanlineAa::new();
1995 ras.filling_rule(FillingRule::NonZero);
1996
1997 let mut path = PathStorage::new();
1998 for ring in mp.rings.as_ref() {
1999 let mut first = true;
2000 for item in ring.items.as_ref() {
2001 match item {
2002 SvgPathElement::Line(l) => {
2003 if first {
2004 path.move_to(f64::from(l.start.x), f64::from(l.start.y));
2005 first = false;
2006 }
2007 path.line_to(f64::from(l.end.x), f64::from(l.end.y));
2008 }
2009 SvgPathElement::QuadraticCurve(q) => {
2010 if first {
2011 path.move_to(f64::from(q.start.x), f64::from(q.start.y));
2012 first = false;
2013 }
2014 path.curve3(f64::from(q.ctrl.x), f64::from(q.ctrl.y), f64::from(q.end.x), f64::from(q.end.y));
2015 }
2016 SvgPathElement::CubicCurve(c) => {
2017 if first {
2018 path.move_to(f64::from(c.start.x), f64::from(c.start.y));
2019 first = false;
2020 }
2021 path.curve4(
2022 f64::from(c.ctrl_1.x), f64::from(c.ctrl_1.y),
2023 f64::from(c.ctrl_2.x), f64::from(c.ctrl_2.y),
2024 f64::from(c.end.x), f64::from(c.end.y),
2025 );
2026 }
2027 }
2028 }
2029 path.close_polygon(PATH_FLAGS_NONE);
2030 }
2031 ras.add_path(&mut path, 0);
2032 ras
2033}
2034
2035#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_precision_loss, clippy::cast_sign_loss)] fn storage_to_multi_polygon(
2043 storage: &mut agg_rust::scanline_storage_aa::ScanlineStorageAa,
2044) -> SvgMultiPolygon {
2045 use agg_rust::rasterizer_scanline_aa::Scanline;
2046 use azul_css::props::basic::SvgPoint;
2047
2048 let mut rows: Vec<(i32, Vec<(i32, i32)>)> = Vec::new(); let mut sl = agg_rust::scanline_u::ScanlineU8::new();
2052 if storage.rewind_scanlines() {
2053 sl.reset(storage.min_x(), storage.max_x());
2054 while storage.sweep_scanline(&mut sl) {
2055 let y = Scanline::y(&sl);
2056 let mut row_spans: Vec<(i32, i32)> = Vec::new();
2057 for span in sl.begin() {
2058 let len = span.len;
2060 if len <= 0 { continue; }
2061 let covers = sl.covers();
2063 let mut x_start = None;
2064 for j in 0..len as usize {
2065 let cov = covers.get(span.cover_offset + j).copied().unwrap_or(0);
2066 if cov > 128 {
2067 if x_start.is_none() { x_start = Some(span.x + j as i32); }
2068 } else if let Some(xs) = x_start.take() {
2069 row_spans.push((xs, span.x + j as i32));
2070 }
2071 }
2072 if let Some(xs) = x_start {
2073 row_spans.push((xs, span.x + len));
2074 }
2075 }
2076 if !row_spans.is_empty() {
2077 rows.push((y, row_spans));
2078 }
2079 }
2080 }
2081
2082 if rows.is_empty() {
2083 return SvgMultiPolygon { rings: SvgPathVec::from_const_slice(&[]) };
2084 }
2085
2086 let mut rings = Vec::new();
2090
2091 for (y, spans) in &rows {
2092 let yf = *y as f32;
2093 for &(x0, x1) in spans {
2094 let x0f = x0 as f32;
2095 let x1f = x1 as f32;
2096 let elements = vec![
2098 SvgPathElement::Line(SvgLine::new(
2099 SvgPoint { x: x0f, y: yf },
2100 SvgPoint { x: x1f, y: yf },
2101 )),
2102 SvgPathElement::Line(SvgLine::new(
2103 SvgPoint { x: x1f, y: yf },
2104 SvgPoint { x: x1f, y: yf + 1.0 },
2105 )),
2106 SvgPathElement::Line(SvgLine::new(
2107 SvgPoint { x: x1f, y: yf + 1.0 },
2108 SvgPoint { x: x0f, y: yf + 1.0 },
2109 )),
2110 SvgPathElement::Line(SvgLine::new(
2111 SvgPoint { x: x0f, y: yf + 1.0 },
2112 SvgPoint { x: x0f, y: yf },
2113 )),
2114 ];
2115 rings.push(SvgPath { items: SvgPathElementVec::from_vec(elements) });
2116 }
2117 }
2118
2119 SvgMultiPolygon { rings: SvgPathVec::from_vec(rings) }
2120}
2121
2122fn svg_bool_op(
2124 a: &SvgMultiPolygon,
2125 b: &SvgMultiPolygon,
2126 op: agg_rust::scanline_boolean_algebra::SBoolOp,
2127) -> SvgMultiPolygon {
2128 use agg_rust::{
2129 scanline_boolean_algebra::sbool_combine_shapes_aa,
2130 scanline_storage_aa::ScanlineStorageAa,
2131 scanline_u::ScanlineU8,
2132 };
2133
2134 let mut ras1 = rasterize_multi_polygon(a);
2135 let mut ras2 = rasterize_multi_polygon(b);
2136
2137 let mut sl1 = ScanlineU8::new();
2138 let mut sl2 = ScanlineU8::new();
2139 let mut sl_result = ScanlineU8::new();
2140 let mut storage1 = ScanlineStorageAa::new();
2141 let mut storage2 = ScanlineStorageAa::new();
2142 let mut storage_result = ScanlineStorageAa::new();
2143
2144 sbool_combine_shapes_aa(
2145 op,
2146 &mut ras1, &mut ras2,
2147 &mut sl1, &mut sl2, &mut sl_result,
2148 &mut storage1, &mut storage2, &mut storage_result,
2149 );
2150
2151 storage_to_multi_polygon(&mut storage_result)
2152}
2153
2154#[must_use] pub fn svg_multi_polygon_union(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
2155 svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::Or)
2156}
2157
2158#[allow(clippy::needless_pass_by_value)]
2160#[must_use] pub fn svg_multi_polygon_union_byval(a: &SvgMultiPolygon, b: SvgMultiPolygon) -> SvgMultiPolygon {
2161 svg_multi_polygon_union(a, &b)
2162}
2163
2164#[must_use] pub fn svg_multi_polygon_intersection(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
2165 svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::And)
2166}
2167
2168#[allow(clippy::needless_pass_by_value)]
2170#[must_use] pub fn svg_multi_polygon_intersection_byval(
2171 a: &SvgMultiPolygon, b: SvgMultiPolygon,
2172) -> SvgMultiPolygon {
2173 svg_multi_polygon_intersection(a, &b)
2174}
2175
2176#[must_use] pub fn svg_multi_polygon_difference(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
2177 svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::AMinusB)
2178}
2179
2180#[allow(clippy::needless_pass_by_value)]
2182#[must_use] pub fn svg_multi_polygon_difference_byval(
2183 a: &SvgMultiPolygon, b: SvgMultiPolygon,
2184) -> SvgMultiPolygon {
2185 svg_multi_polygon_difference(a, &b)
2186}
2187
2188#[must_use] pub fn svg_multi_polygon_xor(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
2189 svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::Xor)
2190}
2191
2192#[allow(clippy::needless_pass_by_value)]
2194#[must_use] pub fn svg_multi_polygon_xor_byval(a: &SvgMultiPolygon, b: SvgMultiPolygon) -> SvgMultiPolygon {
2195 svg_multi_polygon_xor(a, &b)
2196}
2197
2198#[derive(Debug, Clone)]
2207#[repr(C)]
2208pub struct ParsedSvgXmlNode {
2209 pub run_destructor: bool,
2210}
2211
2212impl Drop for ParsedSvgXmlNode {
2213 fn drop(&mut self) { self.run_destructor = false; }
2214}
2215
2216pub fn svgxmlnode_parse(
2220 svg_file_data: &[u8],
2221 _options: SvgParseOptions,
2222) -> Result<ParsedSvgXmlNode, SvgParseError> {
2223 let s = core::str::from_utf8(svg_file_data)
2225 .map_err(|_| SvgParseError::NotAnUtf8Str)?;
2226 let _nodes = crate::xml::parse_xml_string(s)
2227 .map_err(|_| SvgParseError::NoParserAvailable)?;
2228 Ok(ParsedSvgXmlNode { run_destructor: true })
2229}
2230
2231#[derive(Clone)]
2233#[repr(C)]
2234pub struct ParsedSvg {
2235 pub svg_data: azul_css::U8Vec,
2236 pub run_destructor: bool,
2237}
2238
2239impl Drop for ParsedSvg {
2240 fn drop(&mut self) { self.run_destructor = false; }
2241}
2242
2243impl_result!(
2244 ParsedSvg,
2245 SvgParseError,
2246 ResultParsedSvgSvgParseError,
2247 copy = false,
2248 [Debug, Clone]
2249);
2250
2251impl From<ParsedSvg> for azul_core::svg::Svg {
2252 fn from(_parsed: ParsedSvg) -> Self {
2253 Self {
2254 tree: core::ptr::null(),
2255 run_destructor: false,
2256 }
2257 }
2258}
2259
2260impl fmt::Debug for ParsedSvg {
2261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2262 write!(f, "ParsedSvg({} bytes)", self.svg_data.as_ref().len())
2263 }
2264}
2265
2266impl ParsedSvg {
2267 pub fn from_string(
2271 svg_string: &str,
2272 parse_options: SvgParseOptions,
2273 ) -> Result<Self, SvgParseError> {
2274 svg_parse(svg_string.as_bytes(), parse_options)
2275 }
2276
2277 pub fn from_bytes(
2281 svg_bytes: &[u8],
2282 parse_options: SvgParseOptions,
2283 ) -> Result<Self, SvgParseError> {
2284 svg_parse(svg_bytes, parse_options)
2285 }
2286
2287 #[must_use] pub const fn get_root(&self) -> ParsedSvgXmlNode {
2288 svg_root(self)
2289 }
2290
2291 #[must_use] pub fn render(&self, options: SvgRenderOptions) -> Option<RawImage> {
2292 svg_render(self, options)
2293 }
2294
2295 #[must_use] pub fn to_string(&self, _options: SvgXmlOptions) -> String {
2296 String::from_utf8_lossy(self.svg_data.as_ref()).into_owned()
2297 }
2298}
2299
2300pub fn svg_parse(
2305 svg_file_data: &[u8],
2306 _options: SvgParseOptions,
2307) -> Result<ParsedSvg, SvgParseError> {
2308 let s = core::str::from_utf8(svg_file_data)
2310 .map_err(|_| SvgParseError::NotAnUtf8Str)?;
2311 let _nodes = crate::xml::parse_xml_string(s)
2312 .map_err(|_| SvgParseError::NoParserAvailable)?;
2313 Ok(ParsedSvg {
2314 svg_data: svg_file_data.to_vec().into(),
2315 run_destructor: true,
2316 })
2317}
2318
2319#[must_use] pub const fn svg_root(s: &ParsedSvg) -> ParsedSvgXmlNode {
2320 ParsedSvgXmlNode { run_destructor: true }
2321}
2322
2323#[cfg(feature = "cpurender")]
2328#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] #[must_use] pub fn svg_render(s: &ParsedSvg, options: SvgRenderOptions) -> Option<RawImage> {
2330 use azul_core::resources::RawImageData;
2331
2332 let (target_width, target_height) = options.target_size.as_ref().map_or(DEFAULT_SVG_RENDER_SIZE, |s| (s.width as u32, s.height as u32));
2333
2334 if target_width == 0 || target_height == 0 {
2335 return None;
2336 }
2337
2338 let png_data = crate::cpurender::render_svg_to_png(s.svg_data.as_ref(), target_width, target_height).ok()?;
2339
2340 let decoder = png::Decoder::new(std::io::Cursor::new(&png_data));
2342 let mut reader = decoder.read_info().ok()?;
2343 let mut buf = vec![0u8; reader.output_buffer_size()?];
2344 let info = reader.next_frame(&mut buf).ok()?;
2345 buf.truncate(info.buffer_size());
2346
2347 Some(RawImage {
2348 tag: Vec::new().into(),
2349 pixels: RawImageData::U8(buf.into()),
2350 width: info.width as usize,
2351 height: info.height as usize,
2352 premultiplied_alpha: false,
2353 data_format: RawImageFormat::RGBA8,
2354 })
2355}
2356
2357#[cfg(not(feature = "cpurender"))]
2361pub fn svg_render(_s: &ParsedSvg, _options: SvgRenderOptions) -> Option<RawImage> {
2362 None
2363}
2364
2365#[must_use] pub fn svg_to_string(s: &ParsedSvg, _options: SvgXmlOptions) -> String {
2366 String::from_utf8_lossy(s.svg_data.as_ref()).into_owned()
2367}
2368
2369pub trait SvgMultiPolygonTessellation {
2375 fn tessellate_fill(&self, fill_style: SvgFillStyle) -> TessellatedSvgNode;
2376 fn tessellate_stroke(&self, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode;
2377}
2378
2379impl SvgMultiPolygonTessellation for SvgMultiPolygon {
2380 fn tessellate_fill(&self, fill_style: SvgFillStyle) -> TessellatedSvgNode {
2381 tessellate_multi_polygon_fill(self, fill_style)
2382 }
2383 fn tessellate_stroke(&self, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
2384 tessellate_multi_polygon_stroke(self, stroke_style)
2385 }
2386}
2387
2388#[cfg(test)]
2397mod autotest_generated {
2398 use azul_core::resources::RawImageData;
2399
2400 use super::*;
2401
2402 fn pt(x: f32, y: f32) -> SvgPoint {
2407 SvgPoint { x, y }
2408 }
2409
2410 fn ln(x0: f32, y0: f32, x1: f32, y1: f32) -> SvgLine {
2411 SvgLine {
2412 start: pt(x0, y0),
2413 end: pt(x1, y1),
2414 }
2415 }
2416
2417 fn mk_path(items: Vec<SvgPathElement>) -> SvgPath {
2418 SvgPath {
2419 items: SvgPathElementVec::from_vec(items),
2420 }
2421 }
2422
2423 fn empty_path() -> SvgPath {
2424 SvgPath {
2425 items: SvgPathElementVec::from_const_slice(&[]),
2426 }
2427 }
2428
2429 fn square_at(d: f32) -> SvgPath {
2431 mk_path(vec![
2432 SvgPathElement::Line(ln(d, d, d + 10.0, d)),
2433 SvgPathElement::Line(ln(d + 10.0, d, d + 10.0, d + 10.0)),
2434 SvgPathElement::Line(ln(d + 10.0, d + 10.0, d, d + 10.0)),
2435 SvgPathElement::Line(ln(d, d + 10.0, d, d)),
2436 ])
2437 }
2438
2439 fn square_path() -> SvgPath {
2440 square_at(0.0)
2441 }
2442
2443 fn polygon_of(rings: Vec<SvgPath>) -> SvgMultiPolygon {
2444 SvgMultiPolygon {
2445 rings: SvgPathVec::from_vec(rings),
2446 }
2447 }
2448
2449 fn square_polygon() -> SvgMultiPolygon {
2450 polygon_of(vec![square_path()])
2451 }
2452
2453 fn empty_polygon() -> SvgMultiPolygon {
2454 SvgMultiPolygon {
2455 rings: SvgPathVec::from_const_slice(&[]),
2456 }
2457 }
2458
2459 fn rect(x: f32, y: f32, w: f32, h: f32) -> SvgRect {
2460 SvgRect {
2461 width: w,
2462 height: h,
2463 x,
2464 y,
2465 ..SvgRect::default()
2466 }
2467 }
2468
2469 const fn identity_transform() -> SvgTransform {
2473 SvgTransform {
2474 sx: 1.0,
2475 kx: 0.0,
2476 ky: 0.0,
2477 sy: 1.0,
2478 tx: 0.0,
2479 ty: 0.0,
2480 }
2481 }
2482
2483 fn tess(vertices: &[(f32, f32)], indices: &[u32]) -> TessellatedSvgNode {
2484 TessellatedSvgNode {
2485 vertices: vertices
2486 .iter()
2487 .map(|&(x, y)| SvgVertex { x, y })
2488 .collect::<Vec<_>>()
2489 .into(),
2490 indices: indices.to_vec().into(),
2491 }
2492 }
2493
2494 fn mask_image(w: usize, h: usize) -> RawImage {
2495 RawImage {
2496 pixels: RawImageData::U8(vec![0u8; w * h].into()),
2497 width: w,
2498 height: h,
2499 premultiplied_alpha: false,
2500 data_format: RawImageFormat::R8,
2501 tag: Vec::new().into(),
2502 }
2503 }
2504
2505 fn mask_bytes(img: &RawImage) -> Vec<u8> {
2506 match &img.pixels {
2507 RawImageData::U8(v) => v.as_ref().to_vec(),
2508 _ => panic!("a clip mask must always come back as 8-bit data"),
2509 }
2510 }
2511
2512 fn assert_indices_in_range(t: &TessellatedSvgNode) {
2514 let verts = t.vertices.as_ref().len();
2515 for i in t.indices.as_ref() {
2516 if *i == GL_RESTART_INDEX {
2517 continue;
2518 }
2519 assert!(
2520 (*i as usize) < verts,
2521 "index {i} points past a {verts}-vertex buffer"
2522 );
2523 }
2524 }
2525
2526 const MINIMAL_SVG: &[u8] =
2527 br#"<svg viewBox="0 0 8 8"><rect x="0" y="0" width="8" height="8" fill="red"/></svg>"#;
2528
2529 #[test]
2534 fn translate_svg_line_join_maps_every_variant() {
2535 use lyon::tessellation::LineJoin as L;
2536 assert_eq!(translate_svg_line_join(SvgLineJoin::Miter), L::Miter);
2537 assert_eq!(translate_svg_line_join(SvgLineJoin::MiterClip), L::MiterClip);
2538 assert_eq!(translate_svg_line_join(SvgLineJoin::Round), L::Round);
2539 assert_eq!(translate_svg_line_join(SvgLineJoin::Bevel), L::Bevel);
2540 assert_eq!(translate_svg_line_join(SvgLineJoin::default()), L::Miter);
2541 }
2542
2543 #[test]
2544 fn translate_svg_line_cap_maps_every_variant() {
2545 use lyon::tessellation::LineCap as C;
2546 assert_eq!(translate_svg_line_cap(SvgLineCap::Butt), C::Butt);
2547 assert_eq!(translate_svg_line_cap(SvgLineCap::Square), C::Square);
2548 assert_eq!(translate_svg_line_cap(SvgLineCap::Round), C::Round);
2549 assert_eq!(translate_svg_line_cap(SvgLineCap::default()), C::Butt);
2550 }
2551
2552 #[test]
2557 fn translate_svg_stroke_style_carries_every_field_across() {
2558 let s = SvgStrokeStyle {
2559 start_cap: SvgLineCap::Round,
2560 end_cap: SvgLineCap::Square,
2561 line_join: SvgLineJoin::Bevel,
2562 line_width: 3.5,
2563 miter_limit: 7.25,
2564 tolerance: 0.25,
2565 ..SvgStrokeStyle::default()
2566 };
2567 let o = translate_svg_stroke_style(s);
2568 assert_eq!(o.start_cap, lyon::tessellation::LineCap::Round);
2569 assert_eq!(o.end_cap, lyon::tessellation::LineCap::Square);
2570 assert_eq!(o.line_join, lyon::tessellation::LineJoin::Bevel);
2571 assert!((o.line_width - 3.5).abs() < 1e-6, "{o:?}");
2572 assert!((o.miter_limit - 7.25).abs() < 1e-6, "{o:?}");
2573 assert!((o.tolerance - 0.25).abs() < 1e-6, "{o:?}");
2574 }
2575
2576 #[test]
2577 fn translate_svg_stroke_style_accepts_the_azul_default() {
2578 let o = translate_svg_stroke_style(SvgStrokeStyle::default());
2579 assert!(o.miter_limit >= 1.0, "the default must clear lyon's assert");
2580 }
2581
2582 #[test]
2587 #[should_panic(expected = "limit")]
2588 fn translate_svg_stroke_style_miter_limit_below_one_panics() {
2589 let s = SvgStrokeStyle {
2590 miter_limit: 0.0,
2591 ..SvgStrokeStyle::default()
2592 };
2593 let _ = translate_svg_stroke_style(s);
2594 }
2595
2596 #[test]
2597 #[should_panic(expected = "limit")]
2598 fn translate_svg_stroke_style_nan_miter_limit_panics() {
2599 let s = SvgStrokeStyle {
2600 miter_limit: f32::NAN,
2601 ..SvgStrokeStyle::default()
2602 };
2603 let _ = translate_svg_stroke_style(s);
2604 }
2605
2606 #[test]
2611 fn raw_line_intersection_crossing_diagonals_meet_in_the_middle() {
2612 let i = raw_line_intersection(&ln(0.0, 0.0, 10.0, 10.0), &ln(0.0, 10.0, 10.0, 0.0))
2613 .expect("crossing diagonals intersect");
2614 assert!((i.x - 5.0).abs() < 1e-4, "{i:?}");
2615 assert!((i.y - 5.0).abs() < 1e-4, "{i:?}");
2616 }
2617
2618 #[test]
2619 fn raw_line_intersection_parallel_lines_are_none() {
2620 assert_eq!(
2621 raw_line_intersection(&ln(0.0, 0.0, 10.0, 0.0), &ln(0.0, 5.0, 10.0, 5.0)),
2622 None
2623 );
2624 }
2625
2626 #[test]
2627 fn raw_line_intersection_identical_lines_are_none() {
2628 let l = ln(0.0, 0.0, 10.0, 0.0);
2630 assert_eq!(raw_line_intersection(&l, &l), None);
2631 }
2632
2633 #[test]
2634 fn raw_line_intersection_zero_length_lines_are_none() {
2635 assert_eq!(
2636 raw_line_intersection(&ln(1.0, 1.0, 1.0, 1.0), &ln(0.0, 0.0, 2.0, 2.0)),
2637 None
2638 );
2639 assert_eq!(
2640 raw_line_intersection(&ln(0.0, 0.0, 0.0, 0.0), &ln(0.0, 0.0, 0.0, 0.0)),
2641 None
2642 );
2643 }
2644
2645 #[test]
2646 fn raw_line_intersection_never_leaks_nan_for_extreme_inputs() {
2647 let extremes = [
2648 ln(f32::NAN, 0.0, 1.0, 1.0),
2649 ln(0.0, f32::NAN, 1.0, 1.0),
2650 ln(f32::INFINITY, f32::INFINITY, 1.0, 1.0),
2651 ln(f32::NEG_INFINITY, 0.0, f32::INFINITY, 0.0),
2652 ln(f32::MAX, f32::MAX, f32::MIN, f32::MIN),
2653 ln(f32::MIN_POSITIVE, 0.0, -f32::MIN_POSITIVE, 0.0),
2654 ln(0.0, 0.0, 0.0, 0.0),
2655 ln(-1.0, -1.0, 1.0, 1.0),
2656 ];
2657 for p in &extremes {
2658 for q in &extremes {
2659 if let Some(i) = raw_line_intersection(p, q) {
2660 assert!(
2661 !i.x.is_nan() && !i.y.is_nan(),
2662 "NaN escaped for {p:?} x {q:?} -> {i:?}"
2663 );
2664 }
2665 }
2666 }
2667 }
2668
2669 #[test]
2670 fn raw_line_intersection_byval_matches_the_by_ref_form() {
2671 let p = ln(0.0, 0.0, 10.0, 10.0);
2672 let q = ln(0.0, 10.0, 10.0, 0.0);
2673 assert_eq!(
2674 raw_line_intersection_byval(&p, q),
2675 raw_line_intersection(&p, &q)
2676 );
2677 let horizontal = ln(0.0, 0.0, 10.0, 0.0);
2678 let parallel = ln(0.0, 5.0, 10.0, 5.0);
2679 assert_eq!(raw_line_intersection_byval(&horizontal, parallel), None);
2680 }
2681
2682 #[test]
2687 fn shorten_line_end_by_trims_the_end_only() {
2688 let out = shorten_line_end_by(ln(0.0, 0.0, 10.0, 0.0), 4.0);
2689 assert_eq!(out.start, pt(0.0, 0.0));
2690 assert!((out.end.x - 6.0).abs() < 1e-3, "{out:?}");
2691 assert!(out.end.y.abs() < 1e-3, "{out:?}");
2692 }
2693
2694 #[test]
2695 fn shorten_line_end_by_zero_is_the_identity() {
2696 let l = ln(1.0, 2.0, 11.0, 2.0);
2697 let out = shorten_line_end_by(l, 0.0);
2698 assert!((out.end.x - 11.0).abs() < 1e-3, "{out:?}");
2699 assert!((out.end.y - 2.0).abs() < 1e-3, "{out:?}");
2700 }
2701
2702 #[test]
2703 fn shorten_line_end_by_more_than_the_length_overshoots_past_the_start() {
2704 let out = shorten_line_end_by(ln(0.0, 0.0, 10.0, 0.0), 20.0);
2706 assert!((out.end.x + 10.0).abs() < 1e-3, "{out:?}");
2707 }
2708
2709 #[test]
2710 fn shorten_line_end_by_negative_distance_extends_the_line() {
2711 let out = shorten_line_end_by(ln(0.0, 0.0, 10.0, 0.0), -5.0);
2712 assert!((out.end.x - 15.0).abs() < 1e-3, "{out:?}");
2713 }
2714
2715 #[test]
2716 fn shorten_line_end_by_degenerate_segment_yields_nan() {
2717 let out = shorten_line_end_by(ln(5.0, 5.0, 5.0, 5.0), 1.0);
2721 assert!(out.end.x.is_nan() && out.end.y.is_nan(), "{out:?}");
2722 assert_eq!(out.start, pt(5.0, 5.0), "the start is never touched");
2723 }
2724
2725 #[test]
2726 fn shorten_line_end_by_non_finite_distance_does_not_panic() {
2727 for d in [
2728 f32::NAN,
2729 f32::INFINITY,
2730 f32::NEG_INFINITY,
2731 f32::MAX,
2732 f32::MIN,
2733 ] {
2734 let out = shorten_line_end_by(ln(0.0, 0.0, 10.0, 0.0), d);
2735 assert_eq!(out.start, pt(0.0, 0.0), "distance {d}: start must survive");
2736 }
2737 }
2738
2739 #[test]
2740 fn shorten_line_start_by_trims_the_start_only() {
2741 let out = shorten_line_start_by(ln(0.0, 0.0, 10.0, 0.0), 4.0);
2742 assert_eq!(out.end, pt(10.0, 0.0));
2743 assert!((out.start.x - 4.0).abs() < 1e-3, "{out:?}");
2744 assert!(out.start.y.abs() < 1e-3, "{out:?}");
2745 }
2746
2747 #[test]
2748 fn shorten_line_start_by_zero_is_the_identity() {
2749 let out = shorten_line_start_by(ln(2.0, 3.0, 12.0, 3.0), 0.0);
2750 assert!((out.start.x - 2.0).abs() < 1e-3, "{out:?}");
2751 assert!((out.start.y - 3.0).abs() < 1e-3, "{out:?}");
2752 }
2753
2754 #[test]
2755 fn shorten_line_start_by_degenerate_segment_yields_nan() {
2756 let out = shorten_line_start_by(ln(-3.0, 7.0, -3.0, 7.0), 2.0);
2758 assert!(out.start.x.is_nan() && out.start.y.is_nan(), "{out:?}");
2759 assert_eq!(out.end, pt(-3.0, 7.0));
2760 }
2761
2762 #[test]
2767 fn svg_path_offset_zero_distance_returns_the_input_unchanged() {
2768 let p = square_path();
2769 assert_eq!(
2770 svg_path_offset(&p, 0.0, SvgLineJoin::Miter, SvgLineCap::Butt),
2771 p
2772 );
2773 assert_eq!(
2775 svg_path_offset(&p, -0.0, SvgLineJoin::Miter, SvgLineCap::Butt),
2776 p
2777 );
2778 }
2779
2780 #[test]
2781 fn svg_path_offset_empty_path_stays_empty() {
2782 let out = svg_path_offset(&empty_path(), 5.0, SvgLineJoin::Round, SvgLineCap::Round);
2784 assert!(out.items.as_ref().is_empty());
2785 }
2786
2787 #[test]
2788 fn svg_path_offset_single_line_moves_along_its_outwards_normal() {
2789 let p = mk_path(vec![SvgPathElement::Line(ln(0.0, 0.0, 10.0, 0.0))]);
2791 let out = svg_path_offset(&p, 5.0, SvgLineJoin::Miter, SvgLineCap::Butt);
2792 assert_eq!(out.items.as_ref().len(), 1);
2793 match out.items.as_ref()[0] {
2794 SvgPathElement::Line(l) => {
2795 assert!(l.start.x.abs() < 1e-4, "{l:?}");
2796 assert!((l.end.x - 10.0).abs() < 1e-4, "{l:?}");
2797 assert!((l.start.y + 5.0).abs() < 1e-4, "{l:?}");
2798 assert!((l.end.y + 5.0).abs() < 1e-4, "{l:?}");
2799 }
2800 other => panic!("expected a line, got {other:?}"),
2801 }
2802 }
2803
2804 #[test]
2805 fn svg_path_offset_preserves_the_item_count_for_any_distance() {
2806 let p = square_path();
2807 for d in [
2808 1.0_f32,
2809 -1.0,
2810 1e-30,
2811 1e30,
2812 f32::MAX,
2813 f32::MIN,
2814 f32::NAN,
2815 f32::INFINITY,
2816 f32::NEG_INFINITY,
2817 ] {
2818 let out = svg_path_offset(&p, d, SvgLineJoin::Miter, SvgLineCap::Butt);
2819 assert_eq!(
2820 out.items.as_ref().len(),
2821 p.items.as_ref().len(),
2822 "distance {d} changed the element count"
2823 );
2824 }
2825 }
2826
2827 #[test]
2828 fn svg_path_offset_degenerate_segments_are_passed_through_unchanged() {
2829 let p = mk_path(vec![SvgPathElement::Line(ln(4.0, 4.0, 4.0, 4.0))]);
2831 assert_eq!(
2832 svg_path_offset(&p, 9.0, SvgLineJoin::Bevel, SvgLineCap::Square),
2833 p
2834 );
2835 }
2836
2837 #[test]
2838 fn svg_path_offset_ignores_its_join_and_cap_arguments() {
2839 let p = square_path();
2842 let a = svg_path_offset(&p, 3.0, SvgLineJoin::Miter, SvgLineCap::Butt);
2843 let b = svg_path_offset(&p, 3.0, SvgLineJoin::Round, SvgLineCap::Round);
2844 assert_eq!(a, b);
2845 }
2846
2847 #[test]
2848 fn svg_path_offset_handles_curve_elements() {
2849 let p = mk_path(vec![
2850 SvgPathElement::QuadraticCurve(SvgQuadraticCurve {
2851 start: pt(0.0, 0.0),
2852 ctrl: pt(5.0, 10.0),
2853 end: pt(10.0, 0.0),
2854 }),
2855 SvgPathElement::CubicCurve(SvgCubicCurve {
2856 start: pt(10.0, 0.0),
2857 ctrl_1: pt(7.0, -5.0),
2858 ctrl_2: pt(3.0, -5.0),
2859 end: pt(0.0, 0.0),
2860 }),
2861 ]);
2862 let out = svg_path_offset(&p, 2.0, SvgLineJoin::Miter, SvgLineCap::Butt);
2863 assert_eq!(out.items.as_ref().len(), 2);
2864 }
2865
2866 #[test]
2871 fn svg_path_bevel_empty_path_stays_empty() {
2872 assert!(svg_path_bevel(&empty_path(), 2.0).items.as_ref().is_empty());
2874 }
2875
2876 #[test]
2877 fn svg_path_bevel_single_line_expands_to_four_elements() {
2878 let p = mk_path(vec![SvgPathElement::Line(ln(0.0, 0.0, 10.0, 0.0))]);
2881 let out = svg_path_bevel(&p, 2.0);
2882 assert_eq!(out.items.as_ref().len(), 4);
2883 for e in out.items.as_ref() {
2884 let (s, t) = (e.get_start(), e.get_end());
2885 assert!(
2886 s.x.is_finite() && s.y.is_finite() && t.x.is_finite() && t.y.is_finite(),
2887 "{e:?}"
2888 );
2889 }
2890 }
2891
2892 #[test]
2893 fn svg_path_bevel_non_line_pairs_are_passed_straight_through() {
2894 let p = mk_path(vec![
2895 SvgPathElement::Line(ln(0.0, 0.0, 10.0, 0.0)),
2896 SvgPathElement::CubicCurve(SvgCubicCurve {
2897 start: pt(10.0, 0.0),
2898 ctrl_1: pt(12.0, 0.0),
2899 ctrl_2: pt(14.0, 2.0),
2900 end: pt(14.0, 4.0),
2901 }),
2902 ]);
2903 assert_eq!(svg_path_bevel(&p, 1.0).items.as_ref().len(), 4);
2906 }
2907
2908 #[test]
2909 fn svg_path_bevel_zero_distance_keeps_every_coordinate_finite() {
2910 for e in svg_path_bevel(&square_path(), 0.0).items.as_ref() {
2911 let (s, t) = (e.get_start(), e.get_end());
2912 assert!(
2913 s.x.is_finite() && s.y.is_finite() && t.x.is_finite() && t.y.is_finite(),
2914 "{e:?}"
2915 );
2916 }
2917 }
2918
2919 #[test]
2920 fn svg_path_bevel_extreme_distances_do_not_panic() {
2921 let p = square_path();
2922 for d in [
2923 f32::NAN,
2924 f32::INFINITY,
2925 f32::NEG_INFINITY,
2926 f32::MAX,
2927 -f32::MAX,
2928 1e-30,
2929 ] {
2930 assert!(
2931 !svg_path_bevel(&p, d).items.as_ref().is_empty(),
2932 "distance {d} produced an empty path"
2933 );
2934 }
2935 }
2936
2937 #[test]
2938 fn svg_path_bevel_degenerate_segments_do_not_panic() {
2939 let p = mk_path(vec![
2940 SvgPathElement::Line(ln(1.0, 1.0, 1.0, 1.0)),
2941 SvgPathElement::Line(ln(1.0, 1.0, 1.0, 1.0)),
2942 ]);
2943 assert!(!svg_path_bevel(&p, 3.0).items.as_ref().is_empty());
2945 }
2946
2947 #[test]
2952 fn svg_node_contains_point_rect_excludes_its_own_border() {
2953 let node = SvgNode::Rect(rect(0.0, 0.0, 10.0, 10.0));
2954 assert!(svg_node_contains_point(
2955 &node,
2956 pt(5.0, 5.0),
2957 SvgFillRule::Winding,
2958 0.1
2959 ));
2960 assert!(!svg_node_contains_point(
2961 &node,
2962 pt(15.0, 5.0),
2963 SvgFillRule::Winding,
2964 0.1
2965 ));
2966 assert!(!svg_node_contains_point(
2968 &node,
2969 pt(0.0, 0.0),
2970 SvgFillRule::Winding,
2971 0.1
2972 ));
2973 assert!(!svg_node_contains_point(
2974 &node,
2975 pt(10.0, 10.0),
2976 SvgFillRule::Winding,
2977 0.1
2978 ));
2979 }
2980
2981 #[test]
2982 fn svg_node_contains_point_circle_excludes_its_own_rim() {
2983 let node = SvgNode::Circle(SvgCircle {
2984 center_x: 0.0,
2985 center_y: 0.0,
2986 radius: 5.0,
2987 });
2988 assert!(svg_node_contains_point(
2989 &node,
2990 pt(0.0, 0.0),
2991 SvgFillRule::Winding,
2992 0.1
2993 ));
2994 assert!(!svg_node_contains_point(
2995 &node,
2996 pt(5.0, 0.0),
2997 SvgFillRule::Winding,
2998 0.1
2999 ));
3000 assert!(!svg_node_contains_point(
3001 &node,
3002 pt(100.0, 100.0),
3003 SvgFillRule::Winding,
3004 0.1
3005 ));
3006 }
3007
3008 #[test]
3009 fn svg_node_contains_point_nan_and_infinite_points_are_outside() {
3010 let r = SvgNode::Rect(rect(0.0, 0.0, 10.0, 10.0));
3011 let c = SvgNode::Circle(SvgCircle {
3012 center_x: 0.0,
3013 center_y: 0.0,
3014 radius: 5.0,
3015 });
3016 for p in [
3017 pt(f32::NAN, f32::NAN),
3018 pt(f32::NAN, 5.0),
3019 pt(5.0, f32::NAN),
3020 pt(f32::INFINITY, f32::INFINITY),
3021 pt(f32::NEG_INFINITY, 0.0),
3022 ] {
3023 assert!(
3024 !svg_node_contains_point(&r, p, SvgFillRule::Winding, 0.1),
3025 "rect / {p:?}"
3026 );
3027 assert!(
3028 !svg_node_contains_point(&c, p, SvgFillRule::EvenOdd, 0.1),
3029 "circle / {p:?}"
3030 );
3031 }
3032 }
3033
3034 #[test]
3035 fn svg_node_contains_point_empty_geometry_is_never_hit() {
3036 for node in [
3037 SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_const_slice(&[])),
3038 SvgNode::MultiShape(SvgSimpleNodeVec::from_const_slice(&[])),
3039 SvgNode::MultiPolygon(empty_polygon()),
3040 SvgNode::Path(empty_path()),
3041 ] {
3042 assert!(
3043 !svg_node_contains_point(&node, pt(0.0, 0.0), SvgFillRule::Winding, 0.1),
3044 "{node:?}"
3045 );
3046 }
3047 }
3048
3049 #[test]
3050 fn svg_node_contains_point_open_paths_short_circuit_to_false() {
3051 let open = mk_path(vec![
3052 SvgPathElement::Line(ln(0.0, 0.0, 10.0, 0.0)),
3053 SvgPathElement::Line(ln(10.0, 0.0, 10.0, 10.0)),
3054 ]);
3055 assert!(!svg_node_contains_point(
3056 &SvgNode::Path(open.clone()),
3057 pt(5.0, 5.0),
3058 SvgFillRule::Winding,
3059 0.1
3060 ));
3061 assert!(!svg_node_contains_point(
3062 &SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(vec![SvgSimpleNode::Path(open)])),
3063 pt(5.0, 5.0),
3064 SvgFillRule::Winding,
3065 0.1
3066 ));
3067 }
3068
3069 #[test]
3070 fn svg_node_contains_point_closed_square_path_is_hit() {
3071 let node = SvgNode::Path(square_path());
3072 assert!(svg_node_contains_point(
3073 &node,
3074 pt(5.0, 5.0),
3075 SvgFillRule::Winding,
3076 0.1
3077 ));
3078 assert!(!svg_node_contains_point(
3079 &node,
3080 pt(50.0, 5.0),
3081 SvgFillRule::Winding,
3082 0.1
3083 ));
3084 }
3085
3086 #[test]
3087 fn svg_node_contains_point_lone_hole_reports_everything_outside_it() {
3088 let hole = SvgCircle {
3091 center_x: 0.0,
3092 center_y: 0.0,
3093 radius: 5.0,
3094 };
3095 let node = SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(vec![
3096 SvgSimpleNode::CircleHole(hole),
3097 ]));
3098 assert!(!svg_node_contains_point(
3099 &node,
3100 pt(0.0, 0.0),
3101 SvgFillRule::Winding,
3102 0.1
3103 ));
3104 assert!(svg_node_contains_point(
3105 &node,
3106 pt(100.0, 100.0),
3107 SvgFillRule::Winding,
3108 0.1
3109 ));
3110 }
3111
3112 #[test]
3113 fn path_contains_point_square_positive_and_negative_controls() {
3114 let sq = square_path();
3115 assert!(path_contains_point(
3116 &sq,
3117 pt(5.0, 5.0),
3118 SvgFillRule::Winding,
3119 0.1
3120 ));
3121 assert!(path_contains_point(
3122 &sq,
3123 pt(5.0, 5.0),
3124 SvgFillRule::EvenOdd,
3125 0.1
3126 ));
3127 assert!(!path_contains_point(
3128 &sq,
3129 pt(-1.0, 5.0),
3130 SvgFillRule::Winding,
3131 0.1
3132 ));
3133 assert!(!path_contains_point(
3134 &sq,
3135 pt(5.0, 100.0),
3136 SvgFillRule::EvenOdd,
3137 0.1
3138 ));
3139 }
3140
3141 #[test]
3142 fn path_contains_point_empty_path_is_never_hit() {
3143 assert!(!path_contains_point(
3144 &empty_path(),
3145 pt(0.0, 0.0),
3146 SvgFillRule::Winding,
3147 0.1
3148 ));
3149 }
3150
3151 #[test]
3152 fn path_contains_point_tolerance_is_irrelevant_for_straight_edges() {
3153 let sq = square_path();
3156 for t in [0.0_f32, 1e-6, 1.0, 1e6, -1.0] {
3157 assert!(
3158 path_contains_point(&sq, pt(5.0, 5.0), SvgFillRule::Winding, t),
3159 "tolerance {t}"
3160 );
3161 assert!(
3162 !path_contains_point(&sq, pt(-50.0, 5.0), SvgFillRule::Winding, t),
3163 "tolerance {t}"
3164 );
3165 }
3166 }
3167
3168 #[test]
3169 fn polygon_contains_point_square_ring() {
3170 let poly = square_polygon();
3171 assert!(polygon_contains_point(
3172 &poly,
3173 pt(5.0, 5.0),
3174 SvgFillRule::Winding,
3175 0.1
3176 ));
3177 assert!(!polygon_contains_point(
3178 &poly,
3179 pt(-5.0, 5.0),
3180 SvgFillRule::Winding,
3181 0.1
3182 ));
3183 }
3184
3185 #[test]
3186 fn polygon_contains_point_without_rings_is_false() {
3187 assert!(!polygon_contains_point(
3188 &empty_polygon(),
3189 pt(0.0, 0.0),
3190 SvgFillRule::Winding,
3191 0.1
3192 ));
3193 assert!(!polygon_contains_point(
3195 &polygon_of(vec![empty_path()]),
3196 pt(0.0, 0.0),
3197 SvgFillRule::Winding,
3198 0.1
3199 ));
3200 }
3201
3202 #[test]
3207 fn svg_multipolygon_to_lyon_path_of_an_empty_polygon_is_empty() {
3208 assert_eq!(svg_multipolygon_to_lyon_path(&empty_polygon()).iter().count(), 0);
3209 }
3210
3211 #[test]
3212 fn svg_multipolygon_to_lyon_path_skips_rings_without_items() {
3213 let only_empty = polygon_of(vec![empty_path(), empty_path()]);
3214 assert_eq!(svg_multipolygon_to_lyon_path(&only_empty).iter().count(), 0);
3215 let mixed = polygon_of(vec![empty_path(), square_path()]);
3217 assert_eq!(
3218 svg_multipolygon_to_lyon_path(&mixed).iter().count(),
3219 svg_multipolygon_to_lyon_path(&square_polygon()).iter().count()
3220 );
3221 }
3222
3223 #[test]
3224 fn svg_multi_shape_to_lyon_path_of_an_empty_slice_is_empty() {
3225 assert_eq!(svg_multi_shape_to_lyon_path(&[]).iter().count(), 0);
3226 }
3227
3228 #[test]
3229 fn svg_path_to_lyon_path_events_of_an_empty_path_is_empty() {
3230 assert_eq!(svg_path_to_lyon_path_events(&empty_path()).iter().count(), 0);
3231 }
3232
3233 #[test]
3238 fn vertex_buffers_to_tessellated_cpu_node_moves_both_buffers_verbatim() {
3239 let mut vb: VertexBuffers<SvgVertex, u32> = VertexBuffers::new();
3240 vb.vertices.push(SvgVertex { x: 1.0, y: 2.0 });
3241 vb.vertices.push(SvgVertex { x: 3.0, y: 4.0 });
3242 vb.indices.extend_from_slice(&[0, 1, 0]);
3243 let t = vertex_buffers_to_tessellated_cpu_node(vb);
3244 assert_eq!(t.vertices.as_ref().len(), 2);
3245 assert_eq!(t.indices.as_ref(), &[0u32, 1, 0][..]);
3246 assert!((t.vertices.as_ref()[1].x - 3.0).abs() < 1e-6);
3247 }
3248
3249 #[test]
3250 fn vertex_buffers_to_tessellated_cpu_node_of_empty_buffers_is_empty() {
3251 let t = vertex_buffers_to_tessellated_cpu_node(VertexBuffers::<SvgVertex, u32>::new());
3252 assert!(t.vertices.as_ref().is_empty());
3253 assert!(t.indices.as_ref().is_empty());
3254 }
3255
3256 #[test]
3261 fn tessellate_path_fill_of_a_square_produces_whole_triangles() {
3262 let t = tessellate_path_fill(&square_path(), SvgFillStyle::default());
3263 assert!(!t.vertices.as_ref().is_empty());
3264 assert_eq!(t.indices.as_ref().len() % 3, 0);
3265 assert_indices_in_range(&t);
3266 }
3267
3268 #[test]
3269 fn tessellate_path_fill_and_stroke_of_an_empty_path_are_empty() {
3270 let f = tessellate_path_fill(&empty_path(), SvgFillStyle::default());
3271 assert!(f.vertices.as_ref().is_empty() && f.indices.as_ref().is_empty());
3272 let s = tessellate_path_stroke(&empty_path(), SvgStrokeStyle::default());
3273 assert!(s.vertices.as_ref().is_empty() && s.indices.as_ref().is_empty());
3274 }
3275
3276 #[test]
3277 fn tessellate_circle_fill_zero_radius_is_empty() {
3278 let t = tessellate_circle_fill(
3279 &SvgCircle {
3280 center_x: 0.0,
3281 center_y: 0.0,
3282 radius: 0.0,
3283 },
3284 SvgFillStyle::default(),
3285 );
3286 assert!(t.vertices.as_ref().is_empty());
3287 }
3288
3289 #[test]
3290 fn tessellate_circle_fill_negative_radius_matches_the_positive_one() {
3291 let mk = |r: f32| {
3292 tessellate_circle_fill(
3293 &SvgCircle {
3294 center_x: 1.0,
3295 center_y: 2.0,
3296 radius: r,
3297 },
3298 SvgFillStyle::default(),
3299 )
3300 };
3301 let positive = mk(5.0);
3302 assert!(!positive.vertices.as_ref().is_empty());
3303 assert_eq!(positive, mk(-5.0), "lyon takes |radius|; the sign must not matter");
3304 }
3305
3306 #[test]
3307 fn tessellate_rect_fill_always_emits_exactly_two_triangles() {
3308 for r in [
3309 rect(0.0, 0.0, 10.0, 10.0),
3310 rect(0.0, 0.0, 0.0, 0.0), rect(5.0, 5.0, -10.0, -10.0), rect(-f32::MAX, -f32::MAX, f32::MAX, f32::MAX), ] {
3314 let t = tessellate_rect_fill(&r, SvgFillStyle::default());
3315 assert_eq!(t.vertices.as_ref().len(), 4, "{r:?}");
3316 assert_eq!(t.indices.as_ref().len(), 6, "{r:?}");
3317 assert_indices_in_range(&t);
3318 }
3319 }
3320
3321 #[test]
3322 fn tessellate_rect_stroke_of_a_real_rect_produces_geometry() {
3323 let t = tessellate_rect_stroke(&rect(0.0, 0.0, 10.0, 10.0), SvgStrokeStyle::default());
3324 assert!(!t.vertices.as_ref().is_empty());
3325 assert_indices_in_range(&t);
3326 }
3327
3328 #[test]
3329 fn get_radii_maps_origin_and_size_onto_a_box() {
3330 let b = get_radii(&rect(1.0, 2.0, 4.0, 6.0));
3331 assert!((b.min.x - 1.0).abs() < 1e-6, "{b:?}");
3332 assert!((b.min.y - 2.0).abs() < 1e-6, "{b:?}");
3333 assert!((b.max.x - 5.0).abs() < 1e-6, "{b:?}");
3334 assert!((b.max.y - 8.0).abs() < 1e-6, "{b:?}");
3335 }
3336
3337 #[test]
3338 fn get_radii_ignores_the_corner_radii() {
3339 let base = rect(1.0, 2.0, 4.0, 6.0);
3341 let rounded = SvgRect {
3342 radius_top_left: 3.0,
3343 radius_top_right: 4.0,
3344 radius_bottom_left: 5.0,
3345 radius_bottom_right: 9.0,
3346 ..base
3347 };
3348 let (a, b) = (get_radii(&base), get_radii(&rounded));
3349 assert!((a.min.x - b.min.x).abs() < 1e-6 && (a.min.y - b.min.y).abs() < 1e-6);
3350 assert!((a.max.x - b.max.x).abs() < 1e-6 && (a.max.y - b.max.y).abs() < 1e-6);
3351 }
3352
3353 #[test]
3354 fn get_radii_of_a_negative_size_rect_produces_an_inverted_box() {
3355 let b = get_radii(&rect(5.0, 5.0, -3.0, -4.0));
3356 assert!(b.max.x < b.min.x, "{b:?}");
3357 assert!(b.max.y < b.min.y, "{b:?}");
3358 }
3359
3360 #[test]
3361 fn tessellate_multi_polygon_fill_of_an_empty_polygon_is_empty() {
3362 let t = tessellate_multi_polygon_fill(&empty_polygon(), SvgFillStyle::default());
3363 assert!(t.vertices.as_ref().is_empty());
3364 }
3365
3366 #[test]
3367 fn tessellate_multi_polygon_fill_skips_empty_rings() {
3368 let with_empty = polygon_of(vec![empty_path(), square_path()]);
3369 assert_eq!(
3370 tessellate_multi_polygon_fill(&with_empty, SvgFillStyle::default()),
3371 tessellate_multi_polygon_fill(&square_polygon(), SvgFillStyle::default()),
3372 );
3373 }
3374
3375 #[test]
3376 fn tessellate_multi_shape_fill_of_an_empty_slice_is_empty() {
3377 let t = tessellate_multi_shape_fill(&[], SvgFillStyle::default());
3378 assert!(t.vertices.as_ref().is_empty());
3379 }
3380
3381 #[test]
3382 fn tessellate_multi_shape_fill_of_a_plain_shape_produces_geometry() {
3383 let ms = [
3384 SvgSimpleNode::Circle(SvgCircle {
3385 center_x: 40.0,
3386 center_y: 40.0,
3387 radius: 5.0,
3388 }),
3389 SvgSimpleNode::Rect(rect(60.0, 0.0, 5.0, 5.0)),
3390 ];
3391 let t = tessellate_multi_shape_fill(&ms, SvgFillStyle::default());
3392 assert!(!t.vertices.as_ref().is_empty());
3393 assert_indices_in_range(&t);
3394 }
3395
3396 #[test]
3397 fn tessellate_multi_shape_fill_handles_every_simple_node_kind() {
3398 let ms = [
3399 SvgSimpleNode::Path(square_path()),
3400 SvgSimpleNode::Circle(SvgCircle {
3401 center_x: 40.0,
3402 center_y: 40.0,
3403 radius: 5.0,
3404 }),
3405 SvgSimpleNode::CircleHole(SvgCircle {
3406 center_x: 40.0,
3407 center_y: 40.0,
3408 radius: 2.0,
3409 }),
3410 SvgSimpleNode::Rect(rect(60.0, 0.0, 5.0, 5.0)),
3411 SvgSimpleNode::RectHole(rect(61.0, 1.0, 2.0, 2.0)),
3412 ];
3413 assert_indices_in_range(&tessellate_multi_shape_fill(&ms, SvgFillStyle::default()));
3414 assert_indices_in_range(&tessellate_multi_shape_stroke(
3415 &ms,
3416 SvgStrokeStyle::default(),
3417 ));
3418 }
3419
3420 #[test]
3421 fn tessellate_multi_polygon_stroke_of_an_empty_polygon_is_empty() {
3422 let t = tessellate_multi_polygon_stroke(&empty_polygon(), SvgStrokeStyle::default());
3423 assert!(t.vertices.as_ref().is_empty());
3424 }
3425
3426 #[test]
3427 fn tessellate_styled_node_dispatches_on_the_style() {
3428 let geo = SvgNode::Rect(rect(0.0, 0.0, 10.0, 10.0));
3429 let fill = SvgStyledNode {
3430 geometry: geo.clone(),
3431 style: SvgStyle::Fill(SvgFillStyle::default()),
3432 };
3433 let stroke = SvgStyledNode {
3434 geometry: geo.clone(),
3435 style: SvgStyle::Stroke(SvgStrokeStyle::default()),
3436 };
3437 assert_eq!(
3438 tessellate_styled_node(&fill),
3439 tessellate_node_fill(&geo, SvgFillStyle::default())
3440 );
3441 assert_eq!(
3442 tessellate_styled_node(&stroke),
3443 tessellate_node_stroke(&geo, SvgStrokeStyle::default())
3444 );
3445 }
3446
3447 #[test]
3448 fn tessellate_node_fill_of_a_collection_is_the_join_of_its_parts() {
3449 let mp = square_polygon();
3450 let node =
3451 SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_vec(vec![mp.clone(), mp.clone()]));
3452 let one = tessellate_multi_polygon_fill(&mp, SvgFillStyle::default());
3453 assert_eq!(
3454 tessellate_node_fill(&node, SvgFillStyle::default()),
3455 join_tessellated_nodes(&[one.clone(), one])
3456 );
3457 }
3458
3459 #[test]
3460 fn tessellate_node_fill_of_an_empty_collection_is_empty() {
3461 let node = SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_const_slice(&[]));
3462 let t = tessellate_node_fill(&node, SvgFillStyle::default());
3463 assert!(t.vertices.as_ref().is_empty() && t.indices.as_ref().is_empty());
3464 }
3465
3466 #[test]
3467 fn tessellate_svgpathelement_stroke_matches_the_per_kind_helpers() {
3468 let ss = SvgStrokeStyle::default();
3469 let l = ln(0.0, 0.0, 10.0, 10.0);
3470 assert_eq!(
3471 tessellate_svgpathelement_stroke(&SvgPathElement::Line(l), ss),
3472 tessellate_line_stroke(&l, ss)
3473 );
3474 let q = SvgQuadraticCurve {
3475 start: pt(0.0, 0.0),
3476 ctrl: pt(5.0, 10.0),
3477 end: pt(10.0, 0.0),
3478 };
3479 assert_eq!(
3480 tessellate_svgpathelement_stroke(&SvgPathElement::QuadraticCurve(q), ss),
3481 tessellate_quadraticcurve_stroke(&q, ss)
3482 );
3483 let c = SvgCubicCurve {
3484 start: pt(0.0, 0.0),
3485 ctrl_1: pt(3.0, 10.0),
3486 ctrl_2: pt(7.0, -10.0),
3487 end: pt(10.0, 0.0),
3488 };
3489 assert_eq!(
3490 tessellate_svgpathelement_stroke(&SvgPathElement::CubicCurve(c), ss),
3491 tessellate_cubiccurve_stroke(&c, ss)
3492 );
3493 }
3494
3495 #[test]
3496 fn tessellate_line_stroke_of_a_zero_length_line_does_not_panic() {
3497 let t = tessellate_line_stroke(&ln(3.0, 3.0, 3.0, 3.0), SvgStrokeStyle::default());
3498 assert_indices_in_range(&t);
3499 }
3500
3501 #[test]
3506 fn join_tessellated_nodes_of_nothing_is_empty() {
3507 let t = join_tessellated_nodes(&[]);
3508 assert!(t.vertices.as_ref().is_empty());
3509 assert!(t.indices.as_ref().is_empty());
3510 }
3511
3512 #[test]
3513 fn join_tessellated_nodes_offsets_and_terminates_each_buffer() {
3514 let a = tess(&[(0.0, 0.0), (1.0, 0.0)], &[0, 1]);
3515 let b = tess(&[(2.0, 0.0), (3.0, 0.0)], &[0, 1]);
3516 let j = join_tessellated_nodes(&[a, b]);
3517 assert_eq!(j.vertices.as_ref().len(), 4);
3518 assert_eq!(
3519 j.indices.as_ref(),
3520 &[0u32, 1, GL_RESTART_INDEX, 2, 3, GL_RESTART_INDEX][..]
3521 );
3522 assert_indices_in_range(&j);
3523 }
3524
3525 #[test]
3526 fn join_tessellated_nodes_leaves_existing_restart_markers_unshifted() {
3527 let a = tess(&[(0.0, 0.0)], &[0]);
3528 let b = tess(&[(1.0, 0.0), (2.0, 0.0)], &[0, GL_RESTART_INDEX, 1]);
3529 let j = join_tessellated_nodes(&[a, b]);
3530 assert_eq!(
3531 j.indices.as_ref(),
3532 &[
3533 0u32,
3534 GL_RESTART_INDEX,
3535 1,
3536 GL_RESTART_INDEX,
3537 2,
3538 GL_RESTART_INDEX
3539 ][..]
3540 );
3541 }
3542
3543 #[test]
3544 fn join_tessellated_nodes_single_node_is_only_terminated() {
3545 let j = join_tessellated_nodes(&[tess(&[(0.0, 0.0), (1.0, 1.0)], &[0, 1, 0])]);
3546 assert_eq!(j.indices.as_ref(), &[0u32, 1, 0, GL_RESTART_INDEX][..]);
3547 }
3548
3549 #[test]
3550 fn join_tessellated_nodes_can_alias_an_index_onto_the_restart_marker() {
3551 let a = tess(&[(0.0, 0.0)], &[0]);
3557 let b = tess(&[(1.0, 0.0)], &[GL_RESTART_INDEX - 1]);
3558 let j = join_tessellated_nodes(&[a, b]);
3559 assert_eq!(
3560 j.indices.as_ref(),
3561 &[
3562 0u32,
3563 GL_RESTART_INDEX,
3564 GL_RESTART_INDEX,
3565 GL_RESTART_INDEX
3566 ][..]
3567 );
3568 }
3569
3570 #[test]
3571 fn join_tessellated_colored_nodes_of_nothing_is_empty() {
3572 let t = join_tessellated_colored_nodes(&[]);
3573 assert!(t.vertices.as_ref().is_empty());
3574 assert!(t.indices.as_ref().is_empty());
3575 }
3576
3577 #[test]
3578 fn join_tessellated_colored_nodes_offsets_like_the_plain_variant() {
3579 fn colored(xs: &[f32], idx: &[u32]) -> TessellatedColoredSvgNode {
3580 TessellatedColoredSvgNode {
3581 vertices: xs
3582 .iter()
3583 .map(|&x| SvgColoredVertex {
3584 x,
3585 y: 0.0,
3586 z: 0.0,
3587 r: 1.0,
3588 g: 0.0,
3589 b: 0.0,
3590 a: 1.0,
3591 })
3592 .collect::<Vec<_>>()
3593 .into(),
3594 indices: idx.to_vec().into(),
3595 }
3596 }
3597 let j = join_tessellated_colored_nodes(&[
3598 colored(&[0.0, 1.0], &[0, 1]),
3599 colored(&[2.0], &[0]),
3600 ]);
3601 assert_eq!(j.vertices.as_ref().len(), 3);
3602 assert_eq!(
3603 j.indices.as_ref(),
3604 &[0u32, 1, GL_RESTART_INDEX, 2, GL_RESTART_INDEX][..]
3605 );
3606 }
3607
3608 #[test]
3613 fn storage_to_multi_polygon_of_an_untouched_storage_is_empty() {
3614 let mut storage = agg_rust::scanline_storage_aa::ScanlineStorageAa::new();
3615 assert!(storage_to_multi_polygon(&mut storage).rings.as_ref().is_empty());
3616 }
3617
3618 #[test]
3619 fn svg_multi_polygon_boolean_ops_on_two_empties_are_empty() {
3620 let e = empty_polygon();
3621 assert!(svg_multi_polygon_union(&e, &e).rings.as_ref().is_empty());
3622 assert!(svg_multi_polygon_intersection(&e, &e).rings.as_ref().is_empty());
3623 assert!(svg_multi_polygon_difference(&e, &e).rings.as_ref().is_empty());
3624 assert!(svg_multi_polygon_xor(&e, &e).rings.as_ref().is_empty());
3625 }
3626
3627 #[test]
3628 fn svg_multi_polygon_union_of_a_square_with_itself_keeps_the_square() {
3629 let sq = square_polygon();
3630 let out = svg_multi_polygon_union(&sq, &sq);
3631 assert!(
3632 !out.rings.as_ref().is_empty(),
3633 "a shape unioned with itself must not vanish"
3634 );
3635 let b = out.get_bounds();
3636 assert!(b.width > 0.0 && b.height > 0.0, "{b:?}");
3637 }
3638
3639 #[test]
3640 fn svg_multi_polygon_intersection_of_disjoint_squares_is_empty() {
3641 let a = square_polygon();
3642 let b = polygon_of(vec![square_at(100.0)]);
3643 assert!(svg_multi_polygon_intersection(&a, &b)
3644 .rings
3645 .as_ref()
3646 .is_empty());
3647 }
3648
3649 #[test]
3650 fn svg_multi_polygon_self_cancelling_ops_are_empty() {
3651 let sq = square_polygon();
3652 assert!(
3653 svg_multi_polygon_difference(&sq, &sq).rings.as_ref().is_empty(),
3654 "A minus A must be empty"
3655 );
3656 assert!(
3657 svg_multi_polygon_xor(&sq, &sq).rings.as_ref().is_empty(),
3658 "A xor A must be empty"
3659 );
3660 }
3661
3662 #[test]
3663 fn svg_multi_polygon_ops_accept_curve_rings() {
3664 let ring = mk_path(vec![
3665 SvgPathElement::QuadraticCurve(SvgQuadraticCurve {
3666 start: pt(0.0, 0.0),
3667 ctrl: pt(5.0, 12.0),
3668 end: pt(10.0, 0.0),
3669 }),
3670 SvgPathElement::CubicCurve(SvgCubicCurve {
3671 start: pt(10.0, 0.0),
3672 ctrl_1: pt(7.0, -6.0),
3673 ctrl_2: pt(3.0, -6.0),
3674 end: pt(0.0, 0.0),
3675 }),
3676 ]);
3677 let mp = polygon_of(vec![ring]);
3678 assert!(!svg_multi_polygon_union(&mp, &mp).rings.as_ref().is_empty());
3679 }
3680
3681 #[test]
3682 fn svg_multi_polygon_byval_wrappers_match_the_by_ref_forms() {
3683 let a = square_polygon();
3684 let b = polygon_of(vec![square_at(5.0)]);
3685 assert_eq!(
3686 svg_multi_polygon_union_byval(&a, b.clone()),
3687 svg_multi_polygon_union(&a, &b)
3688 );
3689 assert_eq!(
3690 svg_multi_polygon_intersection_byval(&a, b.clone()),
3691 svg_multi_polygon_intersection(&a, &b)
3692 );
3693 assert_eq!(
3694 svg_multi_polygon_difference_byval(&a, b.clone()),
3695 svg_multi_polygon_difference(&a, &b)
3696 );
3697 assert_eq!(
3698 svg_multi_polygon_xor_byval(&a, b.clone()),
3699 svg_multi_polygon_xor(&a, &b)
3700 );
3701 }
3702
3703 #[test]
3708 fn render_node_clipmask_cpu_zero_sized_image_is_none() {
3709 let node = SvgNode::Rect(rect(0.0, 0.0, 4.0, 4.0));
3710 let style = SvgStyle::Fill(SvgFillStyle {
3711 transform: identity_transform(),
3712 ..SvgFillStyle::default()
3713 });
3714 assert_eq!(
3715 render_node_clipmask_cpu(&mut mask_image(0, 4), &node, style),
3716 None
3717 );
3718 assert_eq!(
3719 render_node_clipmask_cpu(&mut mask_image(4, 0), &node, style),
3720 None
3721 );
3722 assert_eq!(
3723 render_node_clipmask_cpu(&mut mask_image(0, 0), &node, style),
3724 None
3725 );
3726 }
3727
3728 #[test]
3729 fn render_node_clipmask_cpu_geometry_less_nodes_are_none() {
3730 let style = SvgStyle::Fill(SvgFillStyle {
3731 transform: identity_transform(),
3732 ..SvgFillStyle::default()
3733 });
3734 for node in [
3735 SvgNode::Path(empty_path()),
3736 SvgNode::MultiPolygon(empty_polygon()),
3737 SvgNode::MultiShape(SvgSimpleNodeVec::from_const_slice(&[])),
3738 SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_const_slice(&[])),
3739 ] {
3740 assert_eq!(
3741 render_node_clipmask_cpu(&mut mask_image(4, 4), &node, style),
3742 None,
3743 "{node:?}"
3744 );
3745 }
3746 }
3747
3748 #[test]
3749 fn render_node_clipmask_cpu_writes_a_single_channel_mask() {
3750 let node = SvgNode::Rect(rect(0.0, 0.0, 4.0, 4.0));
3751 let style = SvgStyle::Fill(SvgFillStyle {
3752 transform: identity_transform(),
3753 ..SvgFillStyle::default()
3754 });
3755 let mut img = mask_image(4, 4);
3756 assert_eq!(render_node_clipmask_cpu(&mut img, &node, style), Some(()));
3757 assert_eq!(img.data_format, RawImageFormat::R8);
3758 assert!(img.premultiplied_alpha);
3759 let bytes = mask_bytes(&img);
3760 assert_eq!(bytes.len(), 16, "one byte per pixel");
3761 assert!(
3762 bytes.iter().any(|&b| b > 0),
3763 "a rect covering the whole image must leave coverage"
3764 );
3765 }
3766
3767 #[test]
3768 fn render_node_clipmask_cpu_default_style_transform_is_not_the_identity() {
3769 let node = SvgNode::Rect(rect(0.0, 0.0, 4.0, 4.0));
3773 let mut zeroed = mask_image(4, 4);
3774 let mut identity = mask_image(4, 4);
3775 assert_eq!(
3776 render_node_clipmask_cpu(&mut zeroed, &node, SvgStyle::Fill(SvgFillStyle::default())),
3777 Some(())
3778 );
3779 assert_eq!(
3780 render_node_clipmask_cpu(
3781 &mut identity,
3782 &node,
3783 SvgStyle::Fill(SvgFillStyle {
3784 transform: identity_transform(),
3785 ..SvgFillStyle::default()
3786 })
3787 ),
3788 Some(())
3789 );
3790 assert_ne!(mask_bytes(&zeroed), mask_bytes(&identity));
3791 }
3792
3793 #[test]
3794 fn render_node_clipmask_cpu_stroke_style_renders_without_panicking() {
3795 let node = SvgNode::Circle(SvgCircle {
3796 center_x: 8.0,
3797 center_y: 8.0,
3798 radius: 4.0,
3799 });
3800 let style = SvgStyle::Stroke(SvgStrokeStyle {
3801 transform: identity_transform(),
3802 line_width: 2.0,
3803 ..SvgStrokeStyle::default()
3804 });
3805 let mut img = mask_image(16, 16);
3806 assert_eq!(render_node_clipmask_cpu(&mut img, &node, style), Some(()));
3807 assert_eq!(mask_bytes(&img).len(), 256);
3808 }
3809
3810 #[test]
3811 fn render_node_clipmask_cpu_geometry_far_outside_the_image_is_clipped_not_crashed() {
3812 let node = SvgNode::Rect(rect(-1.0e6, -1.0e6, 10.0, 10.0));
3813 let style = SvgStyle::Fill(SvgFillStyle {
3814 transform: identity_transform(),
3815 ..SvgFillStyle::default()
3816 });
3817 let mut img = mask_image(4, 4);
3818 assert_eq!(render_node_clipmask_cpu(&mut img, &node, style), Some(()));
3819 assert!(mask_bytes(&img).iter().all(|&b| b == 0));
3820 }
3821
3822 #[test]
3827 fn svg_parse_valid_minimal_input_keeps_the_bytes_verbatim() {
3828 let p = svg_parse(MINIMAL_SVG, SvgParseOptions::default()).expect("positive control");
3829 assert_eq!(p.svg_data.as_ref(), MINIMAL_SVG);
3830 assert!(p.run_destructor);
3831 }
3832
3833 #[test]
3834 fn svg_parse_invalid_utf8_is_rejected() {
3835 for bad in [
3836 &[0xFFu8, 0xFE, 0x00][..],
3837 &[0x80][..],
3838 &[0xED, 0xA0, 0x80][..], &[0xC0, 0x80][..], ] {
3841 assert_eq!(
3842 svg_parse(bad, SvgParseOptions::default()).err(),
3843 Some(SvgParseError::NotAnUtf8Str),
3844 "{bad:?}"
3845 );
3846 }
3847 }
3848
3849 #[test]
3850 fn svg_parse_unclosed_elements_are_rejected() {
3851 for bad in [&b"<svg"[..], &b"<svg>"[..], &b"<svg><g>"[..]] {
3852 assert_eq!(
3853 svg_parse(bad, SvgParseOptions::default()).err(),
3854 Some(SvgParseError::NoParserAvailable),
3855 "{bad:?}"
3856 );
3857 }
3858 }
3859
3860 #[test]
3861 fn svg_parse_accepts_input_that_is_not_svg_at_all() {
3862 for lenient in [
3866 &b""[..],
3867 &b" "[..],
3868 &b"\t\n\r "[..],
3869 &b"garbage"[..],
3870 &b"</svg>"[..],
3871 &b"<html><body/></html>"[..],
3872 ] {
3873 let p = svg_parse(lenient, SvgParseOptions::default())
3874 .unwrap_or_else(|e| panic!("{lenient:?} was rejected with {e:?}"));
3875 assert_eq!(p.svg_data.as_ref(), lenient);
3876 }
3877 }
3878
3879 #[test]
3880 fn svg_parse_garbage_never_panics_and_never_invents_data() {
3881 for data in [
3882 &b"<<<<<<"[..],
3883 &b"\x00\x01\x02\x03"[..],
3884 &b"{\"json\": true}"[..],
3885 &b"<svg <<>>"[..],
3886 &b"&&&;;;"[..],
3887 &b"<!--"[..],
3888 &b"<?xml"[..],
3889 &b"<!DOCTYPE"[..],
3890 ] {
3891 match svg_parse(data, SvgParseOptions::default()) {
3892 Ok(p) => assert_eq!(p.svg_data.as_ref(), data, "{data:?}"),
3893 Err(e) => assert_eq!(e, SvgParseError::NoParserAvailable, "{data:?}"),
3894 }
3895 }
3896 }
3897
3898 #[test]
3899 fn svg_parse_boundary_numeric_attributes_are_kept_as_opaque_strings() {
3900 let src = concat!(
3901 r#"<svg width="1e400" height="-0" a="9223372036854775807" "#,
3902 r#"b="NaN" c="inf" d="0"><rect width="0"/></svg>"#
3903 );
3904 let p = ParsedSvg::from_string(src, SvgParseOptions::default()).expect("parse");
3905 assert_eq!(p.to_string(SvgXmlOptions::default()), src);
3906 }
3907
3908 #[test]
3909 fn svg_parse_unicode_payloads_survive_untouched() {
3910 for s in [
3911 "<svg><text>\u{1F600}</text></svg>",
3912 "<svg><text>e\u{0301}\u{0328}</text></svg>",
3913 "<svg id=\"\u{65E5}\u{672C}\u{8A9E}\"/>",
3914 "<svg>\u{200b}\u{1F600}</svg>",
3915 ] {
3916 let p = ParsedSvg::from_string(s, SvgParseOptions::default())
3917 .unwrap_or_else(|e| panic!("{s:?} -> {e:?}"));
3918 assert_eq!(p.to_string(SvgXmlOptions::default()), s);
3919 }
3920 }
3921
3922 #[test]
3923 fn svg_parse_leading_and_trailing_junk_is_tolerated_and_preserved() {
3924 let src = " <svg/> trailing;garbage";
3925 let p = ParsedSvg::from_string(src, SvgParseOptions::default()).expect("lenient parse");
3926 assert_eq!(p.to_string(SvgXmlOptions::default()), src);
3928 }
3929
3930 #[test]
3931 fn svg_parse_extremely_long_input_does_not_hang() {
3932 let long = format!("<svg>{}</svg>", "a".repeat(1_000_000));
3933 let p = ParsedSvg::from_string(&long, SvgParseOptions::default())
3934 .expect("1 MB of text must parse");
3935 assert_eq!(p.svg_data.as_ref().len(), long.len());
3936 }
3937
3938 #[test]
3939 fn svg_parse_many_sibling_elements_does_not_hang() {
3940 let mut s = String::from("<svg>");
3941 for _ in 0..50_000 {
3942 s.push_str("<g/>");
3943 }
3944 s.push_str("</svg>");
3945 assert!(ParsedSvg::from_string(&s, SvgParseOptions::default()).is_ok());
3946 }
3947
3948 #[test]
3949 fn svg_parse_deeply_nested_input_does_not_stack_overflow() {
3950 let child = std::thread::Builder::new()
3954 .stack_size(128 * 1024 * 1024)
3955 .spawn(|| {
3956 const DEPTH: usize = 10_000;
3957 let mut s = String::from("<svg>");
3958 for _ in 0..DEPTH {
3959 s.push_str("<g>");
3960 }
3961 for _ in 0..DEPTH {
3962 s.push_str("</g>");
3963 }
3964 s.push_str("</svg>");
3965 ParsedSvg::from_string(&s, SvgParseOptions::default()).is_ok()
3966 })
3967 .expect("spawn");
3968 assert!(child
3969 .join()
3970 .expect("10k-deep nesting must not overflow the stack"));
3971 }
3972
3973 #[test]
3974 fn svgxmlnode_parse_mirrors_svg_parse_acceptance() {
3975 assert!(svgxmlnode_parse(MINIMAL_SVG, SvgParseOptions::default()).is_ok());
3976 assert!(svgxmlnode_parse(b"", SvgParseOptions::default()).is_ok());
3977 assert_eq!(
3978 svgxmlnode_parse(&[0xFF, 0xFE], SvgParseOptions::default()).err(),
3979 Some(SvgParseError::NotAnUtf8Str)
3980 );
3981 assert_eq!(
3982 svgxmlnode_parse(b"<svg>", SvgParseOptions::default()).err(),
3983 Some(SvgParseError::NoParserAvailable)
3984 );
3985 let node = svgxmlnode_parse(MINIMAL_SVG, SvgParseOptions::default()).expect("parse");
3986 assert!(node.run_destructor);
3987 }
3988
3989 #[test]
3990 fn parsed_svg_round_trips_through_to_string_and_back() {
3991 let src = r#"<svg viewBox="0 0 8 8"><rect width="8" height="8"/></svg>"#;
3992 let once = ParsedSvg::from_string(src, SvgParseOptions::default()).expect("parse");
3993 let text = once.to_string(SvgXmlOptions::default());
3994 assert_eq!(text, src);
3995 let twice = ParsedSvg::from_string(&text, SvgParseOptions::default()).expect("re-parse");
3996 assert_eq!(
3997 twice.to_string(SvgXmlOptions::default()),
3998 text,
3999 "serialisation must be idempotent"
4000 );
4001 assert_eq!(svg_to_string(&twice, SvgXmlOptions::default()), text);
4002 }
4003
4004 #[test]
4005 fn parsed_svg_from_bytes_and_from_string_agree() {
4006 let a = ParsedSvg::from_bytes(MINIMAL_SVG, SvgParseOptions::default()).expect("bytes");
4007 let b = ParsedSvg::from_string(
4008 core::str::from_utf8(MINIMAL_SVG).expect("ascii"),
4009 SvgParseOptions::default(),
4010 )
4011 .expect("string");
4012 assert_eq!(a.svg_data.as_ref(), b.svg_data.as_ref());
4013 }
4014
4015 #[test]
4016 fn parsed_svg_to_string_replaces_invalid_utf8_instead_of_panicking() {
4017 let p = ParsedSvg {
4020 svg_data: vec![0xFFu8, 0xFE].into(),
4021 run_destructor: true,
4022 };
4023 assert_eq!(
4024 p.to_string(SvgXmlOptions::default()),
4025 "\u{FFFD}\u{FFFD}",
4026 "each invalid byte becomes one replacement char"
4027 );
4028 assert_eq!(
4029 svg_to_string(&p, SvgXmlOptions::default()),
4030 p.to_string(SvgXmlOptions::default())
4031 );
4032 }
4033
4034 #[test]
4035 fn parsed_svg_to_string_of_an_empty_document_is_empty() {
4036 let p = ParsedSvg {
4037 svg_data: Vec::new().into(),
4038 run_destructor: true,
4039 };
4040 assert!(p.to_string(SvgXmlOptions::default()).is_empty());
4041 assert_eq!(format!("{p:?}"), "ParsedSvg(0 bytes)");
4042 }
4043
4044 #[test]
4045 fn parsed_svg_debug_reports_the_byte_length() {
4046 let p = ParsedSvg {
4047 svg_data: vec![1u8, 2, 3].into(),
4048 run_destructor: true,
4049 };
4050 assert_eq!(format!("{p:?}"), "ParsedSvg(3 bytes)");
4051 }
4052
4053 #[test]
4054 fn svg_root_and_get_root_agree() {
4055 let p = svg_parse(MINIMAL_SVG, SvgParseOptions::default()).expect("parse");
4056 assert!(p.get_root().run_destructor);
4057 assert!(svg_root(&p).run_destructor);
4058 let empty = svg_parse(b"", SvgParseOptions::default()).expect("lenient parse");
4060 assert!(empty.get_root().run_destructor);
4061 }
4062
4063 #[test]
4068 fn svg_render_zero_target_size_is_none() {
4069 let p = svg_parse(MINIMAL_SVG, SvgParseOptions::default()).expect("parse");
4070 for size in [
4071 LayoutSize::new(0, 0),
4072 LayoutSize::new(0, 8),
4073 LayoutSize::new(8, 0),
4074 ] {
4075 let opts = SvgRenderOptions {
4076 target_size: OptionLayoutSize::Some(size),
4077 ..SvgRenderOptions::default()
4078 };
4079 assert!(p.render(opts).is_none(), "{size:?}");
4080 assert!(svg_render(&p, opts).is_none(), "{size:?}");
4081 }
4082 }
4083
4084 #[cfg(feature = "cpurender")]
4085 #[test]
4086 fn svg_render_produces_an_image_of_the_requested_size() {
4087 let p = svg_parse(MINIMAL_SVG, SvgParseOptions::default()).expect("parse");
4088 let opts = SvgRenderOptions {
4089 target_size: OptionLayoutSize::Some(LayoutSize::new(8, 8)),
4090 ..SvgRenderOptions::default()
4091 };
4092 let img = p.render(opts).expect("a minimal <svg> must rasterize");
4093 assert_eq!((img.width, img.height), (8, 8));
4094 assert_eq!(img.data_format, RawImageFormat::RGBA8);
4095 assert!(!img.premultiplied_alpha);
4096 }
4097
4098 #[cfg(feature = "cpurender")]
4099 #[test]
4100 fn svg_render_of_a_document_without_an_svg_root_is_none() {
4101 let p = svg_parse(b"<html><body/></html>", SvgParseOptions::default())
4103 .expect("lenient parse");
4104 let opts = SvgRenderOptions {
4105 target_size: OptionLayoutSize::Some(LayoutSize::new(4, 4)),
4106 ..SvgRenderOptions::default()
4107 };
4108 assert!(p.render(opts).is_none());
4109 }
4110
4111 #[cfg(feature = "cpurender")]
4112 #[test]
4113 fn svg_render_one_by_one_target_is_the_smallest_valid_size() {
4114 let p = svg_parse(MINIMAL_SVG, SvgParseOptions::default()).expect("parse");
4115 let opts = SvgRenderOptions {
4116 target_size: OptionLayoutSize::Some(LayoutSize::new(1, 1)),
4117 ..SvgRenderOptions::default()
4118 };
4119 let img = p.render(opts).expect("1x1 must still rasterize");
4120 assert_eq!((img.width, img.height), (1, 1));
4121 }
4122}