Skip to main content

azul_core/
svg.rs

1//! SVG rendering and path tessellation.
2//!
3//! This module provides functionality for parsing, manipulating, and rendering SVG paths.
4//! It includes:
5//!
6//! - **Path tessellation**: Converts SVG paths into triangle meshes for GPU rendering
7//! - **Stroke generation**: Creates stroked paths with various line join and cap styles
8//! - **Transform support**: Applies CSS transforms to SVG elements
9//! - **Style parsing**: Handles SVG fill, stroke, opacity, and other attributes
10//!
11//! The module uses Lyon for geometric tessellation and generates vertex/index buffers
12//! that can be uploaded to WebRender for hardware-accelerated rendering.
13
14use 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
41/// Default miter limit for stroke joins (ratio of miter length to stroke width)
42const DEFAULT_MITER_LIMIT: f32 = 4.0;
43/// Default stroke width in pixels
44const DEFAULT_LINE_WIDTH: f32 = 1.0;
45/// Default tessellation tolerance in pixels (smaller = more vertices, higher quality)
46const DEFAULT_TOLERANCE: f32 = 0.1;
47
48/// Represents the dimensions of an SVG viewport or element.
49#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
50#[repr(C)]
51pub struct SvgSize {
52    /// Width in SVG user units
53    pub width: f32,
54    /// Height in SVG user units
55    pub height: f32,
56}
57
58/// A line segment in 2D space.
59#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
60#[repr(C)]
61pub struct SvgLine {
62    /// Start point of the line
63    pub start: SvgPoint,
64    /// End point of the line
65    pub end: SvgPoint,
66}
67
68impl SvgLine {
69    /// Creates a new line segment from start to end point
70    #[inline]
71    pub const fn new(start: SvgPoint, end: SvgPoint) -> Self {
72        Self { start, end }
73    }
74
75    /// Computes the inward-facing normal vector for this line.
76    ///
77    /// The normal points 90 degrees to the right of the line direction.
78    /// Returns `None` if the line has zero length.
79    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 * dx + dy * dy).sqrt();
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    /// Computes the outward-facing normal vector for this line (opposite of `inwards_normal`).
94    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    /// Reverses the direction of the line by swapping start and end points.
103    pub fn reverse(&mut self) {
104        let temp = self.start;
105        self.start = self.end;
106        self.end = temp;
107    }
108    /// Returns the start point of the line.
109    pub fn get_start(&self) -> SvgPoint {
110        self.start
111    }
112    /// Returns the end point of the line.
113    pub fn get_end(&self) -> SvgPoint {
114        self.end
115    }
116
117    /// Returns the parametric `t` value (0.0–1.0) at the given arc-length offset.
118    pub fn get_t_at_offset(&self, offset: f64) -> f64 {
119        offset / self.get_length()
120    }
121
122    /// Returns the tangent vector of the line.
123    /// For a line, the tangent is constant (same direction everywhere),
124    /// so no `t` parameter is needed.
125    pub fn get_tangent_vector_at_t(&self) -> SvgVector {
126        let dx = self.end.x - self.start.x;
127        let dy = self.end.y - self.start.y;
128        SvgVector {
129            x: dx as f64,
130            y: dy as f64,
131        }
132        .normalize()
133    }
134
135    /// Returns the X coordinate at parametric position `t` (0.0 = start, 1.0 = end).
136    pub fn get_x_at_t(&self, t: f64) -> f64 {
137        self.start.x as f64 + (self.end.x as f64 - self.start.x as f64) * t
138    }
139
140    /// Returns the Y coordinate at parametric position `t` (0.0 = start, 1.0 = end).
141    pub fn get_y_at_t(&self, t: f64) -> f64 {
142        self.start.y as f64 + (self.end.y as f64 - self.start.y as f64) * t
143    }
144
145    /// Returns the Euclidean length of the line segment.
146    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        libm::hypotf(dx, dy) as f64
150    }
151
152    /// Returns the axis-aligned bounding rectangle of this line segment.
153    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    /// Creates a line path element from a SvgLine
192    #[inline]
193    pub const fn line(l: SvgLine) -> Self {
194        SvgPathElement::Line(l)
195    }
196
197    /// Creates a quadratic curve path element from a SvgQuadraticCurve
198    #[inline]
199    pub const fn quadratic_curve(qc: SvgQuadraticCurve) -> Self {
200        SvgPathElement::QuadraticCurve(qc)
201    }
202
203    /// Creates a cubic curve path element from a SvgCubicCurve
204    #[inline]
205    pub const fn cubic_curve(cc: SvgCubicCurve) -> Self {
206        SvgPathElement::CubicCurve(cc)
207    }
208
209    /// Sets the end point of this path element.
210    pub fn set_last(&mut self, point: SvgPoint) {
211        match self {
212            SvgPathElement::Line(l) => l.end = point,
213            SvgPathElement::QuadraticCurve(qc) => qc.end = point,
214            SvgPathElement::CubicCurve(cc) => cc.end = point,
215        }
216    }
217
218    /// Sets the start point of this path element.
219    pub fn set_first(&mut self, point: SvgPoint) {
220        match self {
221            SvgPathElement::Line(l) => l.start = point,
222            SvgPathElement::QuadraticCurve(qc) => qc.start = point,
223            SvgPathElement::CubicCurve(cc) => cc.start = point,
224        }
225    }
226
227    /// Reverses the direction of this path element.
228    pub fn reverse(&mut self) {
229        match self {
230            SvgPathElement::Line(l) => l.reverse(),
231            SvgPathElement::QuadraticCurve(qc) => qc.reverse(),
232            SvgPathElement::CubicCurve(cc) => cc.reverse(),
233        }
234    }
235    /// Returns the start point of this path element.
236    pub fn get_start(&self) -> SvgPoint {
237        match self {
238            SvgPathElement::Line(l) => l.get_start(),
239            SvgPathElement::QuadraticCurve(qc) => qc.get_start(),
240            SvgPathElement::CubicCurve(cc) => cc.get_start(),
241        }
242    }
243    /// Returns the end point of this path element.
244    pub fn get_end(&self) -> SvgPoint {
245        match self {
246            SvgPathElement::Line(l) => l.get_end(),
247            SvgPathElement::QuadraticCurve(qc) => qc.get_end(),
248            SvgPathElement::CubicCurve(cc) => cc.get_end(),
249        }
250    }
251    /// Returns the axis-aligned bounding rectangle of this path element.
252    pub fn get_bounds(&self) -> SvgRect {
253        match self {
254            SvgPathElement::Line(l) => l.get_bounds(),
255            SvgPathElement::QuadraticCurve(qc) => qc.get_bounds(),
256            SvgPathElement::CubicCurve(cc) => cc.get_bounds(),
257        }
258    }
259    /// Returns the arc length of this path element.
260    pub fn get_length(&self) -> f64 {
261        match self {
262            SvgPathElement::Line(l) => l.get_length(),
263            SvgPathElement::QuadraticCurve(qc) => qc.get_length(),
264            SvgPathElement::CubicCurve(cc) => cc.get_length(),
265        }
266    }
267    /// Returns the parametric `t` value at the given arc-length offset.
268    pub fn get_t_at_offset(&self, offset: f64) -> f64 {
269        match self {
270            SvgPathElement::Line(l) => l.get_t_at_offset(offset),
271            SvgPathElement::QuadraticCurve(qc) => qc.get_t_at_offset(offset),
272            SvgPathElement::CubicCurve(cc) => cc.get_t_at_offset(offset),
273        }
274    }
275    /// Returns the normalized tangent vector at parametric position `t`.
276    pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
277        match self {
278            SvgPathElement::Line(l) => l.get_tangent_vector_at_t(),
279            SvgPathElement::QuadraticCurve(qc) => qc.get_tangent_vector_at_t(t),
280            SvgPathElement::CubicCurve(cc) => cc.get_tangent_vector_at_t(t),
281        }
282    }
283    /// Returns the X coordinate at parametric position `t`.
284    pub fn get_x_at_t(&self, t: f64) -> f64 {
285        match self {
286            SvgPathElement::Line(l) => l.get_x_at_t(t),
287            SvgPathElement::QuadraticCurve(qc) => qc.get_x_at_t(t),
288            SvgPathElement::CubicCurve(cc) => cc.get_x_at_t(t),
289        }
290    }
291    /// Returns the Y coordinate at parametric position `t`.
292    pub fn get_y_at_t(&self, t: f64) -> f64 {
293        match self {
294            SvgPathElement::Line(l) => l.get_y_at_t(t),
295            SvgPathElement::QuadraticCurve(qc) => qc.get_y_at_t(t),
296            SvgPathElement::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    /// Creates a new SvgPath from a vector of path elements
326    #[inline]
327    pub const fn create(items: SvgPathElementVec) -> Self {
328        Self { items }
329    }
330
331    /// Returns the start point of the first element, or `None` if the path is empty.
332    pub fn get_start(&self) -> Option<SvgPoint> {
333        self.items.as_ref().first().map(|s| s.get_start())
334    }
335
336    /// Returns the end point of the last element, or `None` if the path is empty.
337    pub fn get_end(&self) -> Option<SvgPoint> {
338        self.items.as_ref().last().map(|s| s.get_end())
339    }
340
341    /// Closes the path by appending a line from the last point to the first point, if needed.
342    pub fn close(&mut self) {
343        let first = match self.items.as_ref().first() {
344            Some(s) => s,
345            None => return,
346        };
347        let last = match self.items.as_ref().last() {
348            Some(s) => s,
349            None => return,
350        };
351        if first.get_start() != last.get_end() {
352            let mut elements = self.items.as_slice().to_vec();
353            elements.push(SvgPathElement::Line(SvgLine {
354                start: last.get_end(),
355                end: first.get_start(),
356            }));
357            self.items = elements.into();
358        }
359    }
360
361    /// Returns `true` if the path's first start point equals its last end point.
362    pub fn is_closed(&self) -> bool {
363        let first = self.items.as_ref().first();
364        let last = self.items.as_ref().last();
365        match (first, last) {
366            (Some(f), Some(l)) => (f.get_start() == l.get_end()),
367            _ => false,
368        }
369    }
370
371    /// Reverses the order and direction of all elements in the path.
372    pub fn reverse(&mut self) {
373        // swap self.items with a default vec
374        let mut vec = SvgPathElementVec::from_const_slice(&[]);
375        core::mem::swap(&mut vec, &mut self.items);
376        let mut vec = vec.into_library_owned_vec();
377
378        // reverse the order of items in the vec
379        vec.reverse();
380
381        // reverse the order inside the item itself
382        // i.e. swap line.start and line.end
383        for item in vec.iter_mut() {
384            item.reverse();
385        }
386
387        // swap back
388        let mut vec = SvgPathElementVec::from_vec(vec);
389        core::mem::swap(&mut vec, &mut self.items);
390    }
391
392    /// Joins another path onto the end of this one, interpolating the join point.
393    pub fn join_with(&mut self, mut path: Self) -> Option<()> {
394        let self_last_point = self.items.as_ref().last()?.get_end();
395        let other_start_point = path.items.as_ref().first()?.get_start();
396        let interpolated_join_point = SvgPoint {
397            x: (self_last_point.x + other_start_point.x) / 2.0,
398            y: (self_last_point.y + other_start_point.y) / 2.0,
399        };
400
401        // swap self.items with a default vec
402        let mut vec = SvgPathElementVec::from_const_slice(&[]);
403        core::mem::swap(&mut vec, &mut self.items);
404        let mut vec = vec.into_library_owned_vec();
405
406        let mut other = SvgPathElementVec::from_const_slice(&[]);
407        core::mem::swap(&mut other, &mut path.items);
408        let mut other = other.into_library_owned_vec();
409
410        let vec_len = vec.len() - 1;
411        vec.get_mut(vec_len)?.set_last(interpolated_join_point);
412        other.get_mut(0)?.set_first(interpolated_join_point);
413        vec.append(&mut other);
414
415        // swap back
416        let mut vec = SvgPathElementVec::from_vec(vec);
417        core::mem::swap(&mut vec, &mut self.items);
418
419        Some(())
420    }
421    /// Returns the axis-aligned bounding rectangle of the entire path.
422    pub fn get_bounds(&self) -> SvgRect {
423        let mut first_bounds = match self.items.as_ref().get(0) {
424            Some(s) => s.get_bounds(),
425            None => return SvgRect::default(),
426        };
427
428        for mp in self.items.as_ref().iter().skip(1) {
429            let mp_bounds = mp.get_bounds();
430            first_bounds.union_with(&mp_bounds);
431        }
432
433        first_bounds
434    }
435}
436
437#[derive(Debug, Clone, PartialEq, PartialOrd)]
438#[repr(C)]
439pub struct SvgMultiPolygon {
440    /// NOTE: If a ring represents a hole, simply reverse the order of points
441    pub rings: SvgPathVec,
442}
443
444impl_option!(
445    SvgMultiPolygon,
446    OptionSvgMultiPolygon,
447    copy = false,
448    [Debug, Clone, PartialEq, PartialOrd]
449);
450
451impl SvgMultiPolygon {
452    /// Creates a new SvgMultiPolygon from a vector of paths (rings)
453    /// NOTE: If a ring represents a hole, simply reverse the order of points
454    #[inline]
455    pub const fn create(rings: SvgPathVec) -> Self {
456        Self { rings }
457    }
458
459    /// Returns the axis-aligned bounding rectangle of all rings in this multi-polygon.
460    pub fn get_bounds(&self) -> SvgRect {
461        let mut first_bounds = match self
462            .rings
463            .get(0)
464            .and_then(|b| b.items.get(0).map(|i| i.get_bounds()))
465        {
466            Some(s) => s,
467            // Empty polygon has zero-sized bounds at origin
468            None => return SvgRect::default(),
469        };
470
471        for ring in self.rings.iter() {
472            for item in ring.items.iter() {
473                first_bounds.union_with(&item.get_bounds());
474            }
475        }
476
477        first_bounds
478    }
479}
480
481impl_vec!(SvgPath, SvgPathVec, SvgPathVecDestructor, SvgPathVecDestructorType, SvgPathVecSlice, OptionSvgPath);
482impl_vec_debug!(SvgPath, SvgPathVec);
483impl_vec_clone!(SvgPath, SvgPathVec, SvgPathVecDestructor);
484impl_vec_partialeq!(SvgPath, SvgPathVec);
485impl_vec_partialord!(SvgPath, SvgPathVec);
486
487impl_vec!(SvgMultiPolygon, SvgMultiPolygonVec, SvgMultiPolygonVecDestructor, SvgMultiPolygonVecDestructorType, SvgMultiPolygonVecSlice, OptionSvgMultiPolygon);
488impl_vec_debug!(SvgMultiPolygon, SvgMultiPolygonVec);
489impl_vec_clone!(
490    SvgMultiPolygon,
491    SvgMultiPolygonVec,
492    SvgMultiPolygonVecDestructor
493);
494impl_vec_partialeq!(SvgMultiPolygon, SvgMultiPolygonVec);
495impl_vec_partialord!(SvgMultiPolygon, SvgMultiPolygonVec);
496
497/// One `SvgNode` corresponds to one SVG `<path></path>` element
498#[derive(Debug, Clone, PartialOrd, PartialEq)]
499#[repr(C, u8)]
500pub enum SvgNode {
501    /// Multiple multipolygons, merged to one CPU buf for efficient drawing
502    MultiPolygonCollection(SvgMultiPolygonVec),
503    MultiPolygon(SvgMultiPolygon),
504    MultiShape(SvgSimpleNodeVec),
505    Path(SvgPath),
506    Circle(SvgCircle),
507    Rect(SvgRect),
508}
509
510/// One `SvgSimpleNode` is either a path, a rect or a circle
511#[derive(Debug, Clone, PartialOrd, PartialEq)]
512#[repr(C, u8)]
513pub enum SvgSimpleNode {
514    Path(SvgPath),
515    Circle(SvgCircle),
516    Rect(SvgRect),
517    CircleHole(SvgCircle),
518    RectHole(SvgRect),
519}
520
521impl_option!(
522    SvgSimpleNode,
523    OptionSvgSimpleNode,
524    copy = false,
525    [Debug, Clone, PartialOrd, PartialEq]
526);
527
528impl_vec!(SvgSimpleNode, SvgSimpleNodeVec, SvgSimpleNodeVecDestructor, SvgSimpleNodeVecDestructorType, SvgSimpleNodeVecSlice, OptionSvgSimpleNode);
529impl_vec_debug!(SvgSimpleNode, SvgSimpleNodeVec);
530impl_vec_clone!(SvgSimpleNode, SvgSimpleNodeVec, SvgSimpleNodeVecDestructor);
531impl_vec_partialeq!(SvgSimpleNode, SvgSimpleNodeVec);
532impl_vec_partialord!(SvgSimpleNode, SvgSimpleNodeVec);
533
534impl SvgSimpleNode {
535    /// Returns the axis-aligned bounding rectangle of this node.
536    pub fn get_bounds(&self) -> SvgRect {
537        match self {
538            SvgSimpleNode::Path(a) => a.get_bounds(),
539            SvgSimpleNode::Circle(a) => a.get_bounds(),
540            SvgSimpleNode::Rect(a) => a.clone(),
541            SvgSimpleNode::CircleHole(a) => a.get_bounds(),
542            SvgSimpleNode::RectHole(a) => a.clone(),
543        }
544    }
545    /// Returns `true` if this node represents a closed shape.
546    pub fn is_closed(&self) -> bool {
547        match self {
548            SvgSimpleNode::Path(a) => a.is_closed(),
549            SvgSimpleNode::Circle(_) => true,
550            SvgSimpleNode::Rect(_) => true,
551            SvgSimpleNode::CircleHole(_) => true,
552            SvgSimpleNode::RectHole(_) => true,
553        }
554    }
555}
556
557impl SvgNode {
558    /// Returns the axis-aligned bounding rectangle of this SVG node.
559    pub fn get_bounds(&self) -> SvgRect {
560        match self {
561            SvgNode::MultiPolygonCollection(a) => {
562                let mut first_mp_bounds = match a.get(0) {
563                    Some(s) => s.get_bounds(),
564                    None => return SvgRect::default(),
565                };
566                for mp in a.iter().skip(1) {
567                    let mp_bounds = mp.get_bounds();
568                    first_mp_bounds.union_with(&mp_bounds);
569                }
570
571                first_mp_bounds
572            }
573            SvgNode::MultiPolygon(a) => a.get_bounds(),
574            SvgNode::MultiShape(a) => {
575                let mut first_mp_bounds = match a.get(0) {
576                    Some(s) => s.get_bounds(),
577                    None => return SvgRect::default(),
578                };
579                for mp in a.iter().skip(1) {
580                    let mp_bounds = mp.get_bounds();
581                    first_mp_bounds.union_with(&mp_bounds);
582                }
583
584                first_mp_bounds
585            }
586            SvgNode::Path(a) => a.get_bounds(),
587            SvgNode::Circle(a) => a.get_bounds(),
588            SvgNode::Rect(a) => a.clone(),
589        }
590    }
591    /// Returns `true` if all sub-paths in this node are closed.
592    pub fn is_closed(&self) -> bool {
593        match self {
594            SvgNode::MultiPolygonCollection(a) => {
595                for mp in a.iter() {
596                    for p in mp.rings.as_ref().iter() {
597                        if !p.is_closed() {
598                            return false;
599                        }
600                    }
601                }
602
603                true
604            }
605            SvgNode::MultiPolygon(a) => {
606                for p in a.rings.as_ref().iter() {
607                    if !p.is_closed() {
608                        return false;
609                    }
610                }
611
612                true
613            }
614            SvgNode::MultiShape(a) => {
615                for p in a.as_ref().iter() {
616                    if !p.is_closed() {
617                        return false;
618                    }
619                }
620
621                true
622            }
623            SvgNode::Path(a) => a.is_closed(),
624            SvgNode::Circle(_) => true,
625            SvgNode::Rect(_) => true,
626        }
627    }
628}
629
630/// An SVG node paired with its visual style (fill or stroke).
631#[derive(Debug, Clone, PartialOrd, PartialEq)]
632#[repr(C)]
633pub struct SvgStyledNode {
634    pub geometry: SvgNode,
635    pub style: SvgStyle,
636}
637
638/// A 2D vertex used in tessellated SVG geometry.
639#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
640#[repr(C)]
641pub struct SvgVertex {
642    pub x: f32,
643    pub y: f32,
644}
645
646impl_option!(
647    SvgVertex,
648    OptionSvgVertex,
649    [Debug, Copy, Clone, PartialOrd, PartialEq]
650);
651
652impl VertexLayoutDescription for SvgVertex {
653    fn get_description() -> VertexLayout {
654        VertexLayout {
655            fields: vec![VertexAttribute {
656                va_name: String::from("vAttrXY").into(),
657                layout_location: None.into(),
658                attribute_type: VertexAttributeType::Float,
659                item_count: 2,
660            }]
661            .into(),
662        }
663    }
664}
665
666/// A 3D vertex with per-vertex RGBA color, used in multi-colored SVG tessellation.
667#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
668#[repr(C)]
669pub struct SvgColoredVertex {
670    pub x: f32,
671    pub y: f32,
672    pub z: f32,
673    pub r: f32,
674    pub g: f32,
675    pub b: f32,
676    pub a: f32,
677}
678
679impl_option!(
680    SvgColoredVertex,
681    OptionSvgColoredVertex,
682    [Debug, Copy, Clone, PartialOrd, PartialEq]
683);
684
685impl VertexLayoutDescription for SvgColoredVertex {
686    fn get_description() -> VertexLayout {
687        VertexLayout {
688            fields: vec![
689                VertexAttribute {
690                    va_name: String::from("vAttrXY").into(),
691                    layout_location: None.into(),
692                    attribute_type: VertexAttributeType::Float,
693                    item_count: 3,
694                },
695                VertexAttribute {
696                    va_name: String::from("vColor").into(),
697                    layout_location: None.into(),
698                    attribute_type: VertexAttributeType::Float,
699                    item_count: 4,
700                },
701            ]
702            .into(),
703        }
704    }
705}
706
707/// A circle defined by center coordinates and radius.
708#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
709#[repr(C)]
710pub struct SvgCircle {
711    pub center_x: f32,
712    pub center_y: f32,
713    pub radius: f32,
714}
715
716impl SvgCircle {
717    /// Returns `true` if the given point lies inside the circle.
718    pub fn contains_point(&self, x: f32, y: f32) -> bool {
719        let x_diff = libm::fabsf(x - self.center_x);
720        let y_diff = libm::fabsf(y - self.center_y);
721        (x_diff * x_diff) + (y_diff * y_diff) < (self.radius * self.radius)
722    }
723    /// Returns the axis-aligned bounding rectangle of this circle.
724    pub fn get_bounds(&self) -> SvgRect {
725        SvgRect {
726            width: self.radius * 2.0,
727            height: self.radius * 2.0,
728            x: self.center_x - self.radius,
729            y: self.center_y - self.radius,
730            radius_top_left: 0.0,
731            radius_top_right: 0.0,
732            radius_bottom_left: 0.0,
733            radius_bottom_right: 0.0,
734        }
735    }
736}
737
738#[derive(Debug, Clone, PartialEq, PartialOrd)]
739#[repr(C)]
740pub struct TessellatedSvgNode {
741    pub vertices: SvgVertexVec,
742    pub indices: U32Vec,
743}
744
745impl_option!(
746    TessellatedSvgNode,
747    OptionTessellatedSvgNode,
748    copy = false,
749    [Debug, Clone, PartialEq, PartialOrd]
750);
751
752impl Default for TessellatedSvgNode {
753    fn default() -> Self {
754        Self {
755            vertices: Vec::new().into(),
756            indices: Vec::new().into(),
757        }
758    }
759}
760
761impl_vec!(TessellatedSvgNode, TessellatedSvgNodeVec, TessellatedSvgNodeVecDestructor, TessellatedSvgNodeVecDestructorType, TessellatedSvgNodeVecSlice, OptionTessellatedSvgNode);
762impl_vec_debug!(TessellatedSvgNode, TessellatedSvgNodeVec);
763impl_vec_partialord!(TessellatedSvgNode, TessellatedSvgNodeVec);
764impl_vec_clone!(
765    TessellatedSvgNode,
766    TessellatedSvgNodeVec,
767    TessellatedSvgNodeVecDestructor
768);
769impl_vec_partialeq!(TessellatedSvgNode, TessellatedSvgNodeVec);
770
771impl TessellatedSvgNode {
772    pub fn empty() -> Self {
773        Self::default()
774    }
775}
776
777impl TessellatedSvgNodeVec {
778    pub fn get_ref(&self) -> TessellatedSvgNodeVecRef {
779        let slice = self.as_ref();
780        TessellatedSvgNodeVecRef {
781            ptr: slice.as_ptr(),
782            len: slice.len(),
783        }
784    }
785}
786
787impl fmt::Debug for TessellatedSvgNodeVecRef {
788    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
789        self.as_slice().fmt(f)
790    }
791}
792
793// C ABI wrapper over &[TessellatedSvgNode]
794#[repr(C)]
795pub struct TessellatedSvgNodeVecRef {
796    pub ptr: *const TessellatedSvgNode,
797    pub len: usize,
798}
799
800impl Clone for TessellatedSvgNodeVecRef {
801    fn clone(&self) -> Self {
802        Self {
803            ptr: self.ptr,
804            len: self.len,
805        }
806    }
807}
808
809impl TessellatedSvgNodeVecRef {
810    pub fn as_slice<'a>(&'a self) -> &'a [TessellatedSvgNode] {
811        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
812    }
813}
814
815#[derive(Debug, Clone, PartialEq, PartialOrd)]
816#[repr(C)]
817pub struct TessellatedColoredSvgNode {
818    pub vertices: SvgColoredVertexVec,
819    pub indices: U32Vec,
820}
821
822impl_option!(
823    TessellatedColoredSvgNode,
824    OptionTessellatedColoredSvgNode,
825    copy = false,
826    [Debug, Clone, PartialEq, PartialOrd]
827);
828
829impl Default for TessellatedColoredSvgNode {
830    fn default() -> Self {
831        Self {
832            vertices: Vec::new().into(),
833            indices: Vec::new().into(),
834        }
835    }
836}
837
838impl_vec!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec, TessellatedColoredSvgNodeVecDestructor, TessellatedColoredSvgNodeVecDestructorType, TessellatedColoredSvgNodeVecSlice, OptionTessellatedColoredSvgNode);
839impl_vec_debug!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
840impl_vec_partialord!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
841impl_vec_clone!(
842    TessellatedColoredSvgNode,
843    TessellatedColoredSvgNodeVec,
844    TessellatedColoredSvgNodeVecDestructor
845);
846impl_vec_partialeq!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
847
848impl TessellatedColoredSvgNode {
849    pub fn empty() -> Self {
850        Self::default()
851    }
852}
853
854impl TessellatedColoredSvgNodeVec {
855    pub fn get_ref(&self) -> TessellatedColoredSvgNodeVecRef {
856        let slice = self.as_ref();
857        TessellatedColoredSvgNodeVecRef {
858            ptr: slice.as_ptr(),
859            len: slice.len(),
860        }
861    }
862}
863
864impl fmt::Debug for TessellatedColoredSvgNodeVecRef {
865    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
866        self.as_slice().fmt(f)
867    }
868}
869
870// C ABI wrapper over &[TessellatedColoredSvgNode]
871#[repr(C)]
872pub struct TessellatedColoredSvgNodeVecRef {
873    pub ptr: *const TessellatedColoredSvgNode,
874    pub len: usize,
875}
876
877impl Clone for TessellatedColoredSvgNodeVecRef {
878    fn clone(&self) -> Self {
879        Self {
880            ptr: self.ptr,
881            len: self.len,
882        }
883    }
884}
885
886impl TessellatedColoredSvgNodeVecRef {
887    pub fn as_slice<'a>(&'a self) -> &'a [TessellatedColoredSvgNode] {
888        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
889    }
890}
891
892impl_vec!(SvgVertex, SvgVertexVec, SvgVertexVecDestructor, SvgVertexVecDestructorType, SvgVertexVecSlice, OptionSvgVertex);
893impl_vec_debug!(SvgVertex, SvgVertexVec);
894impl_vec_partialord!(SvgVertex, SvgVertexVec);
895impl_vec_clone!(SvgVertex, SvgVertexVec, SvgVertexVecDestructor);
896impl_vec_partialeq!(SvgVertex, SvgVertexVec);
897
898impl_vec!(SvgColoredVertex, SvgColoredVertexVec, SvgColoredVertexVecDestructor, SvgColoredVertexVecDestructorType, SvgColoredVertexVecSlice, OptionSvgColoredVertex);
899impl_vec_debug!(SvgColoredVertex, SvgColoredVertexVec);
900impl_vec_partialord!(SvgColoredVertex, SvgColoredVertexVec);
901impl_vec_clone!(
902    SvgColoredVertex,
903    SvgColoredVertexVec,
904    SvgColoredVertexVecDestructor
905);
906impl_vec_partialeq!(SvgColoredVertex, SvgColoredVertexVec);
907
908/// Computes the bbox size and transform matrix uniforms shared by SVG draw methods.
909///
910/// Converts `StyleTransform` list into column-major `[f32; 16]` for OpenGL,
911/// and packages it along with the bbox size uniform.
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    // NOTE: OpenGL draws are column-major, while ComputedTransform3D
930    // is row-major! Need to transpose the matrix!
931    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, PartialOrd)]
954#[repr(C)]
955pub struct TessellatedGPUSvgNode {
956    pub vertex_index_buffer: VertexBuffer,
957}
958
959impl TessellatedGPUSvgNode {
960    /// Uploads the tesselated SVG node to GPU memory
961    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    /// Draw the vertex buffer to the texture with the given color and transform
975    pub fn draw(
976        &self,
977        texture: &mut Texture,
978        target_size: PhysicalSizeU32,
979        color: ColorU,
980        transforms: StyleTransformVec,
981    ) -> bool {
982        let (bbox_uniform, transform_uniform) =
983            compute_svg_transform_uniforms(target_size, transforms.as_ref());
984
985        let color: ColorF = color.into();
986
987        let uniforms = [
988            bbox_uniform,
989            Uniform {
990                uniform_name: "fDrawColor".into(),
991                uniform_type: UniformType::FloatVec4([color.r, color.g, color.b, color.a]),
992            },
993            transform_uniform,
994        ];
995
996        GlShader::draw(
997            texture.gl_context.ptr.svg_shader,
998            texture,
999            &[(&self.vertex_index_buffer, &uniforms[..])],
1000        );
1001
1002        true
1003    }
1004}
1005
1006#[derive(Debug, Clone, PartialEq, PartialOrd)]
1007#[repr(C)]
1008pub struct TessellatedColoredGPUSvgNode {
1009    pub vertex_index_buffer: VertexBuffer,
1010}
1011
1012impl TessellatedColoredGPUSvgNode {
1013    /// Uploads the tesselated SVG node to GPU memory
1014    pub fn new(node: &TessellatedColoredSvgNode, gl: GlContextPtr) -> Self {
1015        let svg_shader_id = gl.ptr.svg_multicolor_shader;
1016        Self {
1017            vertex_index_buffer: VertexBuffer::new(
1018                gl,
1019                svg_shader_id,
1020                node.vertices.as_ref(),
1021                node.indices.as_ref(),
1022                IndexBufferFormat::Triangles,
1023            ),
1024        }
1025    }
1026
1027    /// Draw the vertex buffer to the texture with the given color and transform
1028    pub fn draw(
1029        &self,
1030        texture: &mut Texture,
1031        target_size: PhysicalSizeU32,
1032        transforms: StyleTransformVec,
1033    ) -> bool {
1034        let (bbox_uniform, transform_uniform) =
1035            compute_svg_transform_uniforms(target_size, transforms.as_ref());
1036
1037        let uniforms = [bbox_uniform, transform_uniform];
1038
1039        GlShader::draw(
1040            texture.gl_context.ptr.svg_multicolor_shader,
1041            texture,
1042            &[(&self.vertex_index_buffer, &uniforms[..])],
1043        );
1044
1045        true
1046    }
1047}
1048
1049#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1050#[repr(C, u8)]
1051pub enum SvgStyle {
1052    Fill(SvgFillStyle),
1053    Stroke(SvgStrokeStyle),
1054}
1055
1056impl SvgStyle {
1057    pub fn get_antialias(&self) -> bool {
1058        match self {
1059            SvgStyle::Fill(f) => f.anti_alias,
1060            SvgStyle::Stroke(s) => s.anti_alias,
1061        }
1062    }
1063    pub fn get_high_quality_aa(&self) -> bool {
1064        match self {
1065            SvgStyle::Fill(f) => f.high_quality_aa,
1066            SvgStyle::Stroke(s) => s.high_quality_aa,
1067        }
1068    }
1069    pub fn get_transform(&self) -> SvgTransform {
1070        match self {
1071            SvgStyle::Fill(f) => f.transform,
1072            SvgStyle::Stroke(s) => s.transform,
1073        }
1074    }
1075}
1076/// SVG fill rule for determining the interior of a shape.
1077#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1078#[repr(C)]
1079pub enum SvgFillRule {
1080    Winding,
1081    EvenOdd,
1082}
1083
1084impl Default for SvgFillRule {
1085    fn default() -> Self {
1086        SvgFillRule::Winding
1087    }
1088}
1089
1090#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
1091#[repr(C)]
1092pub struct SvgTransform {
1093    pub sx: f32,
1094    pub kx: f32,
1095    pub ky: f32,
1096    pub sy: f32,
1097    pub tx: f32,
1098    pub ty: f32,
1099}
1100
1101#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1102#[repr(C)]
1103pub struct SvgFillStyle {
1104    /// See the SVG specification.
1105    ///
1106    /// Default value: `LineJoin::Miter`.
1107    pub line_join: SvgLineJoin,
1108    /// See the SVG specification.
1109    ///
1110    /// Must be greater than or equal to 1.0.
1111    /// Default value: `StrokeOptions::DEFAULT_MITER_LIMIT`.
1112    pub miter_limit: f32,
1113    /// Maximum allowed distance to the path when building an approximation.
1114    ///
1115    /// See [Flattening and tolerance](index.html#flattening-and-tolerance).
1116    /// Default value: `StrokeOptions::DEFAULT_TOLERANCE`.
1117    pub tolerance: f32,
1118    /// Whether to use the "winding" or "even / odd" fill rule when tesselating the path
1119    pub fill_rule: SvgFillRule,
1120    /// Whether to apply a transform to the points in the path (warning: will be done on the CPU -
1121    /// expensive)
1122    pub transform: SvgTransform,
1123    /// Whether the fill is intended to be anti-aliased (default: true)
1124    pub anti_alias: bool,
1125    /// Whether the anti-aliasing has to be of high quality (default: false)
1126    pub high_quality_aa: bool,
1127}
1128
1129impl Default for SvgFillStyle {
1130    fn default() -> Self {
1131        Self {
1132            line_join: SvgLineJoin::Miter,
1133            miter_limit: DEFAULT_MITER_LIMIT,
1134            tolerance: DEFAULT_TOLERANCE,
1135            fill_rule: SvgFillRule::default(),
1136            transform: SvgTransform::default(),
1137            anti_alias: true,
1138            high_quality_aa: false,
1139        }
1140    }
1141}
1142
1143#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1144#[repr(C)]
1145pub struct SvgStrokeStyle {
1146    /// Dash pattern
1147    pub dash_pattern: OptionSvgDashPattern,
1148    /// Whether to apply a transform to the points in the path (warning: will be done on the CPU -
1149    /// expensive)
1150    pub transform: SvgTransform,
1151    /// What cap to use at the start of each sub-path.
1152    ///
1153    /// Default value: `LineCap::Butt`.
1154    pub start_cap: SvgLineCap,
1155    /// What cap to use at the end of each sub-path.
1156    ///
1157    /// Default value: `LineCap::Butt`.
1158    pub end_cap: SvgLineCap,
1159    /// See the SVG specification.
1160    ///
1161    /// Default value: `LineJoin::Miter`.
1162    pub line_join: SvgLineJoin,
1163    /// Line width
1164    ///
1165    /// Default value: `StrokeOptions::DEFAULT_LINE_WIDTH`.
1166    pub line_width: f32,
1167    /// See the SVG specification.
1168    ///
1169    /// Must be greater than or equal to 1.0.
1170    /// Default value: `StrokeOptions::DEFAULT_MITER_LIMIT`.
1171    pub miter_limit: f32,
1172    /// Maximum allowed distance to the path when building an approximation.
1173    ///
1174    /// See [Flattening and tolerance](index.html#flattening-and-tolerance).
1175    /// Default value: `StrokeOptions::DEFAULT_TOLERANCE`.
1176    pub tolerance: f32,
1177    /// Apply line width
1178    ///
1179    /// When set to false, the generated vertices will all be positioned in the centre
1180    /// of the line. The width can be applied later on (eg in a vertex shader) by adding
1181    /// the vertex normal multiplied by the line with to each vertex position.
1182    ///
1183    /// Default value: `true`. NOTE: currently unused!
1184    pub apply_line_width: bool,
1185    /// Whether the fill is intended to be anti-aliased (default: true)
1186    pub anti_alias: bool,
1187    /// Whether the anti-aliasing has to be of high quality (default: false)
1188    pub high_quality_aa: bool,
1189}
1190
1191impl Default for SvgStrokeStyle {
1192    fn default() -> Self {
1193        Self {
1194            dash_pattern: OptionSvgDashPattern::None,
1195            transform: SvgTransform::default(),
1196            start_cap: SvgLineCap::default(),
1197            end_cap: SvgLineCap::default(),
1198            line_join: SvgLineJoin::default(),
1199            line_width: DEFAULT_LINE_WIDTH,
1200            miter_limit: DEFAULT_MITER_LIMIT,
1201            tolerance: DEFAULT_TOLERANCE,
1202            apply_line_width: true,
1203            anti_alias: true,
1204            high_quality_aa: false,
1205        }
1206    }
1207}
1208
1209#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1210#[repr(C)]
1211pub struct SvgDashPattern {
1212    pub offset: f32,
1213    pub length_1: f32,
1214    pub gap_1: f32,
1215    pub length_2: f32,
1216    pub gap_2: f32,
1217    pub length_3: f32,
1218    pub gap_3: f32,
1219}
1220
1221impl_option!(
1222    SvgDashPattern,
1223    OptionSvgDashPattern,
1224    [Debug, Copy, Clone, PartialEq, PartialOrd]
1225);
1226
1227/// The shape used at the end of open sub-paths when they are stroked.
1228#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1229#[repr(C)]
1230pub enum SvgLineCap {
1231    Butt,
1232    Square,
1233    Round,
1234}
1235
1236impl Default for SvgLineCap {
1237    fn default() -> Self {
1238        SvgLineCap::Butt
1239    }
1240}
1241
1242/// The shape used at the corners of stroked paths.
1243#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1244#[repr(C)]
1245pub enum SvgLineJoin {
1246    Miter,
1247    MiterClip,
1248    Round,
1249    Bevel,
1250}
1251
1252impl Default for SvgLineJoin {
1253    fn default() -> Self {
1254        SvgLineJoin::Miter
1255    }
1256}
1257
1258pub use core::ffi::c_void;
1259
1260#[derive(Debug, Clone)]
1261#[repr(C)]
1262pub struct SvgXmlNode {
1263    pub node: *const c_void, // usvg::Node
1264    pub run_destructor: bool,
1265}
1266
1267#[derive(Debug, Clone)]
1268#[repr(C)]
1269pub struct Svg {
1270    pub tree: *const c_void, // *mut usvg::Tree,
1271    pub run_destructor: bool,
1272}
1273
1274/// SVG `shape-rendering` property controlling quality vs speed tradeoffs.
1275#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1276#[repr(C)]
1277pub enum ShapeRendering {
1278    OptimizeSpeed,
1279    CrispEdges,
1280    GeometricPrecision,
1281}
1282
1283/// SVG `image-rendering` property controlling image quality vs speed.
1284#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1285#[repr(C)]
1286pub enum ImageRendering {
1287    OptimizeQuality,
1288    OptimizeSpeed,
1289}
1290
1291/// SVG `text-rendering` property controlling text quality vs speed.
1292#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1293#[repr(C)]
1294pub enum TextRendering {
1295    OptimizeSpeed,
1296    OptimizeLegibility,
1297    GeometricPrecision,
1298}
1299
1300/// Font database source for SVG text rendering.
1301#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1302#[repr(C)]
1303pub enum FontDatabase {
1304    Empty,
1305    System,
1306}
1307
1308#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1309#[repr(C)]
1310pub struct SvgRenderOptions {
1311    pub target_size: OptionLayoutSize,
1312    pub background_color: OptionColorU,
1313    pub fit: SvgFitTo,
1314    pub transform: SvgRenderTransform,
1315}
1316
1317#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1318#[repr(C)]
1319pub struct SvgRenderTransform {
1320    pub sx: f32,
1321    pub kx: f32,
1322    pub ky: f32,
1323    pub sy: f32,
1324    pub tx: f32,
1325    pub ty: f32,
1326}
1327
1328#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1329#[repr(C, u8)]
1330pub enum SvgFitTo {
1331    Original,
1332    Width(u32),
1333    Height(u32),
1334    Zoom(f32),
1335}
1336
1337impl Default for SvgFitTo {
1338    fn default() -> Self {
1339        SvgFitTo::Original
1340    }
1341}
1342
1343#[derive(Debug, Clone, PartialEq, PartialOrd)]
1344#[repr(C)]
1345pub struct SvgParseOptions {
1346    /// SVG image path. Used to resolve relative image paths.
1347    pub relative_image_path: OptionString,
1348    /// Default font family. Will be used when no font-family attribute is set in the SVG. Default:
1349    /// Times New Roman
1350    pub default_font_family: AzString,
1351    /// A list of languages. Will be used to resolve a systemLanguage conditional attribute.
1352    /// Format: en, en-US. Default: [en]
1353    pub languages: StringVec,
1354    /// Target DPI. Impact units conversion. Default: 96.0
1355    pub dpi: f32,
1356    /// A default font size. Will be used when no font-size attribute is set in the SVG. Default:
1357    /// 12
1358    pub font_size: f32,
1359    /// Specifies the default shape rendering method. Will be used when an SVG element's
1360    /// shape-rendering property is set to auto. Default: GeometricPrecision
1361    pub shape_rendering: ShapeRendering,
1362    /// Specifies the default text rendering method. Will be used when an SVG element's
1363    /// text-rendering property is set to auto. Default: OptimizeLegibility
1364    pub text_rendering: TextRendering,
1365    /// Specifies the default image rendering method. Will be used when an SVG element's
1366    /// image-rendering property is set to auto. Default: OptimizeQuality
1367    pub image_rendering: ImageRendering,
1368    /// When empty, text elements will be skipped. Default: `System`
1369    pub fontdb: FontDatabase,
1370    /// Keep named groups. If set to true, all non-empty groups with id attribute will not be
1371    /// removed. Default: false
1372    pub keep_named_groups: bool,
1373}
1374
1375impl Default for SvgParseOptions {
1376    fn default() -> Self {
1377        let lang_vec: Vec<AzString> = vec![String::from("en").into()];
1378        SvgParseOptions {
1379            relative_image_path: OptionString::None,
1380            default_font_family: "Times New Roman".to_string().into(),
1381            languages: lang_vec.into(),
1382            dpi: 96.0,
1383            font_size: 12.0,
1384            shape_rendering: ShapeRendering::GeometricPrecision,
1385            text_rendering: TextRendering::OptimizeLegibility,
1386            image_rendering: ImageRendering::OptimizeQuality,
1387            fontdb: FontDatabase::System,
1388            keep_named_groups: false,
1389        }
1390    }
1391}
1392
1393#[derive(Debug, Clone, PartialEq, PartialOrd)]
1394#[repr(C)]
1395pub struct SvgXmlOptions {
1396    pub use_single_quote: bool,
1397    pub indent: Indent,
1398    pub attributes_indent: Indent,
1399}
1400
1401impl Default for SvgXmlOptions {
1402    fn default() -> Self {
1403        SvgXmlOptions {
1404            use_single_quote: false,
1405            indent: Indent::Spaces(2),
1406            attributes_indent: Indent::Spaces(2),
1407        }
1408    }
1409}
1410
1411#[derive(Debug, PartialEq, PartialOrd, Clone)]
1412#[repr(C, u8)]
1413pub enum SvgParseError {
1414    NoParserAvailable,
1415    ElementsLimitReached,
1416    NotAnUtf8Str,
1417    MalformedGZip,
1418    InvalidSize,
1419    ParsingFailed(XmlError),
1420}
1421
1422impl fmt::Display for SvgParseError {
1423    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1424        use self::SvgParseError::*;
1425        match self {
1426            NoParserAvailable => write!(
1427                f,
1428                "Library was compiled without SVG support (no parser available)"
1429            ),
1430            ElementsLimitReached => write!(f, "Error parsing SVG: Elements limit reached"),
1431            NotAnUtf8Str => write!(f, "Error parsing SVG: Not an UTF-8 String"),
1432            MalformedGZip => write!(
1433                f,
1434                "Error parsing SVG: SVG is compressed with a malformed GZIP compression"
1435            ),
1436            InvalidSize => write!(f, "Error parsing SVG: Invalid size"),
1437            ParsingFailed(e) => write!(f, "Error parsing SVG: Parsing SVG as XML failed: {}", e),
1438        }
1439    }
1440}
1441
1442impl_result!(
1443    SvgXmlNode,
1444    SvgParseError,
1445    ResultSvgXmlNodeSvgParseError,
1446    copy = false,
1447    [Debug, Clone]
1448);
1449impl_result!(
1450    Svg,
1451    SvgParseError,
1452    ResultSvgSvgParseError,
1453    copy = false,
1454    [Debug, Clone]
1455);
1456
1457/// Indentation style for SVG XML serialization.
1458#[derive(Debug, Clone, PartialEq, PartialOrd)]
1459#[repr(C, u8)]
1460pub enum Indent {
1461    None,
1462    Spaces(u8),
1463    Tabs,
1464}