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::{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    /// Computes the inward-facing normal vector for this line.
70    ///
71    /// The normal points 90 degrees to the right of the line direction.
72    /// Returns `None` if the line has zero length.
73    pub fn inwards_normal(&self) -> Option<SvgPoint> {
74        let dx = self.end.x - self.start.x;
75        let dy = self.end.y - self.start.y;
76        let edge_length = (dx * dx + dy * dy).sqrt();
77        let x = -dy / edge_length;
78        let y = dx / edge_length;
79
80        if x.is_finite() && y.is_finite() {
81            Some(SvgPoint { x, y })
82        } else {
83            None
84        }
85    }
86
87    pub fn outwards_normal(&self) -> Option<SvgPoint> {
88        let inwards = self.inwards_normal()?;
89        Some(SvgPoint {
90            x: -inwards.x,
91            y: -inwards.y,
92        })
93    }
94
95    pub fn reverse(&mut self) {
96        let temp = self.start;
97        self.start = self.end;
98        self.end = temp;
99    }
100    pub fn get_start(&self) -> SvgPoint {
101        self.start
102    }
103    pub fn get_end(&self) -> SvgPoint {
104        self.end
105    }
106
107    pub fn get_t_at_offset(&self, offset: f64) -> f64 {
108        offset / self.get_length()
109    }
110
111    /// Returns the tangent vector of the line.
112    /// For a line, the tangent is constant (same direction everywhere),
113    /// so no `t` parameter is needed.
114    pub fn get_tangent_vector_at_t(&self) -> SvgVector {
115        let dx = self.end.x - self.start.x;
116        let dy = self.end.y - self.start.y;
117        SvgVector {
118            x: dx as f64,
119            y: dy as f64,
120        }
121        .normalize()
122    }
123
124    pub fn get_x_at_t(&self, t: f64) -> f64 {
125        self.start.x as f64 + (self.end.x as f64 - self.start.x as f64) * t
126    }
127
128    pub fn get_y_at_t(&self, t: f64) -> f64 {
129        self.start.y as f64 + (self.end.y as f64 - self.start.y as f64) * t
130    }
131
132    pub fn get_length(&self) -> f64 {
133        let dx = self.end.x - self.start.x;
134        let dy = self.end.y - self.start.y;
135        libm::hypotf(dx, dy) as f64
136    }
137
138    pub fn get_bounds(&self) -> SvgRect {
139        let min_x = self.start.x.min(self.end.x);
140        let max_x = self.start.x.max(self.end.x);
141
142        let min_y = self.start.y.min(self.end.y);
143        let max_y = self.start.y.max(self.end.y);
144
145        let width = (max_x - min_x).abs();
146        let height = (max_y - min_y).abs();
147
148        SvgRect {
149            width,
150            height,
151            x: min_x,
152            y: min_y,
153            ..SvgRect::default()
154        }
155    }
156}
157
158#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
159#[repr(C, u8)]
160pub enum SvgPathElement {
161    Line(SvgLine),
162    QuadraticCurve(SvgQuadraticCurve),
163    CubicCurve(SvgCubicCurve),
164}
165
166impl SvgPathElement {
167    pub fn set_last(&mut self, point: SvgPoint) {
168        match self {
169            SvgPathElement::Line(l) => l.end = point,
170            SvgPathElement::QuadraticCurve(qc) => qc.end = point,
171            SvgPathElement::CubicCurve(cc) => cc.end = point,
172        }
173    }
174
175    pub fn set_first(&mut self, point: SvgPoint) {
176        match self {
177            SvgPathElement::Line(l) => l.start = point,
178            SvgPathElement::QuadraticCurve(qc) => qc.start = point,
179            SvgPathElement::CubicCurve(cc) => cc.start = point,
180        }
181    }
182
183    pub fn reverse(&mut self) {
184        match self {
185            SvgPathElement::Line(l) => l.reverse(),
186            SvgPathElement::QuadraticCurve(qc) => qc.reverse(),
187            SvgPathElement::CubicCurve(cc) => cc.reverse(),
188        }
189    }
190    pub fn get_start(&self) -> SvgPoint {
191        match self {
192            SvgPathElement::Line(l) => l.get_start(),
193            SvgPathElement::QuadraticCurve(qc) => qc.get_start(),
194            SvgPathElement::CubicCurve(cc) => cc.get_start(),
195        }
196    }
197    pub fn get_end(&self) -> SvgPoint {
198        match self {
199            SvgPathElement::Line(l) => l.get_end(),
200            SvgPathElement::QuadraticCurve(qc) => qc.get_end(),
201            SvgPathElement::CubicCurve(cc) => cc.get_end(),
202        }
203    }
204    pub fn get_bounds(&self) -> SvgRect {
205        match self {
206            SvgPathElement::Line(l) => l.get_bounds(),
207            SvgPathElement::QuadraticCurve(qc) => qc.get_bounds(),
208            SvgPathElement::CubicCurve(cc) => cc.get_bounds(),
209        }
210    }
211    pub fn get_length(&self) -> f64 {
212        match self {
213            SvgPathElement::Line(l) => l.get_length(),
214            SvgPathElement::QuadraticCurve(qc) => qc.get_length(),
215            SvgPathElement::CubicCurve(cc) => cc.get_length(),
216        }
217    }
218    pub fn get_t_at_offset(&self, offset: f64) -> f64 {
219        match self {
220            SvgPathElement::Line(l) => l.get_t_at_offset(offset),
221            SvgPathElement::QuadraticCurve(qc) => qc.get_t_at_offset(offset),
222            SvgPathElement::CubicCurve(cc) => cc.get_t_at_offset(offset),
223        }
224    }
225    pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
226        match self {
227            SvgPathElement::Line(l) => l.get_tangent_vector_at_t(),
228            SvgPathElement::QuadraticCurve(qc) => qc.get_tangent_vector_at_t(t),
229            SvgPathElement::CubicCurve(cc) => cc.get_tangent_vector_at_t(t),
230        }
231    }
232    pub fn get_x_at_t(&self, t: f64) -> f64 {
233        match self {
234            SvgPathElement::Line(l) => l.get_x_at_t(t),
235            SvgPathElement::QuadraticCurve(qc) => qc.get_x_at_t(t),
236            SvgPathElement::CubicCurve(cc) => cc.get_x_at_t(t),
237        }
238    }
239    pub fn get_y_at_t(&self, t: f64) -> f64 {
240        match self {
241            SvgPathElement::Line(l) => l.get_y_at_t(t),
242            SvgPathElement::QuadraticCurve(qc) => qc.get_y_at_t(t),
243            SvgPathElement::CubicCurve(cc) => cc.get_y_at_t(t),
244        }
245    }
246}
247
248impl_vec!(
249    SvgPathElement,
250    SvgPathElementVec,
251    SvgPathElementVecDestructor,
252    SvgPathElementVecDestructorType
253);
254impl_vec_debug!(SvgPathElement, SvgPathElementVec);
255impl_vec_clone!(
256    SvgPathElement,
257    SvgPathElementVec,
258    SvgPathElementVecDestructor
259);
260impl_vec_partialeq!(SvgPathElement, SvgPathElementVec);
261impl_vec_partialord!(SvgPathElement, SvgPathElementVec);
262
263#[derive(Debug, Clone, PartialEq, PartialOrd)]
264#[repr(C)]
265pub struct SvgPath {
266    pub items: SvgPathElementVec,
267}
268
269impl SvgPath {
270    pub fn get_start(&self) -> Option<SvgPoint> {
271        self.items.as_ref().first().map(|s| s.get_start())
272    }
273
274    pub fn get_end(&self) -> Option<SvgPoint> {
275        self.items.as_ref().last().map(|s| s.get_end())
276    }
277
278    pub fn close(&mut self) {
279        let first = match self.items.as_ref().first() {
280            Some(s) => s,
281            None => return,
282        };
283        let last = match self.items.as_ref().last() {
284            Some(s) => s,
285            None => return,
286        };
287        if first.get_start() != last.get_end() {
288            let mut elements = self.items.as_slice().to_vec();
289            elements.push(SvgPathElement::Line(SvgLine {
290                start: last.get_end(),
291                end: first.get_start(),
292            }));
293            self.items = elements.into();
294        }
295    }
296
297    pub fn is_closed(&self) -> bool {
298        let first = self.items.as_ref().first();
299        let last = self.items.as_ref().last();
300        match (first, last) {
301            (Some(f), Some(l)) => (f.get_start() == l.get_end()),
302            _ => false,
303        }
304    }
305
306    // reverses the order of elements in the path
307    pub fn reverse(&mut self) {
308        // swap self.items with a default vec
309        let mut vec = SvgPathElementVec::from_const_slice(&[]);
310        core::mem::swap(&mut vec, &mut self.items);
311        let mut vec = vec.into_library_owned_vec();
312
313        // reverse the order of items in the vec
314        vec.reverse();
315
316        // reverse the order inside the item itself
317        // i.e. swap line.start and line.end
318        for item in vec.iter_mut() {
319            item.reverse();
320        }
321
322        // swap back
323        let mut vec = SvgPathElementVec::from_vec(vec);
324        core::mem::swap(&mut vec, &mut self.items);
325    }
326
327    pub fn join_with(&mut self, mut path: Self) -> Option<()> {
328        let self_last_point = self.items.as_ref().last()?.get_end();
329        let other_start_point = path.items.as_ref().first()?.get_start();
330        let interpolated_join_point = SvgPoint {
331            x: (self_last_point.x + other_start_point.x) / 2.0,
332            y: (self_last_point.y + other_start_point.y) / 2.0,
333        };
334
335        // swap self.items with a default vec
336        let mut vec = SvgPathElementVec::from_const_slice(&[]);
337        core::mem::swap(&mut vec, &mut self.items);
338        let mut vec = vec.into_library_owned_vec();
339
340        let mut other = SvgPathElementVec::from_const_slice(&[]);
341        core::mem::swap(&mut other, &mut path.items);
342        let mut other = other.into_library_owned_vec();
343
344        let vec_len = vec.len() - 1;
345        vec.get_mut(vec_len)?.set_last(interpolated_join_point);
346        other.get_mut(0)?.set_first(interpolated_join_point);
347        vec.append(&mut other);
348
349        // swap back
350        let mut vec = SvgPathElementVec::from_vec(vec);
351        core::mem::swap(&mut vec, &mut self.items);
352
353        Some(())
354    }
355    pub fn get_bounds(&self) -> SvgRect {
356        let mut first_bounds = match self.items.as_ref().get(0) {
357            Some(s) => s.get_bounds(),
358            None => return SvgRect::default(),
359        };
360
361        for mp in self.items.as_ref().iter().skip(1) {
362            let mp_bounds = mp.get_bounds();
363            first_bounds.union_with(&mp_bounds);
364        }
365
366        first_bounds
367    }
368}
369
370#[derive(Debug, Clone, PartialEq, PartialOrd)]
371#[repr(C)]
372pub struct SvgMultiPolygon {
373    /// NOTE: If a ring represends a hole, simply reverse the order of points
374    pub rings: SvgPathVec,
375}
376
377impl SvgMultiPolygon {
378    pub fn get_bounds(&self) -> SvgRect {
379        let mut first_bounds = match self
380            .rings
381            .get(0)
382            .and_then(|b| b.items.get(0).map(|i| i.get_bounds()))
383        {
384            Some(s) => s,
385            None => return SvgRect::default(), // TODO: error?
386        };
387
388        for ring in self.rings.iter() {
389            for item in ring.items.iter() {
390                first_bounds.union_with(&item.get_bounds());
391            }
392        }
393
394        first_bounds
395    }
396}
397
398impl_vec!(
399    SvgPath,
400    SvgPathVec,
401    SvgPathVecDestructor,
402    SvgPathVecDestructorType
403);
404impl_vec_debug!(SvgPath, SvgPathVec);
405impl_vec_clone!(SvgPath, SvgPathVec, SvgPathVecDestructor);
406impl_vec_partialeq!(SvgPath, SvgPathVec);
407impl_vec_partialord!(SvgPath, SvgPathVec);
408
409impl_vec!(
410    SvgMultiPolygon,
411    SvgMultiPolygonVec,
412    SvgMultiPolygonVecDestructor,
413    SvgMultiPolygonVecDestructorType
414);
415impl_vec_debug!(SvgMultiPolygon, SvgMultiPolygonVec);
416impl_vec_clone!(
417    SvgMultiPolygon,
418    SvgMultiPolygonVec,
419    SvgMultiPolygonVecDestructor
420);
421impl_vec_partialeq!(SvgMultiPolygon, SvgMultiPolygonVec);
422impl_vec_partialord!(SvgMultiPolygon, SvgMultiPolygonVec);
423
424/// One `SvgNode` corresponds to one SVG `<path></path>` element
425#[derive(Debug, Clone, PartialOrd, PartialEq)]
426#[repr(C, u8)]
427pub enum SvgNode {
428    /// Multiple multipolygons, merged to one CPU buf for efficient drawing
429    MultiPolygonCollection(SvgMultiPolygonVec),
430    MultiPolygon(SvgMultiPolygon),
431    MultiShape(SvgSimpleNodeVec),
432    Path(SvgPath),
433    Circle(SvgCircle),
434    Rect(SvgRect),
435}
436
437/// One `SvgSimpleNode` is either a path, a rect or a circle
438#[derive(Debug, Clone, PartialOrd, PartialEq)]
439#[repr(C, u8)]
440pub enum SvgSimpleNode {
441    Path(SvgPath),
442    Circle(SvgCircle),
443    Rect(SvgRect),
444    CircleHole(SvgCircle),
445    RectHole(SvgRect),
446}
447
448impl_vec!(
449    SvgSimpleNode,
450    SvgSimpleNodeVec,
451    SvgSimpleNodeVecDestructor,
452    SvgSimpleNodeVecDestructorType
453);
454impl_vec_debug!(SvgSimpleNode, SvgSimpleNodeVec);
455impl_vec_clone!(SvgSimpleNode, SvgSimpleNodeVec, SvgSimpleNodeVecDestructor);
456impl_vec_partialeq!(SvgSimpleNode, SvgSimpleNodeVec);
457impl_vec_partialord!(SvgSimpleNode, SvgSimpleNodeVec);
458
459impl SvgSimpleNode {
460    pub fn get_bounds(&self) -> SvgRect {
461        match self {
462            SvgSimpleNode::Path(a) => a.get_bounds(),
463            SvgSimpleNode::Circle(a) => a.get_bounds(),
464            SvgSimpleNode::Rect(a) => a.clone(),
465            SvgSimpleNode::CircleHole(a) => a.get_bounds(),
466            SvgSimpleNode::RectHole(a) => a.clone(),
467        }
468    }
469    pub fn is_closed(&self) -> bool {
470        match self {
471            SvgSimpleNode::Path(a) => a.is_closed(),
472            SvgSimpleNode::Circle(a) => true,
473            SvgSimpleNode::Rect(a) => true,
474            SvgSimpleNode::CircleHole(a) => true,
475            SvgSimpleNode::RectHole(a) => true,
476        }
477    }
478}
479
480impl SvgNode {
481    pub fn get_bounds(&self) -> SvgRect {
482        match self {
483            SvgNode::MultiPolygonCollection(a) => {
484                let mut first_mp_bounds = match a.get(0) {
485                    Some(s) => s.get_bounds(),
486                    None => return SvgRect::default(),
487                };
488                for mp in a.iter().skip(1) {
489                    let mp_bounds = mp.get_bounds();
490                    first_mp_bounds.union_with(&mp_bounds);
491                }
492
493                first_mp_bounds
494            }
495            SvgNode::MultiPolygon(a) => a.get_bounds(),
496            SvgNode::MultiShape(a) => {
497                let mut first_mp_bounds = match a.get(0) {
498                    Some(s) => s.get_bounds(),
499                    None => return SvgRect::default(),
500                };
501                for mp in a.iter().skip(1) {
502                    let mp_bounds = mp.get_bounds();
503                    first_mp_bounds.union_with(&mp_bounds);
504                }
505
506                first_mp_bounds
507            }
508            SvgNode::Path(a) => a.get_bounds(),
509            SvgNode::Circle(a) => a.get_bounds(),
510            SvgNode::Rect(a) => a.clone(),
511        }
512    }
513    pub fn is_closed(&self) -> bool {
514        match self {
515            SvgNode::MultiPolygonCollection(a) => {
516                for mp in a.iter() {
517                    for p in mp.rings.as_ref().iter() {
518                        if !p.is_closed() {
519                            return false;
520                        }
521                    }
522                }
523
524                true
525            }
526            SvgNode::MultiPolygon(a) => {
527                for p in a.rings.as_ref().iter() {
528                    if !p.is_closed() {
529                        return false;
530                    }
531                }
532
533                true
534            }
535            SvgNode::MultiShape(a) => {
536                for p in a.as_ref().iter() {
537                    if !p.is_closed() {
538                        return false;
539                    }
540                }
541
542                true
543            }
544            SvgNode::Path(a) => a.is_closed(),
545            SvgNode::Circle(a) => true,
546            SvgNode::Rect(a) => true,
547        }
548    }
549}
550
551#[derive(Debug, Clone, PartialOrd, PartialEq)]
552#[repr(C)]
553pub struct SvgStyledNode {
554    pub geometry: SvgNode,
555    pub style: SvgStyle,
556}
557
558#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
559#[repr(C)]
560pub struct SvgVertex {
561    pub x: f32,
562    pub y: f32,
563}
564
565impl VertexLayoutDescription for SvgVertex {
566    fn get_description() -> VertexLayout {
567        VertexLayout {
568            fields: vec![VertexAttribute {
569                va_name: String::from("vAttrXY").into(),
570                layout_location: None.into(),
571                attribute_type: VertexAttributeType::Float,
572                item_count: 2,
573            }]
574            .into(),
575        }
576    }
577}
578
579#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
580#[repr(C)]
581pub struct SvgColoredVertex {
582    pub x: f32,
583    pub y: f32,
584    pub z: f32,
585    pub r: f32,
586    pub g: f32,
587    pub b: f32,
588    pub a: f32,
589}
590
591impl VertexLayoutDescription for SvgColoredVertex {
592    fn get_description() -> VertexLayout {
593        VertexLayout {
594            fields: vec![
595                VertexAttribute {
596                    va_name: String::from("vAttrXY").into(),
597                    layout_location: None.into(),
598                    attribute_type: VertexAttributeType::Float,
599                    item_count: 3,
600                },
601                VertexAttribute {
602                    va_name: String::from("vColor").into(),
603                    layout_location: None.into(),
604                    attribute_type: VertexAttributeType::Float,
605                    item_count: 4,
606                },
607            ]
608            .into(),
609        }
610    }
611}
612
613#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
614#[repr(C)]
615pub struct SvgCircle {
616    pub center_x: f32,
617    pub center_y: f32,
618    pub radius: f32,
619}
620
621impl SvgCircle {
622    pub fn contains_point(&self, x: f32, y: f32) -> bool {
623        let x_diff = libm::fabsf(x - self.center_x);
624        let y_diff = libm::fabsf(y - self.center_y);
625        (x_diff * x_diff) + (y_diff * y_diff) < (self.radius * self.radius)
626    }
627    pub fn get_bounds(&self) -> SvgRect {
628        SvgRect {
629            width: self.radius * 2.0,
630            height: self.radius * 2.0,
631            x: self.center_x - self.radius,
632            y: self.center_y - self.radius,
633            radius_top_left: 0.0,
634            radius_top_right: 0.0,
635            radius_bottom_left: 0.0,
636            radius_bottom_right: 0.0,
637        }
638    }
639}
640
641#[derive(Debug, Clone, PartialEq, PartialOrd)]
642#[repr(C)]
643pub struct TessellatedSvgNode {
644    pub vertices: SvgVertexVec,
645    pub indices: U32Vec,
646}
647
648impl Default for TessellatedSvgNode {
649    fn default() -> Self {
650        Self {
651            vertices: Vec::new().into(),
652            indices: Vec::new().into(),
653        }
654    }
655}
656
657impl_vec!(
658    TessellatedSvgNode,
659    TessellatedSvgNodeVec,
660    TessellatedSvgNodeVecDestructor,
661    TessellatedSvgNodeVecDestructorType
662);
663impl_vec_debug!(TessellatedSvgNode, TessellatedSvgNodeVec);
664impl_vec_partialord!(TessellatedSvgNode, TessellatedSvgNodeVec);
665impl_vec_clone!(
666    TessellatedSvgNode,
667    TessellatedSvgNodeVec,
668    TessellatedSvgNodeVecDestructor
669);
670impl_vec_partialeq!(TessellatedSvgNode, TessellatedSvgNodeVec);
671
672impl TessellatedSvgNode {
673    pub fn empty() -> Self {
674        Self::default()
675    }
676}
677
678impl TessellatedSvgNodeVec {
679    pub fn get_ref(&self) -> TessellatedSvgNodeVecRef {
680        let slice = self.as_ref();
681        TessellatedSvgNodeVecRef {
682            ptr: slice.as_ptr(),
683            len: slice.len(),
684        }
685    }
686}
687
688impl fmt::Debug for TessellatedSvgNodeVecRef {
689    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
690        self.as_slice().fmt(f)
691    }
692}
693
694// C ABI wrapper over &[TessellatedSvgNode]
695#[repr(C)]
696pub struct TessellatedSvgNodeVecRef {
697    pub ptr: *const TessellatedSvgNode,
698    pub len: usize,
699}
700
701impl Clone for TessellatedSvgNodeVecRef {
702    fn clone(&self) -> Self {
703        Self {
704            ptr: self.ptr,
705            len: self.len,
706        }
707    }
708}
709
710impl TessellatedSvgNodeVecRef {
711    pub fn as_slice<'a>(&'a self) -> &'a [TessellatedSvgNode] {
712        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
713    }
714}
715
716#[derive(Debug, Clone, PartialEq, PartialOrd)]
717#[repr(C)]
718pub struct TessellatedColoredSvgNode {
719    pub vertices: SvgColoredVertexVec,
720    pub indices: U32Vec,
721}
722
723impl Default for TessellatedColoredSvgNode {
724    fn default() -> Self {
725        Self {
726            vertices: Vec::new().into(),
727            indices: Vec::new().into(),
728        }
729    }
730}
731
732impl_vec!(
733    TessellatedColoredSvgNode,
734    TessellatedColoredSvgNodeVec,
735    TessellatedColoredSvgNodeVecDestructor,
736    TessellatedColoredSvgNodeVecDestructorType
737);
738impl_vec_debug!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
739impl_vec_partialord!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
740impl_vec_clone!(
741    TessellatedColoredSvgNode,
742    TessellatedColoredSvgNodeVec,
743    TessellatedColoredSvgNodeVecDestructor
744);
745impl_vec_partialeq!(TessellatedColoredSvgNode, TessellatedColoredSvgNodeVec);
746
747impl TessellatedColoredSvgNode {
748    pub fn empty() -> Self {
749        Self::default()
750    }
751}
752
753impl TessellatedColoredSvgNodeVec {
754    pub fn get_ref(&self) -> TessellatedColoredSvgNodeVecRef {
755        let slice = self.as_ref();
756        TessellatedColoredSvgNodeVecRef {
757            ptr: slice.as_ptr(),
758            len: slice.len(),
759        }
760    }
761}
762
763impl fmt::Debug for TessellatedColoredSvgNodeVecRef {
764    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
765        self.as_slice().fmt(f)
766    }
767}
768
769// C ABI wrapper over &[TessellatedColoredSvgNode]
770#[repr(C)]
771pub struct TessellatedColoredSvgNodeVecRef {
772    pub ptr: *const TessellatedColoredSvgNode,
773    pub len: usize,
774}
775
776impl Clone for TessellatedColoredSvgNodeVecRef {
777    fn clone(&self) -> Self {
778        Self {
779            ptr: self.ptr,
780            len: self.len,
781        }
782    }
783}
784
785impl TessellatedColoredSvgNodeVecRef {
786    pub fn as_slice<'a>(&'a self) -> &'a [TessellatedColoredSvgNode] {
787        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
788    }
789}
790
791impl_vec!(
792    SvgVertex,
793    SvgVertexVec,
794    SvgVertexVecDestructor,
795    SvgVertexVecDestructorType
796);
797impl_vec_debug!(SvgVertex, SvgVertexVec);
798impl_vec_partialord!(SvgVertex, SvgVertexVec);
799impl_vec_clone!(SvgVertex, SvgVertexVec, SvgVertexVecDestructor);
800impl_vec_partialeq!(SvgVertex, SvgVertexVec);
801
802impl_vec!(
803    SvgColoredVertex,
804    SvgColoredVertexVec,
805    SvgColoredVertexVecDestructor,
806    SvgColoredVertexVecDestructorType
807);
808impl_vec_debug!(SvgColoredVertex, SvgColoredVertexVec);
809impl_vec_partialord!(SvgColoredVertex, SvgColoredVertexVec);
810impl_vec_clone!(
811    SvgColoredVertex,
812    SvgColoredVertexVec,
813    SvgColoredVertexVecDestructor
814);
815impl_vec_partialeq!(SvgColoredVertex, SvgColoredVertexVec);
816
817#[derive(Debug, Clone, PartialEq, PartialOrd)]
818#[repr(C)]
819pub struct TessellatedGPUSvgNode {
820    pub vertex_index_buffer: VertexBuffer,
821}
822
823impl TessellatedGPUSvgNode {
824    /// Uploads the tesselated SVG node to GPU memory
825    pub fn new(node: &TessellatedSvgNode, gl: GlContextPtr) -> Self {
826        let svg_shader_id = gl.ptr.svg_shader;
827        Self {
828            vertex_index_buffer: VertexBuffer::new(
829                gl,
830                svg_shader_id,
831                node.vertices.as_ref(),
832                node.indices.as_ref(),
833                IndexBufferFormat::Triangles,
834            ),
835        }
836    }
837
838    /// Draw the vertex buffer to the texture with the given color and transform
839    pub fn draw(
840        &self,
841        texture: &mut Texture,
842        target_size: PhysicalSizeU32,
843        color: ColorU,
844        transforms: StyleTransformVec,
845    ) -> bool {
846        let transform_origin = StyleTransformOrigin {
847            x: PixelValue::px(target_size.width as f32 / 2.0),
848            y: PixelValue::px(target_size.height as f32 / 2.0),
849        };
850
851        let computed_transform = ComputedTransform3D::from_style_transform_vec(
852            transforms.as_ref(),
853            &transform_origin,
854            target_size.width as f32,
855            target_size.height as f32,
856            RotationMode::ForWebRender,
857        );
858
859        // NOTE: OpenGL draws are column-major, while ComputedTransform3D
860        // is row-major! Need to transpose the matrix!
861        let column_major = computed_transform.get_column_major();
862
863        let color: ColorF = color.into();
864
865        // uniforms for the SVG shader
866        let uniforms = [
867            Uniform {
868                uniform_name: "vBboxSize".into(),
869                uniform_type: UniformType::FloatVec2([
870                    target_size.width as f32,
871                    target_size.height as f32,
872                ]),
873            },
874            Uniform {
875                uniform_name: "fDrawColor".into(),
876                uniform_type: UniformType::FloatVec4([color.r, color.g, color.b, color.a]),
877            },
878            Uniform {
879                uniform_name: "vTransformMatrix".into(),
880                uniform_type: UniformType::Matrix4 {
881                    transpose: false,
882                    matrix: unsafe { core::mem::transmute(column_major.m) },
883                },
884            },
885        ];
886
887        GlShader::draw(
888            texture.gl_context.ptr.svg_shader,
889            texture,
890            &[(&self.vertex_index_buffer, &uniforms[..])],
891        );
892
893        true
894    }
895}
896
897#[derive(Debug, Clone, PartialEq, PartialOrd)]
898#[repr(C)]
899pub struct TessellatedColoredGPUSvgNode {
900    pub vertex_index_buffer: VertexBuffer,
901}
902
903impl TessellatedColoredGPUSvgNode {
904    /// Uploads the tesselated SVG node to GPU memory
905    pub fn new(node: &TessellatedColoredSvgNode, gl: GlContextPtr) -> Self {
906        let svg_shader_id = gl.ptr.svg_multicolor_shader;
907        Self {
908            vertex_index_buffer: VertexBuffer::new(
909                gl,
910                svg_shader_id,
911                node.vertices.as_ref(),
912                node.indices.as_ref(),
913                IndexBufferFormat::Triangles,
914            ),
915        }
916    }
917
918    /// Draw the vertex buffer to the texture with the given color and transform
919    pub fn draw(
920        &self,
921        texture: &mut Texture,
922        target_size: PhysicalSizeU32,
923        transforms: StyleTransformVec,
924    ) -> bool {
925        use azul_css::props::basic::PixelValue;
926
927        use crate::gl::{GlShader, Uniform, UniformType};
928
929        let transform_origin = StyleTransformOrigin {
930            x: PixelValue::px(target_size.width as f32 / 2.0),
931            y: PixelValue::px(target_size.height as f32 / 2.0),
932        };
933
934        let computed_transform = ComputedTransform3D::from_style_transform_vec(
935            transforms.as_ref(),
936            &transform_origin,
937            target_size.width as f32,
938            target_size.height as f32,
939            RotationMode::ForWebRender,
940        );
941
942        // NOTE: OpenGL draws are column-major, while ComputedTransform3D
943        // is row-major! Need to transpose the matrix!
944        let column_major = computed_transform.get_column_major();
945
946        // uniforms for the SVG shader
947        let uniforms = [
948            Uniform {
949                uniform_name: "vBboxSize".into(),
950                uniform_type: UniformType::FloatVec2([
951                    target_size.width as f32,
952                    target_size.height as f32,
953                ]),
954            },
955            Uniform {
956                uniform_name: "vTransformMatrix".into(),
957                uniform_type: UniformType::Matrix4 {
958                    transpose: false,
959                    matrix: unsafe { core::mem::transmute(column_major.m) },
960                },
961            },
962        ];
963
964        GlShader::draw(
965            texture.gl_context.ptr.svg_multicolor_shader,
966            texture,
967            &[(&self.vertex_index_buffer, &uniforms[..])],
968        );
969
970        true
971    }
972}
973
974#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
975#[repr(C, u8)]
976pub enum SvgStyle {
977    Fill(SvgFillStyle),
978    Stroke(SvgStrokeStyle),
979}
980
981impl SvgStyle {
982    pub fn get_antialias(&self) -> bool {
983        match self {
984            SvgStyle::Fill(f) => f.anti_alias,
985            SvgStyle::Stroke(s) => s.anti_alias,
986        }
987    }
988    pub fn get_high_quality_aa(&self) -> bool {
989        match self {
990            SvgStyle::Fill(f) => f.high_quality_aa,
991            SvgStyle::Stroke(s) => s.high_quality_aa,
992        }
993    }
994    pub fn get_transform(&self) -> SvgTransform {
995        match self {
996            SvgStyle::Fill(f) => f.transform,
997            SvgStyle::Stroke(s) => s.transform,
998        }
999    }
1000}
1001#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1002#[repr(C)]
1003pub enum SvgFillRule {
1004    Winding,
1005    EvenOdd,
1006}
1007
1008impl Default for SvgFillRule {
1009    fn default() -> Self {
1010        SvgFillRule::Winding
1011    }
1012}
1013
1014#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
1015#[repr(C)]
1016pub struct SvgTransform {
1017    pub sx: f32,
1018    pub kx: f32,
1019    pub ky: f32,
1020    pub sy: f32,
1021    pub tx: f32,
1022    pub ty: f32,
1023}
1024
1025#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1026#[repr(C)]
1027pub struct SvgFillStyle {
1028    /// See the SVG specification.
1029    ///
1030    /// Default value: `LineJoin::Miter`.
1031    pub line_join: SvgLineJoin,
1032    /// See the SVG specification.
1033    ///
1034    /// Must be greater than or equal to 1.0.
1035    /// Default value: `StrokeOptions::DEFAULT_MITER_LIMIT`.
1036    pub miter_limit: f32,
1037    /// Maximum allowed distance to the path when building an approximation.
1038    ///
1039    /// See [Flattening and tolerance](index.html#flattening-and-tolerance).
1040    /// Default value: `StrokeOptions::DEFAULT_TOLERANCE`.
1041    pub tolerance: f32,
1042    /// Whether to use the "winding" or "even / odd" fill rule when tesselating the path
1043    pub fill_rule: SvgFillRule,
1044    /// Whether to apply a transform to the points in the path (warning: will be done on the CPU -
1045    /// expensive)
1046    pub transform: SvgTransform,
1047    /// Whether the fill is intended to be anti-aliased (default: true)
1048    pub anti_alias: bool,
1049    /// Whether the anti-aliasing has to be of high quality (default: false)
1050    pub high_quality_aa: bool,
1051}
1052
1053impl Default for SvgFillStyle {
1054    fn default() -> Self {
1055        Self {
1056            line_join: SvgLineJoin::Miter,
1057            miter_limit: DEFAULT_MITER_LIMIT,
1058            tolerance: DEFAULT_TOLERANCE,
1059            fill_rule: SvgFillRule::default(),
1060            transform: SvgTransform::default(),
1061            anti_alias: true,
1062            high_quality_aa: false,
1063        }
1064    }
1065}
1066
1067#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1068#[repr(C)]
1069pub struct SvgStrokeStyle {
1070    /// What cap to use at the start of each sub-path.
1071    ///
1072    /// Default value: `LineCap::Butt`.
1073    pub start_cap: SvgLineCap,
1074    /// What cap to use at the end of each sub-path.
1075    ///
1076    /// Default value: `LineCap::Butt`.
1077    pub end_cap: SvgLineCap,
1078    /// See the SVG specification.
1079    ///
1080    /// Default value: `LineJoin::Miter`.
1081    pub line_join: SvgLineJoin,
1082    /// Dash pattern
1083    pub dash_pattern: OptionSvgDashPattern,
1084    /// Line width
1085    ///
1086    /// Default value: `StrokeOptions::DEFAULT_LINE_WIDTH`.
1087    pub line_width: f32,
1088    /// See the SVG specification.
1089    ///
1090    /// Must be greater than or equal to 1.0.
1091    /// Default value: `StrokeOptions::DEFAULT_MITER_LIMIT`.
1092    pub miter_limit: f32,
1093    /// Maximum allowed distance to the path when building an approximation.
1094    ///
1095    /// See [Flattening and tolerance](index.html#flattening-and-tolerance).
1096    /// Default value: `StrokeOptions::DEFAULT_TOLERANCE`.
1097    pub tolerance: f32,
1098    /// Apply line width
1099    ///
1100    /// When set to false, the generated vertices will all be positioned in the centre
1101    /// of the line. The width can be applied later on (eg in a vertex shader) by adding
1102    /// the vertex normal multiplied by the line with to each vertex position.
1103    ///
1104    /// Default value: `true`. NOTE: currently unused!
1105    pub apply_line_width: bool,
1106    /// Whether to apply a transform to the points in the path (warning: will be done on the CPU -
1107    /// expensive)
1108    pub transform: SvgTransform,
1109    /// Whether the fill is intended to be anti-aliased (default: true)
1110    pub anti_alias: bool,
1111    /// Whether the anti-aliasing has to be of high quality (default: false)
1112    pub high_quality_aa: bool,
1113}
1114
1115impl Default for SvgStrokeStyle {
1116    fn default() -> Self {
1117        Self {
1118            start_cap: SvgLineCap::default(),
1119            end_cap: SvgLineCap::default(),
1120            line_join: SvgLineJoin::default(),
1121            dash_pattern: OptionSvgDashPattern::None,
1122            line_width: DEFAULT_LINE_WIDTH,
1123            miter_limit: DEFAULT_MITER_LIMIT,
1124            tolerance: DEFAULT_TOLERANCE,
1125            apply_line_width: true,
1126            anti_alias: true,
1127            high_quality_aa: false,
1128            transform: SvgTransform::default(),
1129        }
1130    }
1131}
1132
1133#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1134#[repr(C)]
1135pub struct SvgDashPattern {
1136    pub offset: f32,
1137    pub length_1: f32,
1138    pub gap_1: f32,
1139    pub length_2: f32,
1140    pub gap_2: f32,
1141    pub length_3: f32,
1142    pub gap_3: f32,
1143}
1144
1145impl_option!(
1146    SvgDashPattern,
1147    OptionSvgDashPattern,
1148    [Debug, Copy, Clone, PartialEq, PartialOrd]
1149);
1150
1151#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1152#[repr(C)]
1153pub enum SvgLineCap {
1154    Butt,
1155    Square,
1156    Round,
1157}
1158
1159impl Default for SvgLineCap {
1160    fn default() -> Self {
1161        SvgLineCap::Butt
1162    }
1163}
1164
1165#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
1166#[repr(C)]
1167pub enum SvgLineJoin {
1168    Miter,
1169    MiterClip,
1170    Round,
1171    Bevel,
1172}
1173
1174impl Default for SvgLineJoin {
1175    fn default() -> Self {
1176        SvgLineJoin::Miter
1177    }
1178}
1179
1180#[allow(non_camel_case_types)]
1181pub enum c_void {}
1182
1183pub type GlyphId = u16;
1184
1185#[derive(Debug, Clone)]
1186#[repr(C)]
1187pub struct SvgXmlNode {
1188    pub node: *const c_void, // usvg::Node
1189    pub run_destructor: bool,
1190}
1191
1192#[derive(Debug, Clone)]
1193#[repr(C)]
1194pub struct Svg {
1195    pub tree: *const c_void, // *mut usvg::Tree,
1196    pub run_destructor: bool,
1197}
1198
1199#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1200#[repr(C)]
1201pub enum ShapeRendering {
1202    OptimizeSpeed,
1203    CrispEdges,
1204    GeometricPrecision,
1205}
1206
1207#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1208#[repr(C)]
1209pub enum ImageRendering {
1210    OptimizeQuality,
1211    OptimizeSpeed,
1212}
1213
1214#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1215#[repr(C)]
1216pub enum TextRendering {
1217    OptimizeSpeed,
1218    OptimizeLegibility,
1219    GeometricPrecision,
1220}
1221
1222#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1223#[repr(C)]
1224pub enum FontDatabase {
1225    Empty,
1226    System,
1227}
1228
1229#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1230#[repr(C)]
1231pub struct SvgRenderOptions {
1232    pub target_size: OptionLayoutSize,
1233    pub background_color: OptionColorU,
1234    pub fit: SvgFitTo,
1235    pub transform: SvgRenderTransform,
1236}
1237
1238#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1239#[repr(C)]
1240pub struct SvgRenderTransform {
1241    pub sx: f32,
1242    pub kx: f32,
1243    pub ky: f32,
1244    pub sy: f32,
1245    pub tx: f32,
1246    pub ty: f32,
1247}
1248
1249#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1250#[repr(C, u8)]
1251pub enum SvgFitTo {
1252    Original,
1253    Width(u32),
1254    Height(u32),
1255    Zoom(f32),
1256}
1257
1258impl Default for SvgFitTo {
1259    fn default() -> Self {
1260        SvgFitTo::Original
1261    }
1262}
1263
1264#[derive(Debug, Clone, PartialEq, PartialOrd)]
1265#[repr(C)]
1266pub struct SvgParseOptions {
1267    /// SVG image path. Used to resolve relative image paths.
1268    pub relative_image_path: OptionString,
1269    /// Target DPI. Impact units conversion. Default: 96.0
1270    pub dpi: f32,
1271    /// Default font family. Will be used when no font-family attribute is set in the SVG. Default:
1272    /// Times New Roman
1273    pub default_font_family: AzString,
1274    /// A default font size. Will be used when no font-size attribute is set in the SVG. Default:
1275    /// 12
1276    pub font_size: f32,
1277    /// A list of languages. Will be used to resolve a systemLanguage conditional attribute.
1278    /// Format: en, en-US. Default: [en]
1279    pub languages: StringVec,
1280    /// Specifies the default shape rendering method. Will be used when an SVG element's
1281    /// shape-rendering property is set to auto. Default: GeometricPrecision
1282    pub shape_rendering: ShapeRendering,
1283    /// Specifies the default text rendering method. Will be used when an SVG element's
1284    /// text-rendering property is set to auto. Default: OptimizeLegibility
1285    pub text_rendering: TextRendering,
1286    /// Specifies the default image rendering method. Will be used when an SVG element's
1287    /// image-rendering property is set to auto. Default: OptimizeQuality
1288    pub image_rendering: ImageRendering,
1289    /// Keep named groups. If set to true, all non-empty groups with id attribute will not be
1290    /// removed. Default: false
1291    pub keep_named_groups: bool,
1292    /// When empty, text elements will be skipped. Default: `System`
1293    pub fontdb: FontDatabase,
1294}
1295
1296impl Default for SvgParseOptions {
1297    fn default() -> Self {
1298        let lang_vec: Vec<AzString> = vec![String::from("en").into()];
1299        SvgParseOptions {
1300            relative_image_path: OptionString::None,
1301            dpi: 96.0,
1302            default_font_family: "Times New Roman".to_string().into(),
1303            font_size: 12.0,
1304            languages: lang_vec.into(),
1305            shape_rendering: ShapeRendering::GeometricPrecision,
1306            text_rendering: TextRendering::OptimizeLegibility,
1307            image_rendering: ImageRendering::OptimizeQuality,
1308            keep_named_groups: false,
1309            fontdb: FontDatabase::System,
1310        }
1311    }
1312}
1313
1314#[derive(Debug, Clone, PartialEq, PartialOrd)]
1315#[repr(C)]
1316pub struct SvgXmlOptions {
1317    pub use_single_quote: bool,
1318    pub indent: Indent,
1319    pub attributes_indent: Indent,
1320}
1321
1322impl Default for SvgXmlOptions {
1323    fn default() -> Self {
1324        SvgXmlOptions {
1325            use_single_quote: false,
1326            indent: Indent::Spaces(2),
1327            attributes_indent: Indent::Spaces(2),
1328        }
1329    }
1330}
1331
1332#[derive(Debug, PartialEq, PartialOrd, Clone)]
1333#[repr(C, u8)]
1334pub enum SvgParseError {
1335    NoParserAvailable,
1336    ElementsLimitReached,
1337    NotAnUtf8Str,
1338    MalformedGZip,
1339    InvalidSize,
1340    ParsingFailed(XmlError),
1341}
1342
1343impl fmt::Display for SvgParseError {
1344    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1345        use self::SvgParseError::*;
1346        match self {
1347            NoParserAvailable => write!(
1348                f,
1349                "Library was compiled without SVG support (no parser available)"
1350            ),
1351            InvalidFileSuffix => write!(f, "Error parsing SVG: Invalid file suffix"),
1352            FileOpenFailed => write!(f, "Error parsing SVG: Failed to open file"),
1353            NotAnUtf8Str => write!(f, "Error parsing SVG: Not an UTF-8 String"),
1354            MalformedGZip => write!(
1355                f,
1356                "Error parsing SVG: SVG is compressed with a malformed GZIP compression"
1357            ),
1358            InvalidSize => write!(f, "Error parsing SVG: Invalid size"),
1359            ParsingFailed(e) => write!(f, "Error parsing SVG: Parsing SVG as XML failed: {}", e),
1360        }
1361    }
1362}
1363
1364impl_result!(
1365    SvgXmlNode,
1366    SvgParseError,
1367    ResultSvgXmlNodeSvgParseError,
1368    copy = false,
1369    [Debug, Clone]
1370);
1371impl_result!(
1372    Svg,
1373    SvgParseError,
1374    ResultSvgSvgParseError,
1375    copy = false,
1376    [Debug, Clone]
1377);
1378
1379#[derive(Debug, Clone, PartialEq, PartialOrd)]
1380#[repr(C, u8)]
1381pub enum Indent {
1382    None,
1383    Spaces(u8),
1384    Tabs,
1385}