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

            
13
use alloc::boxed::Box;
14
use core::fmt;
15

            
16
#[cfg(not(feature = "svg"))]
17
pub use azul_core::svg::*;
18
// re-export everything except for Svg and SvgXmlNode
19
#[cfg(feature = "svg")]
20
pub 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
};
78
use azul_core::{
79
    geom::PhysicalSizeU32,
80
    gl::{GlContextPtr, Texture},
81
    resources::{RawImage, RawImageFormat},
82
};
83
#[cfg(feature = "svg")]
84
pub use azul_css::props::basic::animation::{
85
    SvgCubicCurve, SvgPoint, SvgQuadraticCurve, SvgRect, SvgVector,
86
};
87
use 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")]
93
use 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

            
103
use crate::xml::XmlError;
104

            
105
#[cfg(feature = "svg")]
106
extern crate agg_rust;
107

            
108
use azul_core::gl::GL_RESTART_INDEX;
109

            
110
/// Kappa constant for approximating a circle with 4 cubic Bezier curves: 4/3 * (sqrt(2) - 1).
111
const CIRCLE_BEZIER_KAPPA: f64 = 0.5522847498;
112

            
113
/// Default render size (width, height) when no target size is specified for SVG rendering.
114
const DEFAULT_SVG_RENDER_SIZE: (u32, u32) = (800, 600);
115

            
116
#[cfg(feature = "svg")]
117
70
fn translate_svg_line_join(e: SvgLineJoin) -> lyon::tessellation::LineJoin {
118
    use azul_core::svg::SvgLineJoin::*;
119
70
    match e {
120
70
        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
70
}
126

            
127
#[cfg(feature = "svg")]
128
140
fn translate_svg_line_cap(e: SvgLineCap) -> lyon::tessellation::LineCap {
129
    use azul_core::svg::SvgLineCap::*;
130
140
    match e {
131
140
        Butt => lyon::tessellation::LineCap::Butt,
132
        Square => lyon::tessellation::LineCap::Square,
133
        Round => lyon::tessellation::LineCap::Round,
134
    }
135
140
}
136

            
137
#[cfg(feature = "svg")]
138
70
fn translate_svg_stroke_style(e: SvgStrokeStyle) -> lyon::tessellation::StrokeOptions {
139
70
    lyon::tessellation::StrokeOptions::tolerance(e.tolerance)
140
70
        .with_start_cap(translate_svg_line_cap(e.start_cap))
141
70
        .with_end_cap(translate_svg_line_cap(e.end_cap))
142
70
        .with_line_join(translate_svg_line_join(e.line_join))
143
70
        .with_line_width(e.line_width)
144
70
        .with_miter_limit(e.miter_limit)
145
    // TODO: e.apply_line_width - not present in lyon 17!
146
70
}
147

            
148
#[cfg(feature = "svg")]
149
35
fn svg_multipolygon_to_lyon_path(polygon: &SvgMultiPolygon) -> Path {
150
35
    let mut builder = Path::builder();
151

            
152
35
    for p in polygon.rings.as_ref().iter() {
153
35
        if p.items.as_ref().is_empty() {
154
            continue;
155
35
        }
156

            
157
35
        let start_item = p.items.as_ref()[0];
158
35
        let first_point = Point2D::new(start_item.get_start().x, start_item.get_start().y);
159

            
160
35
        builder.begin(first_point);
161

            
162
140
        for q in p.items.as_ref().iter().rev()
163
        /* NOTE: REVERSE ITERATOR */
164
        {
165
140
            match q {
166
140
                SvgPathElement::Line(l) => {
167
140
                    builder.line_to(Point2D::new(l.end.x, l.end.y));
168
140
                }
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
35
        builder.end(p.is_closed());
186
    }
187

            
188
35
    builder.build()
189
35
}
190

            
191
#[cfg(feature = "svg")]
192
35
fn svg_multi_shape_to_lyon_path(polygon: &[SvgSimpleNode]) -> Path {
193
    use lyon::{
194
        geom::Box2D,
195
        path::{traits::PathBuilder, Winding},
196
    };
197

            
198
35
    let mut builder = Path::builder();
199

            
200
70
    for p in polygon.iter() {
201
70
        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
35
            SvgSimpleNode::Circle(c) => {
238
35
                builder.add_circle(
239
35
                    Point::new(c.center_x, c.center_y),
240
35
                    c.radius,
241
35
                    Winding::Positive,
242
35
                );
243
35
            }
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
35
            SvgSimpleNode::Rect(c) => {
252
35
                builder.add_rectangle(
253
35
                    &Box2D::from_origin_and_size(
254
35
                        Point::new(c.x, c.y),
255
35
                        Size2D::new(c.width, c.height),
256
35
                    ),
257
35
                    Winding::Positive,
258
35
                );
259
35
            }
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
35
    builder.build()
273
35
}
274

            
275
pub 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)
334
pub fn raw_line_intersection_byval(p: &SvgLine, q: SvgLine) -> Option<SvgPoint> {
335
    raw_line_intersection(p, &q)
336
}
337

            
338
pub 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

            
562
fn 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

            
577
fn 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"
593
pub 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")]
644
140
fn svg_path_to_lyon_path_events(path: &SvgPath) -> Path {
645
140
    let mut builder = Path::builder();
646

            
647
140
    if !path.items.as_ref().is_empty() {
648
105
        let start_item = path.items.as_ref()[0];
649
105
        let first_point = Point2D::new(start_item.get_start().x, start_item.get_start().y);
650

            
651
105
        builder.begin(first_point);
652

            
653
385
        for p in path.items.as_ref().iter() {
654
385
            match p {
655
385
                SvgPathElement::Line(l) => {
656
385
                    builder.line_to(Point2D::new(l.end.x, l.end.y));
657
385
                }
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
105
        builder.end(path.is_closed());
675
35
    }
676

            
677
140
    builder.build()
678
140
}
679

            
680
#[cfg(feature = "svg")]
681
#[inline]
682
490
fn vertex_buffers_to_tessellated_cpu_node(v: VertexBuffers<SvgVertex, u32>) -> TessellatedSvgNode {
683
490
    TessellatedSvgNode {
684
490
        vertices: v.vertices.into(),
685
490
        indices: v.indices.into(),
686
490
    }
687
490
}
688

            
689
#[cfg(feature = "svg")]
690
35
pub fn tessellate_multi_polygon_fill(
691
35
    polygon: &SvgMultiPolygon,
692
35
    fill_style: SvgFillStyle,
693
35
) -> TessellatedSvgNode {
694
35
    let polygon = svg_multipolygon_to_lyon_path(polygon);
695

            
696
35
    let mut geometry = VertexBuffers::new();
697
35
    let mut tessellator = FillTessellator::new();
698

            
699
35
    let tess_result = tessellator.tessellate_path(
700
35
        &polygon,
701
35
        &FillOptions::tolerance(fill_style.tolerance),
702
140
        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
703
140
            let xy_arr = vertex.position();
704
140
            SvgVertex {
705
140
                x: xy_arr.x,
706
140
                y: xy_arr.y,
707
140
            }
708
140
        }),
709
    );
710

            
711
35
    if let Err(_) = tess_result {
712
        TessellatedSvgNode::empty()
713
    } else {
714
35
        vertex_buffers_to_tessellated_cpu_node(geometry)
715
    }
716
35
}
717

            
718
#[cfg(not(feature = "svg"))]
719
pub fn tessellate_multi_polygon_fill(
720
    polygon: &SvgMultiPolygon,
721
    fill_style: SvgFillStyle,
722
) -> TessellatedSvgNode {
723
    TessellatedSvgNode::default()
724
}
725

            
726
#[cfg(feature = "svg")]
727
35
pub fn tessellate_multi_shape_fill(
728
35
    ms: &[SvgSimpleNode],
729
35
    fill_style: SvgFillStyle,
730
35
) -> TessellatedSvgNode {
731
35
    let polygon = svg_multi_shape_to_lyon_path(ms);
732

            
733
35
    let mut geometry = VertexBuffers::new();
734
35
    let mut tessellator = FillTessellator::new();
735

            
736
35
    let tess_result = tessellator.tessellate_path(
737
35
        &polygon,
738
35
        &FillOptions::tolerance(fill_style.tolerance),
739
1540
        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
740
1540
            let xy_arr = vertex.position();
741
1540
            SvgVertex {
742
1540
                x: xy_arr.x,
743
1540
                y: xy_arr.y,
744
1540
            }
745
1540
        }),
746
    );
747

            
748
35
    if let Err(_) = tess_result {
749
        TessellatedSvgNode::empty()
750
    } else {
751
35
        vertex_buffers_to_tessellated_cpu_node(geometry)
752
    }
753
35
}
754

            
755
#[cfg(not(feature = "svg"))]
756
pub fn tessellate_multi_shape_fill(
757
    ms: &[SvgSimpleNode],
758
    fill_style: SvgFillStyle,
759
) -> TessellatedSvgNode {
760
    TessellatedSvgNode::default()
761
}
762

            
763
pub 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")]
799
pub 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"))]
819
pub 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")]
829
pub 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"))]
851
pub 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")]
861
pub 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"))]
891
pub fn tessellate_multi_shape_stroke(
892
    polygon: &[SvgSimpleNode],
893
    stroke_style: SvgStrokeStyle,
894
) -> TessellatedSvgNode {
895
    TessellatedSvgNode::default()
896
}
897

            
898
#[cfg(feature = "svg")]
899
pub 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"))]
929
pub fn tessellate_multi_polygon_stroke(
930
    polygon: &SvgMultiPolygon,
931
    stroke_style: SvgStrokeStyle,
932
) -> TessellatedSvgNode {
933
    TessellatedSvgNode::default()
934
}
935

            
936
#[cfg(feature = "svg")]
937
105
pub fn tessellate_path_fill(path: &SvgPath, fill_style: SvgFillStyle) -> TessellatedSvgNode {
938
105
    let polygon = svg_path_to_lyon_path_events(path);
939

            
940
105
    let mut geometry = VertexBuffers::new();
941
105
    let mut tessellator = FillTessellator::new();
942

            
943
105
    let tess_result = tessellator.tessellate_path(
944
105
        &polygon,
945
105
        &FillOptions::tolerance(fill_style.tolerance),
946
245
        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
947
245
            let xy_arr = vertex.position();
948
245
            SvgVertex {
949
245
                x: xy_arr.x,
950
245
                y: xy_arr.y,
951
245
            }
952
245
        }),
953
    );
954

            
955
105
    if let Err(_) = tess_result {
956
        TessellatedSvgNode::empty()
957
    } else {
958
105
        vertex_buffers_to_tessellated_cpu_node(geometry)
959
    }
960
105
}
961

            
962
#[cfg(not(feature = "svg"))]
963
pub fn tessellate_path_fill(path: &SvgPath, fill_style: SvgFillStyle) -> TessellatedSvgNode {
964
    TessellatedSvgNode::default()
965
}
966

            
967
#[cfg(feature = "svg")]
968
35
pub fn tessellate_path_stroke(path: &SvgPath, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
969
35
    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
970
35
    let polygon = svg_path_to_lyon_path_events(path);
971

            
972
35
    let mut stroke_geometry = VertexBuffers::new();
973
35
    let mut stroke_tess = StrokeTessellator::new();
974

            
975
35
    let tess_result = stroke_tess.tessellate_path(
976
35
        &polygon,
977
35
        &stroke_options,
978
350
        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
979
350
            let xy_arr = vertex.position();
980
350
            SvgVertex {
981
350
                x: xy_arr.x,
982
350
                y: xy_arr.y,
983
350
            }
984
350
        }),
985
    );
986

            
987
35
    if let Err(_) = tess_result {
988
        TessellatedSvgNode::empty()
989
    } else {
990
35
        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
991
    }
992
35
}
993

            
994
#[cfg(not(feature = "svg"))]
995
pub fn tessellate_path_stroke(path: &SvgPath, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
996
    TessellatedSvgNode::default()
997
}
998

            
999
#[cfg(feature = "svg")]
70
pub fn tessellate_circle_fill(c: &SvgCircle, fill_style: SvgFillStyle) -> TessellatedSvgNode {
70
    let center = Point2D::new(c.center_x, c.center_y);
70
    let mut geometry = VertexBuffers::new();
70
    let mut tesselator = FillTessellator::new();
70
    let tess_result = tesselator.tessellate_circle(
70
        center,
70
        c.radius,
70
        &FillOptions::tolerance(fill_style.tolerance),
1260
        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
1260
            let xy_arr = vertex.position();
1260
            SvgVertex {
1260
                x: xy_arr.x,
1260
                y: xy_arr.y,
1260
            }
1260
        }),
    );
70
    if let Err(_) = tess_result {
        TessellatedSvgNode::empty()
    } else {
70
        vertex_buffers_to_tessellated_cpu_node(geometry)
    }
70
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_circle_fill(c: &SvgCircle, fill_style: SvgFillStyle) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(feature = "svg")]
pub fn tessellate_circle_stroke(c: &SvgCircle, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
    let center = Point2D::new(c.center_x, c.center_y);
    let mut stroke_geometry = VertexBuffers::new();
    let mut tesselator = StrokeTessellator::new();
    let tess_result = tesselator.tessellate_circle(
        center,
        c.radius,
        &stroke_options,
        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
            let xy_arr = vertex.position();
            SvgVertex {
                x: xy_arr.x,
                y: xy_arr.y,
            }
        }),
    );
    if let Err(_) = tess_result {
        TessellatedSvgNode::empty()
    } else {
        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
    }
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_circle_stroke(c: &SvgCircle, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
// TODO: radii not respected on latest version of lyon
#[cfg(feature = "svg")]
210
fn get_radii(r: &SvgRect) -> lyon::geom::Box2D<f32> {
210
    let rect = lyon::geom::Box2D::from_origin_and_size(
210
        Point2D::new(r.x, r.y),
210
        Size2D::new(r.width, r.height),
    );
    /*
    let radii = BorderRadii {
        top_left: r.radius_top_left,
        top_right: r.radius_top_right,
        bottom_left: r.radius_bottom_left,
        bottom_right: r.radius_bottom_right
    };*/
210
    rect
210
}
#[cfg(feature = "svg")]
175
pub fn tessellate_rect_fill(r: &SvgRect, fill_style: SvgFillStyle) -> TessellatedSvgNode {
175
    let rect = get_radii(&r);
175
    let mut geometry = VertexBuffers::new();
175
    let mut tesselator = FillTessellator::new();
175
    let tess_result = tesselator.tessellate_rectangle(
175
        &rect,
175
        &FillOptions::tolerance(fill_style.tolerance),
700
        &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
700
            let xy_arr = vertex.position();
700
            SvgVertex {
700
                x: xy_arr.x,
700
                y: xy_arr.y,
700
            }
700
        }),
    );
175
    if let Err(_) = tess_result {
        TessellatedSvgNode::empty()
    } else {
175
        vertex_buffers_to_tessellated_cpu_node(geometry)
    }
175
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_rect_fill(r: &SvgRect, fill_style: SvgFillStyle) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(feature = "svg")]
35
pub fn tessellate_rect_stroke(r: &SvgRect, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
35
    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
35
    let rect = get_radii(&r);
35
    let mut stroke_geometry = VertexBuffers::new();
35
    let mut tesselator = StrokeTessellator::new();
35
    let tess_result = tesselator.tessellate_rectangle(
35
        &rect,
35
        &stroke_options,
350
        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
350
            let xy_arr = vertex.position();
350
            SvgVertex {
350
                x: xy_arr.x,
350
                y: xy_arr.y,
350
            }
350
        }),
    );
35
    if let Err(_) = tess_result {
        TessellatedSvgNode::empty()
    } else {
35
        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
    }
35
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_rect_stroke(r: &SvgRect, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
/// Tessellate the path using lyon
#[cfg(feature = "svg")]
pub fn tessellate_styled_node(node: &SvgStyledNode) -> TessellatedSvgNode {
    match node.style {
        SvgStyle::Fill(fs) => tessellate_node_fill(&node.geometry, fs),
        SvgStyle::Stroke(ss) => tessellate_node_stroke(&node.geometry, ss),
    }
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_styled_node(node: &SvgStyledNode) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(feature = "svg")]
pub fn tessellate_line_stroke(
    svgline: &SvgLine,
    stroke_style: SvgStrokeStyle,
) -> TessellatedSvgNode {
    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
    let mut builder = Path::builder();
    builder.begin(Point2D::new(svgline.start.x, svgline.start.y));
    builder.line_to(Point2D::new(svgline.end.x, svgline.end.y));
    builder.end(/* closed */ false);
    let path = builder.build();
    let mut stroke_geometry = VertexBuffers::new();
    let mut stroke_tess = StrokeTessellator::new();
    let tess_result = stroke_tess.tessellate_path(
        &path,
        &stroke_options,
        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
            let xy_arr = vertex.position();
            SvgVertex {
                x: xy_arr.x,
                y: xy_arr.y,
            }
        }),
    );
    if let Err(_) = tess_result {
        TessellatedSvgNode::empty()
    } else {
        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
    }
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_line_stroke(
    svgline: &SvgLine,
    stroke_style: SvgStrokeStyle,
) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(feature = "svg")]
pub fn tessellate_cubiccurve_stroke(
    svgcubiccurve: &SvgCubicCurve,
    stroke_style: SvgStrokeStyle,
) -> TessellatedSvgNode {
    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
    let mut builder = Path::builder();
    builder.begin(Point2D::new(svgcubiccurve.start.x, svgcubiccurve.start.y));
    builder.cubic_bezier_to(
        Point2D::new(svgcubiccurve.ctrl_1.x, svgcubiccurve.ctrl_1.y),
        Point2D::new(svgcubiccurve.ctrl_2.x, svgcubiccurve.ctrl_2.y),
        Point2D::new(svgcubiccurve.end.x, svgcubiccurve.end.y),
    );
    builder.end(/* closed */ false);
    let path = builder.build();
    let mut stroke_geometry = VertexBuffers::new();
    let mut stroke_tess = StrokeTessellator::new();
    let tess_result = stroke_tess.tessellate_path(
        &path,
        &stroke_options,
        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
            let xy_arr = vertex.position();
            SvgVertex {
                x: xy_arr.x,
                y: xy_arr.y,
            }
        }),
    );
    if let Err(_) = tess_result {
        TessellatedSvgNode::empty()
    } else {
        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
    }
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_cubiccurve_stroke(
    svgline: &SvgCubicCurve,
    stroke_style: SvgStrokeStyle,
) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(feature = "svg")]
pub fn tessellate_quadraticcurve_stroke(
    svgquadraticcurve: &SvgQuadraticCurve,
    stroke_style: SvgStrokeStyle,
) -> TessellatedSvgNode {
    let stroke_options: StrokeOptions = translate_svg_stroke_style(stroke_style);
    let mut builder = Path::builder();
    builder.begin(Point2D::new(
        svgquadraticcurve.start.x,
        svgquadraticcurve.start.y,
    ));
    builder.quadratic_bezier_to(
        Point2D::new(svgquadraticcurve.ctrl.x, svgquadraticcurve.ctrl.y),
        Point2D::new(svgquadraticcurve.end.x, svgquadraticcurve.end.y),
    );
    builder.end(/* closed */ false);
    let path = builder.build();
    let mut stroke_geometry = VertexBuffers::new();
    let mut stroke_tess = StrokeTessellator::new();
    let tess_result = stroke_tess.tessellate_path(
        &path,
        &stroke_options,
        &mut BuffersBuilder::new(&mut stroke_geometry, |vertex: StrokeVertex| {
            let xy_arr = vertex.position();
            SvgVertex {
                x: xy_arr.x,
                y: xy_arr.y,
            }
        }),
    );
    if let Err(_) = tess_result {
        TessellatedSvgNode::empty()
    } else {
        vertex_buffers_to_tessellated_cpu_node(stroke_geometry)
    }
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_quadraticcurve_stroke(
    svgquadraticcurve: &SvgQuadraticCurve,
    stroke_style: SvgStrokeStyle,
) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(feature = "svg")]
pub fn tessellate_svgpathelement_stroke(
    svgpathelement: &SvgPathElement,
    stroke_style: SvgStrokeStyle,
) -> TessellatedSvgNode {
    match svgpathelement {
        SvgPathElement::Line(l) => tessellate_line_stroke(l, stroke_style),
        SvgPathElement::QuadraticCurve(l) => tessellate_quadraticcurve_stroke(l, stroke_style),
        SvgPathElement::CubicCurve(l) => tessellate_cubiccurve_stroke(l, stroke_style),
    }
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_svgpathelement_stroke(
    svgpathelement: &SvgPathElement,
    stroke_style: SvgStrokeStyle,
) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(feature = "svg")]
pub fn join_tessellated_nodes(nodes: &[TessellatedSvgNode]) -> TessellatedSvgNode {
    let mut index_offset = 0;
    // note: can not be parallelized!
    let all_index_offsets = nodes
        .as_ref()
        .iter()
        .map(|t| {
            let i = index_offset;
            index_offset += t.vertices.len();
            i
        })
        .collect::<Vec<_>>();
    let all_vertices = nodes
        .as_ref()
        .iter()
        .flat_map(|t| t.vertices.clone().into_library_owned_vec())
        .collect::<Vec<_>>();
    let all_indices = nodes
        .as_ref()
        .iter()
        .enumerate()
        .flat_map(|(buffer_index, t)| {
            // since the vertex buffers are now joined,
            // offset the indices by the vertex buffers lengths
            // encountered so far
            let vertex_buffer_offset: u32 = all_index_offsets
                .get(buffer_index)
                .copied()
                .unwrap_or(0)
                .min(core::u32::MAX as usize) as u32;
            let mut indices = t.indices.clone().into_library_owned_vec();
            if vertex_buffer_offset != 0 {
                indices.iter_mut().for_each(|i| {
                    if *i != GL_RESTART_INDEX {
                        *i += vertex_buffer_offset;
                    }
                });
            }
            indices.push(GL_RESTART_INDEX);
            indices
        })
        .collect::<Vec<_>>();
    TessellatedSvgNode {
        vertices: all_vertices.into(),
        indices: all_indices.into(),
    }
}
#[cfg(feature = "svg")]
pub fn join_tessellated_colored_nodes(
    nodes: &[TessellatedColoredSvgNode],
) -> TessellatedColoredSvgNode {
    let mut index_offset = 0;
    // note: can not be parallelized!
    let all_index_offsets = nodes
        .as_ref()
        .iter()
        .map(|t| {
            let i = index_offset;
            index_offset += t.vertices.len();
            i
        })
        .collect::<Vec<_>>();
    let all_vertices = nodes
        .as_ref()
        .iter()
        .flat_map(|t| t.vertices.clone().into_library_owned_vec())
        .collect::<Vec<_>>();
    let all_indices = nodes
        .as_ref()
        .iter()
        .enumerate()
        .flat_map(|(buffer_index, t)| {
            // since the vertex buffers are now joined,
            // offset the indices by the vertex buffers lengths
            // encountered so far
            let vertex_buffer_offset: u32 = all_index_offsets
                .get(buffer_index)
                .copied()
                .unwrap_or(0)
                .min(core::u32::MAX as usize) as u32;
            let mut indices = t.indices.clone().into_library_owned_vec();
            if vertex_buffer_offset != 0 {
                indices.iter_mut().for_each(|i| {
                    if *i != GL_RESTART_INDEX {
                        *i += vertex_buffer_offset;
                    }
                });
            }
            indices.push(GL_RESTART_INDEX);
            indices
        })
        .collect::<Vec<_>>();
    TessellatedColoredSvgNode {
        vertices: all_vertices.into(),
        indices: all_indices.into(),
    }
}
#[cfg(not(feature = "svg"))]
pub fn join_tessellated_nodes(nodes: &[TessellatedSvgNode]) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(not(feature = "svg"))]
pub fn join_tessellated_colored_nodes(
    nodes: &[TessellatedColoredSvgNode],
) -> TessellatedColoredSvgNode {
    TessellatedColoredSvgNode::default()
}
#[cfg(feature = "svg")]
pub fn tessellate_node_fill(node: &SvgNode, fs: SvgFillStyle) -> TessellatedSvgNode {
    match &node {
        SvgNode::MultiPolygonCollection(ref mpc) => {
            let tessellated_multipolygons = mpc
                .as_ref()
                .iter()
                .map(|mp| tessellate_multi_polygon_fill(mp, fs))
                .collect::<Vec<_>>();
            join_tessellated_nodes(&tessellated_multipolygons)
        }
        SvgNode::MultiPolygon(ref mp) => tessellate_multi_polygon_fill(mp, fs),
        SvgNode::Path(ref p) => tessellate_path_fill(p, fs),
        SvgNode::Circle(ref c) => tessellate_circle_fill(c, fs),
        SvgNode::Rect(ref r) => tessellate_rect_fill(r, fs),
        SvgNode::MultiShape(ref r) => tessellate_multi_shape_fill(r.as_ref(), fs),
    }
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_node_fill(node: &SvgNode, fs: SvgFillStyle) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
#[cfg(feature = "svg")]
pub fn tessellate_node_stroke(node: &SvgNode, ss: SvgStrokeStyle) -> TessellatedSvgNode {
    match &node {
        SvgNode::MultiPolygonCollection(ref mpc) => {
            let tessellated_multipolygons = mpc
                .as_ref()
                .iter()
                .map(|mp| tessellate_multi_polygon_stroke(mp, ss))
                .collect::<Vec<_>>();
            join_tessellated_nodes(&tessellated_multipolygons)
        }
        SvgNode::MultiPolygon(ref mp) => tessellate_multi_polygon_stroke(mp, ss),
        SvgNode::Path(ref p) => tessellate_path_stroke(p, ss),
        SvgNode::Circle(ref c) => tessellate_circle_stroke(c, ss),
        SvgNode::Rect(ref r) => tessellate_rect_stroke(r, ss),
        SvgNode::MultiShape(ms) => tessellate_multi_shape_stroke(ms.as_ref(), ss),
    }
}
#[cfg(not(feature = "svg"))]
pub fn tessellate_node_stroke(node: &SvgNode, ss: SvgStrokeStyle) -> TessellatedSvgNode {
    TessellatedSvgNode::default()
}
// NOTE: This is a separate step both in order to reuse GPU textures
// and also because texture allocation is heavy and can be offloaded to a different thread
pub fn allocate_clipmask_texture(
    gl_context: GlContextPtr,
    size: PhysicalSizeU32,
    _background: ColorU,
) -> Texture {
    use azul_core::gl::TextureFlags;
    let textures = gl_context.gen_textures(1);
    let texture_id = textures.get(0).unwrap();
    Texture::create(
        *texture_id,
        TextureFlags {
            is_opaque: true,
            is_video_texture: false,
        },
        size,
        ColorU::TRANSPARENT,
        gl_context,
        RawImageFormat::R8,
    )
}
/// Applies an FXAA filter to the texture using the pre-compiled FXAA shader.
///
/// Renders a fullscreen quad with the FXAA fragment shader, reading from
/// the input texture and writing to a temporary texture, then swaps the
/// texture IDs so the caller gets the post-FXAA result.
pub fn apply_fxaa(texture: &mut Texture) -> Option<()> {
    apply_fxaa_with_config(texture, azul_core::gl_fxaa::FxaaConfig::enabled())
}
/// Applies FXAA with custom configuration parameters.
pub fn apply_fxaa_with_config(
    texture: &mut Texture,
    config: azul_core::gl_fxaa::FxaaConfig,
) -> Option<()> {
    use std::mem;
    use azul_core::gl::{GLuint, GlVoidPtrConst, VertexAttributeType};
    use gl_context_loader::gl;
    if !config.enabled || texture.size.width == 0 || texture.size.height == 0 {
        return Some(());
    }
    // FXAA only works on RGBA8 textures
    if texture.format != RawImageFormat::RGBA8 {
        return Some(());
    }
    let texture_size = texture.size;
    let gl_context = &texture.gl_context;
    let fxaa_shader = gl_context.get_fxaa_shader();
    let w = texture_size.width as f32;
    let h = texture_size.height as f32;
    // Save GL state
    let mut current_program = [0_i32];
    let mut current_framebuffers = [0_i32];
    let mut current_texture_2d = [0_i32];
    let mut current_vertex_array_object = [0_i32];
    let mut current_vertex_buffer = [0_i32];
    let mut current_index_buffer = [0_i32];
    let mut current_active_texture = [0_i32];
    let mut current_blend_enabled = [0_u8];
    let mut current_viewport = [0_i32; 4];
    gl_context.get_integer_v(gl::CURRENT_PROGRAM, (&mut current_program[..]).into());
    gl_context.get_integer_v(gl::FRAMEBUFFER, (&mut current_framebuffers[..]).into());
    gl_context.get_integer_v(gl::TEXTURE_2D, (&mut current_texture_2d[..]).into());
    gl_context.get_integer_v(
        gl::VERTEX_ARRAY_BINDING,
        (&mut current_vertex_array_object[..]).into(),
    );
    gl_context.get_integer_v(
        gl::ARRAY_BUFFER_BINDING,
        (&mut current_vertex_buffer[..]).into(),
    );
    gl_context.get_integer_v(
        gl::ELEMENT_ARRAY_BUFFER_BINDING,
        (&mut current_index_buffer[..]).into(),
    );
    gl_context.get_integer_v(
        gl::ACTIVE_TEXTURE,
        (&mut current_active_texture[..]).into(),
    );
    gl_context.get_boolean_v(gl::BLEND, (&mut current_blend_enabled[..]).into());
    gl_context.get_integer_v(gl::VIEWPORT, (&mut current_viewport[..]).into());
    // 1. Create temporary output texture
    let temp_textures = gl_context.gen_textures(1);
    let temp_tex_id = *temp_textures.get(0)?;
    gl_context.bind_texture(gl::TEXTURE_2D, temp_tex_id);
    gl_context.tex_image_2d(
        gl::TEXTURE_2D,
        0,
        gl::RGBA as i32,
        texture_size.width as i32,
        texture_size.height as i32,
        0,
        gl::RGBA,
        gl::UNSIGNED_BYTE,
        None.into(),
    );
    gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
    gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
    gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
    gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
    // 2. Create FBO targeting the temp texture
    let fbos = gl_context.gen_framebuffers(1);
    let fbo_id = *fbos.get(0)?;
    gl_context.bind_framebuffer(gl::FRAMEBUFFER, fbo_id);
    gl_context.framebuffer_texture_2d(
        gl::FRAMEBUFFER,
        gl::COLOR_ATTACHMENT0,
        gl::TEXTURE_2D,
        temp_tex_id,
        0,
    );
    gl_context.draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
    debug_assert!(
        gl_context.check_frame_buffer_status(gl::FRAMEBUFFER) == gl::FRAMEBUFFER_COMPLETE
    );
    // 3. Create fullscreen quad VAO/VBO/IBO
    // Vertices in [-1, 1] range; the FXAA vertex shader converts to [0, 1] UVs
    let quad_vertices: [f32; 8] = [
        -1.0, -1.0, // bottom-left
         1.0, -1.0, // bottom-right
         1.0,  1.0, // top-right
        -1.0,  1.0, // top-left
    ];
    let quad_indices: [u32; 6] = [0, 1, 2, 0, 2, 3];
    let vaos = gl_context.gen_vertex_arrays(1);
    let vao_id = *vaos.get(0)?;
    gl_context.bind_vertex_array(vao_id);
    let vbos = gl_context.gen_buffers(1);
    let vbo_id = *vbos.get(0)?;
    gl_context.bind_buffer(gl::ARRAY_BUFFER, vbo_id);
    gl_context.buffer_data_untyped(
        gl::ARRAY_BUFFER,
        (mem::size_of::<f32>() * quad_vertices.len()) as isize,
        GlVoidPtrConst {
            ptr: quad_vertices.as_ptr() as *const std::ffi::c_void,
            run_destructor: true,
        },
        gl::STATIC_DRAW,
    );
    let ibos = gl_context.gen_buffers(1);
    let ibo_id = *ibos.get(0)?;
    gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, ibo_id);
    gl_context.buffer_data_untyped(
        gl::ELEMENT_ARRAY_BUFFER,
        (mem::size_of::<u32>() * quad_indices.len()) as isize,
        GlVoidPtrConst {
            ptr: quad_indices.as_ptr() as *const std::ffi::c_void,
            run_destructor: true,
        },
        gl::STATIC_DRAW,
    );
    // Set up vertex attribute for vAttrXY (location 0, bound at shader compilation)
    let vertex_type = VertexAttributeType::Float;
    let stride = vertex_type.get_mem_size() * 2; // 2 floats per vertex (x, y)
    gl_context.vertex_attrib_pointer(0, 2, vertex_type.get_gl_id(), false, stride as i32, 0);
    gl_context.enable_vertex_attrib_array(0);
    // 4. Render FXAA pass
    gl_context.use_program(fxaa_shader);
    gl_context.viewport(0, 0, texture_size.width as i32, texture_size.height as i32);
    gl_context.disable(gl::BLEND); // FXAA reads exact colors, blending would corrupt output
    // Bind input texture to GL_TEXTURE0
    gl_context.active_texture(gl::TEXTURE0);
    gl_context.bind_texture(gl::TEXTURE_2D, texture.texture_id);
    // Set uniforms
    let u_texture = gl_context.get_uniform_location(fxaa_shader, "uTexture");
    gl_context.uniform_1i(u_texture, 0);
    let u_texel_size = gl_context.get_uniform_location(fxaa_shader, "uTexelSize");
    gl_context.uniform_2f(u_texel_size, 1.0 / w, 1.0 / h);
    let u_edge_threshold =
        gl_context.get_uniform_location(fxaa_shader, "uEdgeThreshold");
    gl_context.uniform_1f(u_edge_threshold, config.edge_threshold);
    let u_edge_threshold_min =
        gl_context.get_uniform_location(fxaa_shader, "uEdgeThresholdMin");
    gl_context.uniform_1f(u_edge_threshold_min, config.edge_threshold_min);
    // Draw the fullscreen quad
    gl_context.draw_elements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, 0);
    // 5. Swap texture IDs: the temp texture now has the FXAA result.
    // We swap so the caller's texture_id points to the anti-aliased result,
    // and the old texture_id gets cleaned up.
    let old_texture_id = texture.texture_id;
    texture.texture_id = temp_tex_id;
    // Delete the old texture (which was the input)
    gl_context.delete_textures((&[old_texture_id])[..].into());
    // 6. Cleanup: delete FBO, quad buffers
    gl_context.delete_framebuffers((&[fbo_id])[..].into());
    gl_context.disable_vertex_attrib_array(0);
    gl_context.delete_vertex_arrays((&[vao_id])[..].into());
    gl_context.delete_buffers((&[vbo_id, ibo_id])[..].into());
    // Restore GL state
    gl_context.bind_framebuffer(gl::FRAMEBUFFER, current_framebuffers[0] as u32);
    gl_context.bind_texture(gl::TEXTURE_2D, current_texture_2d[0] as u32);
    gl_context.bind_vertex_array(current_vertex_array_object[0] as u32);
    gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, current_index_buffer[0] as u32);
    gl_context.bind_buffer(gl::ARRAY_BUFFER, current_vertex_buffer[0] as u32);
    gl_context.use_program(current_program[0] as u32);
    gl_context.active_texture(current_active_texture[0] as u32);
    gl_context.viewport(
        current_viewport[0],
        current_viewport[1],
        current_viewport[2],
        current_viewport[3],
    );
    if u32::from(current_blend_enabled[0]) == gl::TRUE {
        gl_context.enable(gl::BLEND);
    }
    Some(())
}
#[cfg(feature = "svg")]
pub fn render_node_clipmask_cpu(
    image: &mut RawImage,
    node: &SvgNode,
    style: SvgStyle,
) -> Option<()> {
    use azul_core::resources::RawImageData;
    use agg_rust::{
        basics::{FillingRule, VertexSource, PATH_FLAGS_NONE},
        path_storage::PathStorage,
        color::Rgba8,
        conv_stroke::ConvStroke,
        conv_transform::ConvTransform,
        math_stroke::{LineCap, LineJoin},
        pixfmt_rgba::{PixfmtRgba32, PixelFormat},
        rasterizer_scanline_aa::RasterizerScanlineAa,
        renderer_base::RendererBase,
        renderer_scanline::render_scanlines_aa_solid,
        rendering_buffer::RowAccessor,
        scanline_u::ScanlineU8,
        trans_affine::TransAffine,
    };
    fn agg_translate_node(node: &SvgNode) -> Option<PathStorage> {
        macro_rules! build_path {
            ($path:expr, $p:expr) => {{
                if $p.items.as_ref().is_empty() {
                    return None;
                }
                let start = $p.items.as_ref()[0].get_start();
                $path.move_to(start.x as f64, start.y as f64);
                for path_element in $p.items.as_ref() {
                    match path_element {
                        SvgPathElement::Line(l) => {
                            $path.line_to(l.end.x as f64, l.end.y as f64);
                        }
                        SvgPathElement::QuadraticCurve(qc) => {
                            $path.curve3(
                                qc.ctrl.x as f64, qc.ctrl.y as f64,
                                qc.end.x as f64, qc.end.y as f64,
                            );
                        }
                        SvgPathElement::CubicCurve(cc) => {
                            $path.curve4(
                                cc.ctrl_1.x as f64, cc.ctrl_1.y as f64,
                                cc.ctrl_2.x as f64, cc.ctrl_2.y as f64,
                                cc.end.x as f64, cc.end.y as f64,
                            );
                        }
                    }
                }
                if $p.is_closed() {
                    $path.close_polygon(PATH_FLAGS_NONE);
                }
            }};
        }
        let mut path = PathStorage::new();
        match node {
            SvgNode::MultiPolygonCollection(mpc) => {
                for mp in mpc.iter() {
                    for p in mp.rings.iter() {
                        build_path!(path, p);
                    }
                }
            }
            SvgNode::MultiPolygon(mp) => {
                for p in mp.rings.iter() {
                    build_path!(path, p);
                }
            }
            SvgNode::Path(p) => {
                build_path!(path, p);
            }
            SvgNode::Circle(c) => {
                // Approximate circle with 4 cubic beziers
                let cx = c.center_x as f64;
                let cy = c.center_y as f64;
                let r = c.radius as f64;
                let k = CIRCLE_BEZIER_KAPPA;
                let kr = k * r;
                path.move_to(cx + r, cy);
                path.curve4(cx + r, cy + kr, cx + kr, cy + r, cx, cy + r);
                path.curve4(cx - kr, cy + r, cx - r, cy + kr, cx - r, cy);
                path.curve4(cx - r, cy - kr, cx - kr, cy - r, cx, cy - r);
                path.curve4(cx + kr, cy - r, cx + r, cy - kr, cx + r, cy);
                path.close_polygon(PATH_FLAGS_NONE);
            }
            SvgNode::Rect(r) => {
                let x = r.x as f64;
                let y = r.y as f64;
                let w = r.width as f64;
                let h = r.height as f64;
                path.move_to(x, y);
                path.line_to(x + w, y);
                path.line_to(x + w, y + h);
                path.line_to(x, y + h);
                path.close_polygon(PATH_FLAGS_NONE);
            }
            SvgNode::MultiShape(ms) => {
                for p in ms.as_ref() {
                    match p {
                        SvgSimpleNode::Path(p) => {
                            build_path!(path, p);
                        }
                        SvgSimpleNode::Rect(r) => {
                            let x = r.x as f64;
                            let y = r.y as f64;
                            let w = r.width as f64;
                            let h = r.height as f64;
                            path.move_to(x, y);
                            path.line_to(x + w, y);
                            path.line_to(x + w, y + h);
                            path.line_to(x, y + h);
                            path.close_polygon(PATH_FLAGS_NONE);
                        }
                        SvgSimpleNode::Circle(c) | SvgSimpleNode::CircleHole(c) => {
                            let cx = c.center_x as f64;
                            let cy = c.center_y as f64;
                            let r = c.radius as f64;
                            let k = CIRCLE_BEZIER_KAPPA;
                            let kr = k * r;
                            path.move_to(cx + r, cy);
                            path.curve4(cx + r, cy + kr, cx + kr, cy + r, cx, cy + r);
                            path.curve4(cx - kr, cy + r, cx - r, cy + kr, cx - r, cy);
                            path.curve4(cx - r, cy - kr, cx - kr, cy - r, cx, cy - r);
                            path.curve4(cx + kr, cy - r, cx + r, cy - kr, cx + r, cy);
                            path.close_polygon(PATH_FLAGS_NONE);
                        }
                        SvgSimpleNode::RectHole(r) => {
                            let x = r.x as f64;
                            let y = r.y as f64;
                            let w = r.width as f64;
                            let h = r.height as f64;
                            path.move_to(x, y);
                            path.line_to(x + w, y);
                            path.line_to(x + w, y + h);
                            path.line_to(x, y + h);
                            path.close_polygon(PATH_FLAGS_NONE);
                        }
                    }
                }
            }
        }
        if path.total_vertices() == 0 {
            return None;
        }
        Some(path)
    }
    let w = image.width as u32;
    let h = image.height as u32;
    if w == 0 || h == 0 {
        return None;
    }
    let transform_data = style.get_transform();
    let transform = TransAffine::new_custom(
        transform_data.sx as f64,
        transform_data.ky as f64,
        transform_data.kx as f64,
        transform_data.sy as f64,
        transform_data.tx as f64,
        transform_data.ty as f64,
    );
    let mut agg_path = agg_translate_node(node)?;
    let white = Rgba8::new(255, 255, 255, 255);
    // Create pixel buffer and render
    let mut buf = vec![0u8; (w as usize) * (h as usize) * 4];
    let stride = (w * 4) as i32;
    let mut ra = unsafe { RowAccessor::new_with_buf(buf.as_mut_ptr(), w, h, stride) };
    let mut pf = PixfmtRgba32::new(&mut ra);
    let mut rb = RendererBase::new(pf);
    let mut ras = RasterizerScanlineAa::new();
    let mut sl = ScanlineU8::new();
    match style {
        SvgStyle::Fill(fs) => {
            ras.filling_rule(match fs.fill_rule {
                SvgFillRule::Winding => FillingRule::NonZero,
                SvgFillRule::EvenOdd => FillingRule::EvenOdd,
            });
            if transform.is_identity(0.0001) {
                ras.add_path(&mut agg_path, 0);
            } else {
                let mut transformed = ConvTransform::new(&mut agg_path, transform);
                ras.add_path(&mut transformed, 0);
            }
            render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &white);
        }
        SvgStyle::Stroke(ss) => {
            let mut stroke = ConvStroke::new(agg_path);
            stroke.set_width(ss.line_width as f64);
            stroke.set_miter_limit(ss.miter_limit as f64);
            stroke.set_line_cap(match ss.start_cap {
                SvgLineCap::Butt => LineCap::Butt,
                SvgLineCap::Square => LineCap::Square,
                SvgLineCap::Round => LineCap::Round,
            });
            stroke.set_line_join(match ss.line_join {
                SvgLineJoin::Miter | SvgLineJoin::MiterClip => LineJoin::Miter,
                SvgLineJoin::Round => LineJoin::Round,
                SvgLineJoin::Bevel => LineJoin::Bevel,
            });
            if transform.is_identity(0.0001) {
                ras.add_path(&mut stroke, 0);
            } else {
                let mut transformed = ConvTransform::new(&mut stroke, transform);
                ras.add_path(&mut transformed, 0);
            }
            render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &white);
        }
    }
    // Extract red channel from RGBA buffer
    let red_channel = buf
        .chunks_exact(4)
        .map(|r| r[0])
        .collect::<Vec<_>>();
    image.premultiplied_alpha = true;
    image.pixels = RawImageData::U8(red_channel.into());
    image.data_format = RawImageFormat::R8;
    Some(())
}
#[cfg(not(feature = "svg"))]
pub fn render_node_clipmask_cpu(
    image: &mut RawImage,
    node: &SvgNode,
    style: SvgStyle,
) -> Option<()> {
    None
}
// ============================================================================
// Boolean operations on SvgMultiPolygon — via agg scanline boolean algebra
// ============================================================================
/// Rasterize an `SvgMultiPolygon` into an agg `RasterizerScanlineAa`.
fn rasterize_multi_polygon(mp: &SvgMultiPolygon) -> agg_rust::rasterizer_scanline_aa::RasterizerScanlineAa {
    use agg_rust::{
        basics::{FillingRule, PATH_FLAGS_NONE},
        path_storage::PathStorage,
        rasterizer_scanline_aa::RasterizerScanlineAa,
    };
    let mut ras = RasterizerScanlineAa::new();
    ras.filling_rule(FillingRule::NonZero);
    let mut path = PathStorage::new();
    for ring in mp.rings.as_ref().iter() {
        let mut first = true;
        for item in ring.items.as_ref().iter() {
            match item {
                SvgPathElement::Line(l) => {
                    if first {
                        path.move_to(l.start.x as f64, l.start.y as f64);
                        first = false;
                    }
                    path.line_to(l.end.x as f64, l.end.y as f64);
                }
                SvgPathElement::QuadraticCurve(q) => {
                    if first {
                        path.move_to(q.start.x as f64, q.start.y as f64);
                        first = false;
                    }
                    path.curve3(q.ctrl.x as f64, q.ctrl.y as f64, q.end.x as f64, q.end.y as f64);
                }
                SvgPathElement::CubicCurve(c) => {
                    if first {
                        path.move_to(c.start.x as f64, c.start.y as f64);
                        first = false;
                    }
                    path.curve4(
                        c.ctrl_1.x as f64, c.ctrl_1.y as f64,
                        c.ctrl_2.x as f64, c.ctrl_2.y as f64,
                        c.end.x as f64, c.end.y as f64,
                    );
                }
            }
        }
        path.close_polygon(PATH_FLAGS_NONE);
    }
    ras.add_path(&mut path, 0);
    ras
}
/// Extract polygon contours from a `ScanlineStorageAa` by tracing
/// horizontal span edges across consecutive scanlines.
///
/// For each row, we collect the solid spans (coverage > 128). Then we
/// trace left/right boundaries of connected span groups into closed
/// polygons (go down on the left edge, come back up on the right edge).
fn storage_to_multi_polygon(
    storage: &mut agg_rust::scanline_storage_aa::ScanlineStorageAa,
) -> SvgMultiPolygon {
    use agg_rust::rasterizer_scanline_aa::Scanline;
    use azul_css::props::basic::SvgPoint;
    // Collect solid spans per row
    let mut rows: Vec<(i32, Vec<(i32, i32)>)> = Vec::new(); // (y, [(x_start, x_end)])
    let mut sl = agg_rust::scanline_u::ScanlineU8::new();
    if storage.rewind_scanlines() {
        sl.reset(storage.min_x(), storage.max_x());
        while storage.sweep_scanline(&mut sl) {
            let y = Scanline::y(&sl);
            let mut row_spans: Vec<(i32, i32)> = Vec::new();
            for span in sl.begin() {
                // Span with positive len: per-pixel coverage
                let len = span.len;
                if len <= 0 { continue; }
                // Check if any pixel in the span has enough coverage
                let covers = sl.covers();
                let mut x_start = None;
                for j in 0..len as usize {
                    let cov = covers.get(span.cover_offset + j).copied().unwrap_or(0);
                    if cov > 128 {
                        if x_start.is_none() { x_start = Some(span.x + j as i32); }
                    } else if let Some(xs) = x_start.take() {
                        row_spans.push((xs, span.x + j as i32));
                    }
                }
                if let Some(xs) = x_start {
                    row_spans.push((xs, span.x + len));
                }
            }
            if !row_spans.is_empty() {
                rows.push((y, row_spans));
            }
        }
    }
    if rows.is_empty() {
        return SvgMultiPolygon { rings: SvgPathVec::from_const_slice(&[]) };
    }
    // Simple contour extraction: for each row, create horizontal line segments.
    // Then connect consecutive rows into closed polygons.
    // This produces axis-aligned polygons (staircase approximation).
    let mut rings = Vec::new();
    for (y, spans) in &rows {
        let yf = *y as f32;
        for &(x0, x1) in spans {
            let x0f = x0 as f32;
            let x1f = x1 as f32;
            // Create a small horizontal rectangle for this span
            let elements = vec![
                SvgPathElement::Line(SvgLine::new(
                    SvgPoint { x: x0f, y: yf },
                    SvgPoint { x: x1f, y: yf },
                )),
                SvgPathElement::Line(SvgLine::new(
                    SvgPoint { x: x1f, y: yf },
                    SvgPoint { x: x1f, y: yf + 1.0 },
                )),
                SvgPathElement::Line(SvgLine::new(
                    SvgPoint { x: x1f, y: yf + 1.0 },
                    SvgPoint { x: x0f, y: yf + 1.0 },
                )),
                SvgPathElement::Line(SvgLine::new(
                    SvgPoint { x: x0f, y: yf + 1.0 },
                    SvgPoint { x: x0f, y: yf },
                )),
            ];
            rings.push(SvgPath { items: SvgPathElementVec::from_vec(elements) });
        }
    }
    SvgMultiPolygon { rings: SvgPathVec::from_vec(rings) }
}
/// Perform a boolean operation on two `SvgMultiPolygon` shapes using agg scanline algebra.
fn svg_bool_op(
    a: &SvgMultiPolygon,
    b: &SvgMultiPolygon,
    op: agg_rust::scanline_boolean_algebra::SBoolOp,
) -> SvgMultiPolygon {
    use agg_rust::{
        scanline_boolean_algebra::sbool_combine_shapes_aa,
        scanline_storage_aa::ScanlineStorageAa,
        scanline_u::ScanlineU8,
    };
    let mut ras1 = rasterize_multi_polygon(a);
    let mut ras2 = rasterize_multi_polygon(b);
    let mut sl1 = ScanlineU8::new();
    let mut sl2 = ScanlineU8::new();
    let mut sl_result = ScanlineU8::new();
    let mut storage1 = ScanlineStorageAa::new();
    let mut storage2 = ScanlineStorageAa::new();
    let mut storage_result = ScanlineStorageAa::new();
    sbool_combine_shapes_aa(
        op,
        &mut ras1, &mut ras2,
        &mut sl1, &mut sl2, &mut sl_result,
        &mut storage1, &mut storage2, &mut storage_result,
    );
    storage_to_multi_polygon(&mut storage_result)
}
pub fn svg_multi_polygon_union(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
    svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::Or)
}
pub fn svg_multi_polygon_union_byval(a: &SvgMultiPolygon, b: SvgMultiPolygon) -> SvgMultiPolygon {
    svg_multi_polygon_union(a, &b)
}
pub fn svg_multi_polygon_intersection(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
    svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::And)
}
pub fn svg_multi_polygon_intersection_byval(
    a: &SvgMultiPolygon, b: SvgMultiPolygon,
) -> SvgMultiPolygon {
    svg_multi_polygon_intersection(a, &b)
}
pub fn svg_multi_polygon_difference(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
    svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::AMinusB)
}
pub fn svg_multi_polygon_difference_byval(
    a: &SvgMultiPolygon, b: SvgMultiPolygon,
) -> SvgMultiPolygon {
    svg_multi_polygon_difference(a, &b)
}
pub fn svg_multi_polygon_xor(a: &SvgMultiPolygon, b: &SvgMultiPolygon) -> SvgMultiPolygon {
    svg_bool_op(a, b, agg_rust::scanline_boolean_algebra::SBoolOp::Xor)
}
pub fn svg_multi_polygon_xor_byval(a: &SvgMultiPolygon, b: SvgMultiPolygon) -> SvgMultiPolygon {
    svg_multi_polygon_xor(a, &b)
}
// ============================================================================
// SVG Rendering — ParsedSvg wraps the XML tree, no usvg
// ============================================================================
/// Parsed SVG document — wraps the XML node tree.
///
/// Previously wrapped usvg::Tree; now stores our own XmlNode tree parsed via xmlparser.
/// Rendering uses the agg-rust pipeline in cpurender::render_svg_to_png().
#[derive(Debug, Clone)]
#[repr(C)]
pub struct ParsedSvgXmlNode {
    pub run_destructor: bool,
}
impl Drop for ParsedSvgXmlNode {
    fn drop(&mut self) { self.run_destructor = false; }
}
pub fn svgxmlnode_parse(
    svg_file_data: &[u8],
    _options: SvgParseOptions,
) -> Result<ParsedSvgXmlNode, SvgParseError> {
    // Verify we can parse the XML
    let s = core::str::from_utf8(svg_file_data)
        .map_err(|_| SvgParseError::NotAnUtf8Str)?;
    let _nodes = crate::xml::parse_xml_string(s)
        .map_err(|_| SvgParseError::NoParserAvailable)?;
    Ok(ParsedSvgXmlNode { run_destructor: true })
}
/// Parsed SVG document. Stores the raw SVG bytes for deferred rendering.
#[derive(Clone)]
#[repr(C)]
pub struct ParsedSvg {
    pub svg_data: azul_css::U8Vec,
    pub run_destructor: bool,
}
impl Drop for ParsedSvg {
    fn drop(&mut self) { self.run_destructor = false; }
}
impl_result!(
    ParsedSvg,
    SvgParseError,
    ResultParsedSvgSvgParseError,
    copy = false,
    [Debug, Clone]
);
impl From<ParsedSvg> for azul_core::svg::Svg {
    fn from(_parsed: ParsedSvg) -> Self {
        Self {
            tree: core::ptr::null(),
            run_destructor: false,
        }
    }
}
impl fmt::Debug for ParsedSvg {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "ParsedSvg({} bytes)", self.svg_data.as_ref().len())
    }
}
impl ParsedSvg {
    pub fn from_string(
        svg_string: &str,
        parse_options: SvgParseOptions,
    ) -> Result<Self, SvgParseError> {
        svg_parse(svg_string.as_bytes(), parse_options)
    }
    pub fn from_bytes(
        svg_bytes: &[u8],
        parse_options: SvgParseOptions,
    ) -> Result<Self, SvgParseError> {
        svg_parse(svg_bytes, parse_options)
    }
    pub fn get_root(&self) -> ParsedSvgXmlNode {
        svg_root(self)
    }
    pub fn render(&self, options: SvgRenderOptions) -> Option<RawImage> {
        svg_render(self, options)
    }
    pub fn to_string(&self, _options: SvgXmlOptions) -> String {
        String::from_utf8_lossy(self.svg_data.as_ref()).into_owned()
    }
}
/// Parse SVG data into a ParsedSvg (validates XML, stores bytes for deferred rendering).
pub fn svg_parse(
    svg_file_data: &[u8],
    _options: SvgParseOptions,
) -> Result<ParsedSvg, SvgParseError> {
    // Validate that it's parseable XML
    let s = core::str::from_utf8(svg_file_data)
        .map_err(|_| SvgParseError::NotAnUtf8Str)?;
    let _nodes = crate::xml::parse_xml_string(s)
        .map_err(|_| SvgParseError::NoParserAvailable)?;
    Ok(ParsedSvg {
        svg_data: svg_file_data.to_vec().into(),
        run_destructor: true,
    })
}
pub fn svg_root(s: &ParsedSvg) -> ParsedSvgXmlNode {
    ParsedSvgXmlNode { run_destructor: true }
}
/// Render a ParsedSvg to a RawImage using the agg-rust pipeline.
///
/// Requires the `cpurender` feature (the agg-rust + png rasterization pipeline).
/// Without it, SVG parsing/layout still work but rasterizing yields `None`.
#[cfg(feature = "cpurender")]
pub fn svg_render(s: &ParsedSvg, options: SvgRenderOptions) -> Option<RawImage> {
    use azul_core::resources::RawImageData;
    let (target_width, target_height) = match options.target_size.as_ref() {
        Some(s) => (s.width as u32, s.height as u32),
        None => DEFAULT_SVG_RENDER_SIZE,
    };
    if target_width == 0 || target_height == 0 {
        return None;
    }
    let png_data = crate::cpurender::render_svg_to_png(s.svg_data.as_ref(), target_width, target_height).ok()?;
    // Decode PNG back to raw RGBA (TODO: render_svg_to_rgba to avoid PNG round-trip)
    let decoder = png::Decoder::new(std::io::Cursor::new(&png_data));
    let mut reader = decoder.read_info().ok()?;
    let mut buf = vec![0u8; reader.output_buffer_size()?];
    let info = reader.next_frame(&mut buf).ok()?;
    buf.truncate(info.buffer_size());
    Some(RawImage {
        tag: Vec::new().into(),
        pixels: RawImageData::U8(buf.into()),
        width: info.width as usize,
        height: info.height as usize,
        premultiplied_alpha: false,
        data_format: RawImageFormat::RGBA8,
    })
}
/// `cpurender`-less stub: SVG rasterization needs the agg-rust pipeline, so
/// without that feature there is nothing to render to. Parsing and layout are
/// unaffected — only the raster output is unavailable.
#[cfg(not(feature = "cpurender"))]
pub fn svg_render(_s: &ParsedSvg, _options: SvgRenderOptions) -> Option<RawImage> {
    None
}
pub fn svg_to_string(s: &ParsedSvg, _options: SvgXmlOptions) -> String {
    String::from_utf8_lossy(s.svg_data.as_ref()).into_owned()
}
// ============================================================================
// Lyon tessellation (kept — no usvg dependency)
// ============================================================================
/// Trait for tessellating SvgMultiPolygon shapes
pub trait SvgMultiPolygonTessellation {
    fn tessellate_fill(&self, fill_style: SvgFillStyle) -> TessellatedSvgNode;
    fn tessellate_stroke(&self, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode;
}
impl SvgMultiPolygonTessellation for SvgMultiPolygon {
    fn tessellate_fill(&self, fill_style: SvgFillStyle) -> TessellatedSvgNode {
        tessellate_multi_polygon_fill(self, fill_style)
    }
    fn tessellate_stroke(&self, stroke_style: SvgStrokeStyle) -> TessellatedSvgNode {
        tessellate_multi_polygon_stroke(self, stroke_style)
    }
}