Skip to main content

azul_layout/xml/
svg.rs

1//! SVG tessellation, rendering, and geometric operations.
2//!
3//! This module provides:
4//! - **Tessellation** of SVG primitives (paths, circles, rects, multi-polygons)
5//!   via the lyon tessellation library (behind the `svg` feature flag).
6//! - **CPU clip-mask rendering** via the agg-rust rasterizer (`render_node_clipmask_cpu`).
7//! - **FXAA post-processing** for GPU-rendered textures (`apply_fxaa`).
8//! - **Boolean polygon operations** (union, intersection, difference, XOR)
9//!   on `SvgMultiPolygon` shapes via agg scanline boolean algebra.
10//! - **SVG parsing and rendering** (`svg_parse`, `svg_render`) using an
11//!   XML parser and the agg-rust rendering pipeline.
12
13use alloc::boxed::Box;
14use core::fmt;
15
16#[cfg(not(feature = "svg"))]
17pub use azul_core::svg::*;
18// re-export everything except for Svg and SvgXmlNode
19#[cfg(feature = "svg")]
20pub use azul_core::svg::{
21    c_void,
22    FontDatabase,
23    ImageRendering,
24    Indent,
25    OptionSvgDashPattern,
26    ResultSvgSvgParseError,
27    ResultSvgXmlNodeSvgParseError,
28    ShapeRendering,
29    SvgCircle,
30    SvgColoredVertex,
31    SvgColoredVertexVec,
32    SvgColoredVertexVecDestructor,
33    SvgDashPattern,
34    SvgFillRule,
35    SvgFillStyle,
36    SvgFitTo,
37    SvgLine,
38    SvgLineCap,
39    SvgLineJoin,
40    SvgMultiPolygon,
41    SvgMultiPolygonVec,
42    SvgMultiPolygonVecDestructor,
43    SvgNode,
44    SvgParseError,
45    SvgParseOptions,
46    SvgPath,
47    SvgPathElement,
48    SvgPathElementVec,
49    SvgPathElementVecDestructor,
50    SvgPathVec,
51    SvgPathVecDestructor,
52    SvgRenderOptions,
53    SvgRenderTransform,
54
55    SvgSimpleNode,
56    SvgSimpleNodeVec,
57    SvgSimpleNodeVecDestructor,
58    SvgSize,
59    SvgStrokeStyle,
60    SvgStyle,
61    SvgStyledNode,
62    SvgTransform,
63    SvgVertex,
64    SvgVertexVec,
65    SvgVertexVecDestructor,
66    SvgXmlOptions,
67    TessellatedColoredSvgNode,
68    TessellatedColoredSvgNodeVec,
69    TessellatedColoredSvgNodeVecDestructor,
70    // SvgXmlNode, Svg
71    TessellatedGPUSvgNode,
72    TessellatedSvgNode,
73    TessellatedSvgNodeVec,
74    TessellatedSvgNodeVecDestructor,
75    TessellatedSvgNodeVecRef,
76    TextRendering,
77};
78use azul_core::{
79    geom::PhysicalSizeU32,
80    gl::{GlContextPtr, Texture},
81    resources::{RawImage, RawImageFormat},
82};
83#[cfg(feature = "svg")]
84pub use azul_css::props::basic::animation::{
85    SvgCubicCurve, SvgPoint, SvgQuadraticCurve, SvgRect, SvgVector,
86};
87use azul_css::{
88    impl_result, impl_result_inner,
89    props::basic::{ColorU, LayoutSize, OptionColorU, OptionLayoutSize},
90    AzString, OptionI16, OptionString, OptionU16, StringVec, U8Vec,
91};
92#[cfg(feature = "svg")]
93use lyon::{
94    geom::euclid::{Point2D, Rect, Size2D, UnknownUnit},
95    math::Point,
96    path::Path,
97    tessellation::{
98        BuffersBuilder, FillOptions, FillTessellator, FillVertex, StrokeOptions, StrokeTessellator,
99        StrokeVertex, VertexBuffers,
100    },
101};
102
103use crate::xml::XmlError;
104
105#[cfg(feature = "svg")]
106extern crate agg_rust;
107
108use azul_core::gl::GL_RESTART_INDEX;
109
110/// Kappa constant for approximating a circle with 4 cubic Bezier curves: 4/3 * (sqrt(2) - 1).
111const CIRCLE_BEZIER_KAPPA: f64 = 0.5522847498;
112
113/// Default render size (width, height) when no target size is specified for SVG rendering.
114const DEFAULT_SVG_RENDER_SIZE: (u32, u32) = (800, 600);
115
116#[cfg(feature = "svg")]
117fn translate_svg_line_join(e: SvgLineJoin) -> lyon::tessellation::LineJoin {
118    use azul_core::svg::SvgLineJoin::*;
119    match e {
120        Miter => lyon::tessellation::LineJoin::Miter,
121        MiterClip => lyon::tessellation::LineJoin::MiterClip,
122        Round => lyon::tessellation::LineJoin::Round,
123        Bevel => lyon::tessellation::LineJoin::Bevel,
124    }
125}
126
127#[cfg(feature = "svg")]
128fn translate_svg_line_cap(e: SvgLineCap) -> lyon::tessellation::LineCap {
129    use azul_core::svg::SvgLineCap::*;
130    match e {
131        Butt => lyon::tessellation::LineCap::Butt,
132        Square => lyon::tessellation::LineCap::Square,
133        Round => lyon::tessellation::LineCap::Round,
134    }
135}
136
137#[cfg(feature = "svg")]
138fn translate_svg_stroke_style(e: SvgStrokeStyle) -> lyon::tessellation::StrokeOptions {
139    lyon::tessellation::StrokeOptions::tolerance(e.tolerance)
140        .with_start_cap(translate_svg_line_cap(e.start_cap))
141        .with_end_cap(translate_svg_line_cap(e.end_cap))
142        .with_line_join(translate_svg_line_join(e.line_join))
143        .with_line_width(e.line_width)
144        .with_miter_limit(e.miter_limit)
145    // TODO: e.apply_line_width - not present in lyon 17!
146}
147
148#[cfg(feature = "svg")]
149fn svg_multipolygon_to_lyon_path(polygon: &SvgMultiPolygon) -> Path {
150    let mut builder = Path::builder();
151
152    for p in polygon.rings.as_ref().iter() {
153        if p.items.as_ref().is_empty() {
154            continue;
155        }
156
157        let start_item = p.items.as_ref()[0];
158        let first_point = Point2D::new(start_item.get_start().x, start_item.get_start().y);
159
160        builder.begin(first_point);
161
162        for q in p.items.as_ref().iter().rev()
163        /* NOTE: REVERSE ITERATOR */
164        {
165            match q {
166                SvgPathElement::Line(l) => {
167                    builder.line_to(Point2D::new(l.end.x, l.end.y));
168                }
169                SvgPathElement::QuadraticCurve(qc) => {
170                    builder.quadratic_bezier_to(
171                        Point2D::new(qc.ctrl.x, qc.ctrl.y),
172                        Point2D::new(qc.end.x, qc.end.y),
173                    );
174                }
175                SvgPathElement::CubicCurve(cc) => {
176                    builder.cubic_bezier_to(
177                        Point2D::new(cc.ctrl_1.x, cc.ctrl_1.y),
178                        Point2D::new(cc.ctrl_2.x, cc.ctrl_2.y),
179                        Point2D::new(cc.end.x, cc.end.y),
180                    );
181                }
182            }
183        }
184
185        builder.end(p.is_closed());
186    }
187
188    builder.build()
189}
190
191#[cfg(feature = "svg")]
192fn svg_multi_shape_to_lyon_path(polygon: &[SvgSimpleNode]) -> Path {
193    use lyon::{
194        geom::Box2D,
195        path::{traits::PathBuilder, Winding},
196    };
197
198    let mut builder = Path::builder();
199
200    for p in polygon.iter() {
201        match p {
202            SvgSimpleNode::Path(p) => {
203                if p.items.as_ref().is_empty() {
204                    continue;
205                }
206
207                let start_item = p.items.as_ref()[0];
208                let first_point = Point2D::new(start_item.get_start().x, start_item.get_start().y);
209
210                builder.begin(first_point);
211
212                for q in p.items.as_ref().iter().rev()
213                /* NOTE: REVERSE ITERATOR */
214                {
215                    match q {
216                        SvgPathElement::Line(l) => {
217                            builder.line_to(Point2D::new(l.end.x, l.end.y));
218                        }
219                        SvgPathElement::QuadraticCurve(qc) => {
220                            builder.quadratic_bezier_to(
221                                Point2D::new(qc.ctrl.x, qc.ctrl.y),
222                                Point2D::new(qc.end.x, qc.end.y),
223                            );
224                        }
225                        SvgPathElement::CubicCurve(cc) => {
226                            builder.cubic_bezier_to(
227                                Point2D::new(cc.ctrl_1.x, cc.ctrl_1.y),
228                                Point2D::new(cc.ctrl_2.x, cc.ctrl_2.y),
229                                Point2D::new(cc.end.x, cc.end.y),
230                            );
231                        }
232                    }
233                }
234
235                builder.end(p.is_closed());
236            }
237            SvgSimpleNode::Circle(c) => {
238                builder.add_circle(
239                    Point::new(c.center_x, c.center_y),
240                    c.radius,
241                    Winding::Positive,
242                );
243            }
244            SvgSimpleNode::CircleHole(c) => {
245                builder.add_circle(
246                    Point::new(c.center_x, c.center_y),
247                    c.radius,
248                    Winding::Negative,
249                );
250            }
251            SvgSimpleNode::Rect(c) => {
252                builder.add_rectangle(
253                    &Box2D::from_origin_and_size(
254                        Point::new(c.x, c.y),
255                        Size2D::new(c.width, c.height),
256                    ),
257                    Winding::Positive,
258                );
259            }
260            SvgSimpleNode::RectHole(c) => {
261                builder.add_rectangle(
262                    &Box2D::from_origin_and_size(
263                        Point::new(c.x, c.y),
264                        Size2D::new(c.width, c.height),
265                    ),
266                    Winding::Negative,
267                );
268            }
269        }
270    }
271
272    builder.build()
273}
274
275pub fn raw_line_intersection(p: &SvgLine, q: &SvgLine) -> Option<SvgPoint> {
276    let p_min_x = p.start.x.min(p.end.x);
277    let p_min_y = p.start.y.min(p.end.y);
278    let p_max_x = p.start.x.max(p.end.x);
279    let p_max_y = p.start.y.max(p.end.y);
280
281    let q_min_x = q.start.x.min(q.end.x);
282    let q_min_y = q.start.y.min(q.end.y);
283    let q_max_x = q.start.x.max(q.end.x);
284    let q_max_y = q.start.y.max(q.end.y);
285
286    let int_min_x = p_min_x.max(q_min_x);
287    let int_max_x = p_max_x.min(q_max_x);
288    let int_min_y = p_min_y.max(q_min_y);
289    let int_max_y = p_max_y.min(q_max_y);
290
291    let two = 2.0;
292    let mid_x = (int_min_x + int_max_x) / two;
293    let mid_y = (int_min_y + int_max_y) / two;
294
295    // condition ordinate values by subtracting midpoint
296    let p1x = p.start.x - mid_x;
297    let p1y = p.start.y - mid_y;
298    let p2x = p.end.x - mid_x;
299    let p2y = p.end.y - mid_y;
300    let q1x = q.start.x - mid_x;
301    let q1y = q.start.y - mid_y;
302    let q2x = q.end.x - mid_x;
303    let q2y = q.end.y - mid_y;
304
305    // unrolled computation using homogeneous coordinates eqn
306    let px = p1y - p2y;
307    let py = p2x - p1x;
308    let pw = p1x * p2y - p2x * p1y;
309
310    let qx = q1y - q2y;
311    let qy = q2x - q1x;
312    let qw = q1x * q2y - q2x * q1y;
313
314    let xw = py * qw - qy * pw;
315    let yw = qx * pw - px * qw;
316    let w = px * qy - qx * py;
317
318    let x_int = xw / w;
319    let y_int = yw / w;
320
321    // check for parallel lines
322    if (x_int.is_nan() || x_int.is_infinite()) || (y_int.is_nan() || y_int.is_infinite()) {
323        None
324    } else {
325        // de-condition intersection point
326        Some(SvgPoint {
327            x: x_int + mid_x,
328            y: y_int + mid_y,
329        })
330    }
331}
332
333/// By-value wrapper for raw_line_intersection (for FFI)
334pub fn raw_line_intersection_byval(p: &SvgLine, q: SvgLine) -> Option<SvgPoint> {
335    raw_line_intersection(p, &q)
336}
337
338pub fn svg_path_offset(p: &SvgPath, distance: f32, join: SvgLineJoin, cap: SvgLineCap) -> SvgPath {
339    if distance == 0.0 {
340        return p.clone();
341    }
342
343    let mut items = p.items.as_slice().to_vec();
344    if let Some(mut first) = items.first() {
345        items.push(first.clone());
346    }
347
348    let mut items = items
349        .iter()
350        .map(|l| match l {
351            SvgPathElement::Line(q) => {
352                let normal = match q.outwards_normal() {
353                    Some(s) => SvgPoint {
354                        x: s.x * distance,
355                        y: s.y * distance,
356                    },
357                    None => return l.clone(),
358                };
359
360                SvgPathElement::Line(SvgLine {
361                    start: SvgPoint {
362                        x: q.start.x + normal.x,
363                        y: q.start.y + normal.y,
364                    },
365                    end: SvgPoint {
366                        x: q.end.x + normal.x,
367                        y: q.end.y + normal.y,
368                    },
369                })
370            }
371            SvgPathElement::QuadraticCurve(q) => {
372                let n1 = match (SvgLine {
373                    start: q.start.clone(),
374                    end: q.ctrl.clone(),
375                }
376                .outwards_normal())
377                {
378                    Some(s) => SvgPoint {
379                        x: s.x * distance,
380                        y: s.y * distance,
381                    },
382                    None => return l.clone(),
383                };
384
385                let n2 = match (SvgLine {
386                    start: q.ctrl.clone(),
387                    end: q.end.clone(),
388                }
389                .outwards_normal())
390                {
391                    Some(s) => SvgPoint {
392                        x: s.x * distance,
393                        y: s.y * distance,
394                    },
395                    None => return l.clone(),
396                };
397
398                let nl1 = SvgLine {
399                    start: SvgPoint {
400                        x: q.start.x + n1.x,
401                        y: q.start.y + n1.y,
402                    },
403                    end: SvgPoint {
404                        x: q.ctrl.x + n1.x,
405                        y: q.ctrl.y + n1.y,
406                    },
407                };
408
409                let nl2 = SvgLine {
410                    start: SvgPoint {
411                        x: q.ctrl.x + n2.x,
412                        y: q.ctrl.y + n2.y,
413                    },
414                    end: SvgPoint {
415                        x: q.end.x + n2.x,
416                        y: q.end.y + n2.y,
417                    },
418                };
419
420                let nctrl = match raw_line_intersection(&nl1, &nl2) {
421                    Some(s) => s,
422                    None => return l.clone(),
423                };
424
425                SvgPathElement::QuadraticCurve(SvgQuadraticCurve {
426                    start: nl1.start,
427                    ctrl: nctrl,
428                    end: nl2.end,
429                })
430            }
431            SvgPathElement::CubicCurve(q) => {
432                let n1 = match (SvgLine {
433                    start: q.start.clone(),
434                    end: q.ctrl_1.clone(),
435                }
436                .outwards_normal())
437                {
438                    Some(s) => SvgPoint {
439                        x: s.x * distance,
440                        y: s.y * distance,
441                    },
442                    None => return l.clone(),
443                };
444
445                let n2 = match (SvgLine {
446                    start: q.ctrl_1.clone(),
447                    end: q.ctrl_2.clone(),
448                }
449                .outwards_normal())
450                {
451                    Some(s) => SvgPoint {
452                        x: s.x * distance,
453                        y: s.y * distance,
454                    },
455                    None => return l.clone(),
456                };
457
458                let n3 = match (SvgLine {
459                    start: q.ctrl_2.clone(),
460                    end: q.end.clone(),
461                }
462                .outwards_normal())
463                {
464                    Some(s) => SvgPoint {
465                        x: s.x * distance,
466                        y: s.y * distance,
467                    },
468                    None => return l.clone(),
469                };
470
471                let nl1 = SvgLine {
472                    start: SvgPoint {
473                        x: q.start.x + n1.x,
474                        y: q.start.y + n1.y,
475                    },
476                    end: SvgPoint {
477                        x: q.ctrl_1.x + n1.x,
478                        y: q.ctrl_1.y + n1.y,
479                    },
480                };
481
482                let nl2 = SvgLine {
483                    start: SvgPoint {
484                        x: q.ctrl_1.x + n2.x,
485                        y: q.ctrl_1.y + n2.y,
486                    },
487                    end: SvgPoint {
488                        x: q.ctrl_2.x + n2.x,
489                        y: q.ctrl_2.y + n2.y,
490                    },
491                };
492
493                let nl3 = SvgLine {
494                    start: SvgPoint {
495                        x: q.ctrl_2.x + n3.x,
496                        y: q.ctrl_2.y + n3.y,
497                    },
498                    end: SvgPoint {
499                        x: q.end.x + n3.x,
500                        y: q.end.y + n3.y,
501                    },
502                };
503
504                let nctrl_1 = match raw_line_intersection(&nl1, &nl2) {
505                    Some(s) => s,
506                    None => return l.clone(),
507                };
508
509                let nctrl_2 = match raw_line_intersection(&nl2, &nl3) {
510                    Some(s) => s,
511                    None => return l.clone(),
512                };
513
514                SvgPathElement::CubicCurve(SvgCubicCurve {
515                    start: nl1.start,
516                    ctrl_1: nctrl_1,
517                    ctrl_2: nctrl_2,
518                    end: nl3.end,
519                })
520            }
521        })
522        .collect::<Vec<_>>();
523
524    for i in 0..items.len().saturating_sub(2) {
525        let a_end_line = match items[i] {
526            SvgPathElement::Line(q) => q.clone(),
527            SvgPathElement::QuadraticCurve(q) => SvgLine {
528                start: q.ctrl.clone(),
529                end: q.end.clone(),
530            },
531            SvgPathElement::CubicCurve(q) => SvgLine {
532                start: q.ctrl_2.clone(),
533                end: q.end.clone(),
534            },
535        };
536
537        let b_start_line = match items[i + 1] {
538            SvgPathElement::Line(q) => q.clone(),
539            SvgPathElement::QuadraticCurve(q) => SvgLine {
540                start: q.ctrl.clone(),
541                end: q.start.clone(),
542            },
543            SvgPathElement::CubicCurve(q) => SvgLine {
544                start: q.ctrl_1.clone(),
545                end: q.start.clone(),
546            },
547        };
548
549        if let Some(intersect_pt) = raw_line_intersection(&a_end_line, &b_start_line) {
550            items[i].set_last(intersect_pt.clone());
551            items[i + 1].set_first(intersect_pt);
552        }
553    }
554
555    items.pop();
556
557    SvgPath {
558        items: items.into(),
559    }
560}
561
562fn shorten_line_end_by(line: SvgLine, distance: f32) -> SvgLine {
563    let dx = line.end.x - line.start.x;
564    let dy = line.end.y - line.start.y;
565    let dt = (dx * dx + dy * dy).sqrt();
566    let dt_short = dt - distance;
567
568    SvgLine {
569        start: line.start,
570        end: SvgPoint {
571            x: line.start.x + (dt_short / dt) * dx,
572            y: line.start.y + (dt_short / dt) * dy,
573        },
574    }
575}
576
577fn shorten_line_start_by(line: SvgLine, distance: f32) -> SvgLine {
578    let dx = line.end.x - line.start.x;
579    let dy = line.end.y - line.start.y;
580    let dt = (dx * dx + dy * dy).sqrt();
581    let dt_short = dt - distance;
582
583    SvgLine {
584        start: SvgPoint {
585            x: line.start.x + (1.0 - dt_short / dt) * dx,
586            y: line.start.y + (1.0 - dt_short / dt) * dy,
587        },
588        end: line.end,
589    }
590}
591
592// Creates a "bevel"
593pub fn svg_path_bevel(p: &SvgPath, distance: f32) -> SvgPath {
594    let mut items = p.items.as_slice().to_vec();
595
596    // duplicate first & last items
597    let first = items.first().cloned();
598    let last = items.last().cloned();
599    if let Some(first) = first {
600        items.push(first);
601    }
602    items.reverse();
603    if let Some(last) = last {
604        items.push(last);
605    }
606    items.reverse();
607
608    let mut final_items = Vec::new();
609    for i in 0..items.len().saturating_sub(1) {
610        let a = items[i].clone();
611        let b = items[i + 1].clone();
612        match (a, b) {
613            (SvgPathElement::Line(a), SvgPathElement::Line(b)) => {
614                let a_short = shorten_line_end_by(a, distance);
615                let b_short = shorten_line_start_by(b, distance);
616                final_items.push(SvgPathElement::Line(a_short));
617                final_items.push(SvgPathElement::CubicCurve(SvgCubicCurve {
618                    start: a_short.end,
619                    ctrl_1: a.end,
620                    ctrl_2: b.start,
621                    end: b_short.start,
622                }));
623                final_items.push(SvgPathElement::Line(b_short));
624            }
625            (other_a, other_b) => {
626                final_items.push(other_a);
627                final_items.push(other_b);
628            }
629        }
630    }
631
632    // remove first & last items again
633    final_items.pop();
634    final_items.reverse();
635    final_items.pop();
636    final_items.reverse();
637
638    SvgPath {
639        items: final_items.into(),
640    }
641}
642
643#[cfg(feature = "svg")]
644fn svg_path_to_lyon_path_events(path: &SvgPath) -> Path {
645    let mut builder = Path::builder();
646
647    if !path.items.as_ref().is_empty() {
648        let start_item = path.items.as_ref()[0];
649        let first_point = Point2D::new(start_item.get_start().x, start_item.get_start().y);
650
651        builder.begin(first_point);
652
653        for p in path.items.as_ref().iter() {
654            match p {
655                SvgPathElement::Line(l) => {
656                    builder.line_to(Point2D::new(l.end.x, l.end.y));
657                }
658                SvgPathElement::QuadraticCurve(qc) => {
659                    builder.quadratic_bezier_to(
660                        Point2D::new(qc.ctrl.x, qc.ctrl.y),
661                        Point2D::new(qc.end.x, qc.end.y),
662                    );
663                }
664                SvgPathElement::CubicCurve(cc) => {
665                    builder.cubic_bezier_to(
666                        Point2D::new(cc.ctrl_1.x, cc.ctrl_1.y),
667                        Point2D::new(cc.ctrl_2.x, cc.ctrl_2.y),
668                        Point2D::new(cc.end.x, cc.end.y),
669                    );
670                }
671            }
672        }
673
674        builder.end(path.is_closed());
675    }
676
677    builder.build()
678}
679
680#[cfg(feature = "svg")]
681#[inline]
682fn vertex_buffers_to_tessellated_cpu_node(v: VertexBuffers<SvgVertex, u32>) -> TessellatedSvgNode {
683    TessellatedSvgNode {
684        vertices: v.vertices.into(),
685        indices: v.indices.into(),
686    }
687}
688
689#[cfg(feature = "svg")]
690pub fn tessellate_multi_polygon_fill(
691    polygon: &SvgMultiPolygon,
692    fill_style: SvgFillStyle,
693) -> TessellatedSvgNode {
694    let polygon = svg_multipolygon_to_lyon_path(polygon);
695
696    let mut geometry = VertexBuffers::new();
697    let mut tessellator = FillTessellator::new();
698
699    let tess_result = tessellator.tessellate_path(
700        &polygon,
701        &FillOptions::tolerance(fill_style.tolerance),
702        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
703            let xy_arr = vertex.position();
704            SvgVertex {
705                x: xy_arr.x,
706                y: xy_arr.y,
707            }
708        }),
709    );
710
711    if let Err(_) = tess_result {
712        TessellatedSvgNode::empty()
713    } else {
714        vertex_buffers_to_tessellated_cpu_node(geometry)
715    }
716}
717
718#[cfg(not(feature = "svg"))]
719pub fn tessellate_multi_polygon_fill(
720    polygon: &SvgMultiPolygon,
721    fill_style: SvgFillStyle,
722) -> TessellatedSvgNode {
723    TessellatedSvgNode::default()
724}
725
726#[cfg(feature = "svg")]
727pub fn tessellate_multi_shape_fill(
728    ms: &[SvgSimpleNode],
729    fill_style: SvgFillStyle,
730) -> TessellatedSvgNode {
731    let polygon = svg_multi_shape_to_lyon_path(ms);
732
733    let mut geometry = VertexBuffers::new();
734    let mut tessellator = FillTessellator::new();
735
736    let tess_result = tessellator.tessellate_path(
737        &polygon,
738        &FillOptions::tolerance(fill_style.tolerance),
739        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
740            let xy_arr = vertex.position();
741            SvgVertex {
742                x: xy_arr.x,
743                y: xy_arr.y,
744            }
745        }),
746    );
747
748    if let Err(_) = tess_result {
749        TessellatedSvgNode::empty()
750    } else {
751        vertex_buffers_to_tessellated_cpu_node(geometry)
752    }
753}
754
755#[cfg(not(feature = "svg"))]
756pub fn tessellate_multi_shape_fill(
757    ms: &[SvgSimpleNode],
758    fill_style: SvgFillStyle,
759) -> TessellatedSvgNode {
760    TessellatedSvgNode::default()
761}
762
763pub fn svg_node_contains_point(
764    node: &SvgNode,
765    point: SvgPoint,
766    fill_rule: SvgFillRule,
767    tolerance: f32,
768) -> bool {
769    match node {
770        SvgNode::MultiPolygonCollection(a) => a
771            .as_ref()
772            .iter()
773            .any(|e| polygon_contains_point(e, point, fill_rule, tolerance)),
774        SvgNode::MultiPolygon(a) => polygon_contains_point(a, point, fill_rule, tolerance),
775        SvgNode::Path(a) => {
776            if !a.is_closed() {
777                return false;
778            }
779            path_contains_point(a, point, fill_rule, tolerance)
780        }
781        SvgNode::Circle(a) => a.contains_point(point.x, point.y),
782        SvgNode::Rect(a) => a.contains_point(point),
783        SvgNode::MultiShape(a) => a.as_ref().iter().any(|e| match e {
784            SvgSimpleNode::Path(a) => {
785                if !a.is_closed() {
786                    return false;
787                }
788                path_contains_point(a, point, fill_rule, tolerance)
789            }
790            SvgSimpleNode::Circle(a) => a.contains_point(point.x, point.y),
791            SvgSimpleNode::Rect(a) => a.contains_point(point),
792            SvgSimpleNode::CircleHole(a) => !a.contains_point(point.x, point.y),
793            SvgSimpleNode::RectHole(a) => !a.contains_point(point),
794        }),
795    }
796}
797
798#[cfg(feature = "svg")]
799pub fn path_contains_point(
800    path: &SvgPath,
801    point: SvgPoint,
802    fill_rule: SvgFillRule,
803    tolerance: f32,
804) -> bool {
805    use lyon::{
806        algorithms::hit_test::hit_test_path, math::Point as LyonPoint,
807        path::FillRule as LyonFillRule,
808    };
809    let path = svg_path_to_lyon_path_events(path);
810    let fill_rule = match fill_rule {
811        SvgFillRule::Winding => LyonFillRule::NonZero,
812        SvgFillRule::EvenOdd => LyonFillRule::EvenOdd,
813    };
814    let point = LyonPoint::new(point.x, point.y);
815    hit_test_path(&point, path.iter(), fill_rule, tolerance)
816}
817
818#[cfg(not(feature = "svg"))]
819pub fn path_contains_point(
820    path: &SvgPath,
821    point: SvgPoint,
822    fill_rule: SvgFillRule,
823    tolerance: f32,
824) -> bool {
825    false
826}
827
828#[cfg(feature = "svg")]
829pub fn polygon_contains_point(
830    polygon: &SvgMultiPolygon,
831    point: SvgPoint,
832    fill_rule: SvgFillRule,
833    tolerance: f32,
834) -> bool {
835    use lyon::{
836        algorithms::hit_test::hit_test_path, math::Point as LyonPoint,
837        path::FillRule as LyonFillRule,
838    };
839    polygon.rings.iter().any(|path| {
840        let path = svg_path_to_lyon_path_events(&path);
841        let fill_rule = match fill_rule {
842            SvgFillRule::Winding => LyonFillRule::NonZero,
843            SvgFillRule::EvenOdd => LyonFillRule::EvenOdd,
844        };
845        let point = LyonPoint::new(point.x, point.y);
846        hit_test_path(&point, path.iter(), fill_rule, tolerance)
847    })
848}
849
850#[cfg(not(feature = "svg"))]
851pub fn polygon_contains_point(
852    polygon: &SvgMultiPolygon,
853    point: SvgPoint,
854    fill_rule: SvgFillRule,
855    tolerance: f32,
856) -> bool {
857    false
858}
859
860#[cfg(feature = "svg")]
861pub fn tessellate_multi_shape_stroke(
862    ms: &[SvgSimpleNode],
863    stroke_style: SvgStrokeStyle,
864) -> TessellatedSvgNode {
865    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
866    let polygon = svg_multi_shape_to_lyon_path(ms);
867
868    let mut stroke_geometry = VertexBuffers::new();
869    let mut stroke_tess = StrokeTessellator::new();
870
871    let tess_result = stroke_tess.tessellate_path(
872        &polygon,
873        &stroke_options,
874        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
875            let xy_arr = vertex.position();
876            SvgVertex {
877                x: xy_arr.x,
878                y: xy_arr.y,
879            }
880        }),
881    );
882
883    if let Err(_) = tess_result {
884        TessellatedSvgNode::empty()
885    } else {
886        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
887    }
888}
889
890#[cfg(not(feature = "svg"))]
891pub fn tessellate_multi_shape_stroke(
892    polygon: &[SvgSimpleNode],
893    stroke_style: SvgStrokeStyle,
894) -> TessellatedSvgNode {
895    TessellatedSvgNode::default()
896}
897
898#[cfg(feature = "svg")]
899pub fn tessellate_multi_polygon_stroke(
900    polygon: &SvgMultiPolygon,
901    stroke_style: SvgStrokeStyle,
902) -> TessellatedSvgNode {
903    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
904    let polygon = svg_multipolygon_to_lyon_path(polygon);
905
906    let mut stroke_geometry = VertexBuffers::new();
907    let mut stroke_tess = StrokeTessellator::new();
908
909    let tess_result = stroke_tess.tessellate_path(
910        &polygon,
911        &stroke_options,
912        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
913            let xy_arr = vertex.position();
914            SvgVertex {
915                x: xy_arr.x,
916                y: xy_arr.y,
917            }
918        }),
919    );
920
921    if let Err(_) = tess_result {
922        TessellatedSvgNode::empty()
923    } else {
924        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
925    }
926}
927
928#[cfg(not(feature = "svg"))]
929pub fn tessellate_multi_polygon_stroke(
930    polygon: &SvgMultiPolygon,
931    stroke_style: SvgStrokeStyle,
932) -> TessellatedSvgNode {
933    TessellatedSvgNode::default()
934}
935
936#[cfg(feature = "svg")]
937pub fn tessellate_path_fill(path: &SvgPath, fill_style: SvgFillStyle) -> TessellatedSvgNode {
938    let polygon = svg_path_to_lyon_path_events(path);
939
940    let mut geometry = VertexBuffers::new();
941    let mut tessellator = FillTessellator::new();
942
943    let tess_result = tessellator.tessellate_path(
944        &polygon,
945        &FillOptions::tolerance(fill_style.tolerance),
946        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
947            let xy_arr = vertex.position();
948            SvgVertex {
949                x: xy_arr.x,
950                y: xy_arr.y,
951            }
952        }),
953    );
954
955    if let Err(_) = tess_result {
956        TessellatedSvgNode::empty()
957    } else {
958        vertex_buffers_to_tessellated_cpu_node(geometry)
959    }
960}
961
962#[cfg(not(feature = "svg"))]
963pub fn tessellate_path_fill(path: &SvgPath, fill_style: SvgFillStyle) -> TessellatedSvgNode {
964    TessellatedSvgNode::default()
965}
966
967#[cfg(feature = "svg")]
968pub fn tessellate_path_stroke(path: &SvgPath, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
969    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
970    let polygon = svg_path_to_lyon_path_events(path);
971
972    let mut stroke_geometry = VertexBuffers::new();
973    let mut stroke_tess = StrokeTessellator::new();
974
975    let tess_result = stroke_tess.tessellate_path(
976        &polygon,
977        &stroke_options,
978        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
979            let xy_arr = vertex.position();
980            SvgVertex {
981                x: xy_arr.x,
982                y: xy_arr.y,
983            }
984        }),
985    );
986
987    if let Err(_) = tess_result {
988        TessellatedSvgNode::empty()
989    } else {
990        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
991    }
992}
993
994#[cfg(not(feature = "svg"))]
995pub fn tessellate_path_stroke(path: &SvgPath, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
996    TessellatedSvgNode::default()
997}
998
999#[cfg(feature = "svg")]
1000pub fn tessellate_circle_fill(c: &SvgCircle, fill_style: SvgFillStyle) -> TessellatedSvgNode {
1001    let center = Point2D::new(c.center_x, c.center_y);
1002
1003    let mut geometry = VertexBuffers::new();
1004    let mut tesselator = FillTessellator::new();
1005    let tess_result = tesselator.tessellate_circle(
1006        center,
1007        c.radius,
1008        &FillOptions::tolerance(fill_style.tolerance),
1009        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
1010            let xy_arr = vertex.position();
1011            SvgVertex {
1012                x: xy_arr.x,
1013                y: xy_arr.y,
1014            }
1015        }),
1016    );
1017
1018    if let Err(_) = tess_result {
1019        TessellatedSvgNode::empty()
1020    } else {
1021        vertex_buffers_to_tessellated_cpu_node(geometry)
1022    }
1023}
1024
1025#[cfg(not(feature = "svg"))]
1026pub fn tessellate_circle_fill(c: &SvgCircle, fill_style: SvgFillStyle) -> TessellatedSvgNode {
1027    TessellatedSvgNode::default()
1028}
1029
1030#[cfg(feature = "svg")]
1031pub fn tessellate_circle_stroke(c: &SvgCircle, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
1032    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1033    let center = Point2D::new(c.center_x, c.center_y);
1034
1035    let mut stroke_geometry = VertexBuffers::new();
1036    let mut tesselator = StrokeTessellator::new();
1037
1038    let tess_result = tesselator.tessellate_circle(
1039        center,
1040        c.radius,
1041        &stroke_options,
1042        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
1043            let xy_arr = vertex.position();
1044            SvgVertex {
1045                x: xy_arr.x,
1046                y: xy_arr.y,
1047            }
1048        }),
1049    );
1050
1051    if let Err(_) = tess_result {
1052        TessellatedSvgNode::empty()
1053    } else {
1054        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1055    }
1056}
1057
1058#[cfg(not(feature = "svg"))]
1059pub fn tessellate_circle_stroke(c: &SvgCircle, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
1060    TessellatedSvgNode::default()
1061}
1062
1063// TODO: radii not respected on latest version of lyon
1064#[cfg(feature = "svg")]
1065fn get_radii(r: &SvgRect) -> lyon::geom::Box2D<f32> {
1066    let rect = lyon::geom::Box2D::from_origin_and_size(
1067        Point2D::new(r.x, r.y),
1068        Size2D::new(r.width, r.height),
1069    );
1070    /*
1071    let radii = BorderRadii {
1072        top_left: r.radius_top_left,
1073        top_right: r.radius_top_right,
1074        bottom_left: r.radius_bottom_left,
1075        bottom_right: r.radius_bottom_right
1076    };*/
1077    rect
1078}
1079
1080#[cfg(feature = "svg")]
1081pub fn tessellate_rect_fill(r: &SvgRect, fill_style: SvgFillStyle) -> TessellatedSvgNode {
1082    let rect = get_radii(&r);
1083    let mut geometry = VertexBuffers::new();
1084    let mut tesselator = FillTessellator::new();
1085
1086    let tess_result = tesselator.tessellate_rectangle(
1087        &rect,
1088        &FillOptions::tolerance(fill_style.tolerance),
1089        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
1090            let xy_arr = vertex.position();
1091            SvgVertex {
1092                x: xy_arr.x,
1093                y: xy_arr.y,
1094            }
1095        }),
1096    );
1097
1098    if let Err(_) = tess_result {
1099        TessellatedSvgNode::empty()
1100    } else {
1101        vertex_buffers_to_tessellated_cpu_node(geometry)
1102    }
1103}
1104
1105#[cfg(not(feature = "svg"))]
1106pub fn tessellate_rect_fill(r: &SvgRect, fill_style: SvgFillStyle) -> TessellatedSvgNode {
1107    TessellatedSvgNode::default()
1108}
1109
1110#[cfg(feature = "svg")]
1111pub fn tessellate_rect_stroke(r: &SvgRect, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
1112    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1113    let rect = get_radii(&r);
1114
1115    let mut stroke_geometry = VertexBuffers::new();
1116    let mut tesselator = StrokeTessellator::new();
1117
1118    let tess_result = tesselator.tessellate_rectangle(
1119        &rect,
1120        &stroke_options,
1121        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
1122            let xy_arr = vertex.position();
1123            SvgVertex {
1124                x: xy_arr.x,
1125                y: xy_arr.y,
1126            }
1127        }),
1128    );
1129
1130    if let Err(_) = tess_result {
1131        TessellatedSvgNode::empty()
1132    } else {
1133        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1134    }
1135}
1136
1137#[cfg(not(feature = "svg"))]
1138pub fn tessellate_rect_stroke(r: &SvgRect, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
1139    TessellatedSvgNode::default()
1140}
1141
1142/// Tessellate the path using lyon
1143#[cfg(feature = "svg")]
1144pub fn tessellate_styled_node(node: &SvgStyledNode) -> TessellatedSvgNode {
1145    match node.style {
1146        SvgStyle::Fill(fs) => tessellate_node_fill(&node.geometry, fs),
1147        SvgStyle::Stroke(ss) => tessellate_node_stroke(&node.geometry, ss),
1148    }
1149}
1150
1151#[cfg(not(feature = "svg"))]
1152pub fn tessellate_styled_node(node: &SvgStyledNode) -> TessellatedSvgNode {
1153    TessellatedSvgNode::default()
1154}
1155
1156#[cfg(feature = "svg")]
1157pub fn tessellate_line_stroke(
1158    svgline: &SvgLine,
1159    stroke_style: SvgStrokeStyle,
1160) -> TessellatedSvgNode {
1161    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1162
1163    let mut builder = Path::builder();
1164    builder.begin(Point2D::new(svgline.start.x, svgline.start.y));
1165    builder.line_to(Point2D::new(svgline.end.x, svgline.end.y));
1166    builder.end(/* closed */ false);
1167    let path = builder.build();
1168
1169    let mut stroke_geometry = VertexBuffers::new();
1170    let mut stroke_tess = StrokeTessellator::new();
1171
1172    let tess_result = stroke_tess.tessellate_path(
1173        &path,
1174        &stroke_options,
1175        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
1176            let xy_arr = vertex.position();
1177            SvgVertex {
1178                x: xy_arr.x,
1179                y: xy_arr.y,
1180            }
1181        }),
1182    );
1183
1184    if let Err(_) = tess_result {
1185        TessellatedSvgNode::empty()
1186    } else {
1187        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1188    }
1189}
1190
1191#[cfg(not(feature = "svg"))]
1192pub fn tessellate_line_stroke(
1193    svgline: &SvgLine,
1194    stroke_style: SvgStrokeStyle,
1195) -> TessellatedSvgNode {
1196    TessellatedSvgNode::default()
1197}
1198
1199#[cfg(feature = "svg")]
1200pub fn tessellate_cubiccurve_stroke(
1201    svgcubiccurve: &SvgCubicCurve,
1202    stroke_style: SvgStrokeStyle,
1203) -> TessellatedSvgNode {
1204    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1205
1206    let mut builder = Path::builder();
1207    builder.begin(Point2D::new(svgcubiccurve.start.x, svgcubiccurve.start.y));
1208    builder.cubic_bezier_to(
1209        Point2D::new(svgcubiccurve.ctrl_1.x, svgcubiccurve.ctrl_1.y),
1210        Point2D::new(svgcubiccurve.ctrl_2.x, svgcubiccurve.ctrl_2.y),
1211        Point2D::new(svgcubiccurve.end.x, svgcubiccurve.end.y),
1212    );
1213    builder.end(/* closed */ false);
1214    let path = builder.build();
1215
1216    let mut stroke_geometry = VertexBuffers::new();
1217    let mut stroke_tess = StrokeTessellator::new();
1218
1219    let tess_result = stroke_tess.tessellate_path(
1220        &path,
1221        &stroke_options,
1222        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
1223            let xy_arr = vertex.position();
1224            SvgVertex {
1225                x: xy_arr.x,
1226                y: xy_arr.y,
1227            }
1228        }),
1229    );
1230
1231    if let Err(_) = tess_result {
1232        TessellatedSvgNode::empty()
1233    } else {
1234        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1235    }
1236}
1237
1238#[cfg(not(feature = "svg"))]
1239pub fn tessellate_cubiccurve_stroke(
1240    svgline: &SvgCubicCurve,
1241    stroke_style: SvgStrokeStyle,
1242) -> TessellatedSvgNode {
1243    TessellatedSvgNode::default()
1244}
1245
1246#[cfg(feature = "svg")]
1247pub fn tessellate_quadraticcurve_stroke(
1248    svgquadraticcurve: &SvgQuadraticCurve,
1249    stroke_style: SvgStrokeStyle,
1250) -> TessellatedSvgNode {
1251    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
1252
1253    let mut builder = Path::builder();
1254    builder.begin(Point2D::new(
1255        svgquadraticcurve.start.x,
1256        svgquadraticcurve.start.y,
1257    ));
1258    builder.quadratic_bezier_to(
1259        Point2D::new(svgquadraticcurve.ctrl.x, svgquadraticcurve.ctrl.y),
1260        Point2D::new(svgquadraticcurve.end.x, svgquadraticcurve.end.y),
1261    );
1262    builder.end(/* closed */ false);
1263    let path = builder.build();
1264
1265    let mut stroke_geometry = VertexBuffers::new();
1266    let mut stroke_tess = StrokeTessellator::new();
1267
1268    let tess_result = stroke_tess.tessellate_path(
1269        &path,
1270        &stroke_options,
1271        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
1272            let xy_arr = vertex.position();
1273            SvgVertex {
1274                x: xy_arr.x,
1275                y: xy_arr.y,
1276            }
1277        }),
1278    );
1279
1280    if let Err(_) = tess_result {
1281        TessellatedSvgNode::empty()
1282    } else {
1283        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
1284    }
1285}
1286
1287#[cfg(not(feature = "svg"))]
1288pub fn tessellate_quadraticcurve_stroke(
1289    svgquadraticcurve: &SvgQuadraticCurve,
1290    stroke_style: SvgStrokeStyle,
1291) -> TessellatedSvgNode {
1292    TessellatedSvgNode::default()
1293}
1294
1295#[cfg(feature = "svg")]
1296pub fn tessellate_svgpathelement_stroke(
1297    svgpathelement: &SvgPathElement,
1298    stroke_style: SvgStrokeStyle,
1299) -> TessellatedSvgNode {
1300    match svgpathelement {
1301        SvgPathElement::Line(l) => tessellate_line_stroke(l, stroke_style),
1302        SvgPathElement::QuadraticCurve(l) => tessellate_quadraticcurve_stroke(l, stroke_style),
1303        SvgPathElement::CubicCurve(l) => tessellate_cubiccurve_stroke(l, stroke_style),
1304    }
1305}
1306
1307#[cfg(not(feature = "svg"))]
1308pub fn tessellate_svgpathelement_stroke(
1309    svgpathelement: &SvgPathElement,
1310    stroke_style: SvgStrokeStyle,
1311) -> TessellatedSvgNode {
1312    TessellatedSvgNode::default()
1313}
1314
1315#[cfg(feature = "svg")]
1316pub fn join_tessellated_nodes(nodes: &[TessellatedSvgNode]) -> TessellatedSvgNode {
1317    let mut index_offset = 0;
1318
1319    // note: can not be parallelized!
1320    let all_index_offsets = nodes
1321        .as_ref()
1322        .iter()
1323        .map(|t| {
1324            let i = index_offset;
1325            index_offset += t.vertices.len();
1326            i
1327        })
1328        .collect::<Vec<_>>();
1329
1330    let all_vertices = nodes
1331        .as_ref()
1332        .iter()
1333        .flat_map(|t| t.vertices.clone().into_library_owned_vec())
1334        .collect::<Vec<_>>();
1335
1336    let all_indices = nodes
1337        .as_ref()
1338        .iter()
1339        .enumerate()
1340        .flat_map(|(buffer_index, t)| {
1341            // since the vertex buffers are now joined,
1342            // offset the indices by the vertex buffers lengths
1343            // encountered so far
1344            let vertex_buffer_offset: u32 = all_index_offsets
1345                .get(buffer_index)
1346                .copied()
1347                .unwrap_or(0)
1348                .min(core::u32::MAX as usize) as u32;
1349
1350            let mut indices = t.indices.clone().into_library_owned_vec();
1351            if vertex_buffer_offset != 0 {
1352                indices.iter_mut().for_each(|i| {
1353                    if *i != GL_RESTART_INDEX {
1354                        *i += vertex_buffer_offset;
1355                    }
1356                });
1357            }
1358
1359            indices.push(GL_RESTART_INDEX);
1360
1361            indices
1362        })
1363        .collect::<Vec<_>>();
1364
1365    TessellatedSvgNode {
1366        vertices: all_vertices.into(),
1367        indices: all_indices.into(),
1368    }
1369}
1370
1371#[cfg(feature = "svg")]
1372pub fn join_tessellated_colored_nodes(
1373    nodes: &[TessellatedColoredSvgNode],
1374) -> TessellatedColoredSvgNode {
1375    let mut index_offset = 0;
1376
1377    // note: can not be parallelized!
1378    let all_index_offsets = nodes
1379        .as_ref()
1380        .iter()
1381        .map(|t| {
1382            let i = index_offset;
1383            index_offset += t.vertices.len();
1384            i
1385        })
1386        .collect::<Vec<_>>();
1387
1388    let all_vertices = nodes
1389        .as_ref()
1390        .iter()
1391        .flat_map(|t| t.vertices.clone().into_library_owned_vec())
1392        .collect::<Vec<_>>();
1393
1394    let all_indices = nodes
1395        .as_ref()
1396        .iter()
1397        .enumerate()
1398        .flat_map(|(buffer_index, t)| {
1399            // since the vertex buffers are now joined,
1400            // offset the indices by the vertex buffers lengths
1401            // encountered so far
1402            let vertex_buffer_offset: u32 = all_index_offsets
1403                .get(buffer_index)
1404                .copied()
1405                .unwrap_or(0)
1406                .min(core::u32::MAX as usize) as u32;
1407
1408            let mut indices = t.indices.clone().into_library_owned_vec();
1409            if vertex_buffer_offset != 0 {
1410                indices.iter_mut().for_each(|i| {
1411                    if *i != GL_RESTART_INDEX {
1412                        *i += vertex_buffer_offset;
1413                    }
1414                });
1415            }
1416
1417            indices.push(GL_RESTART_INDEX);
1418
1419            indices
1420        })
1421        .collect::<Vec<_>>();
1422
1423    TessellatedColoredSvgNode {
1424        vertices: all_vertices.into(),
1425        indices: all_indices.into(),
1426    }
1427}
1428
1429#[cfg(not(feature = "svg"))]
1430pub fn join_tessellated_nodes(nodes: &[TessellatedSvgNode]) -> TessellatedSvgNode {
1431    TessellatedSvgNode::default()
1432}
1433
1434#[cfg(not(feature = "svg"))]
1435pub fn join_tessellated_colored_nodes(
1436    nodes: &[TessellatedColoredSvgNode],
1437) -> TessellatedColoredSvgNode {
1438    TessellatedColoredSvgNode::default()
1439}
1440
1441#[cfg(feature = "svg")]
1442pub fn tessellate_node_fill(node: &SvgNode, fs: SvgFillStyle) -> TessellatedSvgNode {
1443    match &node {
1444        SvgNode::MultiPolygonCollection(ref mpc) => {
1445            let tessellated_multipolygons = mpc
1446                .as_ref()
1447                .iter()
1448                .map(|mp| tessellate_multi_polygon_fill(mp, fs))
1449                .collect::<Vec<_>>();
1450            join_tessellated_nodes(&tessellated_multipolygons)
1451        }
1452        SvgNode::MultiPolygon(ref mp) => tessellate_multi_polygon_fill(mp, fs),
1453        SvgNode::Path(ref p) => tessellate_path_fill(p, fs),
1454        SvgNode::Circle(ref c) => tessellate_circle_fill(c, fs),
1455        SvgNode::Rect(ref r) => tessellate_rect_fill(r, fs),
1456        SvgNode::MultiShape(ref r) => tessellate_multi_shape_fill(r.as_ref(), fs),
1457    }
1458}
1459
1460#[cfg(not(feature = "svg"))]
1461pub fn tessellate_node_fill(node: &SvgNode, fs: SvgFillStyle) -> TessellatedSvgNode {
1462    TessellatedSvgNode::default()
1463}
1464
1465#[cfg(feature = "svg")]
1466pub fn tessellate_node_stroke(node: &SvgNode, ss: SvgStrokeStyle) -> TessellatedSvgNode {
1467    match &node {
1468        SvgNode::MultiPolygonCollection(ref mpc) => {
1469            let tessellated_multipolygons = mpc
1470                .as_ref()
1471                .iter()
1472                .map(|mp| tessellate_multi_polygon_stroke(mp, ss))
1473                .collect::<Vec<_>>();
1474            join_tessellated_nodes(&tessellated_multipolygons)
1475        }
1476        SvgNode::MultiPolygon(ref mp) => tessellate_multi_polygon_stroke(mp, ss),
1477        SvgNode::Path(ref p) => tessellate_path_stroke(p, ss),
1478        SvgNode::Circle(ref c) => tessellate_circle_stroke(c, ss),
1479        SvgNode::Rect(ref r) => tessellate_rect_stroke(r, ss),
1480        SvgNode::MultiShape(ms) => tessellate_multi_shape_stroke(ms.as_ref(), ss),
1481    }
1482}
1483
1484#[cfg(not(feature = "svg"))]
1485pub fn tessellate_node_stroke(node: &SvgNode, ss: SvgStrokeStyle) -> TessellatedSvgNode {
1486    TessellatedSvgNode::default()
1487}
1488
1489// NOTE: This is a separate step both in order to reuse GPU textures
1490// and also because texture allocation is heavy and can be offloaded to a different thread
1491pub fn allocate_clipmask_texture(
1492    gl_context: GlContextPtr,
1493    size: PhysicalSizeU32,
1494    _background: ColorU,
1495) -> Texture {
1496    use azul_core::gl::TextureFlags;
1497
1498    let textures = gl_context.gen_textures(1);
1499    let texture_id = textures.get(0).unwrap();
1500
1501    Texture::create(
1502        *texture_id,
1503        TextureFlags {
1504            is_opaque: true,
1505            is_video_texture: false,
1506        },
1507        size,
1508        ColorU::TRANSPARENT,
1509        gl_context,
1510        RawImageFormat::R8,
1511    )
1512}
1513
1514/// Applies an FXAA filter to the texture using the pre-compiled FXAA shader.
1515///
1516/// Renders a fullscreen quad with the FXAA fragment shader, reading from
1517/// the input texture and writing to a temporary texture, then swaps the
1518/// texture IDs so the caller gets the post-FXAA result.
1519pub fn apply_fxaa(texture: &mut Texture) -> Option<()> {
1520    apply_fxaa_with_config(texture, azul_core::gl_fxaa::FxaaConfig::enabled())
1521}
1522
1523/// Applies FXAA with custom configuration parameters.
1524pub fn apply_fxaa_with_config(
1525    texture: &mut Texture,
1526    config: azul_core::gl_fxaa::FxaaConfig,
1527) -> Option<()> {
1528    use std::mem;
1529
1530    use azul_core::gl::{GLuint, GlVoidPtrConst, VertexAttributeType};
1531    use gl_context_loader::gl;
1532
1533    if !config.enabled || texture.size.width == 0 || texture.size.height == 0 {
1534        return Some(());
1535    }
1536
1537    // FXAA only works on RGBA8 textures
1538    if texture.format != RawImageFormat::RGBA8 {
1539        return Some(());
1540    }
1541
1542    let texture_size = texture.size;
1543    let gl_context = &texture.gl_context;
1544    let fxaa_shader = gl_context.get_fxaa_shader();
1545    let w = texture_size.width as f32;
1546    let h = texture_size.height as f32;
1547
1548    // Save GL state
1549    let mut current_program = [0_i32];
1550    let mut current_framebuffers = [0_i32];
1551    let mut current_texture_2d = [0_i32];
1552    let mut current_vertex_array_object = [0_i32];
1553    let mut current_vertex_buffer = [0_i32];
1554    let mut current_index_buffer = [0_i32];
1555    let mut current_active_texture = [0_i32];
1556    let mut current_blend_enabled = [0_u8];
1557    let mut current_viewport = [0_i32; 4];
1558
1559    gl_context.get_integer_v(gl::CURRENT_PROGRAM, (&mut current_program[..]).into());
1560    gl_context.get_integer_v(gl::FRAMEBUFFER, (&mut current_framebuffers[..]).into());
1561    gl_context.get_integer_v(gl::TEXTURE_2D, (&mut current_texture_2d[..]).into());
1562    gl_context.get_integer_v(
1563        gl::VERTEX_ARRAY_BINDING,
1564        (&mut current_vertex_array_object[..]).into(),
1565    );
1566    gl_context.get_integer_v(
1567        gl::ARRAY_BUFFER_BINDING,
1568        (&mut current_vertex_buffer[..]).into(),
1569    );
1570    gl_context.get_integer_v(
1571        gl::ELEMENT_ARRAY_BUFFER_BINDING,
1572        (&mut current_index_buffer[..]).into(),
1573    );
1574    gl_context.get_integer_v(
1575        gl::ACTIVE_TEXTURE,
1576        (&mut current_active_texture[..]).into(),
1577    );
1578    gl_context.get_boolean_v(gl::BLEND, (&mut current_blend_enabled[..]).into());
1579    gl_context.get_integer_v(gl::VIEWPORT, (&mut current_viewport[..]).into());
1580
1581    // 1. Create temporary output texture
1582    let temp_textures = gl_context.gen_textures(1);
1583    let temp_tex_id = *temp_textures.get(0)?;
1584    gl_context.bind_texture(gl::TEXTURE_2D, temp_tex_id);
1585    gl_context.tex_image_2d(
1586        gl::TEXTURE_2D,
1587        0,
1588        gl::RGBA as i32,
1589        texture_size.width as i32,
1590        texture_size.height as i32,
1591        0,
1592        gl::RGBA,
1593        gl::UNSIGNED_BYTE,
1594        None.into(),
1595    );
1596    gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
1597    gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
1598    gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
1599    gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
1600
1601    // 2. Create FBO targeting the temp texture
1602    let fbos = gl_context.gen_framebuffers(1);
1603    let fbo_id = *fbos.get(0)?;
1604    gl_context.bind_framebuffer(gl::FRAMEBUFFER, fbo_id);
1605    gl_context.framebuffer_texture_2d(
1606        gl::FRAMEBUFFER,
1607        gl::COLOR_ATTACHMENT0,
1608        gl::TEXTURE_2D,
1609        temp_tex_id,
1610        0,
1611    );
1612    gl_context.draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
1613
1614    debug_assert!(
1615        gl_context.check_frame_buffer_status(gl::FRAMEBUFFER) == gl::FRAMEBUFFER_COMPLETE
1616    );
1617
1618    // 3. Create fullscreen quad VAO/VBO/IBO
1619    // Vertices in [-1, 1] range; the FXAA vertex shader converts to [0, 1] UVs
1620    let quad_vertices: [f32; 8] = [
1621        -1.0, -1.0, // bottom-left
1622         1.0, -1.0, // bottom-right
1623         1.0,  1.0, // top-right
1624        -1.0,  1.0, // top-left
1625    ];
1626    let quad_indices: [u32; 6] = [0, 1, 2, 0, 2, 3];
1627
1628    let vaos = gl_context.gen_vertex_arrays(1);
1629    let vao_id = *vaos.get(0)?;
1630    gl_context.bind_vertex_array(vao_id);
1631
1632    let vbos = gl_context.gen_buffers(1);
1633    let vbo_id = *vbos.get(0)?;
1634    gl_context.bind_buffer(gl::ARRAY_BUFFER, vbo_id);
1635    gl_context.buffer_data_untyped(
1636        gl::ARRAY_BUFFER,
1637        (mem::size_of::<f32>() * quad_vertices.len()) as isize,
1638        GlVoidPtrConst {
1639            ptr: quad_vertices.as_ptr() as *const std::ffi::c_void,
1640            run_destructor: true,
1641        },
1642        gl::STATIC_DRAW,
1643    );
1644
1645    let ibos = gl_context.gen_buffers(1);
1646    let ibo_id = *ibos.get(0)?;
1647    gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, ibo_id);
1648    gl_context.buffer_data_untyped(
1649        gl::ELEMENT_ARRAY_BUFFER,
1650        (mem::size_of::<u32>() * quad_indices.len()) as isize,
1651        GlVoidPtrConst {
1652            ptr: quad_indices.as_ptr() as *const std::ffi::c_void,
1653            run_destructor: true,
1654        },
1655        gl::STATIC_DRAW,
1656    );
1657
1658    // Set up vertex attribute for vAttrXY (location 0, bound at shader compilation)
1659    let vertex_type = VertexAttributeType::Float;
1660    let stride = vertex_type.get_mem_size() * 2; // 2 floats per vertex (x, y)
1661    gl_context.vertex_attrib_pointer(0, 2, vertex_type.get_gl_id(), false, stride as i32, 0);
1662    gl_context.enable_vertex_attrib_array(0);
1663
1664    // 4. Render FXAA pass
1665    gl_context.use_program(fxaa_shader);
1666    gl_context.viewport(0, 0, texture_size.width as i32, texture_size.height as i32);
1667    gl_context.disable(gl::BLEND); // FXAA reads exact colors, blending would corrupt output
1668
1669    // Bind input texture to GL_TEXTURE0
1670    gl_context.active_texture(gl::TEXTURE0);
1671    gl_context.bind_texture(gl::TEXTURE_2D, texture.texture_id);
1672
1673    // Set uniforms
1674    let u_texture = gl_context.get_uniform_location(fxaa_shader, "uTexture");
1675    gl_context.uniform_1i(u_texture, 0);
1676
1677    let u_texel_size = gl_context.get_uniform_location(fxaa_shader, "uTexelSize");
1678    gl_context.uniform_2f(u_texel_size, 1.0 / w, 1.0 / h);
1679
1680    let u_edge_threshold =
1681        gl_context.get_uniform_location(fxaa_shader, "uEdgeThreshold");
1682    gl_context.uniform_1f(u_edge_threshold, config.edge_threshold);
1683
1684    let u_edge_threshold_min =
1685        gl_context.get_uniform_location(fxaa_shader, "uEdgeThresholdMin");
1686    gl_context.uniform_1f(u_edge_threshold_min, config.edge_threshold_min);
1687
1688    // Draw the fullscreen quad
1689    gl_context.draw_elements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, 0);
1690
1691    // 5. Swap texture IDs: the temp texture now has the FXAA result.
1692    // We swap so the caller's texture_id points to the anti-aliased result,
1693    // and the old texture_id gets cleaned up.
1694    let old_texture_id = texture.texture_id;
1695    texture.texture_id = temp_tex_id;
1696    // Delete the old texture (which was the input)
1697    gl_context.delete_textures((&[old_texture_id])[..].into());
1698
1699    // 6. Cleanup: delete FBO, quad buffers
1700    gl_context.delete_framebuffers((&[fbo_id])[..].into());
1701    gl_context.disable_vertex_attrib_array(0);
1702    gl_context.delete_vertex_arrays((&[vao_id])[..].into());
1703    gl_context.delete_buffers((&[vbo_id, ibo_id])[..].into());
1704
1705    // Restore GL state
1706    gl_context.bind_framebuffer(gl::FRAMEBUFFER, current_framebuffers[0] as u32);
1707    gl_context.bind_texture(gl::TEXTURE_2D, current_texture_2d[0] as u32);
1708    gl_context.bind_vertex_array(current_vertex_array_object[0] as u32);
1709    gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, current_index_buffer[0] as u32);
1710    gl_context.bind_buffer(gl::ARRAY_BUFFER, current_vertex_buffer[0] as u32);
1711    gl_context.use_program(current_program[0] as u32);
1712    gl_context.active_texture(current_active_texture[0] as u32);
1713    gl_context.viewport(
1714        current_viewport[0],
1715        current_viewport[1],
1716        current_viewport[2],
1717        current_viewport[3],
1718    );
1719    if u32::from(current_blend_enabled[0]) == gl::TRUE {
1720        gl_context.enable(gl::BLEND);
1721    }
1722
1723    Some(())
1724}
1725
1726#[cfg(feature = "svg")]
1727pub fn render_node_clipmask_cpu(
1728    image: &mut RawImage,
1729    node: &SvgNode,
1730    style: SvgStyle,
1731) -> Option<()> {
1732    use azul_core::resources::RawImageData;
1733    use agg_rust::{
1734        basics::{FillingRule, VertexSource, PATH_FLAGS_NONE},
1735        path_storage::PathStorage,
1736        color::Rgba8,
1737        conv_stroke::ConvStroke,
1738        conv_transform::ConvTransform,
1739        math_stroke::{LineCap, LineJoin},
1740        pixfmt_rgba::{PixfmtRgba32, PixelFormat},
1741        rasterizer_scanline_aa::RasterizerScanlineAa,
1742        renderer_base::RendererBase,
1743        renderer_scanline::render_scanlines_aa_solid,
1744        rendering_buffer::RowAccessor,
1745        scanline_u::ScanlineU8,
1746        trans_affine::TransAffine,
1747    };
1748
1749    fn agg_translate_node(node: &SvgNode) -> Option<PathStorage> {
1750        macro_rules! build_path {
1751            ($path:expr, $p:expr) => {{
1752                if $p.items.as_ref().is_empty() {
1753                    return None;
1754                }
1755
1756                let start = $p.items.as_ref()[0].get_start();
1757                $path.move_to(start.x as f64, start.y as f64);
1758
1759                for path_element in $p.items.as_ref() {
1760                    match path_element {
1761                        SvgPathElement::Line(l) => {
1762                            $path.line_to(l.end.x as f64, l.end.y as f64);
1763                        }
1764                        SvgPathElement::QuadraticCurve(qc) => {
1765                            $path.curve3(
1766                                qc.ctrl.x as f64, qc.ctrl.y as f64,
1767                                qc.end.x as f64, qc.end.y as f64,
1768                            );
1769                        }
1770                        SvgPathElement::CubicCurve(cc) => {
1771                            $path.curve4(
1772                                cc.ctrl_1.x as f64, cc.ctrl_1.y as f64,
1773                                cc.ctrl_2.x as f64, cc.ctrl_2.y as f64,
1774                                cc.end.x as f64, cc.end.y as f64,
1775                            );
1776                        }
1777                    }
1778                }
1779
1780                if $p.is_closed() {
1781                    $path.close_polygon(PATH_FLAGS_NONE);
1782                }
1783            }};
1784        }
1785
1786        let mut path = PathStorage::new();
1787        match node {
1788            SvgNode::MultiPolygonCollection(mpc) => {
1789                for mp in mpc.iter() {
1790                    for p in mp.rings.iter() {
1791                        build_path!(path, p);
1792                    }
1793                }
1794            }
1795            SvgNode::MultiPolygon(mp) => {
1796                for p in mp.rings.iter() {
1797                    build_path!(path, p);
1798                }
1799            }
1800            SvgNode::Path(p) => {
1801                build_path!(path, p);
1802            }
1803            SvgNode::Circle(c) => {
1804                // Approximate circle with 4 cubic beziers
1805                let cx = c.center_x as f64;
1806                let cy = c.center_y as f64;
1807                let r = c.radius as f64;
1808                let k = CIRCLE_BEZIER_KAPPA;
1809                let kr = k * r;
1810                path.move_to(cx + r, cy);
1811                path.curve4(cx + r, cy + kr, cx + kr, cy + r, cx, cy + r);
1812                path.curve4(cx - kr, cy + r, cx - r, cy + kr, cx - r, cy);
1813                path.curve4(cx - r, cy - kr, cx - kr, cy - r, cx, cy - r);
1814                path.curve4(cx + kr, cy - r, cx + r, cy - kr, cx + r, cy);
1815                path.close_polygon(PATH_FLAGS_NONE);
1816            }
1817            SvgNode::Rect(r) => {
1818                let x = r.x as f64;
1819                let y = r.y as f64;
1820                let w = r.width as f64;
1821                let h = r.height as f64;
1822                path.move_to(x, y);
1823                path.line_to(x + w, y);
1824                path.line_to(x + w, y + h);
1825                path.line_to(x, y + h);
1826                path.close_polygon(PATH_FLAGS_NONE);
1827            }
1828            SvgNode::MultiShape(ms) => {
1829                for p in ms.as_ref() {
1830                    match p {
1831                        SvgSimpleNode::Path(p) => {
1832                            build_path!(path, p);
1833                        }
1834                        SvgSimpleNode::Rect(r) => {
1835                            let x = r.x as f64;
1836                            let y = r.y as f64;
1837                            let w = r.width as f64;
1838                            let h = r.height as f64;
1839                            path.move_to(x, y);
1840                            path.line_to(x + w, y);
1841                            path.line_to(x + w, y + h);
1842                            path.line_to(x, y + h);
1843                            path.close_polygon(PATH_FLAGS_NONE);
1844                        }
1845                        SvgSimpleNode::Circle(c) | SvgSimpleNode::CircleHole(c) => {
1846                            let cx = c.center_x as f64;
1847                            let cy = c.center_y as f64;
1848                            let r = c.radius as f64;
1849                            let k = CIRCLE_BEZIER_KAPPA;
1850                            let kr = k * r;
1851                            path.move_to(cx + r, cy);
1852                            path.curve4(cx + r, cy + kr, cx + kr, cy + r, cx, cy + r);
1853                            path.curve4(cx - kr, cy + r, cx - r, cy + kr, cx - r, cy);
1854                            path.curve4(cx - r, cy - kr, cx - kr, cy - r, cx, cy - r);
1855                            path.curve4(cx + kr, cy - r, cx + r, cy - kr, cx + r, cy);
1856                            path.close_polygon(PATH_FLAGS_NONE);
1857                        }
1858                        SvgSimpleNode::RectHole(r) => {
1859                            let x = r.x as f64;
1860                            let y = r.y as f64;
1861                            let w = r.width as f64;
1862                            let h = r.height as f64;
1863                            path.move_to(x, y);
1864                            path.line_to(x + w, y);
1865                            path.line_to(x + w, y + h);
1866                            path.line_to(x, y + h);
1867                            path.close_polygon(PATH_FLAGS_NONE);
1868                        }
1869                    }
1870                }
1871            }
1872        }
1873        if path.total_vertices() == 0 {
1874            return None;
1875        }
1876        Some(path)
1877    }
1878
1879    let w = image.width as u32;
1880    let h = image.height as u32;
1881    if w == 0 || h == 0 {
1882        return None;
1883    }
1884
1885    let transform_data = style.get_transform();
1886    let transform = TransAffine::new_custom(
1887        transform_data.sx as f64,
1888        transform_data.ky as f64,
1889        transform_data.kx as f64,
1890        transform_data.sy as f64,
1891        transform_data.tx as f64,
1892        transform_data.ty as f64,
1893    );
1894
1895    let mut agg_path = agg_translate_node(node)?;
1896    let white = Rgba8::new(255, 255, 255, 255);
1897
1898    // Create pixel buffer and render
1899    let mut buf = vec![0u8; (w as usize) * (h as usize) * 4];
1900    let stride = (w * 4) as i32;
1901    let mut ra = unsafe { RowAccessor::new_with_buf(buf.as_mut_ptr(), w, h, stride) };
1902    let mut pf = PixfmtRgba32::new(&mut ra);
1903    let mut rb = RendererBase::new(pf);
1904    let mut ras = RasterizerScanlineAa::new();
1905    let mut sl = ScanlineU8::new();
1906
1907    match style {
1908        SvgStyle::Fill(fs) => {
1909            ras.filling_rule(match fs.fill_rule {
1910                SvgFillRule::Winding => FillingRule::NonZero,
1911                SvgFillRule::EvenOdd => FillingRule::EvenOdd,
1912            });
1913            if transform.is_identity(0.0001) {
1914                ras.add_path(&mut agg_path, 0);
1915            } else {
1916                let mut transformed = ConvTransform::new(&mut agg_path, transform);
1917                ras.add_path(&mut transformed, 0);
1918            }
1919            render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &white);
1920        }
1921        SvgStyle::Stroke(ss) => {
1922            let mut stroke = ConvStroke::new(agg_path);
1923            stroke.set_width(ss.line_width as f64);
1924            stroke.set_miter_limit(ss.miter_limit as f64);
1925            stroke.set_line_cap(match ss.start_cap {
1926                SvgLineCap::Butt => LineCap::Butt,
1927                SvgLineCap::Square => LineCap::Square,
1928                SvgLineCap::Round => LineCap::Round,
1929            });
1930            stroke.set_line_join(match ss.line_join {
1931                SvgLineJoin::Miter | SvgLineJoin::MiterClip => LineJoin::Miter,
1932                SvgLineJoin::Round => LineJoin::Round,
1933                SvgLineJoin::Bevel => LineJoin::Bevel,
1934            });
1935            if transform.is_identity(0.0001) {
1936                ras.add_path(&mut stroke, 0);
1937            } else {
1938                let mut transformed = ConvTransform::new(&mut stroke, transform);
1939                ras.add_path(&mut transformed, 0);
1940            }
1941            render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &white);
1942        }
1943    }
1944
1945    // Extract red channel from RGBA buffer
1946    let red_channel = buf
1947        .chunks_exact(4)
1948        .map(|r| r[0])
1949        .collect::<Vec<_>>();
1950
1951    image.premultiplied_alpha = true;
1952    image.pixels = RawImageData::U8(red_channel.into());
1953    image.data_format = RawImageFormat::R8;
1954
1955    Some(())
1956}
1957
1958#[cfg(not(feature = "svg"))]
1959pub fn render_node_clipmask_cpu(
1960    image: &mut RawImage,
1961    node: &SvgNode,
1962    style: SvgStyle,
1963) -> Option<()> {
1964    None
1965}
1966
1967// ============================================================================
1968// Boolean operations on SvgMultiPolygon — via agg scanline boolean algebra
1969// ============================================================================
1970
1971/// Rasterize an `SvgMultiPolygon` into an agg `RasterizerScanlineAa`.
1972fn rasterize_multi_polygon(mp: &SvgMultiPolygon) -> agg_rust::rasterizer_scanline_aa::RasterizerScanlineAa {
1973    use agg_rust::{
1974        basics::{FillingRule, PATH_FLAGS_NONE},
1975        path_storage::PathStorage,
1976        rasterizer_scanline_aa::RasterizerScanlineAa,
1977    };
1978
1979    let mut ras = RasterizerScanlineAa::new();
1980    ras.filling_rule(FillingRule::NonZero);
1981
1982    let mut path = PathStorage::new();
1983    for ring in mp.rings.as_ref().iter() {
1984        let mut first = true;
1985        for item in ring.items.as_ref().iter() {
1986            match item {
1987                SvgPathElement::Line(l) => {
1988                    if first {
1989                        path.move_to(l.start.x as f64, l.start.y as f64);
1990                        first = false;
1991                    }
1992                    path.line_to(l.end.x as f64, l.end.y as f64);
1993                }
1994                SvgPathElement::QuadraticCurve(q) => {
1995                    if first {
1996                        path.move_to(q.start.x as f64, q.start.y as f64);
1997                        first = false;
1998                    }
1999                    path.curve3(q.ctrl.x as f64, q.ctrl.y as f64, q.end.x as f64, q.end.y as f64);
2000                }
2001                SvgPathElement::CubicCurve(c) => {
2002                    if first {
2003                        path.move_to(c.start.x as f64, c.start.y as f64);
2004                        first = false;
2005                    }
2006                    path.curve4(
2007                        c.ctrl_1.x as f64, c.ctrl_1.y as f64,
2008                        c.ctrl_2.x as f64, c.ctrl_2.y as f64,
2009                        c.end.x as f64, c.end.y as f64,
2010                    );
2011                }
2012            }
2013        }
2014        path.close_polygon(PATH_FLAGS_NONE);
2015    }
2016    ras.add_path(&mut path, 0);
2017    ras
2018}
2019
2020/// Extract polygon contours from a `ScanlineStorageAa` by tracing
2021/// horizontal span edges across consecutive scanlines.
2022///
2023/// For each row, we collect the solid spans (coverage > 128). Then we
2024/// trace left/right boundaries of connected span groups into closed
2025/// polygons (go down on the left edge, come back up on the right edge).
2026fn storage_to_multi_polygon(
2027    storage: &mut agg_rust::scanline_storage_aa::ScanlineStorageAa,
2028) -> SvgMultiPolygon {
2029    use agg_rust::rasterizer_scanline_aa::Scanline;
2030    use azul_css::props::basic::SvgPoint;
2031
2032    // Collect solid spans per row
2033    let mut rows: Vec<(i32, Vec<(i32, i32)>)> = Vec::new(); // (y, [(x_start, x_end)])
2034
2035    let mut sl = agg_rust::scanline_u::ScanlineU8::new();
2036    if storage.rewind_scanlines() {
2037        sl.reset(storage.min_x(), storage.max_x());
2038        while storage.sweep_scanline(&mut sl) {
2039            let y = Scanline::y(&sl);
2040            let mut row_spans: Vec<(i32, i32)> = Vec::new();
2041            for span in sl.begin() {
2042                // Span with positive len: per-pixel coverage
2043                let len = span.len;
2044                if len <= 0 { continue; }
2045                // Check if any pixel in the span has enough coverage
2046                let covers = sl.covers();
2047                let mut x_start = None;
2048                for j in 0..len as usize {
2049                    let cov = covers.get(span.cover_offset + j).copied().unwrap_or(0);
2050                    if cov > 128 {
2051                        if x_start.is_none() { x_start = Some(span.x + j as i32); }
2052                    } else if let Some(xs) = x_start.take() {
2053                        row_spans.push((xs, span.x + j as i32));
2054                    }
2055                }
2056                if let Some(xs) = x_start {
2057                    row_spans.push((xs, span.x + len));
2058                }
2059            }
2060            if !row_spans.is_empty() {
2061                rows.push((y, row_spans));
2062            }
2063        }
2064    }
2065
2066    if rows.is_empty() {
2067        return SvgMultiPolygon { rings: SvgPathVec::from_const_slice(&[]) };
2068    }
2069
2070    // Simple contour extraction: for each row, create horizontal line segments.
2071    // Then connect consecutive rows into closed polygons.
2072    // This produces axis-aligned polygons (staircase approximation).
2073    let mut rings = Vec::new();
2074
2075    for (y, spans) in &rows {
2076        let yf = *y as f32;
2077        for &(x0, x1) in spans {
2078            let x0f = x0 as f32;
2079            let x1f = x1 as f32;
2080            // Create a small horizontal rectangle for this span
2081            let elements = vec![
2082                SvgPathElement::Line(SvgLine::new(
2083                    SvgPoint { x: x0f, y: yf },
2084                    SvgPoint { x: x1f, y: yf },
2085                )),
2086                SvgPathElement::Line(SvgLine::new(
2087                    SvgPoint { x: x1f, y: yf },
2088                    SvgPoint { x: x1f, y: yf + 1.0 },
2089                )),
2090                SvgPathElement::Line(SvgLine::new(
2091                    SvgPoint { x: x1f, y: yf + 1.0 },
2092                    SvgPoint { x: x0f, y: yf + 1.0 },
2093                )),
2094                SvgPathElement::Line(SvgLine::new(
2095                    SvgPoint { x: x0f, y: yf + 1.0 },
2096                    SvgPoint { x: x0f, y: yf },
2097                )),
2098            ];
2099            rings.push(SvgPath { items: SvgPathElementVec::from_vec(elements) });
2100        }
2101    }
2102
2103    SvgMultiPolygon { rings: SvgPathVec::from_vec(rings) }
2104}
2105
2106/// Perform a boolean operation on two `SvgMultiPolygon` shapes using agg scanline algebra.
2107fn svg_bool_op(
2108    a: &SvgMultiPolygon,
2109    b: &SvgMultiPolygon,
2110    op: agg_rust::scanline_boolean_algebra::SBoolOp,
2111) -> SvgMultiPolygon {
2112    use agg_rust::{
2113        scanline_boolean_algebra::sbool_combine_shapes_aa,
2114        scanline_storage_aa::ScanlineStorageAa,
2115        scanline_u::ScanlineU8,
2116    };
2117
2118    let mut ras1 = rasterize_multi_polygon(a);
2119    let mut ras2 = rasterize_multi_polygon(b);
2120
2121    let mut sl1 = ScanlineU8::new();
2122    let mut sl2 = ScanlineU8::new();
2123    let mut sl_result = ScanlineU8::new();
2124    let mut storage1 = ScanlineStorageAa::new();
2125    let mut storage2 = ScanlineStorageAa::new();
2126    let mut storage_result = ScanlineStorageAa::new();
2127
2128    sbool_combine_shapes_aa(
2129        op,
2130        &mut ras1, &mut ras2,
2131        &mut sl1, &mut sl2, &mut sl_result,
2132        &mut storage1, &mut storage2, &mut storage_result,
2133    );
2134
2135    storage_to_multi_polygon(&mut storage_result)
2136}
2137
2138pub fn svg_multi_polygon_union(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
2139    svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::Or)
2140}
2141
2142pub fn svg_multi_polygon_union_byval(a: &SvgMultiPolygon, b: SvgMultiPolygon) -> SvgMultiPolygon {
2143    svg_multi_polygon_union(a, &b)
2144}
2145
2146pub fn svg_multi_polygon_intersection(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
2147    svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::And)
2148}
2149
2150pub fn svg_multi_polygon_intersection_byval(
2151    a: &SvgMultiPolygon, b: SvgMultiPolygon,
2152) -> SvgMultiPolygon {
2153    svg_multi_polygon_intersection(a, &b)
2154}
2155
2156pub fn svg_multi_polygon_difference(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
2157    svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::AMinusB)
2158}
2159
2160pub fn svg_multi_polygon_difference_byval(
2161    a: &SvgMultiPolygon, b: SvgMultiPolygon,
2162) -> SvgMultiPolygon {
2163    svg_multi_polygon_difference(a, &b)
2164}
2165
2166pub fn svg_multi_polygon_xor(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
2167    svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::Xor)
2168}
2169
2170pub fn svg_multi_polygon_xor_byval(a: &SvgMultiPolygon, b: SvgMultiPolygon) -> SvgMultiPolygon {
2171    svg_multi_polygon_xor(a, &b)
2172}
2173
2174// ============================================================================
2175// SVG Rendering — ParsedSvg wraps the XML tree, no usvg
2176// ============================================================================
2177
2178/// Parsed SVG document — wraps the XML node tree.
2179///
2180/// Previously wrapped usvg::Tree; now stores our own XmlNode tree parsed via xmlparser.
2181/// Rendering uses the agg-rust pipeline in cpurender::render_svg_to_png().
2182#[derive(Debug, Clone)]
2183#[repr(C)]
2184pub struct ParsedSvgXmlNode {
2185    pub run_destructor: bool,
2186}
2187
2188impl Drop for ParsedSvgXmlNode {
2189    fn drop(&mut self) { self.run_destructor = false; }
2190}
2191
2192pub fn svgxmlnode_parse(
2193    svg_file_data: &[u8],
2194    _options: SvgParseOptions,
2195) -> Result<ParsedSvgXmlNode, SvgParseError> {
2196    // Verify we can parse the XML
2197    let s = core::str::from_utf8(svg_file_data)
2198        .map_err(|_| SvgParseError::NotAnUtf8Str)?;
2199    let _nodes = crate::xml::parse_xml_string(s)
2200        .map_err(|_| SvgParseError::NoParserAvailable)?;
2201    Ok(ParsedSvgXmlNode { run_destructor: true })
2202}
2203
2204/// Parsed SVG document. Stores the raw SVG bytes for deferred rendering.
2205#[derive(Clone)]
2206#[repr(C)]
2207pub struct ParsedSvg {
2208    pub svg_data: azul_css::U8Vec,
2209    pub run_destructor: bool,
2210}
2211
2212impl Drop for ParsedSvg {
2213    fn drop(&mut self) { self.run_destructor = false; }
2214}
2215
2216impl_result!(
2217    ParsedSvg,
2218    SvgParseError,
2219    ResultParsedSvgSvgParseError,
2220    copy = false,
2221    [Debug, Clone]
2222);
2223
2224impl From<ParsedSvg> for azul_core::svg::Svg {
2225    fn from(_parsed: ParsedSvg) -> Self {
2226        Self {
2227            tree: core::ptr::null(),
2228            run_destructor: false,
2229        }
2230    }
2231}
2232
2233impl fmt::Debug for ParsedSvg {
2234    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2235        write!(f, "ParsedSvg({} bytes)", self.svg_data.as_ref().len())
2236    }
2237}
2238
2239impl ParsedSvg {
2240    pub fn from_string(
2241        svg_string: &str,
2242        parse_options: SvgParseOptions,
2243    ) -> Result<Self, SvgParseError> {
2244        svg_parse(svg_string.as_bytes(), parse_options)
2245    }
2246
2247    pub fn from_bytes(
2248        svg_bytes: &[u8],
2249        parse_options: SvgParseOptions,
2250    ) -> Result<Self, SvgParseError> {
2251        svg_parse(svg_bytes, parse_options)
2252    }
2253
2254    pub fn get_root(&self) -> ParsedSvgXmlNode {
2255        svg_root(self)
2256    }
2257
2258    pub fn render(&self, options: SvgRenderOptions) -> Option<RawImage> {
2259        svg_render(self, options)
2260    }
2261
2262    pub fn to_string(&self, _options: SvgXmlOptions) -> String {
2263        String::from_utf8_lossy(self.svg_data.as_ref()).into_owned()
2264    }
2265}
2266
2267/// Parse SVG data into a ParsedSvg (validates XML, stores bytes for deferred rendering).
2268pub fn svg_parse(
2269    svg_file_data: &[u8],
2270    _options: SvgParseOptions,
2271) -> Result<ParsedSvg, SvgParseError> {
2272    // Validate that it's parseable XML
2273    let s = core::str::from_utf8(svg_file_data)
2274        .map_err(|_| SvgParseError::NotAnUtf8Str)?;
2275    let _nodes = crate::xml::parse_xml_string(s)
2276        .map_err(|_| SvgParseError::NoParserAvailable)?;
2277    Ok(ParsedSvg {
2278        svg_data: svg_file_data.to_vec().into(),
2279        run_destructor: true,
2280    })
2281}
2282
2283pub fn svg_root(s: &ParsedSvg) -> ParsedSvgXmlNode {
2284    ParsedSvgXmlNode { run_destructor: true }
2285}
2286
2287/// Render a ParsedSvg to a RawImage using the agg-rust pipeline.
2288pub fn svg_render(s: &ParsedSvg, options: SvgRenderOptions) -> Option<RawImage> {
2289    use azul_core::resources::RawImageData;
2290
2291    let (target_width, target_height) = match options.target_size.as_ref() {
2292        Some(s) => (s.width as u32, s.height as u32),
2293        None => DEFAULT_SVG_RENDER_SIZE,
2294    };
2295
2296    if target_width == 0 || target_height == 0 {
2297        return None;
2298    }
2299
2300    let png_data = crate::cpurender::render_svg_to_png(s.svg_data.as_ref(), target_width, target_height).ok()?;
2301
2302    // Decode PNG back to raw RGBA (TODO: render_svg_to_rgba to avoid PNG round-trip)
2303    let decoder = png::Decoder::new(std::io::Cursor::new(&png_data));
2304    let mut reader = decoder.read_info().ok()?;
2305    let mut buf = vec![0u8; reader.output_buffer_size()?];
2306    let info = reader.next_frame(&mut buf).ok()?;
2307    buf.truncate(info.buffer_size());
2308
2309    Some(RawImage {
2310        tag: Vec::new().into(),
2311        pixels: RawImageData::U8(buf.into()),
2312        width: info.width as usize,
2313        height: info.height as usize,
2314        premultiplied_alpha: false,
2315        data_format: RawImageFormat::RGBA8,
2316    })
2317}
2318
2319pub fn svg_to_string(s: &ParsedSvg, _options: SvgXmlOptions) -> String {
2320    String::from_utf8_lossy(s.svg_data.as_ref()).into_owned()
2321}
2322
2323// ============================================================================
2324// Lyon tessellation (kept — no usvg dependency)
2325// ============================================================================
2326
2327/// Trait for tessellating SvgMultiPolygon shapes
2328pub trait SvgMultiPolygonTessellation {
2329    fn tessellate_fill(&self, fill_style: SvgFillStyle) -> TessellatedSvgNode;
2330    fn tessellate_stroke(&self, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode;
2331}
2332
2333impl SvgMultiPolygonTessellation for SvgMultiPolygon {
2334    fn tessellate_fill(&self, fill_style: SvgFillStyle) -> TessellatedSvgNode {
2335        tessellate_multi_polygon_fill(self, fill_style)
2336    }
2337    fn tessellate_stroke(&self, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
2338        tessellate_multi_polygon_stroke(self, stroke_style)
2339    }
2340}