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]
72 pub const fn new(start: SvgPoint, end: SvgPoint) -> Self {
73 Self { start, end }
74 }
75
76 #[must_use]
81 pub fn inwards_normal(&self) -> Option<SvgPoint> {
82 let dx = self.end.x - self.start.x;
83 let dy = self.end.y - self.start.y;
84 let edge_length = dx.hypot(dy);
85 let x = -dy / edge_length;
86 let y = dx / edge_length;
87
88 if x.is_finite() && y.is_finite() {
89 Some(SvgPoint { x, y })
90 } else {
91 None
92 }
93 }
94
95 #[must_use]
97 pub fn outwards_normal(&self) -> Option<SvgPoint> {
98 let inwards = self.inwards_normal()?;
99 Some(SvgPoint {
100 x: -inwards.x,
101 y: -inwards.y,
102 })
103 }
104
105 pub const fn reverse(&mut self) {
107 core::mem::swap(&mut self.start, &mut self.end);
108 }
109 #[must_use]
111 pub const fn get_start(&self) -> SvgPoint {
112 self.start
113 }
114 #[must_use]
116 pub const fn get_end(&self) -> SvgPoint {
117 self.end
118 }
119
120 #[must_use]
122 pub fn get_t_at_offset(&self, offset: f64) -> f64 {
123 offset / self.get_length()
124 }
125
126 #[must_use]
130 pub fn get_tangent_vector_at_t(&self) -> SvgVector {
131 let dx = self.end.x - self.start.x;
132 let dy = self.end.y - self.start.y;
133 SvgVector {
134 x: f64::from(dx),
135 y: f64::from(dy),
136 }
137 .normalize()
138 }
139
140 #[allow(clippy::suboptimal_flops)] #[must_use]
143 pub fn get_x_at_t(&self, t: f64) -> f64 {
144 f64::from(self.start.x) + (f64::from(self.end.x) - f64::from(self.start.x)) * t
145 }
146
147 #[allow(clippy::suboptimal_flops)] #[must_use]
150 pub fn get_y_at_t(&self, t: f64) -> f64 {
151 f64::from(self.start.y) + (f64::from(self.end.y) - f64::from(self.start.y)) * t
152 }
153
154 #[must_use]
156 pub fn get_length(&self) -> f64 {
157 let dx = self.end.x - self.start.x;
158 let dy = self.end.y - self.start.y;
159 f64::from(libm::hypotf(dx, dy))
160 }
161
162 #[must_use]
164 pub fn get_bounds(&self) -> SvgRect {
165 let min_x = self.start.x.min(self.end.x);
166 let max_x = self.start.x.max(self.end.x);
167
168 let min_y = self.start.y.min(self.end.y);
169 let max_y = self.start.y.max(self.end.y);
170
171 let width = (max_x - min_x).abs();
172 let height = (max_y - min_y).abs();
173
174 SvgRect {
175 width,
176 height,
177 x: min_x,
178 y: min_y,
179 radius_top_left: 0.0,
180 radius_top_right: 0.0,
181 radius_bottom_left: 0.0,
182 radius_bottom_right: 0.0,
183 }
184 }
185}
186
187#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
188#[repr(C, u8)]
189pub enum SvgPathElement {
190 Line(SvgLine),
191 QuadraticCurve(SvgQuadraticCurve),
192 CubicCurve(SvgCubicCurve),
193}
194
195impl_option!(
196 SvgPathElement,
197 OptionSvgPathElement,
198 [Debug, Copy, Clone, PartialEq, PartialOrd]
199);
200
201impl SvgPathElement {
202 #[inline]
204 #[must_use]
205 pub const fn line(l: SvgLine) -> Self {
206 Self::Line(l)
207 }
208
209 #[inline]
211 #[must_use]
212 pub const fn quadratic_curve(qc: SvgQuadraticCurve) -> Self {
213 Self::QuadraticCurve(qc)
214 }
215
216 #[inline]
218 #[must_use]
219 pub const fn cubic_curve(cc: SvgCubicCurve) -> Self {
220 Self::CubicCurve(cc)
221 }
222
223 pub const fn set_last(&mut self, point: SvgPoint) {
225 match self {
226 Self::Line(l) => l.end = point,
227 Self::QuadraticCurve(qc) => qc.end = point,
228 Self::CubicCurve(cc) => cc.end = point,
229 }
230 }
231
232 pub const fn set_first(&mut self, point: SvgPoint) {
234 match self {
235 Self::Line(l) => l.start = point,
236 Self::QuadraticCurve(qc) => qc.start = point,
237 Self::CubicCurve(cc) => cc.start = point,
238 }
239 }
240
241 pub const fn reverse(&mut self) {
243 match self {
244 Self::Line(l) => l.reverse(),
245 Self::QuadraticCurve(qc) => qc.reverse(),
246 Self::CubicCurve(cc) => cc.reverse(),
247 }
248 }
249 #[must_use]
251 pub const fn get_start(&self) -> SvgPoint {
252 match self {
253 Self::Line(l) => l.get_start(),
254 Self::QuadraticCurve(qc) => qc.get_start(),
255 Self::CubicCurve(cc) => cc.get_start(),
256 }
257 }
258 #[must_use]
260 pub const fn get_end(&self) -> SvgPoint {
261 match self {
262 Self::Line(l) => l.get_end(),
263 Self::QuadraticCurve(qc) => qc.get_end(),
264 Self::CubicCurve(cc) => cc.get_end(),
265 }
266 }
267 #[must_use]
269 pub fn get_bounds(&self) -> SvgRect {
270 match self {
271 Self::Line(l) => l.get_bounds(),
272 Self::QuadraticCurve(qc) => qc.get_bounds(),
273 Self::CubicCurve(cc) => cc.get_bounds(),
274 }
275 }
276 #[must_use]
278 pub fn get_length(&self) -> f64 {
279 match self {
280 Self::Line(l) => l.get_length(),
281 Self::QuadraticCurve(qc) => qc.get_length(),
282 Self::CubicCurve(cc) => cc.get_length(),
283 }
284 }
285 #[must_use]
287 pub fn get_t_at_offset(&self, offset: f64) -> f64 {
288 match self {
289 Self::Line(l) => l.get_t_at_offset(offset),
290 Self::QuadraticCurve(qc) => qc.get_t_at_offset(offset),
291 Self::CubicCurve(cc) => cc.get_t_at_offset(offset),
292 }
293 }
294 #[must_use]
296 pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
297 match self {
298 Self::Line(l) => l.get_tangent_vector_at_t(),
299 Self::QuadraticCurve(qc) => qc.get_tangent_vector_at_t(t),
300 Self::CubicCurve(cc) => cc.get_tangent_vector_at_t(t),
301 }
302 }
303 #[must_use]
305 pub fn get_x_at_t(&self, t: f64) -> f64 {
306 match self {
307 Self::Line(l) => l.get_x_at_t(t),
308 Self::QuadraticCurve(qc) => qc.get_x_at_t(t),
309 Self::CubicCurve(cc) => cc.get_x_at_t(t),
310 }
311 }
312 #[must_use]
314 pub fn get_y_at_t(&self, t: f64) -> f64 {
315 match self {
316 Self::Line(l) => l.get_y_at_t(t),
317 Self::QuadraticCurve(qc) => qc.get_y_at_t(t),
318 Self::CubicCurve(cc) => cc.get_y_at_t(t),
319 }
320 }
321}
322
323impl_vec!(
324 SvgPathElement,
325 SvgPathElementVec,
326 SvgPathElementVecDestructor,
327 SvgPathElementVecDestructorType,
328 SvgPathElementVecSlice,
329 OptionSvgPathElement
330);
331impl_vec_debug!(SvgPathElement, SvgPathElementVec);
332impl_vec_clone!(
333 SvgPathElement,
334 SvgPathElementVec,
335 SvgPathElementVecDestructor
336);
337impl_vec_partialeq!(SvgPathElement, SvgPathElementVec);
338impl_vec_partialord!(SvgPathElement, SvgPathElementVec);
339
340#[derive(Debug, Clone, PartialEq, PartialOrd)]
341#[repr(C)]
342pub struct SvgPath {
343 pub items: SvgPathElementVec,
344}
345
346impl_option!(
347 SvgPath,
348 OptionSvgPath,
349 copy = false,
350 [Debug, Clone, PartialEq, PartialOrd]
351);
352
353impl SvgPath {
354 #[inline]
356 #[must_use]
357 pub const fn create(items: SvgPathElementVec) -> Self {
358 Self { items }
359 }
360
361 #[must_use]
363 pub fn get_start(&self) -> Option<SvgPoint> {
364 self.items.as_ref().first().map(SvgPathElement::get_start)
365 }
366
367 #[must_use]
369 pub fn get_end(&self) -> Option<SvgPoint> {
370 self.items.as_ref().last().map(SvgPathElement::get_end)
371 }
372
373 pub fn close(&mut self) {
375 let Some(first) = self.items.as_ref().first() else {
376 return;
377 };
378 let Some(last) = self.items.as_ref().last() else {
379 return;
380 };
381 if first.get_start() != last.get_end() {
382 let mut elements = self.items.as_slice().to_vec();
383 elements.push(SvgPathElement::Line(SvgLine {
384 start: last.get_end(),
385 end: first.get_start(),
386 }));
387 self.items = elements.into();
388 }
389 }
390
391 #[must_use]
393 pub fn is_closed(&self) -> bool {
394 let first = self.items.as_ref().first();
395 let last = self.items.as_ref().last();
396 match (first, last) {
397 (Some(f), Some(l)) => (f.get_start() == l.get_end()),
398 _ => false,
399 }
400 }
401
402 pub fn reverse(&mut self) {
404 let mut vec = SvgPathElementVec::from_const_slice(&[]);
406 core::mem::swap(&mut vec, &mut self.items);
407 let mut vec = vec.into_library_owned_vec();
408
409 vec.reverse();
411
412 for item in &mut vec {
415 item.reverse();
416 }
417
418 let mut vec = SvgPathElementVec::from_vec(vec);
420 core::mem::swap(&mut vec, &mut self.items);
421 }
422
423 pub fn join_with(&mut self, mut path: Self) -> Option<()> {
425 let self_last_point = self.items.as_ref().last()?.get_end();
426 let other_start_point = path.items.as_ref().first()?.get_start();
427 let interpolated_join_point = SvgPoint {
428 x: f32::midpoint(self_last_point.x, other_start_point.x),
429 y: f32::midpoint(self_last_point.y, other_start_point.y),
430 };
431
432 let mut vec = SvgPathElementVec::from_const_slice(&[]);
434 core::mem::swap(&mut vec, &mut self.items);
435 let mut vec = vec.into_library_owned_vec();
436
437 let mut other = SvgPathElementVec::from_const_slice(&[]);
438 core::mem::swap(&mut other, &mut path.items);
439 let mut other = other.into_library_owned_vec();
440
441 let vec_len = vec.len() - 1;
442 vec.get_mut(vec_len)?.set_last(interpolated_join_point);
443 other.get_mut(0)?.set_first(interpolated_join_point);
444 vec.append(&mut other);
445
446 let mut vec = SvgPathElementVec::from_vec(vec);
448 core::mem::swap(&mut vec, &mut self.items);
449
450 Some(())
451 }
452 #[must_use]
454 pub fn get_bounds(&self) -> SvgRect {
455 let mut first_bounds = match self.items.as_ref().first() {
456 Some(s) => s.get_bounds(),
457 None => return SvgRect::default(),
458 };
459
460 for mp in self.items.as_ref().iter().skip(1) {
461 let mp_bounds = mp.get_bounds();
462 first_bounds.union_with(&mp_bounds);
463 }
464
465 first_bounds
466 }
467}
468
469#[derive(Debug, Clone, PartialEq, PartialOrd)]
470#[repr(C)]
471pub struct SvgMultiPolygon {
472 pub rings: SvgPathVec,
474}
475
476impl_option!(
477 SvgMultiPolygon,
478 OptionSvgMultiPolygon,
479 copy = false,
480 [Debug, Clone, PartialEq, PartialOrd]
481);
482
483impl SvgMultiPolygon {
484 const FLATTEN_STEPS: usize = 16;
490
491 #[must_use]
503 pub fn contains_point(&self, x: f32, y: f32) -> bool {
504 let mut winding = 0i32;
505 for ring in self.rings.as_ref() {
506 let points = ring.flatten_to_points(Self::FLATTEN_STEPS);
507 if points.len() < 2 {
508 continue;
509 }
510 for i in 0..points.len() {
511 let a = points[i];
512 let b = points[(i + 1) % points.len()];
513 if a.y <= y {
516 if b.y > y && cross_sign(a, b, x, y) > 0.0 {
517 winding += 1;
518 }
519 } else if b.y <= y && cross_sign(a, b, x, y) < 0.0 {
520 winding -= 1;
521 }
522 }
523 }
524 winding != 0
525 }
526
527 #[inline]
530 #[must_use]
531 pub const fn create(rings: SvgPathVec) -> Self {
532 Self { rings }
533 }
534
535 #[must_use]
537 pub fn get_bounds(&self) -> SvgRect {
538 let mut bounds: Option<SvgRect> = None;
542 for ring in &self.rings {
543 for item in &ring.items {
544 let item_bounds = item.get_bounds();
545 match &mut bounds {
546 Some(b) => b.union_with(&item_bounds),
547 None => bounds = Some(item_bounds),
548 }
549 }
550 }
551 bounds.unwrap_or_default()
553 }
554}
555
556impl_vec!(
557 SvgPath,
558 SvgPathVec,
559 SvgPathVecDestructor,
560 SvgPathVecDestructorType,
561 SvgPathVecSlice,
562 OptionSvgPath
563);
564impl_vec_debug!(SvgPath, SvgPathVec);
565impl_vec_clone!(SvgPath, SvgPathVec, SvgPathVecDestructor);
566impl_vec_partialeq!(SvgPath, SvgPathVec);
567impl_vec_partialord!(SvgPath, SvgPathVec);
568
569impl_vec!(
570 SvgMultiPolygon,
571 SvgMultiPolygonVec,
572 SvgMultiPolygonVecDestructor,
573 SvgMultiPolygonVecDestructorType,
574 SvgMultiPolygonVecSlice,
575 OptionSvgMultiPolygon
576);
577impl_vec_debug!(SvgMultiPolygon, SvgMultiPolygonVec);
578impl_vec_clone!(
579 SvgMultiPolygon,
580 SvgMultiPolygonVec,
581 SvgMultiPolygonVecDestructor
582);
583impl_vec_partialeq!(SvgMultiPolygon, SvgMultiPolygonVec);
584impl_vec_partialord!(SvgMultiPolygon, SvgMultiPolygonVec);
585
586#[derive(Debug, Clone, PartialOrd, PartialEq)]
588#[repr(C, u8)]
589pub enum SvgNode {
590 MultiPolygonCollection(SvgMultiPolygonVec),
592 MultiPolygon(SvgMultiPolygon),
593 MultiShape(SvgSimpleNodeVec),
594 Path(SvgPath),
595 Circle(SvgCircle),
596 Rect(SvgRect),
597}
598
599#[derive(Debug, Clone, PartialOrd, PartialEq)]
601#[repr(C, u8)]
602pub enum SvgSimpleNode {
603 Path(SvgPath),
604 Circle(SvgCircle),
605 Rect(SvgRect),
606 CircleHole(SvgCircle),
607 RectHole(SvgRect),
608}
609
610impl_option!(
611 SvgSimpleNode,
612 OptionSvgSimpleNode,
613 copy = false,
614 [Debug, Clone, PartialOrd, PartialEq]
615);
616
617impl_vec!(
618 SvgSimpleNode,
619 SvgSimpleNodeVec,
620 SvgSimpleNodeVecDestructor,
621 SvgSimpleNodeVecDestructorType,
622 SvgSimpleNodeVecSlice,
623 OptionSvgSimpleNode
624);
625impl_vec_debug!(SvgSimpleNode, SvgSimpleNodeVec);
626impl_vec_clone!(SvgSimpleNode, SvgSimpleNodeVec, SvgSimpleNodeVecDestructor);
627impl_vec_partialeq!(SvgSimpleNode, SvgSimpleNodeVec);
628impl_vec_partialord!(SvgSimpleNode, SvgSimpleNodeVec);
629
630impl SvgSimpleNode {
631 #[allow(clippy::match_same_arms)]
635 #[must_use]
636 pub fn get_bounds(&self) -> SvgRect {
637 match self {
638 Self::Path(a) => a.get_bounds(),
639 Self::Circle(a) => a.get_bounds(),
640 Self::Rect(a) => *a,
641 Self::CircleHole(a) => a.get_bounds(),
642 Self::RectHole(a) => *a,
643 }
644 }
645 #[must_use]
647 pub fn is_closed(&self) -> bool {
648 match self {
649 Self::Path(a) => a.is_closed(),
650 Self::Circle(_) | Self::Rect(_) | Self::CircleHole(_) | Self::RectHole(_) => true,
651 }
652 }
653}
654
655impl SvgNode {
656 #[must_use]
658 pub fn get_bounds(&self) -> SvgRect {
659 match self {
660 Self::MultiPolygonCollection(a) => {
661 let mut first_mp_bounds = match a.get(0) {
662 Some(s) => s.get_bounds(),
663 None => return SvgRect::default(),
664 };
665 for mp in a.iter().skip(1) {
666 let mp_bounds = mp.get_bounds();
667 first_mp_bounds.union_with(&mp_bounds);
668 }
669
670 first_mp_bounds
671 }
672 Self::MultiPolygon(a) => a.get_bounds(),
673 Self::MultiShape(a) => {
674 let mut first_mp_bounds = match a.get(0) {
675 Some(s) => s.get_bounds(),
676 None => return SvgRect::default(),
677 };
678 for mp in a.iter().skip(1) {
679 let mp_bounds = mp.get_bounds();
680 first_mp_bounds.union_with(&mp_bounds);
681 }
682
683 first_mp_bounds
684 }
685 Self::Path(a) => a.get_bounds(),
686 Self::Circle(a) => a.get_bounds(),
687 Self::Rect(a) => *a,
688 }
689 }
690 #[must_use]
692 pub fn is_closed(&self) -> bool {
693 match self {
694 Self::MultiPolygonCollection(a) => {
695 for mp in a {
696 for p in mp.rings.as_ref() {
697 if !p.is_closed() {
698 return false;
699 }
700 }
701 }
702
703 true
704 }
705 Self::MultiPolygon(a) => {
706 for p in a.rings.as_ref() {
707 if !p.is_closed() {
708 return false;
709 }
710 }
711
712 true
713 }
714 Self::MultiShape(a) => {
715 for p in a.as_ref() {
716 if !p.is_closed() {
717 return false;
718 }
719 }
720
721 true
722 }
723 Self::Path(a) => a.is_closed(),
724 Self::Circle(_) | Self::Rect(_) => true,
725 }
726 }
727}
728
729#[derive(Debug, Clone, PartialOrd, PartialEq)]
731#[repr(C)]
732pub struct SvgStyledNode {
733 pub geometry: SvgNode,
734 pub style: SvgStyle,
735}
736
737#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
739#[repr(C)]
740pub struct SvgVertex {
741 pub x: f32,
742 pub y: f32,
743}
744
745impl_option!(
746 SvgVertex,
747 OptionSvgVertex,
748 [Debug, Copy, Clone, PartialOrd, PartialEq]
749);
750
751impl VertexLayoutDescription for SvgVertex {
752 fn get_description() -> VertexLayout {
753 VertexLayout {
754 fields: vec![VertexAttribute {
755 va_name: String::from("vAttrXY").into(),
756 layout_location: None.into(),
757 attribute_type: VertexAttributeType::Float,
758 item_count: 2,
759 }]
760 .into(),
761 }
762 }
763}
764
765#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
767#[repr(C)]
768pub struct SvgColoredVertex {
769 pub x: f32,
770 pub y: f32,
771 pub z: f32,
772 pub r: f32,
773 pub g: f32,
774 pub b: f32,
775 pub a: f32,
776}
777
778impl_option!(
779 SvgColoredVertex,
780 OptionSvgColoredVertex,
781 [Debug, Copy, Clone, PartialOrd, PartialEq]
782);
783
784impl VertexLayoutDescription for SvgColoredVertex {
785 fn get_description() -> VertexLayout {
786 VertexLayout {
787 fields: vec![
788 VertexAttribute {
789 va_name: String::from("vAttrXY").into(),
790 layout_location: None.into(),
791 attribute_type: VertexAttributeType::Float,
792 item_count: 3,
793 },
794 VertexAttribute {
795 va_name: String::from("vColor").into(),
796 layout_location: None.into(),
797 attribute_type: VertexAttributeType::Float,
798 item_count: 4,
799 },
800 ]
801 .into(),
802 }
803 }
804}
805
806#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
808#[repr(C)]
809pub struct SvgCircle {
810 pub center_x: f32,
811 pub center_y: f32,
812 pub radius: f32,
813}
814
815#[allow(clippy::suboptimal_flops)] fn cross_sign(a: azul_css::props::basic::SvgPoint, b: azul_css::props::basic::SvgPoint, x: f32, y: f32) -> f32 {
822 (b.x - a.x) * (y - a.y) - (x - a.x) * (b.y - a.y)
823}
824
825impl SvgPath {
826 #[must_use]
833 pub fn flatten_to_points(&self, steps: usize) -> alloc::vec::Vec<azul_css::props::basic::SvgPoint> {
834 use azul_css::props::basic::SvgPoint;
835
836 let steps = steps.max(1);
837 let mut out: alloc::vec::Vec<SvgPoint> = alloc::vec::Vec::new();
838 let mut push = |p: SvgPoint| {
839 if out.last().is_none_or(|last| {
840 (last.x - p.x).abs() > f32::EPSILON || (last.y - p.y).abs() > f32::EPSILON
841 }) {
842 out.push(p);
843 }
844 };
845 for item in self.items.as_ref() {
846 match item {
847 SvgPathElement::Line(l) => {
848 push(l.start);
849 push(l.end);
850 }
851 SvgPathElement::QuadraticCurve(q) => {
852 push(q.start);
853 for i in 1..=steps {
854 #[allow(clippy::cast_precision_loss)] let t = i as f32 / steps as f32;
856 let inv = 1.0 - t;
857 push(SvgPoint {
858 x: inv * inv * q.start.x + 2.0 * inv * t * q.ctrl.x + t * t * q.end.x,
859 y: inv * inv * q.start.y + 2.0 * inv * t * q.ctrl.y + t * t * q.end.y,
860 });
861 }
862 }
863 SvgPathElement::CubicCurve(c) => {
864 push(c.start);
865 for i in 1..=steps {
866 #[allow(clippy::cast_precision_loss)] let t = i as f32 / steps as f32;
868 let inv = 1.0 - t;
869 let (a, b, cc, d) = (
870 inv * inv * inv,
871 3.0 * inv * inv * t,
872 3.0 * inv * t * t,
873 t * t * t,
874 );
875 push(SvgPoint {
876 x: a * c.start.x + b * c.ctrl_1.x + cc * c.ctrl_2.x + d * c.end.x,
877 y: a * c.start.y + b * c.ctrl_1.y + cc * c.ctrl_2.y + d * c.end.y,
878 });
879 }
880 }
881 }
882 }
883 out
884 }
885}
886
887impl SvgCircle {
888 #[allow(clippy::suboptimal_flops)] #[must_use]
891 pub fn contains_point(&self, x: f32, y: f32) -> bool {
892 let x_diff = libm::fabsf(x - self.center_x);
893 let y_diff = libm::fabsf(y - self.center_y);
894 (x_diff * x_diff) + (y_diff * y_diff) < (self.radius * self.radius)
895 }
896 #[must_use]
898 pub fn get_bounds(&self) -> SvgRect {
899 SvgRect {
900 width: self.radius * 2.0,
901 height: self.radius * 2.0,
902 x: self.center_x - self.radius,
903 y: self.center_y - self.radius,
904 radius_top_left: 0.0,
905 radius_top_right: 0.0,
906 radius_bottom_left: 0.0,
907 radius_bottom_right: 0.0,
908 }
909 }
910}
911
912#[derive(Debug, Clone, PartialEq, PartialOrd)]
913#[repr(C)]
914pub struct TessellatedSvgNode {
915 pub vertices: SvgVertexVec,
916 pub indices: U32Vec,
917}
918
919impl_option!(
920 TessellatedSvgNode,
921 OptionTessellatedSvgNode,
922 copy = false,
923 [Debug, Clone, PartialEq, PartialOrd]
924);
925
926impl Default for TessellatedSvgNode {
927 fn default() -> Self {
928 Self {
929 vertices: Vec::new().into(),
930 indices: Vec::new().into(),
931 }
932 }
933}
934
935impl_vec!(
936 TessellatedSvgNode,
937 TessellatedSvgNodeVec,
938 TessellatedSvgNodeVecDestructor,
939 TessellatedSvgNodeVecDestructorType,
940 TessellatedSvgNodeVecSlice,
941 OptionTessellatedSvgNode
942);
943impl_vec_debug!(TessellatedSvgNode, TessellatedSvgNodeVec);
944impl_vec_partialord!(TessellatedSvgNode, TessellatedSvgNodeVec);
945impl_vec_clone!(
946 TessellatedSvgNode,
947 TessellatedSvgNodeVec,
948 TessellatedSvgNodeVecDestructor
949);
950impl_vec_partialeq!(TessellatedSvgNode, TessellatedSvgNodeVec);
951
952impl TessellatedSvgNode {
953 #[must_use]
954 pub fn empty() -> Self {
955 Self::default()
956 }
957}
958
959impl TessellatedSvgNodeVec {
960 #[must_use]
961 pub fn get_ref(&self) -> TessellatedSvgNodeVecRef {
962 let slice = self.as_ref();
963 TessellatedSvgNodeVecRef {
964 ptr: slice.as_ptr(),
965 len: slice.len(),
966 }
967 }
968}
969
970impl fmt::Debug for TessellatedSvgNodeVecRef {
971 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
972 self.as_slice().fmt(f)
973 }
974}
975
976#[repr(C)]
978pub struct TessellatedSvgNodeVecRef {
979 pub ptr: *const TessellatedSvgNode,
980 pub len: usize,
981}
982
983impl Clone for TessellatedSvgNodeVecRef {
984 fn clone(&self) -> Self {
985 Self {
986 ptr: self.ptr,
987 len: self.len,
988 }
989 }
990}
991
992impl TessellatedSvgNodeVecRef {
993 #[must_use]
994 pub const fn as_slice(&self) -> &[TessellatedSvgNode] {
995 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
996 }
997}
998
999#[derive(Debug, Clone, PartialEq, PartialOrd)]
1000#[repr(C)]
1001pub struct TessellatedColoredSvgNode {
1002 pub vertices: SvgColoredVertexVec,
1003 pub indices: U32Vec,
1004}
1005
1006impl_option!(
1007 TessellatedColoredSvgNode,
1008 OptionTessellatedColoredSvgNode,
1009 copy = false,
1010 [Debug, Clone, PartialEq, PartialOrd]
1011);
1012
1013impl Default for TessellatedColoredSvgNode {
1014 fn default() -> Self {
1015 Self {
1016 vertices: Vec::new().into(),
1017 indices: Vec::new().into(),
1018 }
1019 }
1020}
1021
1022impl_vec!(
1023 TessellatedColoredSvgNode,
1024 TessellatedColoredSvgNodeVec,
1025 TessellatedColoredSvgNodeVecDestructor,
1026 TessellatedColoredSvgNodeVecDestructorType,
1027 TessellatedColoredSvgNodeVecSlice,
1028 OptionTessellatedColoredSvgNode
1029);
1030impl_vec_debug!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
1031impl_vec_partialord!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
1032impl_vec_clone!(
1033 TessellatedColoredSvgNode,
1034 TessellatedColoredSvgNodeVec,
1035 TessellatedColoredSvgNodeVecDestructor
1036);
1037impl_vec_partialeq!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
1038
1039impl TessellatedColoredSvgNode {
1040 #[must_use]
1041 pub fn empty() -> Self {
1042 Self::default()
1043 }
1044}
1045
1046impl TessellatedColoredSvgNodeVec {
1047 #[must_use]
1048 pub fn get_ref(&self) -> TessellatedColoredSvgNodeVecRef {
1049 let slice = self.as_ref();
1050 TessellatedColoredSvgNodeVecRef {
1051 ptr: slice.as_ptr(),
1052 len: slice.len(),
1053 }
1054 }
1055}
1056
1057impl fmt::Debug for TessellatedColoredSvgNodeVecRef {
1058 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1059 self.as_slice().fmt(f)
1060 }
1061}
1062
1063#[repr(C)]
1065pub struct TessellatedColoredSvgNodeVecRef {
1066 pub ptr: *const TessellatedColoredSvgNode,
1067 pub len: usize,
1068}
1069
1070impl Clone for TessellatedColoredSvgNodeVecRef {
1071 fn clone(&self) -> Self {
1072 Self {
1073 ptr: self.ptr,
1074 len: self.len,
1075 }
1076 }
1077}
1078
1079impl TessellatedColoredSvgNodeVecRef {
1080 #[must_use]
1081 pub const fn as_slice(&self) -> &[TessellatedColoredSvgNode] {
1082 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
1083 }
1084}
1085
1086impl_vec!(
1087 SvgVertex,
1088 SvgVertexVec,
1089 SvgVertexVecDestructor,
1090 SvgVertexVecDestructorType,
1091 SvgVertexVecSlice,
1092 OptionSvgVertex
1093);
1094impl_vec_debug!(SvgVertex, SvgVertexVec);
1095impl_vec_partialord!(SvgVertex, SvgVertexVec);
1096impl_vec_clone!(SvgVertex, SvgVertexVec, SvgVertexVecDestructor);
1097impl_vec_partialeq!(SvgVertex, SvgVertexVec);
1098
1099impl_vec!(
1100 SvgColoredVertex,
1101 SvgColoredVertexVec,
1102 SvgColoredVertexVecDestructor,
1103 SvgColoredVertexVecDestructorType,
1104 SvgColoredVertexVecSlice,
1105 OptionSvgColoredVertex
1106);
1107impl_vec_debug!(SvgColoredVertex, SvgColoredVertexVec);
1108impl_vec_partialord!(SvgColoredVertex, SvgColoredVertexVec);
1109impl_vec_clone!(
1110 SvgColoredVertex,
1111 SvgColoredVertexVec,
1112 SvgColoredVertexVecDestructor
1113);
1114impl_vec_partialeq!(SvgColoredVertex, SvgColoredVertexVec);
1115
1116#[allow(clippy::cast_precision_loss)]
1124fn compute_svg_transform_uniforms(
1125 target_size: PhysicalSizeU32,
1126 transforms: &[StyleTransform],
1127) -> (Uniform, Uniform) {
1128 let transform_origin = StyleTransformOrigin {
1129 x: PixelValue::px(target_size.width as f32 / 2.0),
1130 y: PixelValue::px(target_size.height as f32 / 2.0),
1131 };
1132
1133 let computed_transform = ComputedTransform3D::from_style_transform_vec(
1134 transforms,
1135 &transform_origin,
1136 target_size.width as f32,
1137 target_size.height as f32,
1138 RotationMode::ForWebRender,
1139 );
1140
1141 let m = computed_transform.get_column_major().m;
1144 let matrix: [f32; 16] = core::array::from_fn(|i| m[i / 4][i % 4]);
1145
1146 let bbox_uniform = Uniform {
1147 uniform_name: "vBboxSize".into(),
1148 uniform_type: UniformType::FloatVec2([target_size.width as f32, target_size.height as f32]),
1149 };
1150
1151 let transform_uniform = Uniform {
1152 uniform_name: "vTransformMatrix".into(),
1153 uniform_type: UniformType::Matrix4 {
1154 transpose: false,
1155 matrix,
1156 },
1157 };
1158
1159 (bbox_uniform, transform_uniform)
1160}
1161
1162#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
1163#[repr(C)]
1164pub struct TessellatedGPUSvgNode {
1165 pub vertex_index_buffer: VertexBuffer,
1166}
1167
1168impl TessellatedGPUSvgNode {
1169 #[must_use]
1171 pub fn new(node: &TessellatedSvgNode, gl: GlContextPtr) -> Self {
1172 let svg_shader_id = gl.ptr.svg_shader;
1173 Self {
1174 vertex_index_buffer: VertexBuffer::new(
1175 gl,
1176 svg_shader_id,
1177 node.vertices.as_ref(),
1178 node.indices.as_ref(),
1179 IndexBufferFormat::Triangles,
1180 ),
1181 }
1182 }
1183
1184 #[allow(clippy::needless_pass_by_value)] pub fn draw(
1187 &self,
1188 texture: &mut Texture,
1189 target_size: PhysicalSizeU32,
1190 color: ColorU,
1191 transforms: StyleTransformVec,
1192 ) -> bool {
1193 let (bbox_uniform, transform_uniform) =
1194 compute_svg_transform_uniforms(target_size, transforms.as_ref());
1195
1196 let color: ColorF = color.into();
1197
1198 let uniforms = [
1199 bbox_uniform,
1200 Uniform {
1201 uniform_name: "fDrawColor".into(),
1202 uniform_type: UniformType::FloatVec4([color.r, color.g, color.b, color.a]),
1203 },
1204 transform_uniform,
1205 ];
1206
1207 GlShader::draw(
1208 texture.gl_context.ptr.svg_shader,
1209 texture,
1210 &[(&self.vertex_index_buffer, &uniforms[..])],
1211 );
1212
1213 true
1214 }
1215}
1216
1217#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
1218#[repr(C)]
1219pub struct TessellatedColoredGPUSvgNode {
1220 pub vertex_index_buffer: VertexBuffer,
1221}
1222
1223impl TessellatedColoredGPUSvgNode {
1224 #[must_use]
1226 pub fn new(node: &TessellatedColoredSvgNode, gl: GlContextPtr) -> Self {
1227 let svg_shader_id = gl.ptr.svg_multicolor_shader;
1228 Self {
1229 vertex_index_buffer: VertexBuffer::new(
1230 gl,
1231 svg_shader_id,
1232 node.vertices.as_ref(),
1233 node.indices.as_ref(),
1234 IndexBufferFormat::Triangles,
1235 ),
1236 }
1237 }
1238
1239 #[allow(clippy::needless_pass_by_value)] pub fn draw(
1242 &self,
1243 texture: &mut Texture,
1244 target_size: PhysicalSizeU32,
1245 transforms: StyleTransformVec,
1246 ) -> bool {
1247 let (bbox_uniform, transform_uniform) =
1248 compute_svg_transform_uniforms(target_size, transforms.as_ref());
1249
1250 #[allow(clippy::tuple_array_conversions)]
1253 let uniforms = [bbox_uniform, transform_uniform];
1254
1255 GlShader::draw(
1256 texture.gl_context.ptr.svg_multicolor_shader,
1257 texture,
1258 &[(&self.vertex_index_buffer, &uniforms[..])],
1259 );
1260
1261 true
1262 }
1263}
1264
1265#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1266#[repr(C, u8)]
1267pub enum SvgStyle {
1268 Fill(SvgFillStyle),
1269 Stroke(SvgStrokeStyle),
1270}
1271
1272impl SvgStyle {
1273 #[must_use]
1274 pub const fn get_antialias(&self) -> bool {
1275 match self {
1276 Self::Fill(f) => f.anti_alias,
1277 Self::Stroke(s) => s.anti_alias,
1278 }
1279 }
1280 #[must_use]
1281 pub const fn get_high_quality_aa(&self) -> bool {
1282 match self {
1283 Self::Fill(f) => f.high_quality_aa,
1284 Self::Stroke(s) => s.high_quality_aa,
1285 }
1286 }
1287 #[must_use]
1288 pub const fn get_transform(&self) -> SvgTransform {
1289 match self {
1290 Self::Fill(f) => f.transform,
1291 Self::Stroke(s) => s.transform,
1292 }
1293 }
1294}
1295#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
1297#[repr(C)]
1298#[derive(Default)]
1299pub enum SvgFillRule {
1300 #[default]
1301 Winding,
1302 EvenOdd,
1303}
1304
1305#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
1306#[repr(C)]
1307pub struct SvgTransform {
1308 pub sx: f32,
1309 pub kx: f32,
1310 pub ky: f32,
1311 pub sy: f32,
1312 pub tx: f32,
1313 pub ty: f32,
1314}
1315
1316#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1317#[repr(C)]
1318pub struct SvgFillStyle {
1319 pub line_join: SvgLineJoin,
1323 pub miter_limit: f32,
1328 pub tolerance: f32,
1333 pub fill_rule: SvgFillRule,
1335 pub transform: SvgTransform,
1338 pub anti_alias: bool,
1340 pub high_quality_aa: bool,
1342}
1343
1344impl Default for SvgFillStyle {
1345 fn default() -> Self {
1346 Self {
1347 line_join: SvgLineJoin::Miter,
1348 miter_limit: DEFAULT_MITER_LIMIT,
1349 tolerance: DEFAULT_TOLERANCE,
1350 fill_rule: SvgFillRule::default(),
1351 transform: SvgTransform::default(),
1352 anti_alias: true,
1353 high_quality_aa: false,
1354 }
1355 }
1356}
1357
1358#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1359#[repr(C)]
1360pub struct SvgStrokeStyle {
1361 pub dash_pattern: OptionSvgDashPattern,
1363 pub transform: SvgTransform,
1366 pub start_cap: SvgLineCap,
1370 pub end_cap: SvgLineCap,
1374 pub line_join: SvgLineJoin,
1378 pub line_width: f32,
1382 pub miter_limit: f32,
1387 pub tolerance: f32,
1392 pub apply_line_width: bool,
1400 pub anti_alias: bool,
1402 pub high_quality_aa: bool,
1404}
1405
1406impl Default for SvgStrokeStyle {
1407 fn default() -> Self {
1408 Self {
1409 dash_pattern: OptionSvgDashPattern::None,
1410 transform: SvgTransform::default(),
1411 start_cap: SvgLineCap::default(),
1412 end_cap: SvgLineCap::default(),
1413 line_join: SvgLineJoin::default(),
1414 line_width: DEFAULT_LINE_WIDTH,
1415 miter_limit: DEFAULT_MITER_LIMIT,
1416 tolerance: DEFAULT_TOLERANCE,
1417 apply_line_width: true,
1418 anti_alias: true,
1419 high_quality_aa: false,
1420 }
1421 }
1422}
1423
1424#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1425#[repr(C)]
1426pub struct SvgDashPattern {
1427 pub offset: f32,
1428 pub length_1: f32,
1429 pub gap_1: f32,
1430 pub length_2: f32,
1431 pub gap_2: f32,
1432 pub length_3: f32,
1433 pub gap_3: f32,
1434}
1435
1436impl_option!(
1437 SvgDashPattern,
1438 OptionSvgDashPattern,
1439 [Debug, Copy, Clone, PartialEq, PartialOrd]
1440);
1441
1442#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1444#[repr(C)]
1445#[derive(Default)]
1446pub enum SvgLineCap {
1447 #[default]
1448 Butt,
1449 Square,
1450 Round,
1451}
1452
1453#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1455#[repr(C)]
1456#[derive(Default)]
1457pub enum SvgLineJoin {
1458 #[default]
1459 Miter,
1460 MiterClip,
1461 Round,
1462 Bevel,
1463}
1464
1465pub use core::ffi::c_void;
1466
1467#[derive(Debug, Clone)]
1468#[repr(C)]
1469pub struct SvgXmlNode {
1470 pub node: *const c_void, pub run_destructor: bool,
1472}
1473
1474#[derive(Debug, Clone)]
1475#[repr(C)]
1476pub struct Svg {
1477 pub tree: *const c_void, pub run_destructor: bool,
1479}
1480
1481#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1483#[repr(C)]
1484pub enum ShapeRendering {
1485 OptimizeSpeed,
1486 CrispEdges,
1487 GeometricPrecision,
1488}
1489
1490#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1492#[repr(C)]
1493pub enum ImageRendering {
1494 OptimizeQuality,
1495 OptimizeSpeed,
1496}
1497
1498#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1500#[repr(C)]
1501pub enum TextRendering {
1502 OptimizeSpeed,
1503 OptimizeLegibility,
1504 GeometricPrecision,
1505}
1506
1507#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1509#[repr(C)]
1510pub enum FontDatabase {
1511 Empty,
1512 System,
1513}
1514
1515#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1516#[repr(C)]
1517pub struct SvgRenderOptions {
1518 pub target_size: OptionLayoutSize,
1519 pub background_color: OptionColorU,
1520 pub fit: SvgFitTo,
1521 pub transform: SvgRenderTransform,
1522}
1523
1524#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1525#[repr(C)]
1526pub struct SvgRenderTransform {
1527 pub sx: f32,
1528 pub kx: f32,
1529 pub ky: f32,
1530 pub sy: f32,
1531 pub tx: f32,
1532 pub ty: f32,
1533}
1534
1535#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1536#[repr(C, u8)]
1537#[derive(Default)]
1538pub enum SvgFitTo {
1539 #[default]
1540 Original,
1541 Width(u32),
1542 Height(u32),
1543 Zoom(f32),
1544}
1545
1546#[derive(Debug, Clone, PartialEq, PartialOrd)]
1547#[repr(C)]
1548pub struct SvgParseOptions {
1549 pub relative_image_path: OptionString,
1551 pub default_font_family: AzString,
1554 pub languages: StringVec,
1557 pub dpi: f32,
1559 pub font_size: f32,
1562 pub shape_rendering: ShapeRendering,
1565 pub text_rendering: TextRendering,
1568 pub image_rendering: ImageRendering,
1571 pub fontdb: FontDatabase,
1573 pub keep_named_groups: bool,
1576}
1577
1578impl Default for SvgParseOptions {
1579 fn default() -> Self {
1580 let lang_vec: Vec<AzString> = vec![String::from("en").into()];
1581 Self {
1582 relative_image_path: OptionString::None,
1583 default_font_family: "Times New Roman".to_string().into(),
1584 languages: lang_vec.into(),
1585 dpi: 96.0,
1586 font_size: 12.0,
1587 shape_rendering: ShapeRendering::GeometricPrecision,
1588 text_rendering: TextRendering::OptimizeLegibility,
1589 image_rendering: ImageRendering::OptimizeQuality,
1590 fontdb: FontDatabase::System,
1591 keep_named_groups: false,
1592 }
1593 }
1594}
1595
1596#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
1597#[repr(C)]
1598pub struct SvgXmlOptions {
1599 pub use_single_quote: bool,
1600 pub indent: Indent,
1601 pub attributes_indent: Indent,
1602}
1603
1604impl Default for SvgXmlOptions {
1605 fn default() -> Self {
1606 Self {
1607 use_single_quote: false,
1608 indent: Indent::Spaces(2),
1609 attributes_indent: Indent::Spaces(2),
1610 }
1611 }
1612}
1613#[allow(variant_size_differences)]
1614#[derive(Debug, PartialEq, Eq, PartialOrd, Clone)]
1616#[repr(C, u8)]
1617pub enum SvgParseError {
1618 NoParserAvailable,
1619 ElementsLimitReached,
1620 NotAnUtf8Str,
1621 MalformedGZip,
1622 InvalidSize,
1623 ParsingFailed(XmlError),
1624}
1625
1626impl fmt::Display for SvgParseError {
1627 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1628 use self::SvgParseError::{
1629 ElementsLimitReached, InvalidSize, MalformedGZip, NoParserAvailable, NotAnUtf8Str,
1630 ParsingFailed,
1631 };
1632 match self {
1633 NoParserAvailable => write!(
1634 f,
1635 "Library was compiled without SVG support (no parser available)"
1636 ),
1637 ElementsLimitReached => write!(f, "Error parsing SVG: Elements limit reached"),
1638 NotAnUtf8Str => write!(f, "Error parsing SVG: Not an UTF-8 String"),
1639 MalformedGZip => write!(
1640 f,
1641 "Error parsing SVG: SVG is compressed with a malformed GZIP compression"
1642 ),
1643 InvalidSize => write!(f, "Error parsing SVG: Invalid size"),
1644 ParsingFailed(e) => write!(f, "Error parsing SVG: Parsing SVG as XML failed: {e}"),
1645 }
1646 }
1647}
1648
1649impl_result!(
1650 SvgXmlNode,
1651 SvgParseError,
1652 ResultSvgXmlNodeSvgParseError,
1653 copy = false,
1654 [Debug, Clone]
1655);
1656impl_result!(
1657 Svg,
1658 SvgParseError,
1659 ResultSvgSvgParseError,
1660 copy = false,
1661 [Debug, Clone]
1662);
1663
1664#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
1666#[repr(C, u8)]
1667pub enum Indent {
1668 None,
1669 Spaces(u8),
1670 Tabs,
1671}
1672
1673#[cfg(test)]
1674#[path = "svg_test.rs"]
1675mod svg_test;
1676