Skip to main content

grafo/
shape.rs

1//! The `shape` module provides structures and methods for creating and managing graphical shapes
2//! within the Grafo library. It supports both simple and complex shapes, including rectangles and
3//! custom paths with stroke properties. Fill color is per-instance and set via the renderer.
4//!
5//! # Examples
6//!
7//! Creating and using different shapes:
8//!
9//! ```rust
10//! use grafo::Stroke;
11//! use grafo::{Shape, ShapeBuilder, BorderRadii};
12//! use grafo::Color;
13//!
14//! // Create a simple rectangle
15//! let rect = Shape::rect(
16//!     [(0.0, 0.0), (100.0, 50.0)],
17//!     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
18//! );
19//!
20//! // Create a rounded rectangle
21//! let rounded_rect = Shape::rounded_rect(
22//!     [(0.0, 0.0), (100.0, 50.0)],
23//!     BorderRadii::new(10.0),
24//!     Stroke::new(1.5, Color::BLACK), // Black stroke with width 1.5
25//! );
26//!
27//! // Build a custom shape using ShapeBuilder
28//! let custom_shape = Shape::builder()
29//!     .stroke(Stroke::new(3.0, Color::BLACK)) // Black stroke with width 3.0
30//!     .begin((0.0, 0.0))
31//!     .line_to((50.0, 10.0))
32//!     .line_to((50.0, 50.0))
33//!     .close()
34//!     .build();
35//! ```
36
37use crate::util::PoolManager;
38use crate::vertex::{CustomVertex, InstanceTransform};
39use crate::{Color, Stroke};
40use ahash::AHashMap;
41use lyon::lyon_tessellation::{
42    BuffersBuilder, FillOptions, FillTessellator, FillVertex, VertexBuffers,
43};
44use lyon::path::Winding;
45use lyon::tessellation::FillVertexConstructor;
46use smallvec::SmallVec;
47use std::sync::Arc;
48
49pub(crate) struct CachedShape {
50    pub vertex_buffers: Arc<VertexBuffers<CustomVertex, u16>>,
51    /// Whether the original shape was an axis-aligned rectangle.
52    pub(crate) is_rect: bool,
53    /// The local-space bounding rect when `is_rect` is true.
54    pub(crate) rect_bounds: Option<[(f32, f32); 2]>,
55}
56
57pub(crate) enum TessellatedGeometry {
58    Owned(VertexBuffers<CustomVertex, u16>),
59    Shared(Arc<VertexBuffers<CustomVertex, u16>>),
60}
61
62impl TessellatedGeometry {
63    pub(crate) fn vertices(&self) -> &[CustomVertex] {
64        match self {
65            Self::Owned(vertex_buffers) => &vertex_buffers.vertices,
66            Self::Shared(vertex_buffers) => &vertex_buffers.vertices,
67        }
68    }
69
70    pub(crate) fn indices(&self) -> &[u16] {
71        match self {
72            Self::Owned(vertex_buffers) => &vertex_buffers.indices,
73            Self::Shared(vertex_buffers) => &vertex_buffers.indices,
74        }
75    }
76
77    /// Returns the vertex buffers if they are owned, or `None` if they are shared (cached).
78    pub(crate) fn into_owned(self) -> Option<VertexBuffers<CustomVertex, u16>> {
79        match self {
80            Self::Owned(vertex_buffers) => Some(vertex_buffers),
81            Self::Shared(_) => None,
82        }
83    }
84}
85
86impl CachedShape {
87    /// Creates a new `CachedShape` with the specified shape and depth.
88    /// Note that tessellator_cache_key is different from the shape cache key; a Shape cache key is
89    /// the shape identifier, while tesselator_cache_key is used to cache the tessellation of the
90    /// shape and should be based on the shape properties, and not the shape identifier
91    pub fn new(
92        shape: &Shape,
93        tessellator: &mut FillTessellator,
94        pool: &mut PoolManager,
95        tessellator_cache_key: Option<u64>,
96    ) -> Self {
97        let (is_rect, rect_bounds) = match shape {
98            Shape::Rect(r) => (true, Some(r.rect)),
99            _ => (false, None),
100        };
101        let vertices = match shape.tessellate(tessellator, pool, tessellator_cache_key) {
102            TessellatedGeometry::Owned(vertex_buffers) => Arc::new(vertex_buffers),
103            TessellatedGeometry::Shared(vertex_buffers) => vertex_buffers,
104        };
105        Self {
106            vertex_buffers: vertices,
107            is_rect,
108            rect_bounds,
109        }
110    }
111}
112/// Represents a graphical shape, which can be either a custom path or a simple rectangle.
113///
114/// # Variants
115///
116/// - `Path(PathShape)`: A custom path shape defined using Bézier curves and lines.
117/// - `Rect(RectShape)`: A simple rectangular shape with optional rounded corners.
118///
119/// # Examples
120///
121/// ```rust
122/// use grafo::Stroke;
123/// use grafo::{Shape, BorderRadii};
124/// use grafo::Color;
125///
126/// // Create a simple rectangle
127/// let rect = Shape::rect(
128///     [(0.0, 0.0), (100.0, 50.0)],
129///     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
130/// );
131///
132/// // Create a custom path shape
133/// let custom_path = Shape::builder()
134///     .stroke(Stroke::new(1.0, Color::BLACK))
135///     .begin((0.0, 0.0))
136///     .line_to((50.0, 10.0))
137///     .line_to((50.0, 50.0))
138///     .close()
139///     .build();
140/// ```
141#[derive(Debug, Clone)]
142pub enum Shape {
143    /// A custom path shape defined using Bézier curves and lines.
144    Path(PathShape),
145    /// A simple rectangular shape.
146    Rect(RectShape),
147}
148
149impl Shape {
150    /// Creates a new [`ShapeBuilder`] for constructing complex shapes.
151    ///
152    /// # Examples
153    ///
154    /// ```rust
155    /// use grafo::Shape;
156    ///
157    /// let builder = Shape::builder();
158    /// ```
159    pub fn builder() -> ShapeBuilder {
160        ShapeBuilder::new()
161    }
162
163    /// Creates a simple rectangle shape with the specified coordinates and stroke.
164    ///
165    /// # Parameters
166    ///
167    /// - `rect`: An array containing two tuples representing the top-left and bottom-right
168    ///   coordinates of the rectangle.
169    /// - `stroke`: The stroke properties of the rectangle.
170    ///
171    /// # Examples
172    ///
173    /// ```rust
174    /// use grafo::Color;
175    /// use grafo::Stroke;
176    /// use grafo::Shape;
177    ///
178    /// let rect = Shape::rect(
179    ///     [(0.0, 0.0), (100.0, 50.0)],
180    ///     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
181    /// );
182    /// ```
183    pub fn rect(rect: [(f32, f32); 2], stroke: Stroke) -> Shape {
184        let rect_shape = RectShape::new(rect, stroke);
185        Shape::Rect(rect_shape)
186    }
187
188    /// Creates a rectangle shape with rounded corners.
189    ///
190    /// # Parameters
191    ///
192    /// - `rect`: An array containing two tuples representing the top-left and bottom-right
193    ///   coordinates of the rectangle.
194    /// - `border_radii`: The radii for each corner of the rectangle.
195    /// - `stroke`: The stroke properties of the rectangle.
196    ///
197    /// # Examples
198    ///
199    /// ```rust
200    /// use grafo::Color;
201    /// use grafo::Stroke;
202    /// use grafo::{Shape, BorderRadii};
203    ///
204    /// let rounded_rect = Shape::rounded_rect(
205    ///     [(0.0, 0.0), (100.0, 50.0)],
206    ///     BorderRadii::new(10.0),
207    ///     Stroke::new(1.5, Color::BLACK), // Black stroke with width 1.5
208    /// );
209    /// ```
210    pub fn rounded_rect(rect: [(f32, f32); 2], border_radii: BorderRadii, stroke: Stroke) -> Shape {
211        let mut path_builder = lyon::path::Path::builder();
212        let box2d = lyon::math::Box2D::new(rect[0].into(), rect[1].into());
213
214        path_builder.add_rounded_rectangle(&box2d, &border_radii.into(), Winding::Positive);
215        let path = path_builder.build();
216
217        let path_shape = PathShape { path, stroke };
218        Shape::Path(path_shape)
219    }
220
221    pub(crate) fn tessellate(
222        &self,
223        tessellator: &mut FillTessellator,
224        buffers_pool: &mut PoolManager,
225        tesselation_cache_key: Option<u64>,
226    ) -> TessellatedGeometry {
227        match &self {
228            Shape::Path(path_shape) => {
229                path_shape.tessellate(tessellator, buffers_pool, tesselation_cache_key)
230            }
231            Shape::Rect(rect_shape) => {
232                let min_width = rect_shape.rect[0].0;
233                let min_height = rect_shape.rect[0].1;
234                let max_width = rect_shape.rect[1].0;
235                let max_height = rect_shape.rect[1].1;
236
237                // Compute UVs mapping the rectangle to [0,1] in local space
238                let w = (max_width - min_width).max(1e-6);
239                let h = (max_height - min_height).max(1e-6);
240                let uv =
241                    |x: f32, y: f32| -> [f32; 2] { [(x - min_width) / w, (y - min_height) / h] };
242
243                let quad = [
244                    CustomVertex {
245                        position: [min_width, min_height],
246                        tex_coords: uv(min_width, min_height),
247                        normal: [0.0, 0.0],
248                        coverage: 1.0,
249                    },
250                    CustomVertex {
251                        position: [max_width, min_height],
252                        tex_coords: uv(max_width, min_height),
253                        normal: [0.0, 0.0],
254                        coverage: 1.0,
255                    },
256                    CustomVertex {
257                        position: [max_width, max_height],
258                        tex_coords: uv(max_width, max_height),
259                        normal: [0.0, 0.0],
260                        coverage: 1.0,
261                    },
262                    CustomVertex {
263                        position: [min_width, max_height],
264                        tex_coords: uv(min_width, max_height),
265                        normal: [0.0, 0.0],
266                        coverage: 1.0,
267                    },
268                ];
269                let indices = [0u16, 1, 2, 0, 2, 3];
270
271                let mut vertex_buffers = buffers_pool.lyon_vertex_buffers_pool.get_vertex_buffers();
272
273                vertex_buffers.vertices.extend(quad);
274                vertex_buffers.indices.extend(indices);
275
276                // Generate AA fringe geometry for the rect
277                generate_aa_fringe(
278                    &mut vertex_buffers.vertices,
279                    &mut vertex_buffers.indices,
280                    &mut buffers_pool.aa_fringe_scratch,
281                );
282
283                TessellatedGeometry::Owned(vertex_buffers)
284            }
285        }
286    }
287}
288
289impl From<PathShape> for Shape {
290    fn from(value: PathShape) -> Self {
291        Shape::Path(value)
292    }
293}
294
295impl From<RectShape> for Shape {
296    fn from(value: RectShape) -> Self {
297        Shape::Rect(value)
298    }
299}
300
301impl AsRef<Shape> for Shape {
302    fn as_ref(&self) -> &Shape {
303        self
304    }
305}
306
307/// Represents a simple rectangular shape with a fill color and stroke.
308///
309/// You typically do not need to use `RectShape` directly; instead, use the [`Shape::rect`] method.
310///
311/// # Fields
312///
313/// - `rect`: An array containing two tuples representing the top-left and bottom-right
314///   coordinates of the rectangle.
315/// - `stroke`: The stroke properties of the rectangle.
316///
317/// # Examples
318///
319/// ```rust
320/// use grafo::RectShape;
321/// use grafo::Stroke;
322/// use grafo::Color;
323///
324/// let rect_shape = RectShape::new(
325///     [(0.0, 0.0), (100.0, 50.0)],
326///     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
327/// );
328/// ```
329#[derive(Debug, Clone)]
330pub struct RectShape {
331    /// An array containing two tuples representing the top-left and bottom-right coordinates
332    /// of the rectangle.
333    pub(crate) rect: [(f32, f32); 2],
334    /// The stroke properties of the rectangle.
335    #[allow(unused)]
336    pub(crate) stroke: Stroke,
337}
338
339impl RectShape {
340    /// Creates a new `RectShape` with the specified coordinates and stroke.
341    ///
342    /// # Parameters
343    ///
344    /// - `rect`: An array containing two tuples representing the top-left and bottom-right
345    ///   coordinates of the rectangle.
346    /// - `stroke`: The stroke properties of the rectangle.
347    ///
348    /// # Examples
349    ///
350    /// ```rust
351    /// use grafo::RectShape;
352    /// use grafo::Stroke;
353    /// use grafo::Color;
354    ///
355    /// let rect_shape = RectShape::new(
356    ///     [(0.0, 0.0), (100.0, 50.0)],
357    ///     Stroke::new(2.0, Color::BLACK), // Black stroke with width 2.0
358    /// );
359    /// ```
360    pub fn new(rect: [(f32, f32); 2], stroke: Stroke) -> Self {
361        Self { rect, stroke }
362    }
363}
364
365/// Represents a custom path shape with stroke.
366///
367/// You typically do not need to use `PathShape` directly; instead, use the [`Shape::builder`]
368/// method to construct complex shapes.
369///
370/// # Fields
371///
372/// - `path`: The geometric path defining the shape.
373/// - `stroke`: The stroke properties of the shape.
374///
375/// # Examples
376///
377/// ```rust
378/// use grafo::{Shape, PathShape};
379/// use grafo::Stroke;
380/// use grafo::Color;
381///
382/// // Replace this with your own path
383/// let path = lyon::path::Path::builder().build();
384///
385/// let path_shape = PathShape::new(
386///     path,
387///     Stroke::new(1.0, Color::BLACK), // Black stroke with width 1.0
388/// );
389///
390/// let shape = Shape::Path(path_shape);
391/// ```
392#[derive(Clone, Debug)]
393pub struct PathShape {
394    /// The geometric path defining the shape.
395    pub(crate) path: lyon::path::Path,
396    /// The stroke properties of the shape.
397    #[allow(unused)]
398    pub(crate) stroke: Stroke,
399}
400
401struct VertexConverter {}
402
403impl VertexConverter {
404    fn new() -> Self {
405        Self {}
406    }
407}
408
409impl FillVertexConstructor<CustomVertex> for VertexConverter {
410    fn new_vertex(&mut self, vertex: FillVertex) -> CustomVertex {
411        CustomVertex {
412            position: vertex.position().to_array(),
413            tex_coords: [0.0, 0.0],
414            normal: [0.0, 0.0],
415            coverage: 1.0,
416        }
417    }
418}
419
420#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
421struct BoundaryVertexKey {
422    x_bits: u32,
423    y_bits: u32,
424}
425
426impl BoundaryVertexKey {
427    fn from_position(position: [f32; 2]) -> Self {
428        Self {
429            x_bits: normalized_float_bits(position[0]),
430            y_bits: normalized_float_bits(position[1]),
431        }
432    }
433}
434
435#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
436struct BoundaryEdgeKey {
437    start: BoundaryVertexKey,
438    end: BoundaryVertexKey,
439}
440
441impl BoundaryEdgeKey {
442    fn new(start_position: [f32; 2], end_position: [f32; 2]) -> Self {
443        let start = BoundaryVertexKey::from_position(start_position);
444        let end = BoundaryVertexKey::from_position(end_position);
445        if start <= end {
446            Self { start, end }
447        } else {
448            Self {
449                start: end,
450                end: start,
451            }
452        }
453    }
454}
455
456#[derive(Clone, Copy, Debug, PartialEq)]
457struct BoundaryEdge {
458    start_vertex_index: u16,
459    end_vertex_index: u16,
460    opposite_vertex_index: u16,
461    triangle_index: usize,
462}
463
464#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
465struct BoundaryCornerKey {
466    vertex_key: BoundaryVertexKey,
467    component_index: usize,
468}
469
470#[derive(Clone, Copy, Debug, PartialEq)]
471struct BoundaryCornerNormalData {
472    accumulated_normal: [f32; 2],
473    source_vertex_index: u16,
474}
475
476pub(crate) struct AaFringeScratch {
477    edge_use_counts: AHashMap<BoundaryEdgeKey, (usize, BoundaryEdge)>,
478    edge_owners: AHashMap<BoundaryEdgeKey, SmallVec<[usize; 2]>>,
479    incident_triangles_by_vertex: AHashMap<BoundaryVertexKey, SmallVec<[usize; 4]>>,
480    triangle_adjacency: AHashMap<(BoundaryVertexKey, usize), SmallVec<[usize; 4]>>,
481    visited_triangles: AHashMap<usize, usize>,
482    triangle_component_map: AHashMap<(usize, BoundaryVertexKey), usize>,
483    boundary_corner_normals: AHashMap<BoundaryCornerKey, BoundaryCornerNormalData>,
484    outer_vertex_indices: AHashMap<BoundaryCornerKey, u16>,
485    boundary_edges: Vec<BoundaryEdge>,
486    triangle_stack: Vec<usize>,
487}
488
489impl AaFringeScratch {
490    pub(crate) fn new() -> Self {
491        Self {
492            edge_use_counts: AHashMap::new(),
493            edge_owners: AHashMap::new(),
494            incident_triangles_by_vertex: AHashMap::new(),
495            triangle_adjacency: AHashMap::new(),
496            visited_triangles: AHashMap::new(),
497            triangle_component_map: AHashMap::new(),
498            boundary_corner_normals: AHashMap::new(),
499            outer_vertex_indices: AHashMap::new(),
500            boundary_edges: Vec::new(),
501            triangle_stack: Vec::new(),
502        }
503    }
504
505    fn clear(&mut self) {
506        self.edge_use_counts.clear();
507        self.edge_owners.clear();
508        self.incident_triangles_by_vertex.clear();
509        self.triangle_adjacency.clear();
510        self.visited_triangles.clear();
511        self.triangle_component_map.clear();
512        self.boundary_corner_normals.clear();
513        self.outer_vertex_indices.clear();
514        self.boundary_edges.clear();
515        self.triangle_stack.clear();
516    }
517
518    pub(crate) fn trim(&mut self) {
519        self.edge_use_counts.shrink_to_fit();
520        self.edge_owners.shrink_to_fit();
521        self.incident_triangles_by_vertex.shrink_to_fit();
522        self.triangle_adjacency.shrink_to_fit();
523        self.visited_triangles.shrink_to_fit();
524        self.triangle_component_map.shrink_to_fit();
525        self.boundary_corner_normals.shrink_to_fit();
526        self.outer_vertex_indices.shrink_to_fit();
527        self.boundary_edges.shrink_to_fit();
528        self.triangle_stack.shrink_to_fit();
529    }
530}
531
532fn normalized_float_bits(value: f32) -> u32 {
533    if value == 0.0 {
534        0.0f32.to_bits()
535    } else {
536        value.to_bits()
537    }
538}
539
540// ---------------------------------------------------------------------------
541// Anti-Aliasing: Inflated-Geometry Fringe Generation
542// ---------------------------------------------------------------------------
543
544/// Identifies boundary edges (edges belonging to only one triangle) from a triangle index buffer.
545///
546/// Returns a list of `(vertex_a, vertex_b, opposite_vertex)` tuples. The `opposite_vertex` is
547/// the third vertex of the triangle that owns the edge — it is used to determine which side
548/// of the edge faces outward (away from the triangle interior).
549fn build_boundary_data(vertices: &[CustomVertex], indices: &[u16], scratch: &mut AaFringeScratch) {
550    scratch.clear();
551
552    for (triangle_index, tri) in indices.chunks_exact(3).enumerate() {
553        let a = tri[0];
554        let b = tri[1];
555        let c = tri[2];
556        let vertex_keys = [a, b, c].map(|vertex_index| {
557            BoundaryVertexKey::from_position(vertices[vertex_index as usize].position)
558        });
559
560        for &vertex_key in &vertex_keys {
561            scratch
562                .incident_triangles_by_vertex
563                .entry(vertex_key)
564                .or_default()
565                .push(triangle_index);
566        }
567
568        for &(i, j, opp) in &[(a, b, c), (b, c, a), (c, a, b)] {
569            let key =
570                BoundaryEdgeKey::new(vertices[i as usize].position, vertices[j as usize].position);
571            scratch
572                .edge_use_counts
573                .entry(key)
574                .and_modify(|(count, _)| *count += 1)
575                .or_insert((
576                    1,
577                    BoundaryEdge {
578                        start_vertex_index: i,
579                        end_vertex_index: j,
580                        opposite_vertex_index: opp,
581                        triangle_index,
582                    },
583                ));
584            scratch
585                .edge_owners
586                .entry(key)
587                .or_default()
588                .push(triangle_index);
589        }
590    }
591
592    scratch.boundary_edges.extend(
593        scratch
594            .edge_use_counts
595            .values()
596            .filter_map(|(count, boundary_edge)| (*count == 1).then_some(*boundary_edge)),
597    );
598}
599
600fn build_triangle_component_map(scratch: &mut AaFringeScratch) {
601    for (edge_key, owners) in &scratch.edge_owners {
602        if owners.len() < 2 {
603            continue;
604        }
605
606        for &vertex_key in &[edge_key.start, edge_key.end] {
607            for (owner_index, &owner_triangle_index) in owners.iter().enumerate() {
608                for &other_triangle_index in &owners[owner_index + 1..] {
609                    scratch
610                        .triangle_adjacency
611                        .entry((vertex_key, owner_triangle_index))
612                        .or_default()
613                        .push(other_triangle_index);
614                    scratch
615                        .triangle_adjacency
616                        .entry((vertex_key, other_triangle_index))
617                        .or_default()
618                        .push(owner_triangle_index);
619                }
620            }
621        }
622    }
623
624    for (&vertex_key, incident_triangles) in &scratch.incident_triangles_by_vertex {
625        scratch.visited_triangles.clear();
626        let mut component_index = 0usize;
627
628        for &triangle_index in incident_triangles {
629            if scratch.visited_triangles.contains_key(&triangle_index) {
630                continue;
631            }
632
633            scratch.triangle_stack.push(triangle_index);
634            while let Some(current_triangle_index) = scratch.triangle_stack.pop() {
635                if scratch
636                    .visited_triangles
637                    .insert(current_triangle_index, component_index)
638                    .is_some()
639                {
640                    continue;
641                }
642
643                if let Some(neighbors) = scratch
644                    .triangle_adjacency
645                    .get(&(vertex_key, current_triangle_index))
646                {
647                    for &neighbor_triangle_index in neighbors {
648                        if !scratch
649                            .visited_triangles
650                            .contains_key(&neighbor_triangle_index)
651                        {
652                            scratch.triangle_stack.push(neighbor_triangle_index);
653                        }
654                    }
655                }
656            }
657
658            component_index += 1;
659        }
660
661        for (&triangle_index, &component_index) in &scratch.visited_triangles {
662            scratch
663                .triangle_component_map
664                .insert((triangle_index, vertex_key), component_index);
665        }
666    }
667}
668
669/// Generates a thin fringe of antialiasing triangles around shape boundaries.
670///
671/// For each boundary edge, two triangles are added, forming a quad that fades from
672/// `coverage = 1.0` (at the original boundary) to `coverage = 0.0` (at the outer fringe).
673/// The actual screen-space offset is computed in the vertex shader, so the fringe positions
674/// in the buffer are identical to the source boundary vertices — only the `normal` and
675/// `coverage` fields differ.
676#[cfg(test)]
677fn find_boundary_edges<'a>(
678    vertices: &[CustomVertex],
679    indices: &[u16],
680    scratch: &'a mut AaFringeScratch,
681) -> &'a [BoundaryEdge] {
682    build_boundary_data(vertices, indices, scratch);
683    &scratch.boundary_edges
684}
685
686fn generate_aa_fringe(
687    vertices: &mut Vec<CustomVertex>,
688    indices: &mut Vec<u16>,
689    scratch: &mut AaFringeScratch,
690) {
691    build_boundary_data(vertices, indices, scratch);
692
693    if scratch.boundary_edges.is_empty() {
694        return;
695    }
696
697    build_triangle_component_map(scratch);
698
699    // --- Step 1: Compute per-boundary-vertex averaged outward (miter) normals ---
700
701    for boundary_edge in &scratch.boundary_edges {
702        let pa = vertices[boundary_edge.start_vertex_index as usize].position;
703        let pb = vertices[boundary_edge.end_vertex_index as usize].position;
704        let po = vertices[boundary_edge.opposite_vertex_index as usize].position;
705
706        let dx = pb[0] - pa[0];
707        let dy = pb[1] - pa[1];
708        let edge_len = (dx * dx + dy * dy).sqrt();
709        if edge_len < 1e-10 {
710            continue;
711        }
712
713        let n1 = [-dy / edge_len, dx / edge_len];
714        let n2 = [dy / edge_len, -dx / edge_len];
715
716        let to_opp = [po[0] - pa[0], po[1] - pa[1]];
717        let dot1 = n1[0] * to_opp[0] + n1[1] * to_opp[1];
718        let outward = if dot1 < 0.0 { n1 } else { n2 };
719
720        for &vertex_index in &[
721            boundary_edge.start_vertex_index,
722            boundary_edge.end_vertex_index,
723        ] {
724            let vertex_key =
725                BoundaryVertexKey::from_position(vertices[vertex_index as usize].position);
726            let component_index = *scratch
727                .triangle_component_map
728                .get(&(boundary_edge.triangle_index, vertex_key))
729                .unwrap_or_else(|| {
730                    panic!(
731                    "missing triangle component mapping for triangle {} and boundary vertex {:?}",
732                    boundary_edge.triangle_index,
733                    vertex_key
734                )
735                });
736            let entry = scratch
737                .boundary_corner_normals
738                .entry(BoundaryCornerKey {
739                    vertex_key,
740                    component_index,
741                })
742                .or_insert(BoundaryCornerNormalData {
743                    accumulated_normal: [0.0, 0.0],
744                    source_vertex_index: vertex_index,
745                });
746            entry.accumulated_normal[0] += outward[0];
747            entry.accumulated_normal[1] += outward[1];
748        }
749    }
750
751    for boundary_corner_normal in scratch.boundary_corner_normals.values_mut() {
752        let len = (boundary_corner_normal.accumulated_normal[0]
753            * boundary_corner_normal.accumulated_normal[0]
754            + boundary_corner_normal.accumulated_normal[1]
755                * boundary_corner_normal.accumulated_normal[1])
756            .sqrt();
757        if len > 1e-10 {
758            boundary_corner_normal.accumulated_normal[0] /= len;
759            boundary_corner_normal.accumulated_normal[1] /= len;
760        }
761    }
762
763    // --- Step 2: Create outer fringe (duplicate) vertices ---
764
765    vertices.reserve(scratch.boundary_corner_normals.len());
766    indices.reserve(scratch.boundary_edges.len() * 6);
767
768    for (&boundary_corner_key, boundary_corner_normal) in &scratch.boundary_corner_normals {
769        let source_vertex = &vertices[boundary_corner_normal.source_vertex_index as usize];
770        let outer_vertex = CustomVertex {
771            position: source_vertex.position,
772            tex_coords: source_vertex.tex_coords,
773            normal: boundary_corner_normal.accumulated_normal,
774            coverage: 0.0,
775        };
776        let new_idx = vertices.len() as u16;
777        vertices.push(outer_vertex);
778        scratch
779            .outer_vertex_indices
780            .insert(boundary_corner_key, new_idx);
781    }
782
783    // --- Step 3: Emit fringe quads (two triangles per boundary edge) ---
784
785    for boundary_edge in &scratch.boundary_edges {
786        let start_vertex_key = BoundaryVertexKey::from_position(
787            vertices[boundary_edge.start_vertex_index as usize].position,
788        );
789        let end_vertex_key = BoundaryVertexKey::from_position(
790            vertices[boundary_edge.end_vertex_index as usize].position,
791        );
792        let pa = vertices[boundary_edge.start_vertex_index as usize].position;
793        let pb = vertices[boundary_edge.end_vertex_index as usize].position;
794        let po = vertices[boundary_edge.opposite_vertex_index as usize].position;
795
796        let dx = pb[0] - pa[0];
797        let dy = pb[1] - pa[1];
798        let edge_len = (dx * dx + dy * dy).sqrt();
799        if edge_len < 1e-10 {
800            continue;
801        }
802
803        let start_component_index = *scratch
804            .triangle_component_map
805            .get(&(boundary_edge.triangle_index, start_vertex_key))
806            .unwrap_or_else(|| {
807                panic!(
808                    "missing triangle component mapping for triangle {} and boundary vertex {:?}",
809                    boundary_edge.triangle_index, start_vertex_key
810                )
811            });
812        let end_component_index = *scratch
813            .triangle_component_map
814            .get(&(boundary_edge.triangle_index, end_vertex_key))
815            .unwrap_or_else(|| {
816                panic!(
817                    "missing triangle component mapping for triangle {} and boundary vertex {:?}",
818                    boundary_edge.triangle_index, end_vertex_key
819                )
820            });
821
822        let start_boundary_corner_key = BoundaryCornerKey {
823            vertex_key: start_vertex_key,
824            component_index: start_component_index,
825        };
826        let start_outer_vertex_index = match scratch
827            .outer_vertex_indices
828            .get(&start_boundary_corner_key)
829        {
830            Some(&idx) => idx,
831            None => {
832                debug_assert!(
833                    false,
834                    "missing outer vertex index for {:?} (start_vertex_key: {:?}, start_component_index: {})",
835                    start_boundary_corner_key,
836                    start_vertex_key,
837                    start_component_index
838                );
839                continue;
840            }
841        };
842        let end_boundary_corner_key = BoundaryCornerKey {
843            vertex_key: end_vertex_key,
844            component_index: end_component_index,
845        };
846        let end_outer_vertex_index = match scratch
847            .outer_vertex_indices
848            .get(&end_boundary_corner_key)
849        {
850            Some(&idx) => idx,
851            None => {
852                debug_assert!(
853                    false,
854                    "missing outer vertex index for {:?} (end_vertex_key: {:?}, end_component_index: {})",
855                    end_boundary_corner_key,
856                    end_vertex_key,
857                    end_component_index
858                );
859                continue;
860            }
861        };
862
863        let cross = (pb[0] - pa[0]) * (po[1] - pa[1]) - (pb[1] - pa[1]) * (po[0] - pa[0]);
864
865        if cross >= 0.0 {
866            indices.push(boundary_edge.start_vertex_index);
867            indices.push(boundary_edge.end_vertex_index);
868            indices.push(start_outer_vertex_index);
869
870            indices.push(boundary_edge.end_vertex_index);
871            indices.push(end_outer_vertex_index);
872            indices.push(start_outer_vertex_index);
873        } else {
874            indices.push(boundary_edge.start_vertex_index);
875            indices.push(start_outer_vertex_index);
876            indices.push(boundary_edge.end_vertex_index);
877
878            indices.push(boundary_edge.end_vertex_index);
879            indices.push(start_outer_vertex_index);
880            indices.push(end_outer_vertex_index);
881        }
882    }
883}
884
885impl PathShape {
886    /// Creates a new `PathShape` with the specified path and stroke.
887    ///
888    /// # Parameters
889    ///
890    /// - `path`: The geometric path defining the shape.
891    /// - `stroke`: The stroke properties of the shape.
892    ///
893    /// # Examples
894    ///
895    /// ```rust
896    /// use grafo::PathShape;
897    /// use grafo::Stroke;
898    /// use lyon::path::Path;
899    ///
900    /// let path = Path::builder().build();
901    /// let path_shape = PathShape::new(path, Stroke::default());
902    /// ```
903    pub fn new(path: lyon::path::Path, stroke: Stroke) -> Self {
904        Self { path, stroke }
905    }
906
907    /// Tessellates the path shape into vertex and index buffers for rendering.
908    ///
909    /// # Parameters
910    ///
911    /// - `depth`: The depth value used for rendering order.
912    ///
913    /// # Returns
914    ///
915    /// A `VertexBuffers` structure containing the tessellated vertices and indices.
916    /// ```
917    pub(crate) fn tessellate(
918        &self,
919        tessellator: &mut FillTessellator,
920        buffers_pool: &mut PoolManager,
921        tesselation_cache_key: Option<u64>,
922    ) -> TessellatedGeometry {
923        if let Some(cache_key) = tesselation_cache_key {
924            if let Some(cached_vertex_buffers) = buffers_pool
925                .tessellation_cache
926                .get_vertex_buffers(&cache_key)
927            {
928                return TessellatedGeometry::Shared(cached_vertex_buffers);
929            }
930        }
931
932        let mut buffers: VertexBuffers<CustomVertex, u16> =
933            buffers_pool.lyon_vertex_buffers_pool.get_vertex_buffers();
934        self.tesselate_into_buffers(
935            &mut buffers,
936            tessellator,
937            &mut buffers_pool.aa_fringe_scratch,
938        );
939
940        #[allow(clippy::manual_is_multiple_of)]
941        let needs_index_padding = buffers.indices.len() % 2 != 0;
942        if needs_index_padding {
943            buffers.indices.push(0);
944        }
945
946        if let Some(cache_key) = tesselation_cache_key {
947            let shared_vertex_buffers = Arc::new(buffers);
948            buffers_pool
949                .tessellation_cache
950                .insert_vertex_buffers(cache_key, shared_vertex_buffers.clone());
951            TessellatedGeometry::Shared(shared_vertex_buffers)
952        } else {
953            TessellatedGeometry::Owned(buffers)
954        }
955    }
956
957    fn tesselate_into_buffers(
958        &self,
959        buffers: &mut VertexBuffers<CustomVertex, u16>,
960        tessellator: &mut FillTessellator,
961        aa_fringe_scratch: &mut AaFringeScratch,
962    ) {
963        let options = FillOptions::default();
964
965        let vertex_converter = VertexConverter::new();
966
967        tessellator
968            .tessellate_path(
969                &self.path,
970                &options,
971                &mut BuffersBuilder::new(buffers, vertex_converter),
972            )
973            .unwrap();
974
975        // Generate UVs for the tessellated path using its axis-aligned bounding box in local space
976        if !buffers.vertices.is_empty() {
977            // Compute AABB of positions before offset translation
978            let mut min_x = f32::INFINITY;
979            let mut min_y = f32::INFINITY;
980            let mut max_x = f32::NEG_INFINITY;
981            let mut max_y = f32::NEG_INFINITY;
982            for v in buffers.vertices.iter() {
983                let x = v.position[0];
984                let y = v.position[1];
985                if x < min_x {
986                    min_x = x;
987                }
988                if y < min_y {
989                    min_y = y;
990                }
991                if x > max_x {
992                    max_x = x;
993                }
994                if y > max_y {
995                    max_y = y;
996                }
997            }
998            let w = (max_x - min_x).max(1e-6);
999            let h = (max_y - min_y).max(1e-6);
1000            for v in buffers.vertices.iter_mut() {
1001                let u = (v.position[0] - min_x) / w;
1002                let vcoord = (v.position[1] - min_y) / h;
1003                v.tex_coords = [u, vcoord];
1004            }
1005        }
1006
1007        // Generate AA fringe geometry after UVs are computed so fringe vertices
1008        // inherit the correct tex_coords from their source boundary vertices.
1009        generate_aa_fringe(
1010            &mut buffers.vertices,
1011            &mut buffers.indices,
1012            aa_fringe_scratch,
1013        );
1014    }
1015}
1016
1017/// Contains the data required to draw a shape, including vertex and index buffers.
1018///
1019/// This struct is used internally by the renderer and typically does not need to be used
1020/// directly by library users.
1021#[derive(Debug)]
1022pub(crate) struct ShapeDrawData {
1023    /// The shape associated with this draw data.
1024    pub(crate) shape: Shape,
1025    /// Optional cache key for the shape, used for caching tessellated buffers.
1026    pub(crate) cache_key: Option<u64>,
1027    /// Range in the aggregated index buffer (start_index, count)  
1028    pub(crate) index_buffer_range: Option<(usize, usize)>,
1029    /// Indicates whether the shape is empty (no vertices or indices).
1030    pub(crate) is_empty: bool,
1031    /// Stencil reference assigned during render traversal (parent + 1). Cleared after frame.
1032    pub(crate) stencil_ref: Option<u32>,
1033    /// Index into the per-frame instance transform buffer
1034    pub(crate) instance_index: Option<usize>,
1035    /// Optional per-shape transform applied in clip-space (post-normalization)
1036    pub(crate) transform: Option<InstanceTransform>,
1037    /// Optional texture ids associated with this shape for multi-texturing layers.
1038    /// Layer 0: background/base
1039    /// Layer 1: foreground/overlay (e.g. text or decals) blended on top
1040    pub(crate) texture_ids: [Option<u64>; 2],
1041    /// Optional per-instance color override (normalized [0,1]). If None, use shape's fill.
1042    pub(crate) color_override: Option<[f32; 4]>,
1043    /// Whether this node is a leaf in the draw tree (no children).
1044    pub(crate) is_leaf: bool,
1045    /// When `false`, skip stencil increment/decrement for this parent
1046    /// (children render without being clipped to this shape).
1047    pub(crate) clips_children: bool,
1048    /// Whether the underlying shape is an axis-aligned rectangle (`Shape::Rect`).
1049    /// Used to enable scissor-based clipping instead of stencil for rect parents.
1050    pub(crate) is_rect: bool,
1051}
1052
1053impl ShapeDrawData {
1054    pub fn new(shape: impl Into<Shape>, cache_key: Option<u64>) -> Self {
1055        let shape = shape.into();
1056        let is_rect = matches!(shape, Shape::Rect(_));
1057
1058        ShapeDrawData {
1059            shape,
1060            cache_key,
1061            index_buffer_range: None,
1062            is_empty: false,
1063            stencil_ref: None,
1064            instance_index: None,
1065            transform: None,
1066            texture_ids: [None, None],
1067            color_override: None,
1068            is_leaf: true,
1069            clips_children: true,
1070            is_rect,
1071        }
1072    }
1073
1074    /// Tessellates complex shapes and stores the resulting buffers.
1075    #[inline(always)]
1076    pub(crate) fn tessellate(
1077        &mut self,
1078        tessellator: &mut FillTessellator,
1079        buffers_pool: &mut PoolManager,
1080    ) -> TessellatedGeometry {
1081        self.shape
1082            .tessellate(tessellator, buffers_pool, self.cache_key)
1083    }
1084}
1085
1086#[derive(Debug)]
1087pub(crate) struct CachedShapeDrawData {
1088    pub(crate) id: u64,
1089    pub(crate) index_buffer_range: Option<(usize, usize)>,
1090    pub(crate) is_empty: bool,
1091    /// Stencil reference assigned during render traversal (parent + 1). Cleared after frame.
1092    pub(crate) stencil_ref: Option<u32>,
1093    /// Index into the per-frame instance transform buffer
1094    pub(crate) instance_index: Option<usize>,
1095    /// Optional per-shape transform applied in clip-space (post-normalization)
1096    pub(crate) transform: Option<InstanceTransform>,
1097    /// Optional texture ids associated with this cached shape
1098    pub(crate) texture_ids: [Option<u64>; 2],
1099    /// Optional per-instance color override (normalized [0,1]). If None, use cached shape default.
1100    pub(crate) color_override: Option<[f32; 4]>,
1101    /// Whether this node is a leaf in the draw tree (no children).
1102    pub(crate) is_leaf: bool,
1103    /// When `false`, skip stencil increment/decrement for this parent
1104    /// (children render without being clipped to this shape).
1105    pub(crate) clips_children: bool,
1106    /// Whether the underlying shape is an axis-aligned rectangle.
1107    /// Used to enable scissor-based clipping instead of stencil for rect parents.
1108    pub(crate) is_rect: bool,
1109    /// The local-space bounding rect when `is_rect` is true, for scissor computation.
1110    pub(crate) rect_bounds: Option<[(f32, f32); 2]>,
1111}
1112
1113impl CachedShapeDrawData {
1114    pub fn new(id: u64) -> Self {
1115        Self {
1116            id,
1117            index_buffer_range: None,
1118            is_empty: false,
1119            stencil_ref: None,
1120            instance_index: None,
1121            transform: None,
1122            texture_ids: [None, None],
1123            color_override: None,
1124            is_leaf: true,
1125            clips_children: true,
1126            is_rect: false,
1127            rect_bounds: None,
1128        }
1129    }
1130
1131    pub fn new_rect(id: u64, rect_bounds: [(f32, f32); 2]) -> Self {
1132        Self {
1133            is_rect: true,
1134            rect_bounds: Some(rect_bounds),
1135            ..Self::new(id)
1136        }
1137    }
1138}
1139
1140/// A builder for creating complex shapes using a fluent interface.
1141///
1142/// The `ShapeBuilder` allows you to define the stroke and path of a shape using
1143/// method chaining. Fill is assigned per instance through the renderer, and an
1144/// unset fill renders as transparent. You also can get it from the [`Shape::builder`] method.
1145///
1146/// # Examples
1147///
1148/// ```rust
1149/// use grafo::Color;
1150/// use grafo::Stroke;
1151/// use grafo::ShapeBuilder;
1152///
1153/// let custom_shape = ShapeBuilder::new()
1154///     // Fill is set per-instance via the renderer (renderer.set_shape_color)
1155///     .stroke(Stroke::new(3.0, Color::BLACK)) // Black stroke with width 3.0
1156///     .begin((0.0, 0.0))
1157///     .line_to((50.0, 10.0))
1158///     .line_to((50.0, 50.0))
1159///     .close()
1160///     .build();
1161/// ```
1162#[derive(Clone)]
1163pub struct ShapeBuilder {
1164    /// The stroke properties of the shape.
1165    stroke: Stroke,
1166    /// The path builder used to construct the shape's geometric path.
1167    path_builder: lyon::path::Builder,
1168}
1169
1170impl Default for ShapeBuilder {
1171    /// Creates a default `ShapeBuilder` with a black stroke.
1172    ///
1173    /// # Examples
1174    ///
1175    /// ```rust
1176    /// use grafo::ShapeBuilder;    ///
1177    /// let builder = ShapeBuilder::default();
1178    /// ```
1179    fn default() -> Self {
1180        Self::new()
1181    }
1182}
1183
1184impl ShapeBuilder {
1185    /// Creates a new `ShapeBuilder` with a default black stroke.
1186    ///
1187    /// # Examples
1188    ///
1189    /// ```rust
1190    /// use grafo::ShapeBuilder;
1191    ///
1192    /// let builder = ShapeBuilder::new();
1193    /// ```
1194    pub fn new() -> Self {
1195        Self {
1196            stroke: Stroke::new(1.0, Color::rgb(0, 0, 0)),
1197            path_builder: lyon::path::Path::builder(),
1198        }
1199    }
1200
1201    /// Sets the stroke properties of the shape.
1202    ///
1203    /// # Parameters
1204    ///
1205    /// - `stroke`: The desired stroke properties.
1206    ///
1207    /// # Returns
1208    ///
1209    /// The updated `ShapeBuilder` instance.
1210    ///
1211    /// # Examples
1212    ///
1213    /// ```rust
1214    /// use grafo::Stroke;
1215    /// use grafo::Color;
1216    /// use grafo::ShapeBuilder;
1217    ///
1218    /// let builder = ShapeBuilder::new().stroke(Stroke::new(2.0, Color::BLACK)); // Black stroke with width 2.0
1219    /// ```
1220    pub fn stroke(mut self, stroke: Stroke) -> Self {
1221        self.stroke = stroke;
1222        self
1223    }
1224
1225    /// Begin path at point
1226    ///
1227    /// # Parameters
1228    ///
1229    /// - `point`: The start point of the shape.
1230    ///
1231    /// # Returns
1232    ///
1233    /// The updated `ShapeBuilder` instance.
1234    ///
1235    /// # Examples
1236    ///
1237    /// ```rust
1238    /// use grafo::ShapeBuilder;
1239    ///
1240    /// let builder = ShapeBuilder::new().begin((0.0, 0.0));
1241    /// ```
1242    pub fn begin(mut self, point: (f32, f32)) -> Self {
1243        self.path_builder.begin(point.into());
1244        self
1245    }
1246
1247    /// Draws a line from the current point to the specified point.
1248    ///
1249    /// # Parameters
1250    ///
1251    /// - `point`: The end point of the line.
1252    ///
1253    /// # Returns
1254    ///
1255    /// The updated `ShapeBuilder` instance.
1256    ///
1257    /// # Examples
1258    ///
1259    /// ```rust
1260    /// use grafo::ShapeBuilder;
1261    ///
1262    /// let builder = ShapeBuilder::new().begin((0.0, 0.0)).line_to((50.0, 10.0));
1263    /// ```
1264    pub fn line_to(mut self, point: (f32, f32)) -> Self {
1265        self.path_builder.line_to(point.into());
1266        self
1267    }
1268
1269    /// Draws a cubic Bézier curve from the current point to the specified end point.
1270    ///
1271    /// # Parameters
1272    ///
1273    /// - `ctrl`: The first control point.
1274    /// - `ctrl2`: The second control point.
1275    /// - `to`: The end point of the curve.
1276    ///
1277    /// # Returns
1278    ///
1279    /// The updated `ShapeBuilder` instance.
1280    ///
1281    /// # Examples
1282    ///
1283    /// ```rust
1284    /// use grafo::ShapeBuilder;
1285    ///
1286    /// let builder = ShapeBuilder::new()
1287    ///     .begin((0.0, 0.0))
1288    ///     .cubic_bezier_to((20.0, 30.0), (40.0, 30.0), (50.0, 10.0));
1289    /// ```
1290    pub fn cubic_bezier_to(mut self, ctrl: (f32, f32), ctrl2: (f32, f32), to: (f32, f32)) -> Self {
1291        self.path_builder
1292            .cubic_bezier_to(ctrl.into(), ctrl2.into(), to.into());
1293        self
1294    }
1295
1296    /// Draws a quadratic Bézier curve from the current point to the specified end point.
1297    ///
1298    /// # Parameters
1299    ///
1300    /// - `ctrl`: The control point.
1301    /// - `to`: The end point of the curve.
1302    ///
1303    /// # Returns
1304    ///
1305    /// The updated `ShapeBuilder` instance.
1306    ///
1307    /// # Examples
1308    ///
1309    /// ```rust
1310    /// use grafo::ShapeBuilder;
1311    ///
1312    /// let builder = ShapeBuilder::new()
1313    ///     .begin((0.0, 0.0))
1314    ///     .quadratic_bezier_to((25.0, 40.0), (50.0, 10.0));
1315    /// ```
1316    pub fn quadratic_bezier_to(mut self, ctrl: (f32, f32), to: (f32, f32)) -> Self {
1317        self.path_builder
1318            .quadratic_bezier_to(ctrl.into(), to.into());
1319        self
1320    }
1321
1322    /// Closes the current sub-path by drawing a line back to the starting point.
1323    ///
1324    /// # Returns
1325    ///
1326    /// The updated `ShapeBuilder` instance.
1327    ///
1328    /// # Examples
1329    ///
1330    /// ```rust
1331    /// use grafo::ShapeBuilder;
1332    ///
1333    /// let builder = ShapeBuilder::new().begin((0.0, 0.0)).close();
1334    /// ```
1335    pub fn close(mut self) -> Self {
1336        self.path_builder.close();
1337        self
1338    }
1339
1340    /// Builds the [`Shape`] from the accumulated path, fill color, and stroke.
1341    ///
1342    /// # Returns
1343    ///
1344    /// A `Shape` instance representing the constructed shape.
1345    ///
1346    /// # Examples
1347    ///
1348    /// ```rust
1349    /// use grafo::ShapeBuilder;
1350    ///
1351    /// let shape = ShapeBuilder::new()
1352    ///     .begin((0.0, 0.0))
1353    ///     .line_to((50.0, 10.0))
1354    ///     .line_to((50.0, 50.0))
1355    ///     .close()
1356    ///     .build();
1357    /// ```
1358    pub fn build(self) -> Shape {
1359        let path = self.path_builder.build();
1360        Shape::Path(PathShape {
1361            path,
1362            stroke: self.stroke,
1363        })
1364    }
1365}
1366
1367impl From<ShapeBuilder> for Shape {
1368    fn from(value: ShapeBuilder) -> Self {
1369        value.build()
1370    }
1371}
1372
1373/// A set of border radii for a rounded rectangle
1374#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default)]
1375pub struct BorderRadii {
1376    pub top_left: f32,
1377    pub top_right: f32,
1378    pub bottom_left: f32,
1379    pub bottom_right: f32,
1380}
1381
1382/// Represents the radii of each corner for a rounded rectangle.
1383///
1384/// # Fields
1385///
1386/// - `top_left`: Radius of the top-left corner.
1387/// - `top_right`: Radius of the top-right corner.
1388/// - `bottom_left`: Radius of the bottom-left corner.
1389/// - `bottom_right`: Radius of the bottom-right corner.
1390///
1391/// # Examples
1392///
1393/// Creating uniform and non-uniform border radii:
1394///
1395/// ```rust
1396/// use grafo::BorderRadii;
1397///
1398/// // Uniform border radii
1399/// let uniform_radii = BorderRadii::new(10.0);
1400///
1401/// // Custom border radii
1402/// let custom_radii = BorderRadii {
1403///     top_left: 5.0,
1404///     top_right: 10.0,
1405///     bottom_left: 15.0,
1406///     bottom_right: 20.0,
1407/// };
1408/// ```
1409impl BorderRadii {
1410    /// Creates a new `BorderRadii` with the same radius for all corners.
1411    ///
1412    /// # Parameters
1413    ///
1414    /// - `radius`: The radius to apply to all corners.
1415    ///
1416    /// # Returns
1417    ///
1418    /// A `BorderRadii` instance with uniform corner radii.
1419    ///
1420    /// # Examples
1421    ///
1422    /// ```rust
1423    /// use grafo::BorderRadii;
1424    ///
1425    /// let radii = BorderRadii::new(10.0);
1426    /// ```
1427    pub fn new(radius: f32) -> Self {
1428        let r = radius.abs();
1429        BorderRadii {
1430            top_left: r,
1431            top_right: r,
1432            bottom_left: r,
1433            bottom_right: r,
1434        }
1435    }
1436}
1437
1438impl core::fmt::Display for BorderRadii {
1439    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1440        // In the order of a well known convention (CSS) clockwise from top left
1441        write!(
1442            f,
1443            "BorderRadii({}, {}, {}, {})",
1444            self.top_left, self.top_right, self.bottom_left, self.bottom_right
1445        )
1446    }
1447}
1448
1449impl From<BorderRadii> for lyon::path::builder::BorderRadii {
1450    fn from(val: BorderRadii) -> Self {
1451        lyon::path::builder::BorderRadii {
1452            top_left: val.top_left,
1453            top_right: val.top_right,
1454            bottom_left: val.bottom_left,
1455            bottom_right: val.bottom_right,
1456        }
1457    }
1458}
1459
1460pub(crate) trait DrawShapeCommand {
1461    fn index_buffer_range(&self) -> Option<(usize, usize)>; // (start_index, index_count)
1462    fn is_empty(&self) -> bool;
1463    fn stencil_ref_mut(&mut self) -> &mut Option<u32>;
1464    fn instance_index_mut(&mut self) -> &mut Option<usize>;
1465    fn instance_index(&self) -> Option<usize>;
1466    fn transform(&self) -> Option<InstanceTransform>;
1467    fn set_transform(&mut self, t: InstanceTransform);
1468    fn texture_id(&self, layer: usize) -> Option<u64>;
1469    fn set_texture_id(&mut self, layer: usize, id: Option<u64>);
1470    fn instance_color_override(&self) -> Option<[f32; 4]>;
1471    fn set_instance_color_override(&mut self, color: Option<[f32; 4]>);
1472    fn clips_children(&self) -> bool;
1473    fn is_rect(&self) -> bool;
1474    fn rect_bounds(&self) -> Option<[(f32, f32); 2]>;
1475}
1476
1477impl DrawShapeCommand for ShapeDrawData {
1478    #[inline]
1479    fn index_buffer_range(&self) -> Option<(usize, usize)> {
1480        self.index_buffer_range
1481    }
1482
1483    #[inline]
1484    fn is_empty(&self) -> bool {
1485        self.is_empty
1486    }
1487
1488    #[inline]
1489    fn stencil_ref_mut(&mut self) -> &mut Option<u32> {
1490        &mut self.stencil_ref
1491    }
1492
1493    #[inline]
1494    fn instance_index_mut(&mut self) -> &mut Option<usize> {
1495        &mut self.instance_index
1496    }
1497
1498    #[inline]
1499    fn instance_index(&self) -> Option<usize> {
1500        self.instance_index
1501    }
1502
1503    #[inline]
1504    fn transform(&self) -> Option<InstanceTransform> {
1505        self.transform
1506    }
1507
1508    #[inline]
1509    fn set_transform(&mut self, t: InstanceTransform) {
1510        self.transform = Some(t);
1511    }
1512
1513    #[inline]
1514    fn texture_id(&self, layer: usize) -> Option<u64> {
1515        self.texture_ids.get(layer).copied().unwrap_or(None)
1516    }
1517
1518    #[inline]
1519    fn set_texture_id(&mut self, layer: usize, id: Option<u64>) {
1520        if let Some(slot) = self.texture_ids.get_mut(layer) {
1521            *slot = id;
1522        }
1523    }
1524
1525    #[inline]
1526    fn instance_color_override(&self) -> Option<[f32; 4]> {
1527        self.color_override
1528    }
1529
1530    #[inline]
1531    fn set_instance_color_override(&mut self, color: Option<[f32; 4]>) {
1532        self.color_override = color;
1533    }
1534
1535    #[inline]
1536    fn clips_children(&self) -> bool {
1537        self.clips_children
1538    }
1539
1540    #[inline]
1541    fn is_rect(&self) -> bool {
1542        self.is_rect
1543    }
1544
1545    #[inline]
1546    fn rect_bounds(&self) -> Option<[(f32, f32); 2]> {
1547        match &self.shape {
1548            Shape::Rect(r) => Some(r.rect),
1549            _ => None,
1550        }
1551    }
1552}
1553
1554impl DrawShapeCommand for CachedShapeDrawData {
1555    #[inline]
1556    fn index_buffer_range(&self) -> Option<(usize, usize)> {
1557        self.index_buffer_range
1558    }
1559
1560    #[inline]
1561    fn is_empty(&self) -> bool {
1562        self.is_empty
1563    }
1564
1565    #[inline]
1566    fn stencil_ref_mut(&mut self) -> &mut Option<u32> {
1567        &mut self.stencil_ref
1568    }
1569
1570    #[inline]
1571    fn instance_index_mut(&mut self) -> &mut Option<usize> {
1572        &mut self.instance_index
1573    }
1574
1575    #[inline]
1576    fn instance_index(&self) -> Option<usize> {
1577        self.instance_index
1578    }
1579
1580    #[inline]
1581    fn transform(&self) -> Option<InstanceTransform> {
1582        self.transform
1583    }
1584
1585    #[inline]
1586    fn set_transform(&mut self, t: InstanceTransform) {
1587        self.transform = Some(t);
1588    }
1589
1590    #[inline]
1591    fn texture_id(&self, layer: usize) -> Option<u64> {
1592        self.texture_ids.get(layer).copied().unwrap_or(None)
1593    }
1594
1595    #[inline]
1596    fn set_texture_id(&mut self, layer: usize, id: Option<u64>) {
1597        if let Some(slot) = self.texture_ids.get_mut(layer) {
1598            *slot = id;
1599        }
1600    }
1601
1602    #[inline]
1603    fn instance_color_override(&self) -> Option<[f32; 4]> {
1604        self.color_override
1605    }
1606
1607    #[inline]
1608    fn set_instance_color_override(&mut self, color: Option<[f32; 4]>) {
1609        self.color_override = color;
1610    }
1611
1612    #[inline]
1613    fn clips_children(&self) -> bool {
1614        self.clips_children
1615    }
1616
1617    #[inline]
1618    fn is_rect(&self) -> bool {
1619        self.is_rect
1620    }
1621
1622    #[inline]
1623    fn rect_bounds(&self) -> Option<[(f32, f32); 2]> {
1624        self.rect_bounds
1625    }
1626}
1627
1628#[cfg(test)]
1629mod tests {
1630    use super::{
1631        find_boundary_edges, generate_aa_fringe, AaFringeScratch, BoundaryVertexKey, CustomVertex,
1632        RectShape, Shape,
1633    };
1634    use crate::{util::PoolManager, Stroke};
1635    use lyon::lyon_tessellation::FillTessellator;
1636    use std::num::NonZeroUsize;
1637
1638    fn test_vertex(position: [f32; 2]) -> CustomVertex {
1639        CustomVertex {
1640            position,
1641            tex_coords: [0.0, 0.0],
1642            normal: [0.0, 0.0],
1643            coverage: 1.0,
1644        }
1645    }
1646
1647    #[test]
1648    fn aa_fringe_ignores_internal_seams_with_duplicate_vertices() {
1649        let mut vertices = vec![
1650            test_vertex([0.0, 0.0]),
1651            test_vertex([1.0, 0.0]),
1652            test_vertex([1.0, 1.0]),
1653            test_vertex([0.0, 0.0]),
1654            test_vertex([1.0, 1.0]),
1655            test_vertex([0.0, 1.0]),
1656        ];
1657        let mut indices = vec![0, 1, 2, 3, 4, 5];
1658        let mut aa_fringe_scratch = AaFringeScratch::new();
1659
1660        let boundary_edges = find_boundary_edges(&vertices, &indices, &mut aa_fringe_scratch);
1661        assert_eq!(boundary_edges.len(), 4);
1662
1663        generate_aa_fringe(&mut vertices, &mut indices, &mut aa_fringe_scratch);
1664
1665        assert_eq!(vertices.len(), 10);
1666        assert_eq!(indices.len(), 30);
1667
1668        let outer_vertex_count = vertices
1669            .iter()
1670            .filter(|vertex| vertex.coverage == 0.0)
1671            .count();
1672        assert_eq!(outer_vertex_count, 4);
1673
1674        let unique_outer_vertex_positions = vertices
1675            .iter()
1676            .filter(|vertex| vertex.coverage == 0.0)
1677            .map(|vertex| BoundaryVertexKey::from_position(vertex.position))
1678            .collect::<std::collections::BTreeSet<_>>();
1679        assert_eq!(unique_outer_vertex_positions.len(), 4);
1680    }
1681
1682    #[test]
1683    fn rect_tessellation_uses_shared_quad_corners() {
1684        let rect_shape = RectShape::new([(10.0, 20.0), (30.0, 50.0)], Stroke::default());
1685        let mut tessellator = FillTessellator::new();
1686        let mut pool_manager = PoolManager::new(NonZeroUsize::new(1).unwrap());
1687
1688        let tessellated_geometry =
1689            Shape::Rect(rect_shape).tessellate(&mut tessellator, &mut pool_manager, None);
1690
1691        assert_eq!(tessellated_geometry.vertices().len(), 8);
1692        assert_eq!(tessellated_geometry.indices().len(), 30);
1693
1694        let fill_vertex_count = tessellated_geometry
1695            .vertices()
1696            .iter()
1697            .filter(|vertex| vertex.coverage == 1.0)
1698            .count();
1699        assert_eq!(fill_vertex_count, 4);
1700    }
1701
1702    #[test]
1703    fn aa_fringe_keeps_distinct_corners_for_point_touching_triangles() {
1704        let mut vertices = vec![
1705            test_vertex([0.0, 0.0]),
1706            test_vertex([1.0, 0.0]),
1707            test_vertex([0.0, 1.0]),
1708            test_vertex([0.0, 0.0]),
1709            test_vertex([-1.0, 0.0]),
1710            test_vertex([0.0, -1.0]),
1711        ];
1712        let mut indices = vec![0, 1, 2, 3, 4, 5];
1713        let mut aa_fringe_scratch = AaFringeScratch::new();
1714
1715        generate_aa_fringe(&mut vertices, &mut indices, &mut aa_fringe_scratch);
1716
1717        let outer_vertices = vertices
1718            .iter()
1719            .filter(|vertex| vertex.coverage == 0.0)
1720            .collect::<Vec<_>>();
1721        assert_eq!(outer_vertices.len(), 6);
1722
1723        let shared_point_outer_vertices = outer_vertices
1724            .iter()
1725            .filter(|vertex| {
1726                BoundaryVertexKey::from_position(vertex.position)
1727                    == BoundaryVertexKey::from_position([0.0, 0.0])
1728            })
1729            .count();
1730        assert_eq!(shared_point_outer_vertices, 2);
1731    }
1732
1733    #[test]
1734    fn aa_fringe_skips_zero_length_boundary_edges() {
1735        let mut vertices = vec![
1736            test_vertex([5.0, 5.0]),
1737            test_vertex([5.0, 5.0]),
1738            test_vertex([6.0, 5.0]),
1739        ];
1740        let mut indices = vec![0, 1, 2];
1741        let mut aa_fringe_scratch = AaFringeScratch::new();
1742
1743        generate_aa_fringe(&mut vertices, &mut indices, &mut aa_fringe_scratch);
1744
1745        assert_eq!(vertices.len(), 3);
1746        assert_eq!(indices.len(), 3);
1747    }
1748}