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 mut bounds: Option<SvgRect> = None;
463 for ring in &self.rings {
464 for item in &ring.items {
465 let item_bounds = item.get_bounds();
466 match &mut bounds {
467 Some(b) => b.union_with(&item_bounds),
468 None => bounds = Some(item_bounds),
469 }
470 }
471 }
472 bounds.unwrap_or_default()
474 }
475}
476
477impl_vec!(SvgPath, SvgPathVec, SvgPathVecDestructor, SvgPathVecDestructorType, SvgPathVecSlice, OptionSvgPath);
478impl_vec_debug!(SvgPath, SvgPathVec);
479impl_vec_clone!(SvgPath, SvgPathVec, SvgPathVecDestructor);
480impl_vec_partialeq!(SvgPath, SvgPathVec);
481impl_vec_partialord!(SvgPath, SvgPathVec);
482
483impl_vec!(SvgMultiPolygon, SvgMultiPolygonVec, SvgMultiPolygonVecDestructor, SvgMultiPolygonVecDestructorType, SvgMultiPolygonVecSlice, OptionSvgMultiPolygon);
484impl_vec_debug!(SvgMultiPolygon, SvgMultiPolygonVec);
485impl_vec_clone!(
486 SvgMultiPolygon,
487 SvgMultiPolygonVec,
488 SvgMultiPolygonVecDestructor
489);
490impl_vec_partialeq!(SvgMultiPolygon, SvgMultiPolygonVec);
491impl_vec_partialord!(SvgMultiPolygon, SvgMultiPolygonVec);
492
493#[derive(Debug, Clone, PartialOrd, PartialEq)]
495#[repr(C, u8)]
496pub enum SvgNode {
497 MultiPolygonCollection(SvgMultiPolygonVec),
499 MultiPolygon(SvgMultiPolygon),
500 MultiShape(SvgSimpleNodeVec),
501 Path(SvgPath),
502 Circle(SvgCircle),
503 Rect(SvgRect),
504}
505
506#[derive(Debug, Clone, PartialOrd, PartialEq)]
508#[repr(C, u8)]
509pub enum SvgSimpleNode {
510 Path(SvgPath),
511 Circle(SvgCircle),
512 Rect(SvgRect),
513 CircleHole(SvgCircle),
514 RectHole(SvgRect),
515}
516
517impl_option!(
518 SvgSimpleNode,
519 OptionSvgSimpleNode,
520 copy = false,
521 [Debug, Clone, PartialOrd, PartialEq]
522);
523
524impl_vec!(SvgSimpleNode, SvgSimpleNodeVec, SvgSimpleNodeVecDestructor, SvgSimpleNodeVecDestructorType, SvgSimpleNodeVecSlice, OptionSvgSimpleNode);
525impl_vec_debug!(SvgSimpleNode, SvgSimpleNodeVec);
526impl_vec_clone!(SvgSimpleNode, SvgSimpleNodeVec, SvgSimpleNodeVecDestructor);
527impl_vec_partialeq!(SvgSimpleNode, SvgSimpleNodeVec);
528impl_vec_partialord!(SvgSimpleNode, SvgSimpleNodeVec);
529
530impl SvgSimpleNode {
531 #[allow(clippy::match_same_arms)]
535 #[must_use] pub fn get_bounds(&self) -> SvgRect {
536 match self {
537 Self::Path(a) => a.get_bounds(),
538 Self::Circle(a) => a.get_bounds(),
539 Self::Rect(a) => *a,
540 Self::CircleHole(a) => a.get_bounds(),
541 Self::RectHole(a) => *a,
542 }
543 }
544 #[must_use] pub fn is_closed(&self) -> bool {
546 match self {
547 Self::Path(a) => a.is_closed(),
548 Self::Circle(_) | Self::Rect(_) | Self::CircleHole(_) | Self::RectHole(_) => true,
549 }
550 }
551}
552
553impl SvgNode {
554 #[must_use] pub fn get_bounds(&self) -> SvgRect {
556 match self {
557 Self::MultiPolygonCollection(a) => {
558 let mut first_mp_bounds = match a.get(0) {
559 Some(s) => s.get_bounds(),
560 None => return SvgRect::default(),
561 };
562 for mp in a.iter().skip(1) {
563 let mp_bounds = mp.get_bounds();
564 first_mp_bounds.union_with(&mp_bounds);
565 }
566
567 first_mp_bounds
568 }
569 Self::MultiPolygon(a) => a.get_bounds(),
570 Self::MultiShape(a) => {
571 let mut first_mp_bounds = match a.get(0) {
572 Some(s) => s.get_bounds(),
573 None => return SvgRect::default(),
574 };
575 for mp in a.iter().skip(1) {
576 let mp_bounds = mp.get_bounds();
577 first_mp_bounds.union_with(&mp_bounds);
578 }
579
580 first_mp_bounds
581 }
582 Self::Path(a) => a.get_bounds(),
583 Self::Circle(a) => a.get_bounds(),
584 Self::Rect(a) => *a,
585 }
586 }
587 #[must_use] pub fn is_closed(&self) -> bool {
589 match self {
590 Self::MultiPolygonCollection(a) => {
591 for mp in a {
592 for p in mp.rings.as_ref() {
593 if !p.is_closed() {
594 return false;
595 }
596 }
597 }
598
599 true
600 }
601 Self::MultiPolygon(a) => {
602 for p in a.rings.as_ref() {
603 if !p.is_closed() {
604 return false;
605 }
606 }
607
608 true
609 }
610 Self::MultiShape(a) => {
611 for p in a.as_ref() {
612 if !p.is_closed() {
613 return false;
614 }
615 }
616
617 true
618 }
619 Self::Path(a) => a.is_closed(),
620 Self::Circle(_) | Self::Rect(_) => true,
621 }
622 }
623}
624
625#[derive(Debug, Clone, PartialOrd, PartialEq)]
627#[repr(C)]
628pub struct SvgStyledNode {
629 pub geometry: SvgNode,
630 pub style: SvgStyle,
631}
632
633#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
635#[repr(C)]
636pub struct SvgVertex {
637 pub x: f32,
638 pub y: f32,
639}
640
641impl_option!(
642 SvgVertex,
643 OptionSvgVertex,
644 [Debug, Copy, Clone, PartialOrd, PartialEq]
645);
646
647impl VertexLayoutDescription for SvgVertex {
648 fn get_description() -> VertexLayout {
649 VertexLayout {
650 fields: vec![VertexAttribute {
651 va_name: String::from("vAttrXY").into(),
652 layout_location: None.into(),
653 attribute_type: VertexAttributeType::Float,
654 item_count: 2,
655 }]
656 .into(),
657 }
658 }
659}
660
661#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
663#[repr(C)]
664pub struct SvgColoredVertex {
665 pub x: f32,
666 pub y: f32,
667 pub z: f32,
668 pub r: f32,
669 pub g: f32,
670 pub b: f32,
671 pub a: f32,
672}
673
674impl_option!(
675 SvgColoredVertex,
676 OptionSvgColoredVertex,
677 [Debug, Copy, Clone, PartialOrd, PartialEq]
678);
679
680impl VertexLayoutDescription for SvgColoredVertex {
681 fn get_description() -> VertexLayout {
682 VertexLayout {
683 fields: vec![
684 VertexAttribute {
685 va_name: String::from("vAttrXY").into(),
686 layout_location: None.into(),
687 attribute_type: VertexAttributeType::Float,
688 item_count: 3,
689 },
690 VertexAttribute {
691 va_name: String::from("vColor").into(),
692 layout_location: None.into(),
693 attribute_type: VertexAttributeType::Float,
694 item_count: 4,
695 },
696 ]
697 .into(),
698 }
699 }
700}
701
702#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
704#[repr(C)]
705pub struct SvgCircle {
706 pub center_x: f32,
707 pub center_y: f32,
708 pub radius: f32,
709}
710
711impl SvgCircle {
712 #[allow(clippy::suboptimal_flops)] #[must_use] pub fn contains_point(&self, x: f32, y: f32) -> bool {
715 let x_diff = libm::fabsf(x - self.center_x);
716 let y_diff = libm::fabsf(y - self.center_y);
717 (x_diff * x_diff) + (y_diff * y_diff) < (self.radius * self.radius)
718 }
719 #[must_use] pub fn get_bounds(&self) -> SvgRect {
721 SvgRect {
722 width: self.radius * 2.0,
723 height: self.radius * 2.0,
724 x: self.center_x - self.radius,
725 y: self.center_y - self.radius,
726 radius_top_left: 0.0,
727 radius_top_right: 0.0,
728 radius_bottom_left: 0.0,
729 radius_bottom_right: 0.0,
730 }
731 }
732}
733
734#[derive(Debug, Clone, PartialEq, PartialOrd)]
735#[repr(C)]
736pub struct TessellatedSvgNode {
737 pub vertices: SvgVertexVec,
738 pub indices: U32Vec,
739}
740
741impl_option!(
742 TessellatedSvgNode,
743 OptionTessellatedSvgNode,
744 copy = false,
745 [Debug, Clone, PartialEq, PartialOrd]
746);
747
748impl Default for TessellatedSvgNode {
749 fn default() -> Self {
750 Self {
751 vertices: Vec::new().into(),
752 indices: Vec::new().into(),
753 }
754 }
755}
756
757impl_vec!(TessellatedSvgNode, TessellatedSvgNodeVec, TessellatedSvgNodeVecDestructor, TessellatedSvgNodeVecDestructorType, TessellatedSvgNodeVecSlice, OptionTessellatedSvgNode);
758impl_vec_debug!(TessellatedSvgNode, TessellatedSvgNodeVec);
759impl_vec_partialord!(TessellatedSvgNode, TessellatedSvgNodeVec);
760impl_vec_clone!(
761 TessellatedSvgNode,
762 TessellatedSvgNodeVec,
763 TessellatedSvgNodeVecDestructor
764);
765impl_vec_partialeq!(TessellatedSvgNode, TessellatedSvgNodeVec);
766
767impl TessellatedSvgNode {
768 #[must_use] pub fn empty() -> Self {
769 Self::default()
770 }
771}
772
773impl TessellatedSvgNodeVec {
774 #[must_use] pub fn get_ref(&self) -> TessellatedSvgNodeVecRef {
775 let slice = self.as_ref();
776 TessellatedSvgNodeVecRef {
777 ptr: slice.as_ptr(),
778 len: slice.len(),
779 }
780 }
781}
782
783impl fmt::Debug for TessellatedSvgNodeVecRef {
784 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
785 self.as_slice().fmt(f)
786 }
787}
788
789#[repr(C)]
791pub struct TessellatedSvgNodeVecRef {
792 pub ptr: *const TessellatedSvgNode,
793 pub len: usize,
794}
795
796impl Clone for TessellatedSvgNodeVecRef {
797 fn clone(&self) -> Self {
798 Self {
799 ptr: self.ptr,
800 len: self.len,
801 }
802 }
803}
804
805impl TessellatedSvgNodeVecRef {
806 #[must_use] pub const fn as_slice(&self) -> &[TessellatedSvgNode] {
807 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
808 }
809}
810
811#[derive(Debug, Clone, PartialEq, PartialOrd)]
812#[repr(C)]
813pub struct TessellatedColoredSvgNode {
814 pub vertices: SvgColoredVertexVec,
815 pub indices: U32Vec,
816}
817
818impl_option!(
819 TessellatedColoredSvgNode,
820 OptionTessellatedColoredSvgNode,
821 copy = false,
822 [Debug, Clone, PartialEq, PartialOrd]
823);
824
825impl Default for TessellatedColoredSvgNode {
826 fn default() -> Self {
827 Self {
828 vertices: Vec::new().into(),
829 indices: Vec::new().into(),
830 }
831 }
832}
833
834impl_vec!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec, TessellatedColoredSvgNodeVecDestructor, TessellatedColoredSvgNodeVecDestructorType, TessellatedColoredSvgNodeVecSlice, OptionTessellatedColoredSvgNode);
835impl_vec_debug!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
836impl_vec_partialord!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
837impl_vec_clone!(
838 TessellatedColoredSvgNode,
839 TessellatedColoredSvgNodeVec,
840 TessellatedColoredSvgNodeVecDestructor
841);
842impl_vec_partialeq!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
843
844impl TessellatedColoredSvgNode {
845 #[must_use] pub fn empty() -> Self {
846 Self::default()
847 }
848}
849
850impl TessellatedColoredSvgNodeVec {
851 #[must_use] pub fn get_ref(&self) -> TessellatedColoredSvgNodeVecRef {
852 let slice = self.as_ref();
853 TessellatedColoredSvgNodeVecRef {
854 ptr: slice.as_ptr(),
855 len: slice.len(),
856 }
857 }
858}
859
860impl fmt::Debug for TessellatedColoredSvgNodeVecRef {
861 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
862 self.as_slice().fmt(f)
863 }
864}
865
866#[repr(C)]
868pub struct TessellatedColoredSvgNodeVecRef {
869 pub ptr: *const TessellatedColoredSvgNode,
870 pub len: usize,
871}
872
873impl Clone for TessellatedColoredSvgNodeVecRef {
874 fn clone(&self) -> Self {
875 Self {
876 ptr: self.ptr,
877 len: self.len,
878 }
879 }
880}
881
882impl TessellatedColoredSvgNodeVecRef {
883 #[must_use] pub const fn as_slice(&self) -> &[TessellatedColoredSvgNode] {
884 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
885 }
886}
887
888impl_vec!(SvgVertex, SvgVertexVec, SvgVertexVecDestructor, SvgVertexVecDestructorType, SvgVertexVecSlice, OptionSvgVertex);
889impl_vec_debug!(SvgVertex, SvgVertexVec);
890impl_vec_partialord!(SvgVertex, SvgVertexVec);
891impl_vec_clone!(SvgVertex, SvgVertexVec, SvgVertexVecDestructor);
892impl_vec_partialeq!(SvgVertex, SvgVertexVec);
893
894impl_vec!(SvgColoredVertex, SvgColoredVertexVec, SvgColoredVertexVecDestructor, SvgColoredVertexVecDestructorType, SvgColoredVertexVecSlice, OptionSvgColoredVertex);
895impl_vec_debug!(SvgColoredVertex, SvgColoredVertexVec);
896impl_vec_partialord!(SvgColoredVertex, SvgColoredVertexVec);
897impl_vec_clone!(
898 SvgColoredVertex,
899 SvgColoredVertexVec,
900 SvgColoredVertexVecDestructor
901);
902impl_vec_partialeq!(SvgColoredVertex, SvgColoredVertexVec);
903
904#[allow(clippy::cast_precision_loss)]
912fn compute_svg_transform_uniforms(
913 target_size: PhysicalSizeU32,
914 transforms: &[StyleTransform],
915) -> (Uniform, Uniform) {
916 let transform_origin = StyleTransformOrigin {
917 x: PixelValue::px(target_size.width as f32 / 2.0),
918 y: PixelValue::px(target_size.height as f32 / 2.0),
919 };
920
921 let computed_transform = ComputedTransform3D::from_style_transform_vec(
922 transforms,
923 &transform_origin,
924 target_size.width as f32,
925 target_size.height as f32,
926 RotationMode::ForWebRender,
927 );
928
929 let m = computed_transform.get_column_major().m;
932 let matrix: [f32; 16] = core::array::from_fn(|i| m[i / 4][i % 4]);
933
934 let bbox_uniform = Uniform {
935 uniform_name: "vBboxSize".into(),
936 uniform_type: UniformType::FloatVec2([
937 target_size.width as f32,
938 target_size.height as f32,
939 ]),
940 };
941
942 let transform_uniform = Uniform {
943 uniform_name: "vTransformMatrix".into(),
944 uniform_type: UniformType::Matrix4 {
945 transpose: false,
946 matrix,
947 },
948 };
949
950 (bbox_uniform, transform_uniform)
951}
952
953#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
954#[repr(C)]
955pub struct TessellatedGPUSvgNode {
956 pub vertex_index_buffer: VertexBuffer,
957}
958
959impl TessellatedGPUSvgNode {
960 #[must_use] pub fn new(node: &TessellatedSvgNode, gl: GlContextPtr) -> Self {
962 let svg_shader_id = gl.ptr.svg_shader;
963 Self {
964 vertex_index_buffer: VertexBuffer::new(
965 gl,
966 svg_shader_id,
967 node.vertices.as_ref(),
968 node.indices.as_ref(),
969 IndexBufferFormat::Triangles,
970 ),
971 }
972 }
973
974 #[allow(clippy::needless_pass_by_value)] pub fn draw(
977 &self,
978 texture: &mut Texture,
979 target_size: PhysicalSizeU32,
980 color: ColorU,
981 transforms: StyleTransformVec,
982 ) -> bool {
983 let (bbox_uniform, transform_uniform) =
984 compute_svg_transform_uniforms(target_size, transforms.as_ref());
985
986 let color: ColorF = color.into();
987
988 let uniforms = [
989 bbox_uniform,
990 Uniform {
991 uniform_name: "fDrawColor".into(),
992 uniform_type: UniformType::FloatVec4([color.r, color.g, color.b, color.a]),
993 },
994 transform_uniform,
995 ];
996
997 GlShader::draw(
998 texture.gl_context.ptr.svg_shader,
999 texture,
1000 &[(&self.vertex_index_buffer, &uniforms[..])],
1001 );
1002
1003 true
1004 }
1005}
1006
1007#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
1008#[repr(C)]
1009pub struct TessellatedColoredGPUSvgNode {
1010 pub vertex_index_buffer: VertexBuffer,
1011}
1012
1013impl TessellatedColoredGPUSvgNode {
1014 #[must_use] pub fn new(node: &TessellatedColoredSvgNode, gl: GlContextPtr) -> Self {
1016 let svg_shader_id = gl.ptr.svg_multicolor_shader;
1017 Self {
1018 vertex_index_buffer: VertexBuffer::new(
1019 gl,
1020 svg_shader_id,
1021 node.vertices.as_ref(),
1022 node.indices.as_ref(),
1023 IndexBufferFormat::Triangles,
1024 ),
1025 }
1026 }
1027
1028 #[allow(clippy::needless_pass_by_value)] pub fn draw(
1031 &self,
1032 texture: &mut Texture,
1033 target_size: PhysicalSizeU32,
1034 transforms: StyleTransformVec,
1035 ) -> bool {
1036 let (bbox_uniform, transform_uniform) =
1037 compute_svg_transform_uniforms(target_size, transforms.as_ref());
1038
1039 #[allow(clippy::tuple_array_conversions)]
1042 let uniforms = [bbox_uniform, transform_uniform];
1043
1044 GlShader::draw(
1045 texture.gl_context.ptr.svg_multicolor_shader,
1046 texture,
1047 &[(&self.vertex_index_buffer, &uniforms[..])],
1048 );
1049
1050 true
1051 }
1052}
1053
1054#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1055#[repr(C, u8)]
1056pub enum SvgStyle {
1057 Fill(SvgFillStyle),
1058 Stroke(SvgStrokeStyle),
1059}
1060
1061impl SvgStyle {
1062 #[must_use] pub const fn get_antialias(&self) -> bool {
1063 match self {
1064 Self::Fill(f) => f.anti_alias,
1065 Self::Stroke(s) => s.anti_alias,
1066 }
1067 }
1068 #[must_use] pub const fn get_high_quality_aa(&self) -> bool {
1069 match self {
1070 Self::Fill(f) => f.high_quality_aa,
1071 Self::Stroke(s) => s.high_quality_aa,
1072 }
1073 }
1074 #[must_use] pub const fn get_transform(&self) -> SvgTransform {
1075 match self {
1076 Self::Fill(f) => f.transform,
1077 Self::Stroke(s) => s.transform,
1078 }
1079 }
1080}
1081#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
1083#[repr(C)]
1084#[derive(Default)]
1085pub enum SvgFillRule {
1086 #[default]
1087 Winding,
1088 EvenOdd,
1089}
1090
1091
1092#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
1093#[repr(C)]
1094pub struct SvgTransform {
1095 pub sx: f32,
1096 pub kx: f32,
1097 pub ky: f32,
1098 pub sy: f32,
1099 pub tx: f32,
1100 pub ty: f32,
1101}
1102
1103#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1104#[repr(C)]
1105pub struct SvgFillStyle {
1106 pub line_join: SvgLineJoin,
1110 pub miter_limit: f32,
1115 pub tolerance: f32,
1120 pub fill_rule: SvgFillRule,
1122 pub transform: SvgTransform,
1125 pub anti_alias: bool,
1127 pub high_quality_aa: bool,
1129}
1130
1131impl Default for SvgFillStyle {
1132 fn default() -> Self {
1133 Self {
1134 line_join: SvgLineJoin::Miter,
1135 miter_limit: DEFAULT_MITER_LIMIT,
1136 tolerance: DEFAULT_TOLERANCE,
1137 fill_rule: SvgFillRule::default(),
1138 transform: SvgTransform::default(),
1139 anti_alias: true,
1140 high_quality_aa: false,
1141 }
1142 }
1143}
1144
1145#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1146#[repr(C)]
1147pub struct SvgStrokeStyle {
1148 pub dash_pattern: OptionSvgDashPattern,
1150 pub transform: SvgTransform,
1153 pub start_cap: SvgLineCap,
1157 pub end_cap: SvgLineCap,
1161 pub line_join: SvgLineJoin,
1165 pub line_width: f32,
1169 pub miter_limit: f32,
1174 pub tolerance: f32,
1179 pub apply_line_width: bool,
1187 pub anti_alias: bool,
1189 pub high_quality_aa: bool,
1191}
1192
1193impl Default for SvgStrokeStyle {
1194 fn default() -> Self {
1195 Self {
1196 dash_pattern: OptionSvgDashPattern::None,
1197 transform: SvgTransform::default(),
1198 start_cap: SvgLineCap::default(),
1199 end_cap: SvgLineCap::default(),
1200 line_join: SvgLineJoin::default(),
1201 line_width: DEFAULT_LINE_WIDTH,
1202 miter_limit: DEFAULT_MITER_LIMIT,
1203 tolerance: DEFAULT_TOLERANCE,
1204 apply_line_width: true,
1205 anti_alias: true,
1206 high_quality_aa: false,
1207 }
1208 }
1209}
1210
1211#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1212#[repr(C)]
1213pub struct SvgDashPattern {
1214 pub offset: f32,
1215 pub length_1: f32,
1216 pub gap_1: f32,
1217 pub length_2: f32,
1218 pub gap_2: f32,
1219 pub length_3: f32,
1220 pub gap_3: f32,
1221}
1222
1223impl_option!(
1224 SvgDashPattern,
1225 OptionSvgDashPattern,
1226 [Debug, Copy, Clone, PartialEq, PartialOrd]
1227);
1228
1229#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1231#[repr(C)]
1232#[derive(Default)]
1233pub enum SvgLineCap {
1234 #[default]
1235 Butt,
1236 Square,
1237 Round,
1238}
1239
1240
1241#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1243#[repr(C)]
1244#[derive(Default)]
1245pub enum SvgLineJoin {
1246 #[default]
1247 Miter,
1248 MiterClip,
1249 Round,
1250 Bevel,
1251}
1252
1253
1254pub use core::ffi::c_void;
1255
1256#[derive(Debug, Clone)]
1257#[repr(C)]
1258pub struct SvgXmlNode {
1259 pub node: *const c_void, pub run_destructor: bool,
1261}
1262
1263#[derive(Debug, Clone)]
1264#[repr(C)]
1265pub struct Svg {
1266 pub tree: *const c_void, pub run_destructor: bool,
1268}
1269
1270#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1272#[repr(C)]
1273pub enum ShapeRendering {
1274 OptimizeSpeed,
1275 CrispEdges,
1276 GeometricPrecision,
1277}
1278
1279#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1281#[repr(C)]
1282pub enum ImageRendering {
1283 OptimizeQuality,
1284 OptimizeSpeed,
1285}
1286
1287#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1289#[repr(C)]
1290pub enum TextRendering {
1291 OptimizeSpeed,
1292 OptimizeLegibility,
1293 GeometricPrecision,
1294}
1295
1296#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1298#[repr(C)]
1299pub enum FontDatabase {
1300 Empty,
1301 System,
1302}
1303
1304#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1305#[repr(C)]
1306pub struct SvgRenderOptions {
1307 pub target_size: OptionLayoutSize,
1308 pub background_color: OptionColorU,
1309 pub fit: SvgFitTo,
1310 pub transform: SvgRenderTransform,
1311}
1312
1313#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1314#[repr(C)]
1315pub struct SvgRenderTransform {
1316 pub sx: f32,
1317 pub kx: f32,
1318 pub ky: f32,
1319 pub sy: f32,
1320 pub tx: f32,
1321 pub ty: f32,
1322}
1323
1324#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1325#[repr(C, u8)]
1326#[derive(Default)]
1327pub enum SvgFitTo {
1328 #[default]
1329 Original,
1330 Width(u32),
1331 Height(u32),
1332 Zoom(f32),
1333}
1334
1335
1336#[derive(Debug, Clone, PartialEq, PartialOrd)]
1337#[repr(C)]
1338pub struct SvgParseOptions {
1339 pub relative_image_path: OptionString,
1341 pub default_font_family: AzString,
1344 pub languages: StringVec,
1347 pub dpi: f32,
1349 pub font_size: f32,
1352 pub shape_rendering: ShapeRendering,
1355 pub text_rendering: TextRendering,
1358 pub image_rendering: ImageRendering,
1361 pub fontdb: FontDatabase,
1363 pub keep_named_groups: bool,
1366}
1367
1368impl Default for SvgParseOptions {
1369 fn default() -> Self {
1370 let lang_vec: Vec<AzString> = vec![String::from("en").into()];
1371 Self {
1372 relative_image_path: OptionString::None,
1373 default_font_family: "Times New Roman".to_string().into(),
1374 languages: lang_vec.into(),
1375 dpi: 96.0,
1376 font_size: 12.0,
1377 shape_rendering: ShapeRendering::GeometricPrecision,
1378 text_rendering: TextRendering::OptimizeLegibility,
1379 image_rendering: ImageRendering::OptimizeQuality,
1380 fontdb: FontDatabase::System,
1381 keep_named_groups: false,
1382 }
1383 }
1384}
1385
1386#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
1387#[repr(C)]
1388pub struct SvgXmlOptions {
1389 pub use_single_quote: bool,
1390 pub indent: Indent,
1391 pub attributes_indent: Indent,
1392}
1393
1394impl Default for SvgXmlOptions {
1395 fn default() -> Self {
1396 Self {
1397 use_single_quote: false,
1398 indent: Indent::Spaces(2),
1399 attributes_indent: Indent::Spaces(2),
1400 }
1401 }
1402}
1403#[allow(variant_size_differences)] #[derive(Debug, PartialEq, Eq, PartialOrd, Clone)]
1405#[repr(C, u8)]
1406pub enum SvgParseError {
1407 NoParserAvailable,
1408 ElementsLimitReached,
1409 NotAnUtf8Str,
1410 MalformedGZip,
1411 InvalidSize,
1412 ParsingFailed(XmlError),
1413}
1414
1415impl fmt::Display for SvgParseError {
1416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1417 use self::SvgParseError::{NoParserAvailable, ElementsLimitReached, NotAnUtf8Str, MalformedGZip, InvalidSize, ParsingFailed};
1418 match self {
1419 NoParserAvailable => write!(
1420 f,
1421 "Library was compiled without SVG support (no parser available)"
1422 ),
1423 ElementsLimitReached => write!(f, "Error parsing SVG: Elements limit reached"),
1424 NotAnUtf8Str => write!(f, "Error parsing SVG: Not an UTF-8 String"),
1425 MalformedGZip => write!(
1426 f,
1427 "Error parsing SVG: SVG is compressed with a malformed GZIP compression"
1428 ),
1429 InvalidSize => write!(f, "Error parsing SVG: Invalid size"),
1430 ParsingFailed(e) => write!(f, "Error parsing SVG: Parsing SVG as XML failed: {e}"),
1431 }
1432 }
1433}
1434
1435impl_result!(
1436 SvgXmlNode,
1437 SvgParseError,
1438 ResultSvgXmlNodeSvgParseError,
1439 copy = false,
1440 [Debug, Clone]
1441);
1442impl_result!(
1443 Svg,
1444 SvgParseError,
1445 ResultSvgSvgParseError,
1446 copy = false,
1447 [Debug, Clone]
1448);
1449
1450#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
1452#[repr(C, u8)]
1453pub enum Indent {
1454 None,
1455 Spaces(u8),
1456 Tabs,
1457}
1458
1459#[cfg(test)]
1460#[allow(clippy::pedantic, clippy::nursery, clippy::float_cmp)]
1461mod autotest_generated {
1462 use super::*;
1463
1464 fn pt(x: f32, y: f32) -> SvgPoint {
1467 SvgPoint { x, y }
1468 }
1469
1470 fn line_el(x1: f32, y1: f32, x2: f32, y2: f32) -> SvgPathElement {
1471 SvgPathElement::line(SvgLine::new(pt(x1, y1), pt(x2, y2)))
1472 }
1473
1474 fn make_path(items: Vec<SvgPathElement>) -> SvgPath {
1475 SvgPath::create(SvgPathElementVec::from_vec(items))
1476 }
1477
1478 fn quad() -> SvgQuadraticCurve {
1479 SvgQuadraticCurve {
1480 start: pt(0.0, 0.0),
1481 ctrl: pt(5.0, 10.0),
1482 end: pt(10.0, 0.0),
1483 }
1484 }
1485
1486 fn cubic() -> SvgCubicCurve {
1487 SvgCubicCurve {
1488 start: pt(0.0, 0.0),
1489 ctrl_1: pt(0.0, 10.0),
1490 ctrl_2: pt(10.0, 10.0),
1491 end: pt(10.0, 0.0),
1492 }
1493 }
1494
1495 fn rect_contains(outer: &SvgRect, inner: &SvgRect) -> bool {
1497 outer.x <= inner.x
1498 && outer.y <= inner.y
1499 && outer.x + outer.width >= inner.x + inner.width
1500 && outer.y + outer.height >= inner.y + inner.height
1501 }
1502
1503 #[test]
1506 fn svgline_new_keeps_fields_including_extreme_values() {
1507 let l = SvgLine::new(pt(1.0, 2.0), pt(3.0, 4.0));
1508 assert_eq!(l.get_start(), pt(1.0, 2.0));
1509 assert_eq!(l.get_end(), pt(3.0, 4.0));
1510
1511 let extreme = SvgLine::new(pt(f32::MIN, f32::MAX), pt(f32::MAX, f32::MIN));
1512 assert_eq!(extreme.start.x, f32::MIN);
1513 assert_eq!(extreme.end.x, f32::MAX);
1514
1515 let nan = SvgLine::new(pt(f32::NAN, 0.0), pt(0.0, f32::NAN));
1517 assert!(nan.get_start().x.is_nan());
1518 assert!(nan.get_end().y.is_nan());
1519 }
1520
1521 #[test]
1522 fn svgline_reverse_is_an_involution() {
1523 let orig = SvgLine::new(pt(-1.5, 2.5), pt(7.0, -9.0));
1524 let mut l = orig;
1525 l.reverse();
1526 assert_eq!(l.get_start(), orig.get_end());
1527 assert_eq!(l.get_end(), orig.get_start());
1528 l.reverse();
1529 assert_eq!(l, orig);
1530 }
1531
1532 #[test]
1533 fn svgline_reverse_does_not_panic_on_extreme_values() {
1534 let mut l = SvgLine::new(pt(f32::INFINITY, f32::NAN), pt(f32::NEG_INFINITY, f32::MAX));
1535 l.reverse();
1536 assert!(l.get_start().x.is_infinite() && l.get_start().x < 0.0);
1537 assert!(l.get_end().y.is_nan());
1538 }
1539
1540 #[test]
1543 fn svgline_inwards_normal_is_unit_length_and_90deg_right() {
1544 let l = SvgLine::new(pt(0.0, 0.0), pt(10.0, 0.0));
1546 let n = l.inwards_normal().expect("non-degenerate line has a normal");
1547 assert!((n.x - 0.0).abs() < 1e-6);
1548 assert!((n.y - 1.0).abs() < 1e-6);
1549 let len = (n.x * n.x + n.y * n.y).sqrt();
1550 assert!((len - 1.0).abs() < 1e-6, "normal must be unit length");
1551 }
1552
1553 #[test]
1554 fn svgline_outwards_normal_is_the_negated_inwards_normal() {
1555 let l = SvgLine::new(pt(3.0, -4.0), pt(-7.0, 11.0));
1556 let i = l.inwards_normal().expect("non-degenerate line has a normal");
1557 let o = l.outwards_normal().expect("non-degenerate line has a normal");
1558 assert!((i.x + o.x).abs() < 1e-6);
1559 assert!((i.y + o.y).abs() < 1e-6);
1560 }
1561
1562 #[test]
1563 fn svgline_normals_are_none_for_zero_length_line() {
1564 let l = SvgLine::new(pt(5.0, 5.0), pt(5.0, 5.0));
1566 assert_eq!(l.inwards_normal(), None);
1567 assert_eq!(l.outwards_normal(), None);
1568 }
1569
1570 #[test]
1571 fn svgline_normals_are_none_for_nan_and_infinite_coords() {
1572 let nan = SvgLine::new(pt(f32::NAN, 0.0), pt(1.0, 1.0));
1573 assert_eq!(nan.inwards_normal(), None);
1574 assert_eq!(nan.outwards_normal(), None);
1575
1576 let inf = SvgLine::new(pt(f32::NEG_INFINITY, f32::NEG_INFINITY), pt(1.0, 1.0));
1578 assert_eq!(inf.inwards_normal(), None);
1579 assert_eq!(inf.outwards_normal(), None);
1580 }
1581
1582 #[test]
1583 fn svgline_inwards_normal_on_overflowing_line_stays_defined() {
1584 let l = SvgLine::new(pt(-f32::MAX, -f32::MAX), pt(f32::MAX, f32::MAX));
1587 match l.inwards_normal() {
1588 None => {}
1589 Some(n) => assert!(
1590 n.x.is_finite() && n.y.is_finite(),
1591 "inwards_normal returned a non-finite point: {n:?}"
1592 ),
1593 }
1594 }
1595
1596 #[test]
1599 fn svgline_get_x_y_at_t_hit_the_endpoints_exactly() {
1600 let l = SvgLine::new(pt(2.0, -3.0), pt(12.0, 17.0));
1601 assert_eq!(l.get_x_at_t(0.0), 2.0);
1602 assert_eq!(l.get_y_at_t(0.0), -3.0);
1603 assert_eq!(l.get_x_at_t(1.0), 12.0);
1604 assert_eq!(l.get_y_at_t(1.0), 17.0);
1605 assert!((l.get_x_at_t(0.5) - 7.0).abs() < 1e-9);
1606 assert!((l.get_y_at_t(0.5) - 7.0).abs() < 1e-9);
1607 }
1608
1609 #[test]
1610 fn svgline_get_x_at_t_extrapolates_for_out_of_range_t() {
1611 let l = SvgLine::new(pt(0.0, 0.0), pt(10.0, 10.0));
1613 assert!((l.get_x_at_t(-1.0) - -10.0).abs() < 1e-9);
1614 assert!((l.get_y_at_t(2.0) - 20.0).abs() < 1e-9);
1615 }
1616
1617 #[test]
1618 fn svgline_get_x_y_at_t_nan_and_inf_do_not_panic() {
1619 let l = SvgLine::new(pt(0.0, 0.0), pt(10.0, 10.0));
1620 assert!(l.get_x_at_t(f64::NAN).is_nan());
1621 assert!(l.get_y_at_t(f64::NAN).is_nan());
1622 assert!(l.get_x_at_t(f64::INFINITY).is_infinite());
1623 assert!(l.get_y_at_t(f64::NEG_INFINITY).is_infinite());
1624
1625 let vertical = SvgLine::new(pt(4.0, 0.0), pt(4.0, 10.0));
1627 assert!(vertical.get_x_at_t(f64::INFINITY).is_nan());
1628 }
1629
1630 #[test]
1631 fn svgline_get_x_at_t_at_f32_extremes_saturates_to_inf_not_panic() {
1632 let l = SvgLine::new(pt(-f32::MAX, 0.0), pt(f32::MAX, 0.0));
1633 assert!(l.get_x_at_t(0.5).abs() < 1e-9);
1635 assert!(l.get_x_at_t(1.0).is_finite());
1636 assert!(l.get_x_at_t(f64::MAX).is_infinite());
1637 }
1638
1639 #[test]
1640 fn svgline_get_length_is_euclidean_and_direction_independent() {
1641 let l = SvgLine::new(pt(0.0, 0.0), pt(3.0, 4.0));
1642 assert!((l.get_length() - 5.0).abs() < 1e-6);
1643 let mut r = l;
1644 r.reverse();
1645 assert!((r.get_length() - l.get_length()).abs() < 1e-9);
1646 assert_eq!(SvgLine::new(pt(1.0, 1.0), pt(1.0, 1.0)).get_length(), 0.0);
1647 }
1648
1649 #[test]
1650 fn svgline_get_length_overflow_and_nan_are_defined() {
1651 let huge = SvgLine::new(pt(-f32::MAX, 0.0), pt(f32::MAX, 0.0));
1653 let len = huge.get_length();
1654 assert!(len.is_infinite() && len > 0.0);
1655
1656 let nan = SvgLine::new(pt(f32::NAN, 0.0), pt(0.0, 0.0));
1657 assert!(nan.get_length().is_nan());
1658 }
1659
1660 #[test]
1661 fn svgline_get_t_at_offset_maps_arc_length_to_t() {
1662 let l = SvgLine::new(pt(0.0, 0.0), pt(10.0, 0.0));
1663 assert_eq!(l.get_t_at_offset(0.0), 0.0);
1664 assert!((l.get_t_at_offset(5.0) - 0.5).abs() < 1e-6);
1665 assert!((l.get_t_at_offset(10.0) - 1.0).abs() < 1e-6);
1666 assert!((l.get_t_at_offset(-5.0) + 0.5).abs() < 1e-6);
1668 assert!((l.get_t_at_offset(20.0) - 2.0).abs() < 1e-6);
1669 }
1670
1671 #[test]
1672 fn svgline_get_t_at_offset_on_zero_length_line_is_nan_or_inf_not_a_panic() {
1673 let l = SvgLine::new(pt(1.0, 1.0), pt(1.0, 1.0));
1675 assert!(l.get_t_at_offset(0.0).is_nan());
1676 assert!(l.get_t_at_offset(5.0).is_infinite());
1677 assert!(l.get_t_at_offset(-5.0).is_infinite());
1678 assert!(l.get_t_at_offset(f64::NAN).is_nan());
1679 }
1680
1681 #[test]
1682 fn svgline_get_t_at_offset_round_trips_through_get_x_at_t() {
1683 let l = SvgLine::new(pt(2.0, 2.0), pt(2.0, 12.0));
1684 let t = l.get_t_at_offset(l.get_length());
1685 assert!((l.get_y_at_t(t) - 12.0).abs() < 1e-6);
1686 assert!((l.get_x_at_t(t) - 2.0).abs() < 1e-6);
1687 }
1688
1689 #[test]
1690 fn svgline_tangent_vector_is_normalized_and_zero_for_degenerate_lines() {
1691 let l = SvgLine::new(pt(0.0, 0.0), pt(0.0, 5.0));
1692 let t = l.get_tangent_vector_at_t();
1693 assert!((t.x - 0.0).abs() < 1e-9);
1694 assert!((t.y - 1.0).abs() < 1e-9);
1695
1696 let degenerate = SvgLine::new(pt(3.0, 3.0), pt(3.0, 3.0));
1698 let t = degenerate.get_tangent_vector_at_t();
1699 assert_eq!(t, SvgVector { x: 0.0, y: 0.0 });
1700 }
1701
1702 #[test]
1705 fn svgline_get_bounds_is_orientation_independent() {
1706 let l = SvgLine::new(pt(10.0, 20.0), pt(-5.0, 4.0));
1707 let mut r = l;
1708 r.reverse();
1709 assert_eq!(l.get_bounds(), r.get_bounds());
1710
1711 let b = l.get_bounds();
1712 assert_eq!(b.x, -5.0);
1713 assert_eq!(b.y, 4.0);
1714 assert_eq!(b.width, 15.0);
1715 assert_eq!(b.height, 16.0);
1716 assert_eq!(b.radius_top_left, 0.0);
1717 }
1718
1719 #[test]
1720 fn svgline_get_bounds_of_degenerate_line_is_zero_sized() {
1721 let b = SvgLine::new(pt(7.0, 8.0), pt(7.0, 8.0)).get_bounds();
1722 assert_eq!((b.x, b.y, b.width, b.height), (7.0, 8.0, 0.0, 0.0));
1723 }
1724
1725 #[test]
1726 fn svgline_get_bounds_overflows_to_inf_width_without_panicking() {
1727 let b = SvgLine::new(pt(-f32::MAX, 0.0), pt(f32::MAX, 1.0)).get_bounds();
1729 assert!(b.width.is_infinite() && b.width > 0.0);
1730 assert_eq!(b.x, -f32::MAX);
1731 assert_eq!(b.height, 1.0);
1732 }
1733
1734 #[test]
1737 fn svgpathelement_constructors_wrap_the_right_variant() {
1738 let l = SvgLine::new(pt(0.0, 0.0), pt(1.0, 1.0));
1739 assert!(matches!(SvgPathElement::line(l), SvgPathElement::Line(_)));
1740 assert!(matches!(
1741 SvgPathElement::quadratic_curve(quad()),
1742 SvgPathElement::QuadraticCurve(_)
1743 ));
1744 assert!(matches!(
1745 SvgPathElement::cubic_curve(cubic()),
1746 SvgPathElement::CubicCurve(_)
1747 ));
1748 }
1749
1750 #[test]
1751 fn svgpathelement_constructors_accept_extreme_geometry() {
1752 let l = SvgLine::new(pt(f32::NAN, f32::INFINITY), pt(f32::MIN, f32::MAX));
1753 let el = SvgPathElement::line(l);
1754 assert!(el.get_start().x.is_nan());
1755 assert_eq!(el.get_end().x, f32::MIN);
1756 }
1757
1758 #[test]
1761 fn svgpathelement_set_first_last_round_trip_for_every_variant() {
1762 let a = pt(-1.0, -2.0);
1763 let b = pt(3.0, 4.0);
1764
1765 for mut el in [
1766 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(1.0, 1.0))),
1767 SvgPathElement::quadratic_curve(quad()),
1768 SvgPathElement::cubic_curve(cubic()),
1769 ] {
1770 el.set_first(a);
1771 el.set_last(b);
1772 assert_eq!(el.get_start(), a);
1773 assert_eq!(el.get_end(), b);
1774 }
1775 }
1776
1777 #[test]
1778 fn svgpathelement_set_first_last_accept_zero_min_max_and_nan() {
1779 let mut el = SvgPathElement::cubic_curve(cubic());
1780
1781 el.set_first(pt(0.0, 0.0));
1782 el.set_last(pt(0.0, 0.0));
1783 assert_eq!(el.get_start(), pt(0.0, 0.0));
1784 assert_eq!(el.get_end(), pt(0.0, 0.0));
1785
1786 el.set_first(pt(f32::MIN, f32::MIN));
1787 el.set_last(pt(f32::MAX, f32::MAX));
1788 assert_eq!(el.get_start().x, f32::MIN);
1789 assert_eq!(el.get_end().x, f32::MAX);
1790
1791 el.set_first(pt(f32::NAN, f32::NEG_INFINITY));
1792 assert!(el.get_start().x.is_nan());
1793 assert!(el.get_start().y.is_infinite());
1794 }
1795
1796 #[test]
1797 fn svgpathelement_reverse_is_an_involution_for_every_variant() {
1798 for el in [
1799 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(1.0, 1.0))),
1800 SvgPathElement::quadratic_curve(quad()),
1801 SvgPathElement::cubic_curve(cubic()),
1802 ] {
1803 let mut r = el;
1804 r.reverse();
1805 assert_eq!(r.get_start(), el.get_end());
1806 assert_eq!(r.get_end(), el.get_start());
1807 r.reverse();
1808 assert_eq!(r, el, "reverse() applied twice must be the identity");
1809 }
1810 }
1811
1812 #[test]
1815 fn svgpathelement_get_length_is_non_negative_for_every_variant() {
1816 for el in [
1817 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(3.0, 4.0))),
1818 SvgPathElement::quadratic_curve(quad()),
1819 SvgPathElement::cubic_curve(cubic()),
1820 ] {
1821 let len = el.get_length();
1822 assert!(len.is_finite() && len >= 0.0, "bad length: {len}");
1823 }
1824 }
1825
1826 #[test]
1827 fn svgpathelement_get_x_y_at_t_hit_endpoints_for_every_variant() {
1828 for el in [
1829 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(10.0, 0.0))),
1830 SvgPathElement::quadratic_curve(quad()),
1831 SvgPathElement::cubic_curve(cubic()),
1832 ] {
1833 assert!((el.get_x_at_t(0.0) - f64::from(el.get_start().x)).abs() < 1e-6);
1834 assert!((el.get_y_at_t(0.0) - f64::from(el.get_start().y)).abs() < 1e-6);
1835 assert!((el.get_x_at_t(1.0) - f64::from(el.get_end().x)).abs() < 1e-6);
1836 assert!((el.get_y_at_t(1.0) - f64::from(el.get_end().y)).abs() < 1e-6);
1837 }
1838 }
1839
1840 #[test]
1841 fn svgpathelement_get_x_y_at_t_nan_inf_do_not_panic() {
1842 for el in [
1843 SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(10.0, 0.0))),
1844 SvgPathElement::quadratic_curve(quad()),
1845 SvgPathElement::cubic_curve(cubic()),
1846 ] {
1847 assert!(el.get_x_at_t(f64::NAN).is_nan());
1848 assert!(el.get_y_at_t(f64::NAN).is_nan());
1849 let _ = el.get_x_at_t(f64::INFINITY);
1851 let _ = el.get_y_at_t(f64::NEG_INFINITY);
1852 let _ = el.get_x_at_t(f64::MIN);
1853 let _ = el.get_y_at_t(f64::MAX);
1854 }
1855 }
1856
1857 #[test]
1858 fn svgpathelement_line_tangent_ignores_t_even_when_t_is_nan() {
1859 let el = SvgPathElement::line(SvgLine::new(pt(0.0, 0.0), pt(0.0, 8.0)));
1861 let at_half = el.get_tangent_vector_at_t(0.5);
1862 assert_eq!(el.get_tangent_vector_at_t(f64::NAN), at_half);
1863 assert_eq!(el.get_tangent_vector_at_t(f64::INFINITY), at_half);
1864 assert_eq!(at_half, SvgVector { x: 0.0, y: 1.0 });
1865 }
1866
1867 #[test]
1868 fn svgpathelement_curve_tangent_at_nan_is_nan_not_a_panic() {
1869 let el = SvgPathElement::quadratic_curve(quad());
1870 let v = el.get_tangent_vector_at_t(f64::NAN);
1871 assert!(v.x.is_nan() && v.y.is_nan());
1872 }
1873
1874 #[test]
1875 fn svgpathelement_curve_tangent_is_unit_length_in_range() {
1876 for el in [
1877 SvgPathElement::quadratic_curve(quad()),
1878 SvgPathElement::cubic_curve(cubic()),
1879 ] {
1880 for t in [0.0_f64, 0.25, 0.5, 0.75, 1.0] {
1881 let v = el.get_tangent_vector_at_t(t);
1882 let len = (v.x * v.x + v.y * v.y).sqrt();
1883 assert!(
1885 len.abs() < 1e-9 || (len - 1.0).abs() < 1e-6,
1886 "tangent at t={t} has length {len}"
1887 );
1888 }
1889 }
1890 }
1891
1892 #[test]
1893 fn svgpathelement_curve_t_at_offset_saturates_at_1_past_the_end() {
1894 for el in [
1897 SvgPathElement::quadratic_curve(quad()),
1898 SvgPathElement::cubic_curve(cubic()),
1899 ] {
1900 assert!((el.get_t_at_offset(f64::MAX) - 1.0).abs() < 1e-9);
1901 assert!((el.get_t_at_offset(1.0e30) - 1.0).abs() < 1e-9);
1902 assert!((el.get_t_at_offset(f64::NAN) - 1.0).abs() < 1e-9);
1904 }
1905 }
1906
1907 #[test]
1908 fn svgpathelement_curve_t_at_offset_is_monotonic_and_in_range() {
1909 let el = SvgPathElement::cubic_curve(cubic());
1910 let len = el.get_length();
1911 let t_quarter = el.get_t_at_offset(len * 0.25);
1912 let t_half = el.get_t_at_offset(len * 0.5);
1913 assert!(t_quarter.is_finite() && t_half.is_finite());
1914 assert!((0.0..=1.0).contains(&t_quarter), "t={t_quarter}");
1915 assert!((0.0..=1.0).contains(&t_half), "t={t_half}");
1916 assert!(t_quarter <= t_half, "t must not decrease with offset");
1917 }
1918
1919 #[test]
1920 fn svgpathelement_curve_t_at_offset_negative_offset_is_non_positive() {
1921 let el = SvgPathElement::cubic_curve(cubic());
1922 let t = el.get_t_at_offset(-10.0);
1923 assert!(t.is_finite(), "negative offset produced {t}");
1924 assert!(t <= 0.0, "negative offset must not map to a forward t: {t}");
1925 }
1926
1927 #[test]
1928 fn svgpathelement_get_bounds_contains_both_endpoints() {
1929 for el in [
1930 SvgPathElement::line(SvgLine::new(pt(-3.0, 2.0), pt(9.0, -4.0))),
1931 SvgPathElement::quadratic_curve(quad()),
1932 SvgPathElement::cubic_curve(cubic()),
1933 ] {
1934 let b = el.get_bounds();
1935 let (s, e) = (el.get_start(), el.get_end());
1936 assert!(b.width >= 0.0 && b.height >= 0.0);
1937 assert!(s.x >= b.x && s.x <= b.x + b.width);
1938 assert!(e.x >= b.x && e.x <= b.x + b.width);
1939 assert!(s.y >= b.y && s.y <= b.y + b.height);
1940 assert!(e.y >= b.y && e.y <= b.y + b.height);
1941 }
1942 }
1943
1944 #[test]
1947 fn svgpath_empty_getters_are_none_and_bounds_are_default() {
1948 let p = make_path(Vec::new());
1949 assert_eq!(p.get_start(), None);
1950 assert_eq!(p.get_end(), None);
1951 assert!(!p.is_closed(), "an empty path is not closed");
1952 assert_eq!(p.get_bounds(), SvgRect::default());
1953 }
1954
1955 #[test]
1956 fn svgpath_start_and_end_come_from_first_and_last_element() {
1957 let p = make_path(vec![
1958 line_el(0.0, 0.0, 1.0, 1.0),
1959 line_el(1.0, 1.0, 5.0, 5.0),
1960 line_el(5.0, 5.0, 9.0, 2.0),
1961 ]);
1962 assert_eq!(p.get_start(), Some(pt(0.0, 0.0)));
1963 assert_eq!(p.get_end(), Some(pt(9.0, 2.0)));
1964 }
1965
1966 #[test]
1967 fn svgpath_close_makes_is_closed_true_and_is_idempotent() {
1968 let mut p = make_path(vec![
1969 line_el(0.0, 0.0, 10.0, 0.0),
1970 line_el(10.0, 0.0, 10.0, 10.0),
1971 ]);
1972 assert!(!p.is_closed());
1973 p.close();
1974 assert!(p.is_closed(), "close() must establish is_closed()");
1975 assert_eq!(p.items.len(), 3);
1976 assert_eq!(p.get_end(), p.get_start());
1977
1978 p.close();
1980 assert_eq!(p.items.len(), 3);
1981 }
1982
1983 #[test]
1984 fn svgpath_close_on_empty_path_is_a_noop() {
1985 let mut p = make_path(Vec::new());
1986 p.close();
1987 assert_eq!(p.items.len(), 0);
1988 assert!(!p.is_closed());
1989 }
1990
1991 #[test]
1992 fn svgpath_close_with_nan_coords_appends_once_and_does_not_panic() {
1993 let mut p = make_path(vec![line_el(f32::NAN, 0.0, 10.0, 0.0)]);
1996 p.close();
1997 assert_eq!(p.items.len(), 2);
1998 assert!(!p.is_closed(), "a NaN start point can never compare equal");
1999 }
2000
2001 #[test]
2002 fn svgpath_is_closed_for_single_degenerate_element() {
2003 let p = make_path(vec![line_el(4.0, 4.0, 4.0, 4.0)]);
2004 assert!(p.is_closed(), "start == end for the only element");
2005
2006 let open = make_path(vec![line_el(4.0, 4.0, 5.0, 4.0)]);
2007 assert!(!open.is_closed());
2008 }
2009
2010 #[test]
2011 fn svgpath_reverse_is_an_involution_and_swaps_endpoints() {
2012 let orig = make_path(vec![
2013 line_el(0.0, 0.0, 1.0, 1.0),
2014 SvgPathElement::cubic_curve(cubic()),
2015 line_el(10.0, 0.0, 20.0, 5.0),
2016 ]);
2017
2018 let mut p = orig.clone();
2019 p.reverse();
2020 assert_eq!(p.get_start(), orig.get_end());
2021 assert_eq!(p.get_end(), orig.get_start());
2022 assert_eq!(p.items.len(), orig.items.len());
2023
2024 p.reverse();
2025 assert_eq!(p, orig, "reverse() applied twice must be the identity");
2026 }
2027
2028 #[test]
2029 fn svgpath_reverse_on_empty_path_does_not_panic() {
2030 let mut p = make_path(Vec::new());
2031 p.reverse();
2032 assert_eq!(p.items.len(), 0);
2033 }
2034
2035 #[test]
2036 fn svgpath_join_with_interpolates_the_join_point() {
2037 let mut a = make_path(vec![line_el(0.0, 0.0, 10.0, 0.0)]);
2038 let b = make_path(vec![line_el(20.0, 10.0, 30.0, 10.0)]);
2039
2040 assert_eq!(a.join_with(b), Some(()));
2041 assert_eq!(a.items.len(), 2);
2042
2043 let mid = pt(15.0, 5.0);
2045 assert_eq!(a.items.as_ref()[0].get_end(), mid);
2046 assert_eq!(a.items.as_ref()[1].get_start(), mid);
2047 assert_eq!(a.get_start(), Some(pt(0.0, 0.0)));
2048 assert_eq!(a.get_end(), Some(pt(30.0, 10.0)));
2049 }
2050
2051 #[test]
2052 fn svgpath_join_with_empty_other_returns_none_and_leaves_self_intact() {
2053 let mut a = make_path(vec![line_el(0.0, 0.0, 10.0, 0.0)]);
2054 let before = a.clone();
2055 assert_eq!(a.join_with(make_path(Vec::new())), None);
2056 assert_eq!(a, before, "a failed join must not corrupt the receiver");
2057 }
2058
2059 #[test]
2060 fn svgpath_join_with_on_empty_self_returns_none_without_underflow() {
2061 let mut a = make_path(Vec::new());
2064 let b = make_path(vec![line_el(0.0, 0.0, 1.0, 1.0)]);
2065 assert_eq!(a.join_with(b), None);
2066 assert_eq!(a.items.len(), 0);
2067 }
2068
2069 #[test]
2070 fn svgpath_join_with_extreme_coords_does_not_panic() {
2071 let mut a = make_path(vec![line_el(0.0, 0.0, f32::MAX, f32::MAX)]);
2072 let b = make_path(vec![line_el(-f32::MAX, -f32::MAX, 0.0, 0.0)]);
2073 assert_eq!(a.join_with(b), Some(()));
2074 let join = a.items.as_ref()[0].get_end();
2076 assert!(join.x.is_finite() && join.y.is_finite(), "join: {join:?}");
2077 }
2078
2079 #[test]
2080 fn svgpath_get_bounds_unions_every_element() {
2081 let p = make_path(vec![
2082 line_el(0.0, 0.0, 10.0, 0.0),
2083 line_el(10.0, 0.0, 10.0, 20.0),
2084 line_el(10.0, 20.0, -5.0, -8.0),
2085 ]);
2086 let b = p.get_bounds();
2087 assert_eq!(b.x, -5.0);
2088 assert_eq!(b.y, -8.0);
2089 assert_eq!(b.width, 15.0);
2090 assert_eq!(b.height, 28.0);
2091
2092 for el in p.items.as_ref() {
2093 assert!(
2094 rect_contains(&b, &el.get_bounds()),
2095 "path bounds must contain every element's bounds"
2096 );
2097 }
2098 }
2099
2100 #[test]
2103 fn svgmultipolygon_empty_has_default_bounds() {
2104 let mp = SvgMultiPolygon::create(SvgPathVec::from_vec(Vec::new()));
2105 assert_eq!(mp.get_bounds(), SvgRect::default());
2106 }
2107
2108 #[test]
2109 fn svgmultipolygon_bounds_union_all_rings() {
2110 let mp = SvgMultiPolygon::create(SvgPathVec::from_vec(vec![
2111 make_path(vec![line_el(0.0, 0.0, 10.0, 10.0)]),
2112 make_path(vec![line_el(-4.0, 30.0, 2.0, 33.0)]),
2113 ]));
2114 let b = mp.get_bounds();
2115 assert_eq!(b.x, -4.0);
2116 assert_eq!(b.y, 0.0);
2117 assert_eq!(b.width, 14.0);
2118 assert_eq!(b.height, 33.0);
2119 }
2120
2121 #[test]
2122 fn svgmultipolygon_bounds_must_contain_geometry_after_an_empty_first_ring() {
2123 let mp = SvgMultiPolygon::create(SvgPathVec::from_vec(vec![
2127 make_path(Vec::new()),
2128 make_path(vec![line_el(100.0, 100.0, 200.0, 200.0)]),
2129 ]));
2130 let b = mp.get_bounds();
2131 let expected = SvgRect {
2132 width: 100.0,
2133 height: 100.0,
2134 x: 100.0,
2135 y: 100.0,
2136 ..SvgRect::default()
2137 };
2138 assert!(
2139 rect_contains(&b, &expected),
2140 "bounds {b:?} must contain the geometry of the non-empty ring {expected:?}"
2141 );
2142 }
2143
2144 #[test]
2147 fn svgsimplenode_is_closed_per_variant() {
2148 let circle = SvgCircle {
2149 center_x: 0.0,
2150 center_y: 0.0,
2151 radius: 5.0,
2152 };
2153 assert!(SvgSimpleNode::Circle(circle).is_closed());
2154 assert!(SvgSimpleNode::CircleHole(circle).is_closed());
2155 assert!(SvgSimpleNode::Rect(SvgRect::default()).is_closed());
2156 assert!(SvgSimpleNode::RectHole(SvgRect::default()).is_closed());
2157
2158 assert!(!SvgSimpleNode::Path(make_path(vec![line_el(0.0, 0.0, 1.0, 0.0)])).is_closed());
2159 assert!(SvgSimpleNode::Path(make_path(vec![line_el(2.0, 2.0, 2.0, 2.0)])).is_closed());
2160 assert!(!SvgSimpleNode::Path(make_path(Vec::new())).is_closed());
2162 }
2163
2164 #[test]
2165 fn svgsimplenode_get_bounds_per_variant() {
2166 let circle = SvgCircle {
2167 center_x: 10.0,
2168 center_y: 10.0,
2169 radius: 2.0,
2170 };
2171 let b = SvgSimpleNode::Circle(circle).get_bounds();
2172 assert_eq!((b.x, b.y, b.width, b.height), (8.0, 8.0, 4.0, 4.0));
2173 assert_eq!(SvgSimpleNode::CircleHole(circle).get_bounds(), b);
2174
2175 let rect = SvgRect {
2176 width: 3.0,
2177 height: 4.0,
2178 x: 1.0,
2179 y: 2.0,
2180 ..SvgRect::default()
2181 };
2182 assert_eq!(SvgSimpleNode::Rect(rect).get_bounds(), rect);
2183 assert_eq!(SvgSimpleNode::RectHole(rect).get_bounds(), rect);
2184
2185 assert_eq!(
2187 SvgSimpleNode::Path(make_path(Vec::new())).get_bounds(),
2188 SvgRect::default()
2189 );
2190 }
2191
2192 #[test]
2195 fn svgnode_get_bounds_of_empty_collections_is_default() {
2196 assert_eq!(
2197 SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_vec(Vec::new())).get_bounds(),
2198 SvgRect::default()
2199 );
2200 assert_eq!(
2201 SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(Vec::new())).get_bounds(),
2202 SvgRect::default()
2203 );
2204 assert_eq!(
2205 SvgNode::Path(make_path(Vec::new())).get_bounds(),
2206 SvgRect::default()
2207 );
2208 assert_eq!(
2209 SvgNode::MultiPolygon(SvgMultiPolygon::create(SvgPathVec::from_vec(Vec::new())))
2210 .get_bounds(),
2211 SvgRect::default()
2212 );
2213 }
2214
2215 #[test]
2216 fn svgnode_is_closed_is_vacuously_true_for_empty_collections() {
2217 assert!(SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_vec(Vec::new())).is_closed());
2218 assert!(SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(Vec::new())).is_closed());
2219 assert!(
2220 SvgNode::MultiPolygon(SvgMultiPolygon::create(SvgPathVec::from_vec(Vec::new())))
2221 .is_closed()
2222 );
2223 assert!(!SvgNode::Path(make_path(Vec::new())).is_closed());
2225 }
2226
2227 #[test]
2228 fn svgnode_is_closed_false_when_any_subpath_is_open() {
2229 let open = make_path(vec![line_el(0.0, 0.0, 1.0, 0.0)]);
2230 let mut closed = make_path(vec![
2231 line_el(0.0, 0.0, 1.0, 0.0),
2232 line_el(1.0, 0.0, 1.0, 1.0),
2233 ]);
2234 closed.close();
2235
2236 let mp = SvgMultiPolygon::create(SvgPathVec::from_vec(vec![closed.clone(), open.clone()]));
2237 assert!(!SvgNode::MultiPolygon(mp.clone()).is_closed());
2238 assert!(!SvgNode::MultiPolygonCollection(SvgMultiPolygonVec::from_vec(vec![mp])).is_closed());
2239 assert!(!SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(vec![
2240 SvgSimpleNode::Path(open),
2241 SvgSimpleNode::Circle(SvgCircle {
2242 center_x: 0.0,
2243 center_y: 0.0,
2244 radius: 1.0,
2245 }),
2246 ]))
2247 .is_closed());
2248
2249 let all_closed = SvgMultiPolygon::create(SvgPathVec::from_vec(vec![closed]));
2250 assert!(SvgNode::MultiPolygon(all_closed).is_closed());
2251 }
2252
2253 #[test]
2254 fn svgnode_get_bounds_contains_all_children() {
2255 let a = make_path(vec![line_el(0.0, 0.0, 10.0, 10.0)]);
2256 let b = make_path(vec![line_el(100.0, 100.0, 200.0, 200.0)]);
2257 let node = SvgNode::MultiShape(SvgSimpleNodeVec::from_vec(vec![
2258 SvgSimpleNode::Path(a.clone()),
2259 SvgSimpleNode::Path(b.clone()),
2260 ]));
2261 let bounds = node.get_bounds();
2262 assert!(rect_contains(&bounds, &a.get_bounds()));
2263 assert!(rect_contains(&bounds, &b.get_bounds()));
2264 }
2265
2266 #[test]
2267 fn svgnode_rect_and_circle_bounds_are_passthrough() {
2268 let rect = SvgRect {
2269 width: 5.0,
2270 height: 6.0,
2271 x: -1.0,
2272 y: -2.0,
2273 ..SvgRect::default()
2274 };
2275 assert_eq!(SvgNode::Rect(rect).get_bounds(), rect);
2276 assert!(SvgNode::Rect(rect).is_closed());
2277
2278 let circle = SvgCircle {
2279 center_x: 0.0,
2280 center_y: 0.0,
2281 radius: 1.0,
2282 };
2283 assert_eq!(SvgNode::Circle(circle).get_bounds(), circle.get_bounds());
2284 assert!(SvgNode::Circle(circle).is_closed());
2285 }
2286
2287 #[test]
2290 fn svgcircle_contains_point_is_strict_and_correct() {
2291 let c = SvgCircle {
2292 center_x: 0.0,
2293 center_y: 0.0,
2294 radius: 1.0,
2295 };
2296 assert!(c.contains_point(0.0, 0.0));
2297 assert!(c.contains_point(0.5, 0.5));
2298 assert!(!c.contains_point(1.0, 0.0));
2300 assert!(!c.contains_point(0.0, -1.0));
2301 assert!(!c.contains_point(2.0, 0.0));
2302 assert!(!c.contains_point(-0.8, -0.8));
2303 }
2304
2305 #[test]
2306 fn svgcircle_zero_radius_contains_nothing_not_even_its_center() {
2307 let c = SvgCircle {
2308 center_x: 3.0,
2309 center_y: 3.0,
2310 radius: 0.0,
2311 };
2312 assert!(!c.contains_point(3.0, 3.0));
2313 assert!(!c.contains_point(0.0, 0.0));
2314 }
2315
2316 #[test]
2317 fn svgcircle_negative_radius_behaves_like_its_absolute_value() {
2318 let neg = SvgCircle {
2321 center_x: 0.0,
2322 center_y: 0.0,
2323 radius: -2.0,
2324 };
2325 let pos = SvgCircle {
2326 center_x: 0.0,
2327 center_y: 0.0,
2328 radius: 2.0,
2329 };
2330 assert_eq!(neg.contains_point(0.0, 0.0), pos.contains_point(0.0, 0.0));
2331 assert_eq!(neg.contains_point(1.9, 0.0), pos.contains_point(1.9, 0.0));
2332 assert_eq!(neg.contains_point(5.0, 0.0), pos.contains_point(5.0, 0.0));
2333 }
2334
2335 #[test]
2336 fn svgcircle_contains_point_nan_and_inf_are_false_not_a_panic() {
2337 let c = SvgCircle {
2338 center_x: 0.0,
2339 center_y: 0.0,
2340 radius: 1.0,
2341 };
2342 assert!(!c.contains_point(f32::NAN, 0.0));
2344 assert!(!c.contains_point(0.0, f32::NAN));
2345 assert!(!c.contains_point(f32::INFINITY, 0.0));
2346 assert!(!c.contains_point(f32::NEG_INFINITY, f32::NEG_INFINITY));
2347
2348 let nan_r = SvgCircle {
2349 center_x: 0.0,
2350 center_y: 0.0,
2351 radius: f32::NAN,
2352 };
2353 assert!(!nan_r.contains_point(0.0, 0.0));
2354 }
2355
2356 #[test]
2357 fn svgcircle_contains_point_at_f32_extremes_does_not_panic() {
2358 let c = SvgCircle {
2359 center_x: 0.0,
2360 center_y: 0.0,
2361 radius: f32::MAX,
2362 };
2363 assert!(!c.contains_point(f32::MAX, f32::MAX));
2365 assert!(c.contains_point(1.0, 1.0));
2366 assert!(!c.contains_point(f32::MIN, 0.0));
2367 }
2368
2369 #[test]
2370 fn svgcircle_get_bounds_is_the_enclosing_square() {
2371 let c = SvgCircle {
2372 center_x: 5.0,
2373 center_y: -5.0,
2374 radius: 2.5,
2375 };
2376 let b = c.get_bounds();
2377 assert_eq!((b.x, b.y, b.width, b.height), (2.5, -7.5, 5.0, 5.0));
2378 assert!(!c.contains_point(b.x + 0.1, b.y + 0.1));
2382 assert!(c.contains_point(c.center_x, c.center_y));
2383 }
2384
2385 #[test]
2386 fn svgcircle_get_bounds_with_nan_and_huge_radius_does_not_panic() {
2387 let nan = SvgCircle {
2388 center_x: 0.0,
2389 center_y: 0.0,
2390 radius: f32::NAN,
2391 };
2392 let b = nan.get_bounds();
2393 assert!(b.width.is_nan() && b.x.is_nan());
2394
2395 let huge = SvgCircle {
2396 center_x: 0.0,
2397 center_y: 0.0,
2398 radius: f32::MAX,
2399 };
2400 let b = huge.get_bounds();
2401 assert!(b.width.is_infinite() && b.width > 0.0);
2403 assert_eq!(b.x, -f32::MAX);
2404 }
2405
2406 #[test]
2409 fn tessellated_svg_node_empty_is_a_neutral_value() {
2410 let e = TessellatedSvgNode::empty();
2411 assert!(e.vertices.as_ref().is_empty());
2412 assert!(e.indices.as_ref().is_empty());
2413 assert_eq!(e, TessellatedSvgNode::default());
2414 }
2415
2416 #[test]
2417 fn tessellated_colored_svg_node_empty_is_a_neutral_value() {
2418 let e = TessellatedColoredSvgNode::empty();
2419 assert!(e.vertices.as_ref().is_empty());
2420 assert!(e.indices.as_ref().is_empty());
2421 assert_eq!(e, TessellatedColoredSvgNode::default());
2422 }
2423
2424 #[test]
2425 fn tessellated_svg_node_vec_ref_round_trips_through_as_slice() {
2426 let node = TessellatedSvgNode {
2427 vertices: vec![SvgVertex { x: 1.0, y: 2.0 }].into(),
2428 indices: vec![0_u32].into(),
2429 };
2430 let v = TessellatedSvgNodeVec::from_vec(vec![node.clone(), TessellatedSvgNode::empty()]);
2431 let r = v.get_ref();
2432 assert_eq!(r.len, 2);
2433 let slice = r.as_slice();
2434 assert_eq!(slice.len(), 2);
2435 assert_eq!(slice[0], node);
2436 assert_eq!(slice[1], TessellatedSvgNode::empty());
2437 }
2438
2439 #[test]
2440 fn tessellated_svg_node_vec_ref_on_empty_vec_yields_an_empty_slice() {
2441 let v = TessellatedSvgNodeVec::from_vec(Vec::new());
2444 let r = v.get_ref();
2445 assert_eq!(r.len, 0);
2446 assert!(r.as_slice().is_empty());
2447 assert!(!r.ptr.is_null(), "raw ptr must never be null");
2448 }
2449
2450 #[test]
2451 fn tessellated_colored_svg_node_vec_ref_round_trips_through_as_slice() {
2452 let node = TessellatedColoredSvgNode {
2453 vertices: vec![SvgColoredVertex {
2454 x: 1.0,
2455 y: 2.0,
2456 z: 3.0,
2457 r: 1.0,
2458 g: 0.5,
2459 b: 0.25,
2460 a: 1.0,
2461 }]
2462 .into(),
2463 indices: vec![0_u32, 1, 2].into(),
2464 };
2465 let v = TessellatedColoredSvgNodeVec::from_vec(vec![node.clone()]);
2466 let r = v.get_ref();
2467 assert_eq!(r.len, 1);
2468 assert_eq!(r.as_slice().len(), 1);
2469 assert_eq!(r.as_slice()[0], node);
2470 }
2471
2472 #[test]
2473 fn tessellated_colored_svg_node_vec_ref_on_empty_vec_yields_an_empty_slice() {
2474 let v = TessellatedColoredSvgNodeVec::from_vec(Vec::new());
2475 let r = v.get_ref();
2476 assert_eq!(r.len, 0);
2477 assert!(r.as_slice().is_empty());
2478 assert!(!r.ptr.is_null(), "raw ptr must never be null");
2479 }
2480
2481 fn matrix_of(u: &Uniform) -> [f32; 16] {
2484 match u.uniform_type {
2485 UniformType::Matrix4 { transpose, matrix } => {
2486 assert!(!transpose, "SVG shaders expect a pre-transposed matrix");
2487 matrix
2488 }
2489 _ => panic!("expected a Matrix4 uniform"),
2490 }
2491 }
2492
2493 fn bbox_of(u: &Uniform) -> [f32; 2] {
2494 match u.uniform_type {
2495 UniformType::FloatVec2(v) => v,
2496 _ => panic!("expected a FloatVec2 uniform"),
2497 }
2498 }
2499
2500 #[test]
2501 fn compute_svg_transform_uniforms_zero_size_no_transforms_is_finite() {
2502 let (bbox, tf) = compute_svg_transform_uniforms(
2503 PhysicalSizeU32 {
2504 width: 0,
2505 height: 0,
2506 },
2507 &[],
2508 );
2509 assert_eq!(bbox.uniform_name.as_str(), "vBboxSize");
2510 assert_eq!(bbox_of(&bbox), [0.0, 0.0]);
2511
2512 assert_eq!(tf.uniform_name.as_str(), "vTransformMatrix");
2513 let m = matrix_of(&tf);
2514 assert!(
2515 m.iter().all(|f| f.is_finite()),
2516 "a zero-sized target must not produce NaN/inf in the matrix: {m:?}"
2517 );
2518 }
2519
2520 #[test]
2521 fn compute_svg_transform_uniforms_at_u32_max_does_not_panic() {
2522 let (bbox, tf) = compute_svg_transform_uniforms(
2523 PhysicalSizeU32 {
2524 width: u32::MAX,
2525 height: u32::MAX,
2526 },
2527 &[],
2528 );
2529 let b = bbox_of(&bbox);
2530 assert!(b[0].is_finite() && b[0] > 0.0);
2531 assert_eq!(b[0], u32::MAX as f32);
2532 assert_eq!(b[1], u32::MAX as f32);
2533 let m = matrix_of(&tf);
2534 assert!(m.iter().all(|f| f.is_finite()), "matrix: {m:?}");
2535 }
2536
2537 #[test]
2538 fn compute_svg_transform_uniforms_translation_changes_the_matrix() {
2539 let size = PhysicalSizeU32 {
2540 width: 800,
2541 height: 600,
2542 };
2543 let identity = matrix_of(&compute_svg_transform_uniforms(size, &[]).1);
2544 let translated = matrix_of(
2545 &compute_svg_transform_uniforms(
2546 size,
2547 &[
2548 StyleTransform::TranslateX(PixelValue::px(10.0)),
2549 StyleTransform::TranslateY(PixelValue::px(-20.0)),
2550 ],
2551 )
2552 .1,
2553 );
2554 assert!(
2555 translated.iter().all(|f| f.is_finite()),
2556 "matrix: {translated:?}"
2557 );
2558 assert!(
2559 identity != translated,
2560 "a translation must actually alter the transform matrix"
2561 );
2562 }
2563
2564 #[test]
2565 fn compute_svg_transform_uniforms_extreme_translation_does_not_panic() {
2566 let size = PhysicalSizeU32 {
2567 width: 1,
2568 height: 1,
2569 };
2570 let (bbox, tf) = compute_svg_transform_uniforms(
2571 size,
2572 &[
2573 StyleTransform::TranslateX(PixelValue::px(f32::MAX)),
2574 StyleTransform::TranslateY(PixelValue::px(-f32::MAX)),
2575 ],
2576 );
2577 assert_eq!(bbox_of(&bbox), [1.0, 1.0]);
2578 let _ = matrix_of(&tf);
2581 }
2582
2583 #[test]
2586 fn svgstyle_getters_read_through_to_both_variants() {
2587 let fill = SvgStyle::Fill(SvgFillStyle::default());
2588 assert!(fill.get_antialias());
2589 assert!(!fill.get_high_quality_aa());
2590 assert_eq!(fill.get_transform(), SvgTransform::default());
2591
2592 let stroke = SvgStyle::Stroke(SvgStrokeStyle::default());
2593 assert!(stroke.get_antialias());
2594 assert!(!stroke.get_high_quality_aa());
2595 assert_eq!(stroke.get_transform(), SvgTransform::default());
2596 }
2597
2598 #[test]
2599 fn svgstyle_getters_reflect_non_default_fields() {
2600 let transform = SvgTransform {
2601 sx: 2.0,
2602 kx: 0.5,
2603 ky: -0.5,
2604 sy: 3.0,
2605 tx: 10.0,
2606 ty: -10.0,
2607 };
2608 let fill = SvgStyle::Fill(SvgFillStyle {
2609 anti_alias: false,
2610 high_quality_aa: true,
2611 transform,
2612 ..SvgFillStyle::default()
2613 });
2614 assert!(!fill.get_antialias());
2615 assert!(fill.get_high_quality_aa());
2616 assert_eq!(fill.get_transform(), transform);
2617
2618 let stroke = SvgStyle::Stroke(SvgStrokeStyle {
2619 anti_alias: false,
2620 high_quality_aa: true,
2621 transform,
2622 ..SvgStrokeStyle::default()
2623 });
2624 assert!(!stroke.get_antialias());
2625 assert!(stroke.get_high_quality_aa());
2626 assert_eq!(stroke.get_transform(), transform);
2627 }
2628
2629 #[test]
2632 fn svgparseerror_display_is_non_empty_for_every_variant() {
2633 let variants = [
2634 SvgParseError::NoParserAvailable,
2635 SvgParseError::ElementsLimitReached,
2636 SvgParseError::NotAnUtf8Str,
2637 SvgParseError::MalformedGZip,
2638 SvgParseError::InvalidSize,
2639 SvgParseError::ParsingFailed(XmlError::NoRootNode),
2640 ];
2641
2642 for v in &variants {
2643 let s = v.to_string();
2644 assert!(!s.is_empty(), "empty Display output for {v:?}");
2645 assert!(
2646 !s.contains("\u{0}"),
2647 "Display output must not contain NUL bytes"
2648 );
2649 }
2650 }
2651
2652 #[test]
2653 fn svgparseerror_display_of_parsing_failed_embeds_the_inner_error() {
2654 let inner = XmlError::NoRootNode;
2655 let s = SvgParseError::ParsingFailed(inner.clone()).to_string();
2656 assert!(s.starts_with("Error parsing SVG:"), "got: {s}");
2657 assert!(
2658 s.contains(&inner.to_string()),
2659 "outer message {s:?} must embed the inner XmlError message"
2660 );
2661 }
2662}