1use alloc::{
15 string::{String, ToString},
16 vec::Vec,
17};
18use core::fmt;
19
20use azul_css::{
21 props::{
22 basic::{
23 ColorF, ColorU, OptionColorU, OptionLayoutSize, PixelValue, SvgCubicCurve, SvgPoint,
24 SvgQuadraticCurve, SvgRect, SvgVector,
25 },
26 style::{StyleTransform, StyleTransformOrigin, StyleTransformVec},
27 },
28 AzString, OptionString, StringVec, U32Vec,
29};
30
31use crate::{
32 geom::PhysicalSizeU32,
33 gl::{
34 GlContextPtr, GlShader, IndexBufferFormat, Texture, Uniform, UniformType, VertexAttribute,
35 VertexAttributeType, VertexBuffer, VertexLayout, VertexLayoutDescription,
36 },
37 transform::{ComputedTransform3D, RotationMode},
38 xml::XmlError,
39};
40
41const DEFAULT_MITER_LIMIT: f32 = 4.0;
43const DEFAULT_LINE_WIDTH: f32 = 1.0;
45const DEFAULT_TOLERANCE: f32 = 0.1;
47
48#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
50#[repr(C)]
51pub struct SvgSize {
52 pub width: f32,
54 pub height: f32,
56}
57
58#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
60#[repr(C)]
61pub struct SvgLine {
62 pub start: SvgPoint,
64 pub end: SvgPoint,
66}
67
68impl SvgLine {
69 #[inline]
71 #[must_use] pub const fn new(start: SvgPoint, end: SvgPoint) -> Self {
72 Self { start, end }
73 }
74
75 #[must_use] pub fn inwards_normal(&self) -> Option<SvgPoint> {
80 let dx = self.end.x - self.start.x;
81 let dy = self.end.y - self.start.y;
82 let edge_length = dx.hypot(dy);
83 let x = -dy / edge_length;
84 let y = dx / edge_length;
85
86 if x.is_finite() && y.is_finite() {
87 Some(SvgPoint { x, y })
88 } else {
89 None
90 }
91 }
92
93 #[must_use] pub fn outwards_normal(&self) -> Option<SvgPoint> {
95 let inwards = self.inwards_normal()?;
96 Some(SvgPoint {
97 x: -inwards.x,
98 y: -inwards.y,
99 })
100 }
101
102 pub const fn reverse(&mut self) {
104 core::mem::swap(&mut self.start, &mut self.end);
105 }
106 #[must_use] pub const fn get_start(&self) -> SvgPoint {
108 self.start
109 }
110 #[must_use] pub const fn get_end(&self) -> SvgPoint {
112 self.end
113 }
114
115 #[must_use] pub fn get_t_at_offset(&self, offset: f64) -> f64 {
117 offset / self.get_length()
118 }
119
120 #[must_use] pub fn get_tangent_vector_at_t(&self) -> SvgVector {
124 let dx = self.end.x - self.start.x;
125 let dy = self.end.y - self.start.y;
126 SvgVector {
127 x: f64::from(dx),
128 y: f64::from(dy),
129 }
130 .normalize()
131 }
132
133 #[allow(clippy::suboptimal_flops)] #[must_use] pub fn get_x_at_t(&self, t: f64) -> f64 {
136 f64::from(self.start.x) + (f64::from(self.end.x) - f64::from(self.start.x)) * t
137 }
138
139 #[allow(clippy::suboptimal_flops)] #[must_use] pub fn get_y_at_t(&self, t: f64) -> f64 {
142 f64::from(self.start.y) + (f64::from(self.end.y) - f64::from(self.start.y)) * t
143 }
144
145 #[must_use] pub fn get_length(&self) -> f64 {
147 let dx = self.end.x - self.start.x;
148 let dy = self.end.y - self.start.y;
149 f64::from(libm::hypotf(dx, dy))
150 }
151
152 #[must_use] pub fn get_bounds(&self) -> SvgRect {
154 let min_x = self.start.x.min(self.end.x);
155 let max_x = self.start.x.max(self.end.x);
156
157 let min_y = self.start.y.min(self.end.y);
158 let max_y = self.start.y.max(self.end.y);
159
160 let width = (max_x - min_x).abs();
161 let height = (max_y - min_y).abs();
162
163 SvgRect {
164 width,
165 height,
166 x: min_x,
167 y: min_y,
168 radius_top_left: 0.0,
169 radius_top_right: 0.0,
170 radius_bottom_left: 0.0,
171 radius_bottom_right: 0.0,
172 }
173 }
174}
175
176#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
177#[repr(C, u8)]
178pub enum SvgPathElement {
179 Line(SvgLine),
180 QuadraticCurve(SvgQuadraticCurve),
181 CubicCurve(SvgCubicCurve),
182}
183
184impl_option!(
185 SvgPathElement,
186 OptionSvgPathElement,
187 [Debug, Copy, Clone, PartialEq, PartialOrd]
188);
189
190impl SvgPathElement {
191 #[inline]
193 #[must_use] pub const fn line(l: SvgLine) -> Self {
194 Self::Line(l)
195 }
196
197 #[inline]
199 #[must_use] pub const fn quadratic_curve(qc: SvgQuadraticCurve) -> Self {
200 Self::QuadraticCurve(qc)
201 }
202
203 #[inline]
205 #[must_use] pub const fn cubic_curve(cc: SvgCubicCurve) -> Self {
206 Self::CubicCurve(cc)
207 }
208
209 pub const fn set_last(&mut self, point: SvgPoint) {
211 match self {
212 Self::Line(l) => l.end = point,
213 Self::QuadraticCurve(qc) => qc.end = point,
214 Self::CubicCurve(cc) => cc.end = point,
215 }
216 }
217
218 pub const fn set_first(&mut self, point: SvgPoint) {
220 match self {
221 Self::Line(l) => l.start = point,
222 Self::QuadraticCurve(qc) => qc.start = point,
223 Self::CubicCurve(cc) => cc.start = point,
224 }
225 }
226
227 pub const fn reverse(&mut self) {
229 match self {
230 Self::Line(l) => l.reverse(),
231 Self::QuadraticCurve(qc) => qc.reverse(),
232 Self::CubicCurve(cc) => cc.reverse(),
233 }
234 }
235 #[must_use] pub const fn get_start(&self) -> SvgPoint {
237 match self {
238 Self::Line(l) => l.get_start(),
239 Self::QuadraticCurve(qc) => qc.get_start(),
240 Self::CubicCurve(cc) => cc.get_start(),
241 }
242 }
243 #[must_use] pub const fn get_end(&self) -> SvgPoint {
245 match self {
246 Self::Line(l) => l.get_end(),
247 Self::QuadraticCurve(qc) => qc.get_end(),
248 Self::CubicCurve(cc) => cc.get_end(),
249 }
250 }
251 #[must_use] pub fn get_bounds(&self) -> SvgRect {
253 match self {
254 Self::Line(l) => l.get_bounds(),
255 Self::QuadraticCurve(qc) => qc.get_bounds(),
256 Self::CubicCurve(cc) => cc.get_bounds(),
257 }
258 }
259 #[must_use] pub fn get_length(&self) -> f64 {
261 match self {
262 Self::Line(l) => l.get_length(),
263 Self::QuadraticCurve(qc) => qc.get_length(),
264 Self::CubicCurve(cc) => cc.get_length(),
265 }
266 }
267 #[must_use] pub fn get_t_at_offset(&self, offset: f64) -> f64 {
269 match self {
270 Self::Line(l) => l.get_t_at_offset(offset),
271 Self::QuadraticCurve(qc) => qc.get_t_at_offset(offset),
272 Self::CubicCurve(cc) => cc.get_t_at_offset(offset),
273 }
274 }
275 #[must_use] pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
277 match self {
278 Self::Line(l) => l.get_tangent_vector_at_t(),
279 Self::QuadraticCurve(qc) => qc.get_tangent_vector_at_t(t),
280 Self::CubicCurve(cc) => cc.get_tangent_vector_at_t(t),
281 }
282 }
283 #[must_use] pub fn get_x_at_t(&self, t: f64) -> f64 {
285 match self {
286 Self::Line(l) => l.get_x_at_t(t),
287 Self::QuadraticCurve(qc) => qc.get_x_at_t(t),
288 Self::CubicCurve(cc) => cc.get_x_at_t(t),
289 }
290 }
291 #[must_use] pub fn get_y_at_t(&self, t: f64) -> f64 {
293 match self {
294 Self::Line(l) => l.get_y_at_t(t),
295 Self::QuadraticCurve(qc) => qc.get_y_at_t(t),
296 Self::CubicCurve(cc) => cc.get_y_at_t(t),
297 }
298 }
299}
300
301impl_vec!(SvgPathElement, SvgPathElementVec, SvgPathElementVecDestructor, SvgPathElementVecDestructorType, SvgPathElementVecSlice, OptionSvgPathElement);
302impl_vec_debug!(SvgPathElement, SvgPathElementVec);
303impl_vec_clone!(
304 SvgPathElement,
305 SvgPathElementVec,
306 SvgPathElementVecDestructor
307);
308impl_vec_partialeq!(SvgPathElement, SvgPathElementVec);
309impl_vec_partialord!(SvgPathElement, SvgPathElementVec);
310
311#[derive(Debug, Clone, PartialEq, PartialOrd)]
312#[repr(C)]
313pub struct SvgPath {
314 pub items: SvgPathElementVec,
315}
316
317impl_option!(
318 SvgPath,
319 OptionSvgPath,
320 copy = false,
321 [Debug, Clone, PartialEq, PartialOrd]
322);
323
324impl SvgPath {
325 #[inline]
327 #[must_use] pub const fn create(items: SvgPathElementVec) -> Self {
328 Self { items }
329 }
330
331 #[must_use] pub fn get_start(&self) -> Option<SvgPoint> {
333 self.items.as_ref().first().map(SvgPathElement::get_start)
334 }
335
336 #[must_use] pub fn get_end(&self) -> Option<SvgPoint> {
338 self.items.as_ref().last().map(SvgPathElement::get_end)
339 }
340
341 pub fn close(&mut self) {
343 let Some(first) = self.items.as_ref().first() else {
344 return;
345 };
346 let Some(last) = self.items.as_ref().last() else {
347 return;
348 };
349 if first.get_start() != last.get_end() {
350 let mut elements = self.items.as_slice().to_vec();
351 elements.push(SvgPathElement::Line(SvgLine {
352 start: last.get_end(),
353 end: first.get_start(),
354 }));
355 self.items = elements.into();
356 }
357 }
358
359 #[must_use] pub fn is_closed(&self) -> bool {
361 let first = self.items.as_ref().first();
362 let last = self.items.as_ref().last();
363 match (first, last) {
364 (Some(f), Some(l)) => (f.get_start() == l.get_end()),
365 _ => false,
366 }
367 }
368
369 pub fn reverse(&mut self) {
371 let mut vec = SvgPathElementVec::from_const_slice(&[]);
373 core::mem::swap(&mut vec, &mut self.items);
374 let mut vec = vec.into_library_owned_vec();
375
376 vec.reverse();
378
379 for item in &mut vec {
382 item.reverse();
383 }
384
385 let mut vec = SvgPathElementVec::from_vec(vec);
387 core::mem::swap(&mut vec, &mut self.items);
388 }
389
390 pub fn join_with(&mut self, mut path: Self) -> Option<()> {
392 let self_last_point = self.items.as_ref().last()?.get_end();
393 let other_start_point = path.items.as_ref().first()?.get_start();
394 let interpolated_join_point = SvgPoint {
395 x: f32::midpoint(self_last_point.x, other_start_point.x),
396 y: f32::midpoint(self_last_point.y, other_start_point.y),
397 };
398
399 let mut vec = SvgPathElementVec::from_const_slice(&[]);
401 core::mem::swap(&mut vec, &mut self.items);
402 let mut vec = vec.into_library_owned_vec();
403
404 let mut other = SvgPathElementVec::from_const_slice(&[]);
405 core::mem::swap(&mut other, &mut path.items);
406 let mut other = other.into_library_owned_vec();
407
408 let vec_len = vec.len() - 1;
409 vec.get_mut(vec_len)?.set_last(interpolated_join_point);
410 other.get_mut(0)?.set_first(interpolated_join_point);
411 vec.append(&mut other);
412
413 let mut vec = SvgPathElementVec::from_vec(vec);
415 core::mem::swap(&mut vec, &mut self.items);
416
417 Some(())
418 }
419 #[must_use] pub fn get_bounds(&self) -> SvgRect {
421 let mut first_bounds = match self.items.as_ref().first() {
422 Some(s) => s.get_bounds(),
423 None => return SvgRect::default(),
424 };
425
426 for mp in self.items.as_ref().iter().skip(1) {
427 let mp_bounds = mp.get_bounds();
428 first_bounds.union_with(&mp_bounds);
429 }
430
431 first_bounds
432 }
433}
434
435#[derive(Debug, Clone, PartialEq, PartialOrd)]
436#[repr(C)]
437pub struct SvgMultiPolygon {
438 pub rings: SvgPathVec,
440}
441
442impl_option!(
443 SvgMultiPolygon,
444 OptionSvgMultiPolygon,
445 copy = false,
446 [Debug, Clone, PartialEq, PartialOrd]
447);
448
449impl SvgMultiPolygon {
450 #[inline]
453 #[must_use] pub const fn create(rings: SvgPathVec) -> Self {
454 Self { rings }
455 }
456
457 #[must_use] pub fn get_bounds(&self) -> SvgRect {
459 let Some(mut first_bounds) = self
460 .rings
461 .get(0)
462 .and_then(|b| b.items.get(0).map(SvgPathElement::get_bounds))
463 else {
464 return SvgRect::default();
466 };
467
468 for ring in &self.rings {
469 for item in &ring.items {
470 first_bounds.union_with(&item.get_bounds());
471 }
472 }
473
474 first_bounds
475 }
476}
477
478impl_vec!(SvgPath, SvgPathVec, SvgPathVecDestructor, SvgPathVecDestructorType, SvgPathVecSlice, OptionSvgPath);
479impl_vec_debug!(SvgPath, SvgPathVec);
480impl_vec_clone!(SvgPath, SvgPathVec, SvgPathVecDestructor);
481impl_vec_partialeq!(SvgPath, SvgPathVec);
482impl_vec_partialord!(SvgPath, SvgPathVec);
483
484impl_vec!(SvgMultiPolygon, SvgMultiPolygonVec, SvgMultiPolygonVecDestructor, SvgMultiPolygonVecDestructorType, SvgMultiPolygonVecSlice, OptionSvgMultiPolygon);
485impl_vec_debug!(SvgMultiPolygon, SvgMultiPolygonVec);
486impl_vec_clone!(
487 SvgMultiPolygon,
488 SvgMultiPolygonVec,
489 SvgMultiPolygonVecDestructor
490);
491impl_vec_partialeq!(SvgMultiPolygon, SvgMultiPolygonVec);
492impl_vec_partialord!(SvgMultiPolygon, SvgMultiPolygonVec);
493
494#[derive(Debug, Clone, PartialOrd, PartialEq)]
496#[repr(C, u8)]
497pub enum SvgNode {
498 MultiPolygonCollection(SvgMultiPolygonVec),
500 MultiPolygon(SvgMultiPolygon),
501 MultiShape(SvgSimpleNodeVec),
502 Path(SvgPath),
503 Circle(SvgCircle),
504 Rect(SvgRect),
505}
506
507#[derive(Debug, Clone, PartialOrd, PartialEq)]
509#[repr(C, u8)]
510pub enum SvgSimpleNode {
511 Path(SvgPath),
512 Circle(SvgCircle),
513 Rect(SvgRect),
514 CircleHole(SvgCircle),
515 RectHole(SvgRect),
516}
517
518impl_option!(
519 SvgSimpleNode,
520 OptionSvgSimpleNode,
521 copy = false,
522 [Debug, Clone, PartialOrd, PartialEq]
523);
524
525impl_vec!(SvgSimpleNode, SvgSimpleNodeVec, SvgSimpleNodeVecDestructor, SvgSimpleNodeVecDestructorType, SvgSimpleNodeVecSlice, OptionSvgSimpleNode);
526impl_vec_debug!(SvgSimpleNode, SvgSimpleNodeVec);
527impl_vec_clone!(SvgSimpleNode, SvgSimpleNodeVec, SvgSimpleNodeVecDestructor);
528impl_vec_partialeq!(SvgSimpleNode, SvgSimpleNodeVec);
529impl_vec_partialord!(SvgSimpleNode, SvgSimpleNodeVec);
530
531impl SvgSimpleNode {
532 #[allow(clippy::match_same_arms)]
536 #[must_use] pub fn get_bounds(&self) -> SvgRect {
537 match self {
538 Self::Path(a) => a.get_bounds(),
539 Self::Circle(a) => a.get_bounds(),
540 Self::Rect(a) => *a,
541 Self::CircleHole(a) => a.get_bounds(),
542 Self::RectHole(a) => *a,
543 }
544 }
545 #[must_use] pub fn is_closed(&self) -> bool {
547 match self {
548 Self::Path(a) => a.is_closed(),
549 Self::Circle(_) | Self::Rect(_) | Self::CircleHole(_) | Self::RectHole(_) => true,
550 }
551 }
552}
553
554impl SvgNode {
555 #[must_use] pub fn get_bounds(&self) -> SvgRect {
557 match self {
558 Self::MultiPolygonCollection(a) => {
559 let mut first_mp_bounds = match a.get(0) {
560 Some(s) => s.get_bounds(),
561 None => return SvgRect::default(),
562 };
563 for mp in a.iter().skip(1) {
564 let mp_bounds = mp.get_bounds();
565 first_mp_bounds.union_with(&mp_bounds);
566 }
567
568 first_mp_bounds
569 }
570 Self::MultiPolygon(a) => a.get_bounds(),
571 Self::MultiShape(a) => {
572 let mut first_mp_bounds = match a.get(0) {
573 Some(s) => s.get_bounds(),
574 None => return SvgRect::default(),
575 };
576 for mp in a.iter().skip(1) {
577 let mp_bounds = mp.get_bounds();
578 first_mp_bounds.union_with(&mp_bounds);
579 }
580
581 first_mp_bounds
582 }
583 Self::Path(a) => a.get_bounds(),
584 Self::Circle(a) => a.get_bounds(),
585 Self::Rect(a) => *a,
586 }
587 }
588 #[must_use] pub fn is_closed(&self) -> bool {
590 match self {
591 Self::MultiPolygonCollection(a) => {
592 for mp in a {
593 for p in mp.rings.as_ref() {
594 if !p.is_closed() {
595 return false;
596 }
597 }
598 }
599
600 true
601 }
602 Self::MultiPolygon(a) => {
603 for p in a.rings.as_ref() {
604 if !p.is_closed() {
605 return false;
606 }
607 }
608
609 true
610 }
611 Self::MultiShape(a) => {
612 for p in a.as_ref() {
613 if !p.is_closed() {
614 return false;
615 }
616 }
617
618 true
619 }
620 Self::Path(a) => a.is_closed(),
621 Self::Circle(_) | Self::Rect(_) => true,
622 }
623 }
624}
625
626#[derive(Debug, Clone, PartialOrd, PartialEq)]
628#[repr(C)]
629pub struct SvgStyledNode {
630 pub geometry: SvgNode,
631 pub style: SvgStyle,
632}
633
634#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
636#[repr(C)]
637pub struct SvgVertex {
638 pub x: f32,
639 pub y: f32,
640}
641
642impl_option!(
643 SvgVertex,
644 OptionSvgVertex,
645 [Debug, Copy, Clone, PartialOrd, PartialEq]
646);
647
648impl VertexLayoutDescription for SvgVertex {
649 fn get_description() -> VertexLayout {
650 VertexLayout {
651 fields: vec![VertexAttribute {
652 va_name: String::from("vAttrXY").into(),
653 layout_location: None.into(),
654 attribute_type: VertexAttributeType::Float,
655 item_count: 2,
656 }]
657 .into(),
658 }
659 }
660}
661
662#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
664#[repr(C)]
665pub struct SvgColoredVertex {
666 pub x: f32,
667 pub y: f32,
668 pub z: f32,
669 pub r: f32,
670 pub g: f32,
671 pub b: f32,
672 pub a: f32,
673}
674
675impl_option!(
676 SvgColoredVertex,
677 OptionSvgColoredVertex,
678 [Debug, Copy, Clone, PartialOrd, PartialEq]
679);
680
681impl VertexLayoutDescription for SvgColoredVertex {
682 fn get_description() -> VertexLayout {
683 VertexLayout {
684 fields: vec![
685 VertexAttribute {
686 va_name: String::from("vAttrXY").into(),
687 layout_location: None.into(),
688 attribute_type: VertexAttributeType::Float,
689 item_count: 3,
690 },
691 VertexAttribute {
692 va_name: String::from("vColor").into(),
693 layout_location: None.into(),
694 attribute_type: VertexAttributeType::Float,
695 item_count: 4,
696 },
697 ]
698 .into(),
699 }
700 }
701}
702
703#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
705#[repr(C)]
706pub struct SvgCircle {
707 pub center_x: f32,
708 pub center_y: f32,
709 pub radius: f32,
710}
711
712impl SvgCircle {
713 #[allow(clippy::suboptimal_flops)] #[must_use] pub fn contains_point(&self, x: f32, y: f32) -> bool {
716 let x_diff = libm::fabsf(x - self.center_x);
717 let y_diff = libm::fabsf(y - self.center_y);
718 (x_diff * x_diff) + (y_diff * y_diff) < (self.radius * self.radius)
719 }
720 #[must_use] pub fn get_bounds(&self) -> SvgRect {
722 SvgRect {
723 width: self.radius * 2.0,
724 height: self.radius * 2.0,
725 x: self.center_x - self.radius,
726 y: self.center_y - self.radius,
727 radius_top_left: 0.0,
728 radius_top_right: 0.0,
729 radius_bottom_left: 0.0,
730 radius_bottom_right: 0.0,
731 }
732 }
733}
734
735#[derive(Debug, Clone, PartialEq, PartialOrd)]
736#[repr(C)]
737pub struct TessellatedSvgNode {
738 pub vertices: SvgVertexVec,
739 pub indices: U32Vec,
740}
741
742impl_option!(
743 TessellatedSvgNode,
744 OptionTessellatedSvgNode,
745 copy = false,
746 [Debug, Clone, PartialEq, PartialOrd]
747);
748
749impl Default for TessellatedSvgNode {
750 fn default() -> Self {
751 Self {
752 vertices: Vec::new().into(),
753 indices: Vec::new().into(),
754 }
755 }
756}
757
758impl_vec!(TessellatedSvgNode, TessellatedSvgNodeVec, TessellatedSvgNodeVecDestructor, TessellatedSvgNodeVecDestructorType, TessellatedSvgNodeVecSlice, OptionTessellatedSvgNode);
759impl_vec_debug!(TessellatedSvgNode, TessellatedSvgNodeVec);
760impl_vec_partialord!(TessellatedSvgNode, TessellatedSvgNodeVec);
761impl_vec_clone!(
762 TessellatedSvgNode,
763 TessellatedSvgNodeVec,
764 TessellatedSvgNodeVecDestructor
765);
766impl_vec_partialeq!(TessellatedSvgNode, TessellatedSvgNodeVec);
767
768impl TessellatedSvgNode {
769 #[must_use] pub fn empty() -> Self {
770 Self::default()
771 }
772}
773
774impl TessellatedSvgNodeVec {
775 #[must_use] pub fn get_ref(&self) -> TessellatedSvgNodeVecRef {
776 let slice = self.as_ref();
777 TessellatedSvgNodeVecRef {
778 ptr: slice.as_ptr(),
779 len: slice.len(),
780 }
781 }
782}
783
784impl fmt::Debug for TessellatedSvgNodeVecRef {
785 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
786 self.as_slice().fmt(f)
787 }
788}
789
790#[repr(C)]
792pub struct TessellatedSvgNodeVecRef {
793 pub ptr: *const TessellatedSvgNode,
794 pub len: usize,
795}
796
797impl Clone for TessellatedSvgNodeVecRef {
798 fn clone(&self) -> Self {
799 Self {
800 ptr: self.ptr,
801 len: self.len,
802 }
803 }
804}
805
806impl TessellatedSvgNodeVecRef {
807 #[must_use] pub const fn as_slice(&self) -> &[TessellatedSvgNode] {
808 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
809 }
810}
811
812#[derive(Debug, Clone, PartialEq, PartialOrd)]
813#[repr(C)]
814pub struct TessellatedColoredSvgNode {
815 pub vertices: SvgColoredVertexVec,
816 pub indices: U32Vec,
817}
818
819impl_option!(
820 TessellatedColoredSvgNode,
821 OptionTessellatedColoredSvgNode,
822 copy = false,
823 [Debug, Clone, PartialEq, PartialOrd]
824);
825
826impl Default for TessellatedColoredSvgNode {
827 fn default() -> Self {
828 Self {
829 vertices: Vec::new().into(),
830 indices: Vec::new().into(),
831 }
832 }
833}
834
835impl_vec!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec, TessellatedColoredSvgNodeVecDestructor, TessellatedColoredSvgNodeVecDestructorType, TessellatedColoredSvgNodeVecSlice, OptionTessellatedColoredSvgNode);
836impl_vec_debug!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
837impl_vec_partialord!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
838impl_vec_clone!(
839 TessellatedColoredSvgNode,
840 TessellatedColoredSvgNodeVec,
841 TessellatedColoredSvgNodeVecDestructor
842);
843impl_vec_partialeq!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
844
845impl TessellatedColoredSvgNode {
846 #[must_use] pub fn empty() -> Self {
847 Self::default()
848 }
849}
850
851impl TessellatedColoredSvgNodeVec {
852 #[must_use] pub fn get_ref(&self) -> TessellatedColoredSvgNodeVecRef {
853 let slice = self.as_ref();
854 TessellatedColoredSvgNodeVecRef {
855 ptr: slice.as_ptr(),
856 len: slice.len(),
857 }
858 }
859}
860
861impl fmt::Debug for TessellatedColoredSvgNodeVecRef {
862 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
863 self.as_slice().fmt(f)
864 }
865}
866
867#[repr(C)]
869pub struct TessellatedColoredSvgNodeVecRef {
870 pub ptr: *const TessellatedColoredSvgNode,
871 pub len: usize,
872}
873
874impl Clone for TessellatedColoredSvgNodeVecRef {
875 fn clone(&self) -> Self {
876 Self {
877 ptr: self.ptr,
878 len: self.len,
879 }
880 }
881}
882
883impl TessellatedColoredSvgNodeVecRef {
884 #[must_use] pub const fn as_slice(&self) -> &[TessellatedColoredSvgNode] {
885 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
886 }
887}
888
889impl_vec!(SvgVertex, SvgVertexVec, SvgVertexVecDestructor, SvgVertexVecDestructorType, SvgVertexVecSlice, OptionSvgVertex);
890impl_vec_debug!(SvgVertex, SvgVertexVec);
891impl_vec_partialord!(SvgVertex, SvgVertexVec);
892impl_vec_clone!(SvgVertex, SvgVertexVec, SvgVertexVecDestructor);
893impl_vec_partialeq!(SvgVertex, SvgVertexVec);
894
895impl_vec!(SvgColoredVertex, SvgColoredVertexVec, SvgColoredVertexVecDestructor, SvgColoredVertexVecDestructorType, SvgColoredVertexVecSlice, OptionSvgColoredVertex);
896impl_vec_debug!(SvgColoredVertex, SvgColoredVertexVec);
897impl_vec_partialord!(SvgColoredVertex, SvgColoredVertexVec);
898impl_vec_clone!(
899 SvgColoredVertex,
900 SvgColoredVertexVec,
901 SvgColoredVertexVecDestructor
902);
903impl_vec_partialeq!(SvgColoredVertex, SvgColoredVertexVec);
904
905#[allow(clippy::cast_precision_loss)]
913fn compute_svg_transform_uniforms(
914 target_size: PhysicalSizeU32,
915 transforms: &[StyleTransform],
916) -> (Uniform, Uniform) {
917 let transform_origin = StyleTransformOrigin {
918 x: PixelValue::px(target_size.width as f32 / 2.0),
919 y: PixelValue::px(target_size.height as f32 / 2.0),
920 };
921
922 let computed_transform = ComputedTransform3D::from_style_transform_vec(
923 transforms,
924 &transform_origin,
925 target_size.width as f32,
926 target_size.height as f32,
927 RotationMode::ForWebRender,
928 );
929
930 let m = computed_transform.get_column_major().m;
933 let matrix: [f32; 16] = core::array::from_fn(|i| m[i / 4][i % 4]);
934
935 let bbox_uniform = Uniform {
936 uniform_name: "vBboxSize".into(),
937 uniform_type: UniformType::FloatVec2([
938 target_size.width as f32,
939 target_size.height as f32,
940 ]),
941 };
942
943 let transform_uniform = Uniform {
944 uniform_name: "vTransformMatrix".into(),
945 uniform_type: UniformType::Matrix4 {
946 transpose: false,
947 matrix,
948 },
949 };
950
951 (bbox_uniform, transform_uniform)
952}
953
954#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
955#[repr(C)]
956pub struct TessellatedGPUSvgNode {
957 pub vertex_index_buffer: VertexBuffer,
958}
959
960impl TessellatedGPUSvgNode {
961 #[must_use] pub fn new(node: &TessellatedSvgNode, gl: GlContextPtr) -> Self {
963 let svg_shader_id = gl.ptr.svg_shader;
964 Self {
965 vertex_index_buffer: VertexBuffer::new(
966 gl,
967 svg_shader_id,
968 node.vertices.as_ref(),
969 node.indices.as_ref(),
970 IndexBufferFormat::Triangles,
971 ),
972 }
973 }
974
975 #[allow(clippy::needless_pass_by_value)] pub fn draw(
978 &self,
979 texture: &mut Texture,
980 target_size: PhysicalSizeU32,
981 color: ColorU,
982 transforms: StyleTransformVec,
983 ) -> bool {
984 let (bbox_uniform, transform_uniform) =
985 compute_svg_transform_uniforms(target_size, transforms.as_ref());
986
987 let color: ColorF = color.into();
988
989 let uniforms = [
990 bbox_uniform,
991 Uniform {
992 uniform_name: "fDrawColor".into(),
993 uniform_type: UniformType::FloatVec4([color.r, color.g, color.b, color.a]),
994 },
995 transform_uniform,
996 ];
997
998 GlShader::draw(
999 texture.gl_context.ptr.svg_shader,
1000 texture,
1001 &[(&self.vertex_index_buffer, &uniforms[..])],
1002 );
1003
1004 true
1005 }
1006}
1007
1008#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
1009#[repr(C)]
1010pub struct TessellatedColoredGPUSvgNode {
1011 pub vertex_index_buffer: VertexBuffer,
1012}
1013
1014impl TessellatedColoredGPUSvgNode {
1015 #[must_use] pub fn new(node: &TessellatedColoredSvgNode, gl: GlContextPtr) -> Self {
1017 let svg_shader_id = gl.ptr.svg_multicolor_shader;
1018 Self {
1019 vertex_index_buffer: VertexBuffer::new(
1020 gl,
1021 svg_shader_id,
1022 node.vertices.as_ref(),
1023 node.indices.as_ref(),
1024 IndexBufferFormat::Triangles,
1025 ),
1026 }
1027 }
1028
1029 #[allow(clippy::needless_pass_by_value)] pub fn draw(
1032 &self,
1033 texture: &mut Texture,
1034 target_size: PhysicalSizeU32,
1035 transforms: StyleTransformVec,
1036 ) -> bool {
1037 let (bbox_uniform, transform_uniform) =
1038 compute_svg_transform_uniforms(target_size, transforms.as_ref());
1039
1040 #[allow(clippy::tuple_array_conversions)]
1043 let uniforms = [bbox_uniform, transform_uniform];
1044
1045 GlShader::draw(
1046 texture.gl_context.ptr.svg_multicolor_shader,
1047 texture,
1048 &[(&self.vertex_index_buffer, &uniforms[..])],
1049 );
1050
1051 true
1052 }
1053}
1054
1055#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1056#[repr(C, u8)]
1057pub enum SvgStyle {
1058 Fill(SvgFillStyle),
1059 Stroke(SvgStrokeStyle),
1060}
1061
1062impl SvgStyle {
1063 #[must_use] pub const fn get_antialias(&self) -> bool {
1064 match self {
1065 Self::Fill(f) => f.anti_alias,
1066 Self::Stroke(s) => s.anti_alias,
1067 }
1068 }
1069 #[must_use] pub const fn get_high_quality_aa(&self) -> bool {
1070 match self {
1071 Self::Fill(f) => f.high_quality_aa,
1072 Self::Stroke(s) => s.high_quality_aa,
1073 }
1074 }
1075 #[must_use] pub const fn get_transform(&self) -> SvgTransform {
1076 match self {
1077 Self::Fill(f) => f.transform,
1078 Self::Stroke(s) => s.transform,
1079 }
1080 }
1081}
1082#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
1084#[repr(C)]
1085#[derive(Default)]
1086pub enum SvgFillRule {
1087 #[default]
1088 Winding,
1089 EvenOdd,
1090}
1091
1092
1093#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
1094#[repr(C)]
1095pub struct SvgTransform {
1096 pub sx: f32,
1097 pub kx: f32,
1098 pub ky: f32,
1099 pub sy: f32,
1100 pub tx: f32,
1101 pub ty: f32,
1102}
1103
1104#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1105#[repr(C)]
1106pub struct SvgFillStyle {
1107 pub line_join: SvgLineJoin,
1111 pub miter_limit: f32,
1116 pub tolerance: f32,
1121 pub fill_rule: SvgFillRule,
1123 pub transform: SvgTransform,
1126 pub anti_alias: bool,
1128 pub high_quality_aa: bool,
1130}
1131
1132impl Default for SvgFillStyle {
1133 fn default() -> Self {
1134 Self {
1135 line_join: SvgLineJoin::Miter,
1136 miter_limit: DEFAULT_MITER_LIMIT,
1137 tolerance: DEFAULT_TOLERANCE,
1138 fill_rule: SvgFillRule::default(),
1139 transform: SvgTransform::default(),
1140 anti_alias: true,
1141 high_quality_aa: false,
1142 }
1143 }
1144}
1145
1146#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1147#[repr(C)]
1148pub struct SvgStrokeStyle {
1149 pub dash_pattern: OptionSvgDashPattern,
1151 pub transform: SvgTransform,
1154 pub start_cap: SvgLineCap,
1158 pub end_cap: SvgLineCap,
1162 pub line_join: SvgLineJoin,
1166 pub line_width: f32,
1170 pub miter_limit: f32,
1175 pub tolerance: f32,
1180 pub apply_line_width: bool,
1188 pub anti_alias: bool,
1190 pub high_quality_aa: bool,
1192}
1193
1194impl Default for SvgStrokeStyle {
1195 fn default() -> Self {
1196 Self {
1197 dash_pattern: OptionSvgDashPattern::None,
1198 transform: SvgTransform::default(),
1199 start_cap: SvgLineCap::default(),
1200 end_cap: SvgLineCap::default(),
1201 line_join: SvgLineJoin::default(),
1202 line_width: DEFAULT_LINE_WIDTH,
1203 miter_limit: DEFAULT_MITER_LIMIT,
1204 tolerance: DEFAULT_TOLERANCE,
1205 apply_line_width: true,
1206 anti_alias: true,
1207 high_quality_aa: false,
1208 }
1209 }
1210}
1211
1212#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1213#[repr(C)]
1214pub struct SvgDashPattern {
1215 pub offset: f32,
1216 pub length_1: f32,
1217 pub gap_1: f32,
1218 pub length_2: f32,
1219 pub gap_2: f32,
1220 pub length_3: f32,
1221 pub gap_3: f32,
1222}
1223
1224impl_option!(
1225 SvgDashPattern,
1226 OptionSvgDashPattern,
1227 [Debug, Copy, Clone, PartialEq, PartialOrd]
1228);
1229
1230#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1232#[repr(C)]
1233#[derive(Default)]
1234pub enum SvgLineCap {
1235 #[default]
1236 Butt,
1237 Square,
1238 Round,
1239}
1240
1241
1242#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1244#[repr(C)]
1245#[derive(Default)]
1246pub enum SvgLineJoin {
1247 #[default]
1248 Miter,
1249 MiterClip,
1250 Round,
1251 Bevel,
1252}
1253
1254
1255pub use core::ffi::c_void;
1256
1257#[derive(Debug, Clone)]
1258#[repr(C)]
1259pub struct SvgXmlNode {
1260 pub node: *const c_void, pub run_destructor: bool,
1262}
1263
1264#[derive(Debug, Clone)]
1265#[repr(C)]
1266pub struct Svg {
1267 pub tree: *const c_void, pub run_destructor: bool,
1269}
1270
1271#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1273#[repr(C)]
1274pub enum ShapeRendering {
1275 OptimizeSpeed,
1276 CrispEdges,
1277 GeometricPrecision,
1278}
1279
1280#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1282#[repr(C)]
1283pub enum ImageRendering {
1284 OptimizeQuality,
1285 OptimizeSpeed,
1286}
1287
1288#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1290#[repr(C)]
1291pub enum TextRendering {
1292 OptimizeSpeed,
1293 OptimizeLegibility,
1294 GeometricPrecision,
1295}
1296
1297#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1299#[repr(C)]
1300pub enum FontDatabase {
1301 Empty,
1302 System,
1303}
1304
1305#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1306#[repr(C)]
1307pub struct SvgRenderOptions {
1308 pub target_size: OptionLayoutSize,
1309 pub background_color: OptionColorU,
1310 pub fit: SvgFitTo,
1311 pub transform: SvgRenderTransform,
1312}
1313
1314#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1315#[repr(C)]
1316pub struct SvgRenderTransform {
1317 pub sx: f32,
1318 pub kx: f32,
1319 pub ky: f32,
1320 pub sy: f32,
1321 pub tx: f32,
1322 pub ty: f32,
1323}
1324
1325#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1326#[repr(C, u8)]
1327#[derive(Default)]
1328pub enum SvgFitTo {
1329 #[default]
1330 Original,
1331 Width(u32),
1332 Height(u32),
1333 Zoom(f32),
1334}
1335
1336
1337#[derive(Debug, Clone, PartialEq, PartialOrd)]
1338#[repr(C)]
1339pub struct SvgParseOptions {
1340 pub relative_image_path: OptionString,
1342 pub default_font_family: AzString,
1345 pub languages: StringVec,
1348 pub dpi: f32,
1350 pub font_size: f32,
1353 pub shape_rendering: ShapeRendering,
1356 pub text_rendering: TextRendering,
1359 pub image_rendering: ImageRendering,
1362 pub fontdb: FontDatabase,
1364 pub keep_named_groups: bool,
1367}
1368
1369impl Default for SvgParseOptions {
1370 fn default() -> Self {
1371 let lang_vec: Vec<AzString> = vec![String::from("en").into()];
1372 Self {
1373 relative_image_path: OptionString::None,
1374 default_font_family: "Times New Roman".to_string().into(),
1375 languages: lang_vec.into(),
1376 dpi: 96.0,
1377 font_size: 12.0,
1378 shape_rendering: ShapeRendering::GeometricPrecision,
1379 text_rendering: TextRendering::OptimizeLegibility,
1380 image_rendering: ImageRendering::OptimizeQuality,
1381 fontdb: FontDatabase::System,
1382 keep_named_groups: false,
1383 }
1384 }
1385}
1386
1387#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
1388#[repr(C)]
1389pub struct SvgXmlOptions {
1390 pub use_single_quote: bool,
1391 pub indent: Indent,
1392 pub attributes_indent: Indent,
1393}
1394
1395impl Default for SvgXmlOptions {
1396 fn default() -> Self {
1397 Self {
1398 use_single_quote: false,
1399 indent: Indent::Spaces(2),
1400 attributes_indent: Indent::Spaces(2),
1401 }
1402 }
1403}
1404#[allow(variant_size_differences)] #[derive(Debug, PartialEq, Eq, PartialOrd, Clone)]
1406#[repr(C, u8)]
1407pub enum SvgParseError {
1408 NoParserAvailable,
1409 ElementsLimitReached,
1410 NotAnUtf8Str,
1411 MalformedGZip,
1412 InvalidSize,
1413 ParsingFailed(XmlError),
1414}
1415
1416impl fmt::Display for SvgParseError {
1417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1418 use self::SvgParseError::{NoParserAvailable, ElementsLimitReached, NotAnUtf8Str, MalformedGZip, InvalidSize, ParsingFailed};
1419 match self {
1420 NoParserAvailable => write!(
1421 f,
1422 "Library was compiled without SVG support (no parser available)"
1423 ),
1424 ElementsLimitReached => write!(f, "Error parsing SVG: Elements limit reached"),
1425 NotAnUtf8Str => write!(f, "Error parsing SVG: Not an UTF-8 String"),
1426 MalformedGZip => write!(
1427 f,
1428 "Error parsing SVG: SVG is compressed with a malformed GZIP compression"
1429 ),
1430 InvalidSize => write!(f, "Error parsing SVG: Invalid size"),
1431 ParsingFailed(e) => write!(f, "Error parsing SVG: Parsing SVG as XML failed: {e}"),
1432 }
1433 }
1434}
1435
1436impl_result!(
1437 SvgXmlNode,
1438 SvgParseError,
1439 ResultSvgXmlNodeSvgParseError,
1440 copy = false,
1441 [Debug, Clone]
1442);
1443impl_result!(
1444 Svg,
1445 SvgParseError,
1446 ResultSvgSvgParseError,
1447 copy = false,
1448 [Debug, Clone]
1449);
1450
1451#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
1453#[repr(C, u8)]
1454pub enum Indent {
1455 None,
1456 Spaces(u8),
1457 Tabs,
1458}
1459
1460#[cfg(test)]
1461#[allow(clippy::pedantic, clippy::nursery, clippy::float_cmp)]
1462mod autotest_generated {
1463 use super::*;
1464
1465 fn pt(x: f32, y: f32) -> SvgPoint {
1468 SvgPoint { x, y }
1469 }
1470
1471 fn line_el(x1: f32, y1: f32, x2: f32, y2: f32) -> SvgPathElement {
1472 SvgPathElement::line(SvgLine::new(pt(x1, y1), pt(x2, y2)))
1473 }
1474
1475 fn make_path(items: Vec<SvgPathElement>) -> SvgPath {
1476 SvgPath::create(SvgPathElementVec::from_vec(items))
1477 }
1478
1479 fn quad() -> SvgQuadraticCurve {
1480 SvgQuadraticCurve {
1481 start: pt(0.0, 0.0),
1482 ctrl: pt(5.0, 10.0),
1483 end: pt(10.0, 0.0),
1484 }
1485 }
1486
1487 fn cubic() -> SvgCubicCurve {
1488 SvgCubicCurve {
1489 start: pt(0.0, 0.0),
1490 ctrl_1: pt(0.0, 10.0),
1491 ctrl_2: pt(10.0, 10.0),
1492 end: pt(10.0, 0.0),
1493 }
1494 }
1495
1496 fn rect_contains(outer: &SvgRect, inner: &SvgRect) -> bool {
1498 outer.x <= inner.x
1499 && outer.y <= inner.y
1500 && outer.x + outer.width >= inner.x + inner.width
1501 && outer.y + outer.height >= inner.y + inner.height
1502 }
1503
1504 #[test]
1507 fn svgline_new_keeps_fields_including_extreme_values() {
1508 let l = SvgLine::new(pt(1.0, 2.0), pt(3.0, 4.0));
1509 assert_eq!(l.get_start(), pt(1.0, 2.0));
1510 assert_eq!(l.get_end(), pt(3.0, 4.0));
1511
1512 let extreme = SvgLine::new(pt(f32::MIN, f32::MAX), pt(f32::MAX, f32::MIN));
1513 assert_eq!(extreme.start.x, f32::MIN);
1514 assert_eq!(extreme.end.x, f32::MAX);
1515
1516 let nan = SvgLine::new(pt(f32::NAN, 0.0), pt(0.0, f32::NAN));
1518 assert!(nan.get_start().x.is_nan());
1519 assert!(nan.get_end().y.is_nan());
1520 }
1521
1522 #[test]
1523 fn svgline_reverse_is_an_involution() {
1524 let orig = SvgLine::new(pt(-1.5, 2.5), pt(7.0, -9.0));
1525 let mut l = orig;
1526 l.reverse();
1527 assert_eq!(l.get_start(), orig.get_end());
1528 assert_eq!(l.get_end(), orig.get_start());
1529 l.reverse();
1530 assert_eq!(l, orig);
1531 }
1532
1533 #[test]
1534 fn svgline_reverse_does_not_panic_on_extreme_values() {
1535 let mut l = SvgLine::new(pt(f32::INFINITY, f32::NAN), pt(f32::NEG_INFINITY, f32::MAX));
1536 l.reverse();
1537 assert!(l.get_start().x.is_infinite() && l.get_start().x < 0.0);
1538 assert!(l.get_end().y.is_nan());
1539 }
1540
1541 #[test]
1544 fn svgline_inwards_normal_is_unit_length_and_90deg_right() {
1545 let l = SvgLine::new(pt(0.0, 0.0), pt(10.0, 0.0));
1547 let n = l.inwards_normal().expect("non-degenerate line has a normal");
1548 assert!((n.x - 0.0).abs() < 1e-6);
1549 assert!((n.y - 1.0).abs() < 1e-6);
1550 let len = (n.x * n.x + n.y * n.y).sqrt();
1551 assert!((len - 1.0).abs() < 1e-6, "normal must be unit length");
1552 }
1553
1554 #[test]
1555 fn svgline_outwards_normal_is_the_negated_inwards_normal() {
1556 let l = SvgLine::new(pt(3.0, -4.0), pt(-7.0, 11.0));
1557 let i = l.inwards_normal().expect("non-degenerate line has a normal");
1558 let o = l.outwards_normal().expect("non-degenerate line has a normal");
1559 assert!((i.x + o.x).abs() < 1e-6);
1560 assert!((i.y + o.y).abs() < 1e-6);
1561 }
1562
1563 #[test]
1564 fn svgline_normals_are_none_for_zero_length_line() {
1565 let l = SvgLine::new(pt(5.0, 5.0), pt(5.0, 5.0));
1567 assert_eq!(l.inwards_normal(), None);
1568 assert_eq!(l.outwards_normal(), None);
1569 }
1570
1571 #[test]
1572 fn svgline_normals_are_none_for_nan_and_infinite_coords() {
1573 let nan = SvgLine::new(pt(f32::NAN, 0.0), pt(1.0, 1.0));
1574 assert_eq!(nan.inwards_normal(), None);
1575 assert_eq!(nan.outwards_normal(), None);
1576
1577 let inf = SvgLine::new(pt(f32::NEG_INFINITY, f32::NEG_INFINITY), pt(1.0, 1.0));
1579 assert_eq!(inf.inwards_normal(), None);
1580 assert_eq!(inf.outwards_normal(), None);
1581 }
1582
1583 #[test]
1584 fn svgline_inwards_normal_on_overflowing_line_stays_defined() {
1585 let l = SvgLine::new(pt(-f32::MAX, -f32::MAX), pt(f32::MAX, f32::MAX));
1588 match l.inwards_normal() {
1589 None => {}
1590 Some(n) => assert!(
1591 n.x.is_finite() && n.y.is_finite(),
1592 "inwards_normal returned a non-finite point: {n:?}"
1593 ),
1594 }
1595 }
1596
1597 #[test]
1600 fn svgline_get_x_y_at_t_hit_the_endpoints_exactly() {
1601 let l = SvgLine::new(pt(2.0, -3.0), pt(12.0, 17.0));
1602 assert_eq!(l.get_x_at_t(0.0), 2.0);
1603 assert_eq!(l.get_y_at_t(0.0), -3.0);
1604 assert_eq!(l.get_x_at_t(1.0), 12.0);
1605 assert_eq!(l.get_y_at_t(1.0), 17.0);
1606 assert!((l.get_x_at_t(0.5) - 7.0).abs() < 1e-9);
1607 assert!((l.get_y_at_t(0.5) - 7.0).abs() < 1e-9);
1608 }
1609
1610 #[test]
1611 fn svgline_get_x_at_t_extrapolates_for_out_of_range_t() {
1612 let l = SvgLine::new(pt(0.0, 0.0), pt(10.0, 10.0));
1614 assert!((l.get_x_at_t(-1.0) - -10.0).abs() < 1e-9);
1615 assert!((l.get_y_at_t(2.0) - 20.0).abs() < 1e-9);
1616 }
1617
1618 #[test]
1619 fn svgline_get_x_y_at_t_nan_and_inf_do_not_panic() {
1620 let l = SvgLine::new(pt(0.0, 0.0), pt(10.0, 10.0));
1621 assert!(l.get_x_at_t(f64::NAN).is_nan());
1622 assert!(l.get_y_at_t(f64::NAN).is_nan());
1623 assert!(l.get_x_at_t(f64::INFINITY).is_infinite());
1624 assert!(l.get_y_at_t(f64::NEG_INFINITY).is_infinite());
1625
1626 let vertical = SvgLine::new(pt(4.0, 0.0), pt(4.0, 10.0));
1628 assert!(vertical.get_x_at_t(f64::INFINITY).is_nan());
1629 }
1630
1631 #[test]
1632 fn svgline_get_x_at_t_at_f32_extremes_saturates_to_inf_not_panic() {
1633 let l = SvgLine::new(pt(-f32::MAX, 0.0), pt(f32::MAX, 0.0));
1634 assert!(l.get_x_at_t(0.5).abs() < 1e-9);
1636 assert!(l.get_x_at_t(1.0).is_finite());
1637 assert!(l.get_x_at_t(f64::MAX).is_infinite());
1638 }
1639
1640 #[test]
1641 fn svgline_get_length_is_euclidean_and_direction_independent() {
1642 let l = SvgLine::new(pt(0.0, 0.0), pt(3.0, 4.0));
1643 assert!((l.get_length() - 5.0).abs() < 1e-6);
1644 let mut r = l;
1645 r.reverse();
1646 assert!((r.get_length() - l.get_length()).abs() < 1e-9);
1647 assert_eq!(SvgLine::new(pt(1.0, 1.0), pt(1.0, 1.0)).get_length(), 0.0);
1648 }
1649
1650 #[test]
1651 fn svgline_get_length_overflow_and_nan_are_defined() {
1652 let huge = SvgLine::new(pt(-f32::MAX, 0.0), pt(f32::MAX, 0.0));
1654 let len = huge.get_length();
1655 assert!(len.is_infinite() && len > 0.0);
1656
1657 let nan = SvgLine::new(pt(f32::NAN, 0.0), pt(0.0, 0.0));
1658 assert!(nan.get_length().is_nan());
1659 }
1660
1661 #[test]
1662 fn svgline_get_t_at_offset_maps_arc_length_to_t() {
1663 let l = SvgLine::new(pt(0.0, 0.0), pt(10.0, 0.0));
1664 assert_eq!(l.get_t_at_offset(0.0), 0.0);
1665 assert!((l.get_t_at_offset(5.0) - 0.5).abs() < 1e-6);
1666 assert!((l.get_t_at_offset(10.0) - 1.0).abs() < 1e-6);
1667 assert!((l.get_t_at_offset(-5.0) + 0.5).abs() < 1e-6);
1669 assert!((l.get_t_at_offset(20.0) - 2.0).abs() < 1e-6);
1670 }
1671
1672 #[test]
1673 fn svgline_get_t_at_offset_on_zero_length_line_is_nan_or_inf_not_a_panic() {
1674 let l = SvgLine::new(pt(1.0, 1.0), pt(1.0, 1.0));
1676 assert!(l.get_t_at_offset(0.0).is_nan());
1677 assert!(l.get_t_at_offset(5.0).is_infinite());
1678 assert!(l.get_t_at_offset(-5.0).is_infinite());
1679 assert!(l.get_t_at_offset(f64::NAN).is_nan());
1680 }
1681
1682 #[test]
1683 fn svgline_get_t_at_offset_round_trips_through_get_x_at_t() {
1684 let l = SvgLine::new(pt(2.0, 2.0), pt(2.0, 12.0));
1685 let t = l.get_t_at_offset(l.get_length());
1686 assert!((l.get_y_at_t(t) - 12.0).abs() < 1e-6);
1687 assert!((l.get_x_at_t(t) - 2.0).abs() < 1e-6);
1688 }
1689
1690 #[test]
1691 fn svgline_tangent_vector_is_normalized_and_zero_for_degenerate_lines() {
1692 let l = SvgLine::new(pt(0.0, 0.0), pt(0.0, 5.0));
1693 let t = l.get_tangent_vector_at_t();
1694 assert!((t.x - 0.0).abs() < 1e-9);
1695 assert!((t.y - 1.0).abs() < 1e-9);
1696
1697 let degenerate = SvgLine::new(pt(3.0, 3.0), pt(3.0, 3.0));
1699 let t = degenerate.get_tangent_vector_at_t();
1700 assert_eq!(t, SvgVector { x: 0.0, y: 0.0 });
1701 }
1702
1703 #[test]
1706 fn svgline_get_bounds_is_orientation_independent() {
1707 let l = SvgLine::new(pt(10.0, 20.0), pt(-5.0, 4.0));
1708 let mut r = l;
1709 r.reverse();
1710 assert_eq!(l.get_bounds(), r.get_bounds());
1711
1712 let b = l.get_bounds();
1713 assert_eq!(b.x, -5.0);
1714 assert_eq!(b.y, 4.0);
1715 assert_eq!(b.width, 15.0);
1716 assert_eq!(b.height, 16.0);
1717 assert_eq!(b.radius_top_left, 0.0);
1718 }
1719
1720 #[test]
1721 fn svgline_get_bounds_of_degenerate_line_is_zero_sized() {
1722 let b = SvgLine::new(pt(7.0, 8.0), pt(7.0, 8.0)).get_bounds();
1723 assert_eq!((b.x, b.y, b.width, b.height), (7.0, 8.0, 0.0, 0.0));
1724 }
1725
1726 #[test]
1727 fn svgline_get_bounds_overflows_to_inf_width_without_panicking() {
1728 let b = SvgLine::new(pt(-f32::MAX, 0.0), pt(f32::MAX, 1.0)).get_bounds();
1730 assert!(b.width.is_infinite() && b.width > 0.0);
1731 assert_eq!(b.x, -f32::MAX);
1732 assert_eq!(b.height, 1.0);
1733 }
1734
1735 #[test]
1738 fn svgpathelement_constructors_wrap_the_right_variant() {
1739 let l = SvgLine::new(pt(0.0, 0.0), pt(1.0, 1.0));
1740 assert!(matches!(SvgPathElement::line(l), SvgPathElement::Line(_)));
1741 assert!(matches!(
1742 SvgPathElement::quadratic_curve(quad()),
1743 SvgPathElement::QuadraticCurve(_)
1744 ));
1745 assert!(matches!(
1746 SvgPathElement::cubic_curve(cubic()),
1747 SvgPathElement::CubicCurve(_)
1748 ));
1749 }
1750
1751 #[test]
1752 fn svgpathelement_constructors_accept_extreme_geometry() {
1753 let l = SvgLine::new(pt(f32::NAN, f32::INFINITY), pt(f32::MIN, f32::MAX));
1754 let el = SvgPathElement::line(l);
1755 assert!(el.get_start().x.is_nan());
1756 assert_eq!(el.get_end().x, f32::MIN);
1757 }
1758
1759 #[test]
1762 fn svgpathelement_set_first_last_round_trip_for_every_variant() {
1763 let a = pt(-1.0, -2.0);
1764 let b = pt(3.0, 4.0);
1765
1766 for mut el in [
1767 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(1.0, 1.0))),
1768 SvgPathElement::quadratic_curve(quad()),
1769 SvgPathElement::cubic_curve(cubic()),
1770 ] {
1771 el.set_first(a);
1772 el.set_last(b);
1773 assert_eq!(el.get_start(), a);
1774 assert_eq!(el.get_end(), b);
1775 }
1776 }
1777
1778 #[test]
1779 fn svgpathelement_set_first_last_accept_zero_min_max_and_nan() {
1780 let mut el = SvgPathElement::cubic_curve(cubic());
1781
1782 el.set_first(pt(0.0, 0.0));
1783 el.set_last(pt(0.0, 0.0));
1784 assert_eq!(el.get_start(), pt(0.0, 0.0));
1785 assert_eq!(el.get_end(), pt(0.0, 0.0));
1786
1787 el.set_first(pt(f32::MIN, f32::MIN));
1788 el.set_last(pt(f32::MAX, f32::MAX));
1789 assert_eq!(el.get_start().x, f32::MIN);
1790 assert_eq!(el.get_end().x, f32::MAX);
1791
1792 el.set_first(pt(f32::NAN, f32::NEG_INFINITY));
1793 assert!(el.get_start().x.is_nan());
1794 assert!(el.get_start().y.is_infinite());
1795 }
1796
1797 #[test]
1798 fn svgpathelement_reverse_is_an_involution_for_every_variant() {
1799 for el in [
1800 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(1.0, 1.0))),
1801 SvgPathElement::quadratic_curve(quad()),
1802 SvgPathElement::cubic_curve(cubic()),
1803 ] {
1804 let mut r = el;
1805 r.reverse();
1806 assert_eq!(r.get_start(), el.get_end());
1807 assert_eq!(r.get_end(), el.get_start());
1808 r.reverse();
1809 assert_eq!(r, el, "reverse() applied twice must be the identity");
1810 }
1811 }
1812
1813 #[test]
1816 fn svgpathelement_get_length_is_non_negative_for_every_variant() {
1817 for el in [
1818 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(3.0, 4.0))),
1819 SvgPathElement::quadratic_curve(quad()),
1820 SvgPathElement::cubic_curve(cubic()),
1821 ] {
1822 let len = el.get_length();
1823 assert!(len.is_finite() && len >= 0.0, "bad length: {len}");
1824 }
1825 }
1826
1827 #[test]
1828 fn svgpathelement_get_x_y_at_t_hit_endpoints_for_every_variant() {
1829 for el in [
1830 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(10.0, 0.0))),
1831 SvgPathElement::quadratic_curve(quad()),
1832 SvgPathElement::cubic_curve(cubic()),
1833 ] {
1834 assert!((el.get_x_at_t(0.0) - f64::from(el.get_start().x)).abs() < 1e-6);
1835 assert!((el.get_y_at_t(0.0) - f64::from(el.get_start().y)).abs() < 1e-6);
1836 assert!((el.get_x_at_t(1.0) - f64::from(el.get_end().x)).abs() < 1e-6);
1837 assert!((el.get_y_at_t(1.0) - f64::from(el.get_end().y)).abs() < 1e-6);
1838 }
1839 }
1840
1841 #[test]
1842 fn svgpathelement_get_x_y_at_t_nan_inf_do_not_panic() {
1843 for el in [
1844 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(10.0, 0.0))),
1845 SvgPathElement::quadratic_curve(quad()),
1846 SvgPathElement::cubic_curve(cubic()),
1847 ] {
1848 assert!(el.get_x_at_t(f64::NAN).is_nan());
1849 assert!(el.get_y_at_t(f64::NAN).is_nan());
1850 let _ = el.get_x_at_t(f64::INFINITY);
1852 let _ = el.get_y_at_t(f64::NEG_INFINITY);
1853 let _ = el.get_x_at_t(f64::MIN);
1854 let _ = el.get_y_at_t(f64::MAX);
1855 }
1856 }
1857
1858 #[test]
1859 fn svgpathelement_line_tangent_ignores_t_even_when_t_is_nan() {
1860 let el = SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(0.0, 8.0)));
1862 let at_half = el.get_tangent_vector_at_t(0.5);
1863 assert_eq!(el.get_tangent_vector_at_t(f64::NAN), at_half);
1864 assert_eq!(el.get_tangent_vector_at_t(f64::INFINITY), at_half);
1865 assert_eq!(at_half, SvgVector { x: 0.0, y: 1.0 });
1866 }
1867
1868 #[test]
1869 fn svgpathelement_curve_tangent_at_nan_is_nan_not_a_panic() {
1870 let el = SvgPathElement::quadratic_curve(quad());
1871 let v = el.get_tangent_vector_at_t(f64::NAN);
1872 assert!(v.x.is_nan() && v.y.is_nan());
1873 }
1874
1875 #[test]
1876 fn svgpathelement_curve_tangent_is_unit_length_in_range() {
1877 for el in [
1878 SvgPathElement::quadratic_curve(quad()),
1879 SvgPathElement::cubic_curve(cubic()),
1880 ] {
1881 for t in [0.0_f64, 0.25, 0.5, 0.75, 1.0] {
1882 let v = el.get_tangent_vector_at_t(t);
1883 let len = (v.x * v.x + v.y * v.y).sqrt();
1884 assert!(
1886 len.abs() < 1e-9 || (len - 1.0).abs() < 1e-6,
1887 "tangent at t={t} has length {len}"
1888 );
1889 }
1890 }
1891 }
1892
1893 #[test]
1894 fn svgpathelement_curve_t_at_offset_saturates_at_1_past_the_end() {
1895 for el in [
1898 SvgPathElement::quadratic_curve(quad()),
1899 SvgPathElement::cubic_curve(cubic()),
1900 ] {
1901 assert!((el.get_t_at_offset(f64::MAX) - 1.0).abs() < 1e-9);
1902 assert!((el.get_t_at_offset(1.0e30) - 1.0).abs() < 1e-9);
1903 assert!((el.get_t_at_offset(f64::NAN) - 1.0).abs() < 1e-9);
1905 }
1906 }
1907
1908 #[test]
1909 fn svgpathelement_curve_t_at_offset_is_monotonic_and_in_range() {
1910 let el = SvgPathElement::cubic_curve(cubic());
1911 let len = el.get_length();
1912 let t_quarter = el.get_t_at_offset(len * 0.25);
1913 let t_half = el.get_t_at_offset(len * 0.5);
1914 assert!(t_quarter.is_finite() && t_half.is_finite());
1915 assert!((0.0..=1.0).contains(&t_quarter), "t={t_quarter}");
1916 assert!((0.0..=1.0).contains(&t_half), "t={t_half}");
1917 assert!(t_quarter <= t_half, "t must not decrease with offset");
1918 }
1919
1920 #[test]
1921 fn svgpathelement_curve_t_at_offset_negative_offset_is_non_positive() {
1922 let el = SvgPathElement::cubic_curve(cubic());
1923 let t = el.get_t_at_offset(-10.0);
1924 assert!(t.is_finite(), "negative offset produced {t}");
1925 assert!(t <= 0.0, "negative offset must not map to a forward t: {t}");
1926 }
1927
1928 #[test]
1929 fn svgpathelement_get_bounds_contains_both_endpoints() {
1930 for el in [
1931 SvgPathElement::line(SvgLine::new(pt(-3.0, 2.0), pt(9.0, -4.0))),
1932 SvgPathElement::quadratic_curve(quad()),
1933 SvgPathElement::cubic_curve(cubic()),
1934 ] {
1935 let b = el.get_bounds();
1936 let (s, e) = (el.get_start(), el.get_end());
1937 assert!(b.width >= 0.0 && b.height >= 0.0);
1938 assert!(s.x >= b.x && s.x <= b.x + b.width);
1939 assert!(e.x >= b.x && e.x <= b.x + b.width);
1940 assert!(s.y >= b.y && s.y <= b.y + b.height);
1941 assert!(e.y >= b.y && e.y <= b.y + b.height);
1942 }
1943 }
1944
1945 #[test]
1948 fn svgpath_empty_getters_are_none_and_bounds_are_default() {
1949 let p = make_path(Vec::new());
1950 assert_eq!(p.get_start(), None);
1951 assert_eq!(p.get_end(), None);
1952 assert!(!p.is_closed(), "an empty path is not closed");
1953 assert_eq!(p.get_bounds(), SvgRect::default());
1954 }
1955
1956 #[test]
1957 fn svgpath_start_and_end_come_from_first_and_last_element() {
1958 let p = make_path(vec![
1959 line_el(0.0, 0.0, 1.0, 1.0),
1960 line_el(1.0, 1.0, 5.0, 5.0),
1961 line_el(5.0, 5.0, 9.0, 2.0),
1962 ]);
1963 assert_eq!(p.get_start(), Some(pt(0.0, 0.0)));
1964 assert_eq!(p.get_end(), Some(pt(9.0, 2.0)));
1965 }
1966
1967 #[test]
1968 fn svgpath_close_makes_is_closed_true_and_is_idempotent() {
1969 let mut p = make_path(vec![
1970 line_el(0.0, 0.0, 10.0, 0.0),
1971 line_el(10.0, 0.0, 10.0, 10.0),
1972 ]);
1973 assert!(!p.is_closed());
1974 p.close();
1975 assert!(p.is_closed(), "close() must establish is_closed()");
1976 assert_eq!(p.items.len(), 3);
1977 assert_eq!(p.get_end(), p.get_start());
1978
1979 p.close();
1981 assert_eq!(p.items.len(), 3);
1982 }
1983
1984 #[test]
1985 fn svgpath_close_on_empty_path_is_a_noop() {
1986 let mut p = make_path(Vec::new());
1987 p.close();
1988 assert_eq!(p.items.len(), 0);
1989 assert!(!p.is_closed());
1990 }
1991
1992 #[test]
1993 fn svgpath_close_with_nan_coords_appends_once_and_does_not_panic() {
1994 let mut p = make_path(vec![line_el(f32::NAN, 0.0, 10.0, 0.0)]);
1997 p.close();
1998 assert_eq!(p.items.len(), 2);
1999 assert!(!p.is_closed(), "a NaN start point can never compare equal");
2000 }
2001
2002 #[test]
2003 fn svgpath_is_closed_for_single_degenerate_element() {
2004 let p = make_path(vec![line_el(4.0, 4.0, 4.0, 4.0)]);
2005 assert!(p.is_closed(), "start == end for the only element");
2006
2007 let open = make_path(vec![line_el(4.0, 4.0, 5.0, 4.0)]);
2008 assert!(!open.is_closed());
2009 }
2010
2011 #[test]
2012 fn svgpath_reverse_is_an_involution_and_swaps_endpoints() {
2013 let orig = make_path(vec![
2014 line_el(0.0, 0.0, 1.0, 1.0),
2015 SvgPathElement::cubic_curve(cubic()),
2016 line_el(10.0, 0.0, 20.0, 5.0),
2017 ]);
2018
2019 let mut p = orig.clone();
2020 p.reverse();
2021 assert_eq!(p.get_start(), orig.get_end());
2022 assert_eq!(p.get_end(), orig.get_start());
2023 assert_eq!(p.items.len(), orig.items.len());
2024
2025 p.reverse();
2026 assert_eq!(p, orig, "reverse() applied twice must be the identity");
2027 }
2028
2029 #[test]
2030 fn svgpath_reverse_on_empty_path_does_not_panic() {
2031 let mut p = make_path(Vec::new());
2032 p.reverse();
2033 assert_eq!(p.items.len(), 0);
2034 }
2035
2036 #[test]
2037 fn svgpath_join_with_interpolates_the_join_point() {
2038 let mut a = make_path(vec![line_el(0.0, 0.0, 10.0, 0.0)]);
2039 let b = make_path(vec![line_el(20.0, 10.0, 30.0, 10.0)]);
2040
2041 assert_eq!(a.join_with(b), Some(()));
2042 assert_eq!(a.items.len(), 2);
2043
2044 let mid = pt(15.0, 5.0);
2046 assert_eq!(a.items.as_ref()[0].get_end(), mid);
2047 assert_eq!(a.items.as_ref()[1].get_start(), mid);
2048 assert_eq!(a.get_start(), Some(pt(0.0, 0.0)));
2049 assert_eq!(a.get_end(), Some(pt(30.0, 10.0)));
2050 }
2051
2052 #[test]
2053 fn svgpath_join_with_empty_other_returns_none_and_leaves_self_intact() {
2054 let mut a = make_path(vec![line_el(0.0, 0.0, 10.0, 0.0)]);
2055 let before = a.clone();
2056 assert_eq!(a.join_with(make_path(Vec::new())), None);
2057 assert_eq!(a, before, "a failed join must not corrupt the receiver");
2058 }
2059
2060 #[test]
2061 fn svgpath_join_with_on_empty_self_returns_none_without_underflow() {
2062 let mut a = make_path(Vec::new());
2065 let b = make_path(vec![line_el(0.0, 0.0, 1.0, 1.0)]);
2066 assert_eq!(a.join_with(b), None);
2067 assert_eq!(a.items.len(), 0);
2068 }
2069
2070 #[test]
2071 fn svgpath_join_with_extreme_coords_does_not_panic() {
2072 let mut a = make_path(vec![line_el(0.0, 0.0, f32::MAX, f32::MAX)]);
2073 let b = make_path(vec![line_el(-f32::MAX, -f32::MAX, 0.0, 0.0)]);
2074 assert_eq!(a.join_with(b), Some(()));
2075 let join = a.items.as_ref()[0].get_end();
2077 assert!(join.x.is_finite() && join.y.is_finite(), "join: {join:?}");
2078 }
2079
2080 #[test]
2081 fn svgpath_get_bounds_unions_every_element() {
2082 let p = make_path(vec![
2083 line_el(0.0, 0.0, 10.0, 0.0),
2084 line_el(10.0, 0.0, 10.0, 20.0),
2085 line_el(10.0, 20.0, -5.0, -8.0),
2086 ]);
2087 let b = p.get_bounds();
2088 assert_eq!(b.x, -5.0);
2089 assert_eq!(b.y, -8.0);
2090 assert_eq!(b.width, 15.0);
2091 assert_eq!(b.height, 28.0);
2092
2093 for el in p.items.as_ref() {
2094 assert!(
2095 rect_contains(&b, &el.get_bounds()),
2096 "path bounds must contain every element's bounds"
2097 );
2098 }
2099 }
2100
2101 #[test]
2104 fn svgmultipolygon_empty_has_default_bounds() {
2105 let mp = SvgMultiPolygon::create(SvgPathVec::from_vec(Vec::new()));
2106 assert_eq!(mp.get_bounds(), SvgRect::default());
2107 }
2108
2109 #[test]
2110 fn svgmultipolygon_bounds_union_all_rings() {
2111 let mp = SvgMultiPolygon::create(SvgPathVec::from_vec(vec![
2112 make_path(vec![line_el(0.0, 0.0, 10.0, 10.0)]),
2113 make_path(vec![line_el(-4.0, 30.0, 2.0, 33.0)]),
2114 ]));
2115 let b = mp.get_bounds();
2116 assert_eq!(b.x, -4.0);
2117 assert_eq!(b.y, 0.0);
2118 assert_eq!(b.width, 14.0);
2119 assert_eq!(b.height, 33.0);
2120 }
2121
2122 #[test]
2123 fn svgmultipolygon_bounds_must_contain_geometry_after_an_empty_first_ring() {
2124 let mp = SvgMultiPolygon::create(SvgPathVec::from_vec(vec![
2128 make_path(Vec::new()),
2129 make_path(vec![line_el(100.0, 100.0, 200.0, 200.0)]),
2130 ]));
2131 let b = mp.get_bounds();
2132 let expected = SvgRect {
2133 width: 100.0,
2134 height: 100.0,
2135 x: 100.0,
2136 y: 100.0,
2137 ..SvgRect::default()
2138 };
2139 assert!(
2140 rect_contains(&b, &expected),
2141 "bounds {b:?} must contain the geometry of the non-empty ring {expected:?}"
2142 );
2143 }
2144
2145 #[test]
2148 fn svgsimplenode_is_closed_per_variant() {
2149 let circle = SvgCircle {
2150 center_x: 0.0,
2151 center_y: 0.0,
2152 radius: 5.0,
2153 };
2154 assert!(SvgSimpleNode::Circle(circle).is_closed());
2155 assert!(SvgSimpleNode::CircleHole(circle).is_closed());
2156 assert!(SvgSimpleNode::Rect(SvgRect::default()).is_closed());
2157 assert!(SvgSimpleNode::RectHole(SvgRect::default()).is_closed());
2158
2159 assert!(!SvgSimpleNode::Path(make_path(vec![line_el(0.0, 0.0, 1.0, 0.0)])).is_closed());
2160 assert!(SvgSimpleNode::Path(make_path(vec![line_el(2.0, 2.0, 2.0, 2.0)])).is_closed());
2161 assert!(!SvgSimpleNode::Path(make_path(Vec::new())).is_closed());
2163 }
2164
2165 #[test]
2166 fn svgsimplenode_get_bounds_per_variant() {
2167 let circle = SvgCircle {
2168 center_x: 10.0,
2169 center_y: 10.0,
2170 radius: 2.0,
2171 };
2172 let b = SvgSimpleNode::Circle(circle).get_bounds();
2173 assert_eq!((b.x, b.y, b.width, b.height), (8.0, 8.0, 4.0, 4.0));
2174 assert_eq!(SvgSimpleNode::CircleHole(circle).get_bounds(), b);
2175
2176 let rect = SvgRect {
2177 width: 3.0,
2178 height: 4.0,
2179 x: 1.0,
2180 y: 2.0,
2181 ..SvgRect::default()
2182 };
2183 assert_eq!(SvgSimpleNode::Rect(rect).get_bounds(), rect);
2184 assert_eq!(SvgSimpleNode::RectHole(rect).get_bounds(), rect);
2185
2186 assert_eq!(
2188 SvgSimpleNode::Path(make_path(Vec::new())).get_bounds(),
2189 SvgRect::default()
2190 );
2191 }
2192
2193 #[test]
2196 fn svgnode_get_bounds_of_empty_collections_is_default() {
2197 assert_eq!(
2198 SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_vec(Vec::new())).get_bounds(),
2199 SvgRect::default()
2200 );
2201 assert_eq!(
2202 SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(Vec::new())).get_bounds(),
2203 SvgRect::default()
2204 );
2205 assert_eq!(
2206 SvgNode::Path(make_path(Vec::new())).get_bounds(),
2207 SvgRect::default()
2208 );
2209 assert_eq!(
2210 SvgNode::MultiPolygon(SvgMultiPolygon::create(SvgPathVec::from_vec(Vec::new())))
2211 .get_bounds(),
2212 SvgRect::default()
2213 );
2214 }
2215
2216 #[test]
2217 fn svgnode_is_closed_is_vacuously_true_for_empty_collections() {
2218 assert!(SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_vec(Vec::new())).is_closed());
2219 assert!(SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(Vec::new())).is_closed());
2220 assert!(
2221 SvgNode::MultiPolygon(SvgMultiPolygon::create(SvgPathVec::from_vec(Vec::new())))
2222 .is_closed()
2223 );
2224 assert!(!SvgNode::Path(make_path(Vec::new())).is_closed());
2226 }
2227
2228 #[test]
2229 fn svgnode_is_closed_false_when_any_subpath_is_open() {
2230 let open = make_path(vec![line_el(0.0, 0.0, 1.0, 0.0)]);
2231 let mut closed = make_path(vec![
2232 line_el(0.0, 0.0, 1.0, 0.0),
2233 line_el(1.0, 0.0, 1.0, 1.0),
2234 ]);
2235 closed.close();
2236
2237 let mp = SvgMultiPolygon::create(SvgPathVec::from_vec(vec![closed.clone(), open.clone()]));
2238 assert!(!SvgNode::MultiPolygon(mp.clone()).is_closed());
2239 assert!(!SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_vec(vec![mp])).is_closed());
2240 assert!(!SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(vec![
2241 SvgSimpleNode::Path(open),
2242 SvgSimpleNode::Circle(SvgCircle {
2243 center_x: 0.0,
2244 center_y: 0.0,
2245 radius: 1.0,
2246 }),
2247 ]))
2248 .is_closed());
2249
2250 let all_closed = SvgMultiPolygon::create(SvgPathVec::from_vec(vec![closed]));
2251 assert!(SvgNode::MultiPolygon(all_closed).is_closed());
2252 }
2253
2254 #[test]
2255 fn svgnode_get_bounds_contains_all_children() {
2256 let a = make_path(vec![line_el(0.0, 0.0, 10.0, 10.0)]);
2257 let b = make_path(vec![line_el(100.0, 100.0, 200.0, 200.0)]);
2258 let node = SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(vec![
2259 SvgSimpleNode::Path(a.clone()),
2260 SvgSimpleNode::Path(b.clone()),
2261 ]));
2262 let bounds = node.get_bounds();
2263 assert!(rect_contains(&bounds, &a.get_bounds()));
2264 assert!(rect_contains(&bounds, &b.get_bounds()));
2265 }
2266
2267 #[test]
2268 fn svgnode_rect_and_circle_bounds_are_passthrough() {
2269 let rect = SvgRect {
2270 width: 5.0,
2271 height: 6.0,
2272 x: -1.0,
2273 y: -2.0,
2274 ..SvgRect::default()
2275 };
2276 assert_eq!(SvgNode::Rect(rect).get_bounds(), rect);
2277 assert!(SvgNode::Rect(rect).is_closed());
2278
2279 let circle = SvgCircle {
2280 center_x: 0.0,
2281 center_y: 0.0,
2282 radius: 1.0,
2283 };
2284 assert_eq!(SvgNode::Circle(circle).get_bounds(), circle.get_bounds());
2285 assert!(SvgNode::Circle(circle).is_closed());
2286 }
2287
2288 #[test]
2291 fn svgcircle_contains_point_is_strict_and_correct() {
2292 let c = SvgCircle {
2293 center_x: 0.0,
2294 center_y: 0.0,
2295 radius: 1.0,
2296 };
2297 assert!(c.contains_point(0.0, 0.0));
2298 assert!(c.contains_point(0.5, 0.5));
2299 assert!(!c.contains_point(1.0, 0.0));
2301 assert!(!c.contains_point(0.0, -1.0));
2302 assert!(!c.contains_point(2.0, 0.0));
2303 assert!(!c.contains_point(-0.8, -0.8));
2304 }
2305
2306 #[test]
2307 fn svgcircle_zero_radius_contains_nothing_not_even_its_center() {
2308 let c = SvgCircle {
2309 center_x: 3.0,
2310 center_y: 3.0,
2311 radius: 0.0,
2312 };
2313 assert!(!c.contains_point(3.0, 3.0));
2314 assert!(!c.contains_point(0.0, 0.0));
2315 }
2316
2317 #[test]
2318 fn svgcircle_negative_radius_behaves_like_its_absolute_value() {
2319 let neg = SvgCircle {
2322 center_x: 0.0,
2323 center_y: 0.0,
2324 radius: -2.0,
2325 };
2326 let pos = SvgCircle {
2327 center_x: 0.0,
2328 center_y: 0.0,
2329 radius: 2.0,
2330 };
2331 assert_eq!(neg.contains_point(0.0, 0.0), pos.contains_point(0.0, 0.0));
2332 assert_eq!(neg.contains_point(1.9, 0.0), pos.contains_point(1.9, 0.0));
2333 assert_eq!(neg.contains_point(5.0, 0.0), pos.contains_point(5.0, 0.0));
2334 }
2335
2336 #[test]
2337 fn svgcircle_contains_point_nan_and_inf_are_false_not_a_panic() {
2338 let c = SvgCircle {
2339 center_x: 0.0,
2340 center_y: 0.0,
2341 radius: 1.0,
2342 };
2343 assert!(!c.contains_point(f32::NAN, 0.0));
2345 assert!(!c.contains_point(0.0, f32::NAN));
2346 assert!(!c.contains_point(f32::INFINITY, 0.0));
2347 assert!(!c.contains_point(f32::NEG_INFINITY, f32::NEG_INFINITY));
2348
2349 let nan_r = SvgCircle {
2350 center_x: 0.0,
2351 center_y: 0.0,
2352 radius: f32::NAN,
2353 };
2354 assert!(!nan_r.contains_point(0.0, 0.0));
2355 }
2356
2357 #[test]
2358 fn svgcircle_contains_point_at_f32_extremes_does_not_panic() {
2359 let c = SvgCircle {
2360 center_x: 0.0,
2361 center_y: 0.0,
2362 radius: f32::MAX,
2363 };
2364 assert!(!c.contains_point(f32::MAX, f32::MAX));
2366 assert!(c.contains_point(1.0, 1.0));
2367 assert!(!c.contains_point(f32::MIN, 0.0));
2368 }
2369
2370 #[test]
2371 fn svgcircle_get_bounds_is_the_enclosing_square() {
2372 let c = SvgCircle {
2373 center_x: 5.0,
2374 center_y: -5.0,
2375 radius: 2.5,
2376 };
2377 let b = c.get_bounds();
2378 assert_eq!((b.x, b.y, b.width, b.height), (2.5, -7.5, 5.0, 5.0));
2379 assert!(!c.contains_point(b.x + 0.1, b.y + b.height / 2.0));
2381 assert!(c.contains_point(c.center_x, c.center_y));
2382 }
2383
2384 #[test]
2385 fn svgcircle_get_bounds_with_nan_and_huge_radius_does_not_panic() {
2386 let nan = SvgCircle {
2387 center_x: 0.0,
2388 center_y: 0.0,
2389 radius: f32::NAN,
2390 };
2391 let b = nan.get_bounds();
2392 assert!(b.width.is_nan() && b.x.is_nan());
2393
2394 let huge = SvgCircle {
2395 center_x: 0.0,
2396 center_y: 0.0,
2397 radius: f32::MAX,
2398 };
2399 let b = huge.get_bounds();
2400 assert!(b.width.is_infinite() && b.width > 0.0);
2402 assert_eq!(b.x, -f32::MAX);
2403 }
2404
2405 #[test]
2408 fn tessellated_svg_node_empty_is_a_neutral_value() {
2409 let e = TessellatedSvgNode::empty();
2410 assert!(e.vertices.as_ref().is_empty());
2411 assert!(e.indices.as_ref().is_empty());
2412 assert_eq!(e, TessellatedSvgNode::default());
2413 }
2414
2415 #[test]
2416 fn tessellated_colored_svg_node_empty_is_a_neutral_value() {
2417 let e = TessellatedColoredSvgNode::empty();
2418 assert!(e.vertices.as_ref().is_empty());
2419 assert!(e.indices.as_ref().is_empty());
2420 assert_eq!(e, TessellatedColoredSvgNode::default());
2421 }
2422
2423 #[test]
2424 fn tessellated_svg_node_vec_ref_round_trips_through_as_slice() {
2425 let node = TessellatedSvgNode {
2426 vertices: vec![SvgVertex { x: 1.0, y: 2.0 }].into(),
2427 indices: vec![0_u32].into(),
2428 };
2429 let v = TessellatedSvgNodeVec::from_vec(vec![node.clone(), TessellatedSvgNode::empty()]);
2430 let r = v.get_ref();
2431 assert_eq!(r.len, 2);
2432 let slice = r.as_slice();
2433 assert_eq!(slice.len(), 2);
2434 assert_eq!(slice[0], node);
2435 assert_eq!(slice[1], TessellatedSvgNode::empty());
2436 }
2437
2438 #[test]
2439 fn tessellated_svg_node_vec_ref_on_empty_vec_yields_an_empty_slice() {
2440 let v = TessellatedSvgNodeVec::from_vec(Vec::new());
2443 let r = v.get_ref();
2444 assert_eq!(r.len, 0);
2445 assert!(r.as_slice().is_empty());
2446 assert!(!r.ptr.is_null(), "raw ptr must never be null");
2447 }
2448
2449 #[test]
2450 fn tessellated_colored_svg_node_vec_ref_round_trips_through_as_slice() {
2451 let node = TessellatedColoredSvgNode {
2452 vertices: vec![SvgColoredVertex {
2453 x: 1.0,
2454 y: 2.0,
2455 z: 3.0,
2456 r: 1.0,
2457 g: 0.5,
2458 b: 0.25,
2459 a: 1.0,
2460 }]
2461 .into(),
2462 indices: vec![0_u32, 1, 2].into(),
2463 };
2464 let v = TessellatedColoredSvgNodeVec::from_vec(vec![node.clone()]);
2465 let r = v.get_ref();
2466 assert_eq!(r.len, 1);
2467 assert_eq!(r.as_slice().len(), 1);
2468 assert_eq!(r.as_slice()[0], node);
2469 }
2470
2471 #[test]
2472 fn tessellated_colored_svg_node_vec_ref_on_empty_vec_yields_an_empty_slice() {
2473 let v = TessellatedColoredSvgNodeVec::from_vec(Vec::new());
2474 let r = v.get_ref();
2475 assert_eq!(r.len, 0);
2476 assert!(r.as_slice().is_empty());
2477 assert!(!r.ptr.is_null(), "raw ptr must never be null");
2478 }
2479
2480 fn matrix_of(u: &Uniform) -> [f32; 16] {
2483 match u.uniform_type {
2484 UniformType::Matrix4 { transpose, matrix } => {
2485 assert!(!transpose, "SVG shaders expect a pre-transposed matrix");
2486 matrix
2487 }
2488 _ => panic!("expected a Matrix4 uniform"),
2489 }
2490 }
2491
2492 fn bbox_of(u: &Uniform) -> [f32; 2] {
2493 match u.uniform_type {
2494 UniformType::FloatVec2(v) => v,
2495 _ => panic!("expected a FloatVec2 uniform"),
2496 }
2497 }
2498
2499 #[test]
2500 fn compute_svg_transform_uniforms_zero_size_no_transforms_is_finite() {
2501 let (bbox, tf) = compute_svg_transform_uniforms(
2502 PhysicalSizeU32 {
2503 width: 0,
2504 height: 0,
2505 },
2506 &[],
2507 );
2508 assert_eq!(bbox.uniform_name.as_str(), "vBboxSize");
2509 assert_eq!(bbox_of(&bbox), [0.0, 0.0]);
2510
2511 assert_eq!(tf.uniform_name.as_str(), "vTransformMatrix");
2512 let m = matrix_of(&tf);
2513 assert!(
2514 m.iter().all(|f| f.is_finite()),
2515 "a zero-sized target must not produce NaN/inf in the matrix: {m:?}"
2516 );
2517 }
2518
2519 #[test]
2520 fn compute_svg_transform_uniforms_at_u32_max_does_not_panic() {
2521 let (bbox, tf) = compute_svg_transform_uniforms(
2522 PhysicalSizeU32 {
2523 width: u32::MAX,
2524 height: u32::MAX,
2525 },
2526 &[],
2527 );
2528 let b = bbox_of(&bbox);
2529 assert!(b[0].is_finite() && b[0] > 0.0);
2530 assert_eq!(b[0], u32::MAX as f32);
2531 assert_eq!(b[1], u32::MAX as f32);
2532 let m = matrix_of(&tf);
2533 assert!(m.iter().all(|f| f.is_finite()), "matrix: {m:?}");
2534 }
2535
2536 #[test]
2537 fn compute_svg_transform_uniforms_translation_changes_the_matrix() {
2538 let size = PhysicalSizeU32 {
2539 width: 800,
2540 height: 600,
2541 };
2542 let identity = matrix_of(&compute_svg_transform_uniforms(size, &[]).1);
2543 let translated = matrix_of(
2544 &compute_svg_transform_uniforms(
2545 size,
2546 &[
2547 StyleTransform::TranslateX(PixelValue::px(10.0)),
2548 StyleTransform::TranslateY(PixelValue::px(-20.0)),
2549 ],
2550 )
2551 .1,
2552 );
2553 assert!(
2554 translated.iter().all(|f| f.is_finite()),
2555 "matrix: {translated:?}"
2556 );
2557 assert!(
2558 identity != translated,
2559 "a translation must actually alter the transform matrix"
2560 );
2561 }
2562
2563 #[test]
2564 fn compute_svg_transform_uniforms_extreme_translation_does_not_panic() {
2565 let size = PhysicalSizeU32 {
2566 width: 1,
2567 height: 1,
2568 };
2569 let (bbox, tf) = compute_svg_transform_uniforms(
2570 size,
2571 &[
2572 StyleTransform::TranslateX(PixelValue::px(f32::MAX)),
2573 StyleTransform::TranslateY(PixelValue::px(-f32::MAX)),
2574 ],
2575 );
2576 assert_eq!(bbox_of(&bbox), [1.0, 1.0]);
2577 let _ = matrix_of(&tf);
2580 }
2581
2582 #[test]
2585 fn svgstyle_getters_read_through_to_both_variants() {
2586 let fill = SvgStyle::Fill(SvgFillStyle::default());
2587 assert!(fill.get_antialias());
2588 assert!(!fill.get_high_quality_aa());
2589 assert_eq!(fill.get_transform(), SvgTransform::default());
2590
2591 let stroke = SvgStyle::Stroke(SvgStrokeStyle::default());
2592 assert!(stroke.get_antialias());
2593 assert!(!stroke.get_high_quality_aa());
2594 assert_eq!(stroke.get_transform(), SvgTransform::default());
2595 }
2596
2597 #[test]
2598 fn svgstyle_getters_reflect_non_default_fields() {
2599 let transform = SvgTransform {
2600 sx: 2.0,
2601 kx: 0.5,
2602 ky: -0.5,
2603 sy: 3.0,
2604 tx: 10.0,
2605 ty: -10.0,
2606 };
2607 let fill = SvgStyle::Fill(SvgFillStyle {
2608 anti_alias: false,
2609 high_quality_aa: true,
2610 transform,
2611 ..SvgFillStyle::default()
2612 });
2613 assert!(!fill.get_antialias());
2614 assert!(fill.get_high_quality_aa());
2615 assert_eq!(fill.get_transform(), transform);
2616
2617 let stroke = SvgStyle::Stroke(SvgStrokeStyle {
2618 anti_alias: false,
2619 high_quality_aa: true,
2620 transform,
2621 ..SvgStrokeStyle::default()
2622 });
2623 assert!(!stroke.get_antialias());
2624 assert!(stroke.get_high_quality_aa());
2625 assert_eq!(stroke.get_transform(), transform);
2626 }
2627
2628 #[test]
2631 fn svgparseerror_display_is_non_empty_for_every_variant() {
2632 let variants = [
2633 SvgParseError::NoParserAvailable,
2634 SvgParseError::ElementsLimitReached,
2635 SvgParseError::NotAnUtf8Str,
2636 SvgParseError::MalformedGZip,
2637 SvgParseError::InvalidSize,
2638 SvgParseError::ParsingFailed(XmlError::NoRootNode),
2639 ];
2640
2641 for v in &variants {
2642 let s = v.to_string();
2643 assert!(!s.is_empty(), "empty Display output for {v:?}");
2644 assert!(
2645 !s.contains("\u{0}"),
2646 "Display output must not contain NUL bytes"
2647 );
2648 }
2649 }
2650
2651 #[test]
2652 fn svgparseerror_display_of_parsing_failed_embeds_the_inner_error() {
2653 let inner = XmlError::NoRootNode;
2654 let s = SvgParseError::ParsingFailed(inner.clone()).to_string();
2655 assert!(s.starts_with("Error parsing SVG:"), "got: {s}");
2656 assert!(
2657 s.contains(&inner.to_string()),
2658 "outer message {s:?} must embed the inner XmlError message"
2659 );
2660 }
2661}