Skip to main content

vizia_core/context/
draw.rs

1use skia_safe::canvas::SaveLayerRec;
2use skia_safe::path_builder::ArcSize;
3use skia_safe::rrect::Corner;
4use skia_safe::wrapper::PointerWrapper;
5use skia_safe::{
6    BlurStyle, ClipOp, MaskFilter, Matrix, Paint, PaintStyle, Path, PathBuilder, PathDirection,
7    PathEffect, Point, RRect, Rect, SamplingOptions, TileMode,
8};
9use std::any::{Any, TypeId};
10use std::f32::consts::SQRT_2;
11use vizia_style::LengthPercentageOrAuto;
12
13use hashbrown::HashMap;
14
15use crate::cache::CachedData;
16use crate::events::ViewHandler;
17use crate::prelude::*;
18use crate::resource::{ImageOrSvg, ResourceManager};
19use crate::text::{TextContext, resolved_text_direction};
20use vizia_input::MouseState;
21
22use super::ModelData;
23
24/// A context used when drawing a view.
25///
26/// The `DrawContext` is provided by the [`draw`](crate::view::View::draw) method in [`View`] and can be used to immutably access the
27/// computed style and layout properties of the current view.
28///
29/// # Example
30/// ```
31/// # use vizia_core::prelude::*;
32/// # use vizia_core::vg;
33/// # let cx = &mut Context::default();
34///
35/// pub struct CustomView {}
36///
37/// impl CustomView {
38///     pub fn new(cx: &mut Context) -> Handle<Self> {
39///         Self{}.build(cx, |_|{})
40///     }
41/// }
42///
43/// impl View for CustomView {
44///     fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
45///         // Get the computed bounds after layout of the current view
46///         let bounds = cx.bounds();
47///         // Draw to the canvas using the bounds of the current view
48///         let path = vg::Path::new();
49///         path.rect(bounds.x, bounds.y, bounds.w, bounds.h);
50///         let mut paint = vg::Paint::default();
51///         paint.set_color(Color::rgb(200, 100, 100));
52///         canvas.draw_path(&path, &paint);
53///     }
54/// }
55/// ```
56pub struct DrawContext<'a> {
57    pub(crate) current: Entity,
58    pub(crate) style: &'a Style,
59    pub(crate) cache: &'a mut CachedData,
60    pub(crate) tree: &'a Tree<Entity>,
61    pub(crate) models: &'a HashMap<Entity, HashMap<TypeId, Box<dyn ModelData>>>,
62    pub(crate) views: &'a mut HashMap<Entity, Box<dyn ViewHandler>>,
63    pub(crate) resource_manager: &'a ResourceManager,
64    pub(crate) text_context: &'a mut TextContext,
65    pub(crate) modifiers: &'a Modifiers,
66    pub(crate) mouse: &'a MouseState<Entity>,
67    pub(crate) windows: &'a mut HashMap<Entity, WindowState>,
68}
69
70macro_rules! get_units_property {
71    (
72        $(#[$meta:meta])*
73        $name:ident
74    ) => {
75        $(#[$meta])*
76        pub fn $name(&self) -> Units {
77            let result = self.style.$name.get(self.current);
78            if let Some(Units::Pixels(p)) = result {
79                Units::Pixels(self.logical_to_physical(*p))
80            } else {
81                result.copied().unwrap_or_default()
82            }
83        }
84    };
85}
86
87impl DrawContext<'_> {
88    pub fn with_current<T>(&mut self, entity: Entity, f: impl FnOnce(&mut DrawContext) -> T) -> T {
89        let current = self.current;
90        self.current = entity;
91        let t = f(self);
92        self.current = current;
93        t
94    }
95
96    /// Returns the bounds of the current view.
97    pub fn bounds(&self) -> BoundingBox {
98        self.cache.get_bounds(self.current)
99    }
100
101    /// Marks the current view as needing to be redrawn.
102    pub fn needs_redraw(&mut self) {
103        let parent_window = self.tree.get_parent_window(self.current).unwrap_or(Entity::root());
104        if let Some(window_state) = self.windows.get_mut(&parent_window) {
105            window_state.redraw_list.insert(self.current);
106        }
107    }
108
109    /// Returns the z-index of the current view.
110    pub fn z_index(&self) -> i32 {
111        self.style.z_index.get(self.current).copied().unwrap_or_default()
112    }
113
114    /// Returns the scale factor.
115    pub fn scale_factor(&self) -> f32 {
116        self.style.dpi_factor as f32
117    }
118
119    /// Returns a reference to the keyboard modifiers state.
120    pub fn modifiers(&self) -> &Modifiers {
121        self.modifiers
122    }
123
124    /// Returns a reference to the mouse state.
125    pub fn mouse(&self) -> &MouseState<Entity> {
126        self.mouse
127    }
128
129    /// Returns the clip path of the current view.
130    pub fn clip_path(&self) -> Option<skia_safe::Path> {
131        // A cached entry (including None) is authoritative for this entity.
132        if let Some(clip_path) = self.cache.clip_path.get(self.current) {
133            return clip_path.clone();
134        }
135
136        if self.style.ignore_clipping.get(self.current).copied().unwrap_or(false) {
137            return None;
138        }
139
140        // If there is no cached value yet, walk ancestors to find an inherited clip.
141        let mut current = self.current;
142        while let Some(parent) = self.tree.get_parent(current) {
143            // A cached parent entry (including None) is authoritative.
144            if let Some(clip_path) = self.cache.clip_path.get(parent) {
145                return clip_path.clone();
146            }
147
148            if self.style.ignore_clipping.get(parent).copied().unwrap_or(false) {
149                return None;
150            }
151            current = parent;
152        }
153
154        None
155    }
156
157    /// Returns the 2D transform of the current view.
158    pub fn transform(&self) -> Matrix {
159        self.cache.transform.get(self.current).copied().unwrap_or_default()
160    }
161
162    /// Returns the visibility of the current view.
163    pub fn visibility(&self) -> Option<Visibility> {
164        self.style.visibility.get(self.current).copied()
165    }
166
167    /// Returns the display of the current view.
168    pub fn display(&self) -> Display {
169        self.style.display.get(self.current).copied().unwrap_or(Display::Flex)
170    }
171
172    /// Returns the opacity of the current view.
173    pub fn opacity(&self) -> f32 {
174        self.style
175            .opacity
176            .get_resolved(self.current, &self.style.custom_opacity_props)
177            .unwrap_or(Opacity(1.0))
178            .0
179    }
180
181    /// Returns the lookup pattern to pick the default font.
182    pub fn default_font(&self) -> &[FamilyOwned] {
183        &self.style.default_font
184    }
185
186    /// Returns the font-size of the current view in physical pixels.
187    pub fn font_size(&self) -> f32 {
188        let fs = self
189            .style
190            .font_size
191            .get_resolved(self.current, &self.style.custom_font_size_props)
192            .and_then(|f| f.0.to_px())
193            .unwrap_or(16.0);
194        self.logical_to_physical(fs)
195    }
196
197    /// Returns the font-weight of the current view.
198    pub fn font_weight(&self) -> FontWeight {
199        self.style.font_weight.get(self.current).copied().unwrap_or_default()
200    }
201
202    /// Returns the font-width of the current view.
203    pub fn font_width(&self) -> FontWidth {
204        self.style.font_width.get(self.current).copied().unwrap_or_default()
205    }
206
207    /// Returns the font-slant of the current view.
208    pub fn font_slant(&self) -> FontSlant {
209        self.style.font_slant.get(self.current).copied().unwrap_or_default()
210    }
211
212    /// Returns the font variation settings of the current view.
213    pub fn font_variation_settings(&self) -> &[FontVariation] {
214        self.style.font_variation_settings.get(self.current).map(Vec::as_slice).unwrap_or_default()
215    }
216
217    /// Function to convert logical points to physical pixels.
218    pub fn logical_to_physical(&self, logical: f32) -> f32 {
219        self.style.logical_to_physical(logical)
220    }
221
222    /// Function to convert physical pixels to logical points.
223    pub fn physical_to_logical(&self, physical: f32) -> f32 {
224        self.style.physical_to_logical(physical)
225    }
226
227    /// Returns the top border width of the current view in physical pixels.
228    pub fn border_top_width(&self) -> f32 {
229        let bounds = self.bounds();
230        self.style
231            .border_top_width
232            .get_resolved(self.current, &self.style.custom_length_props)
233            .map(|l| l.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round())
234            .unwrap_or(0.0)
235    }
236
237    /// Returns the right border width of the current view in physical pixels.
238    pub fn border_right_width(&self) -> f32 {
239        let bounds = self.bounds();
240        self.style
241            .border_right_width
242            .get_resolved(self.current, &self.style.custom_length_props)
243            .map(|l| l.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round())
244            .unwrap_or(0.0)
245    }
246
247    /// Returns the bottom border width of the current view in physical pixels.
248    pub fn border_bottom_width(&self) -> f32 {
249        let bounds = self.bounds();
250        self.style
251            .border_bottom_width
252            .get_resolved(self.current, &self.style.custom_length_props)
253            .map(|l| l.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round())
254            .unwrap_or(0.0)
255    }
256
257    /// Returns the left border width of the current view in physical pixels.
258    pub fn border_left_width(&self) -> f32 {
259        let bounds = self.bounds();
260        self.style
261            .border_left_width
262            .get_resolved(self.current, &self.style.custom_length_props)
263            .map(|l| l.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round())
264            .unwrap_or(0.0)
265    }
266
267    /// Returns the top border color of the current view.
268    pub fn border_top_color(&self) -> Color {
269        self.style
270            .border_top_color
271            .get_resolved(self.current, &self.style.custom_color_props)
272            .map(|c| Color::rgba(c.r(), c.g(), c.b(), c.a()))
273            .unwrap_or(Color::rgba(0, 0, 0, 0))
274    }
275
276    /// Returns the right border color of the current view.
277    pub fn border_right_color(&self) -> Color {
278        self.style
279            .border_right_color
280            .get_resolved(self.current, &self.style.custom_color_props)
281            .map(|c| Color::rgba(c.r(), c.g(), c.b(), c.a()))
282            .unwrap_or(Color::rgba(0, 0, 0, 0))
283    }
284
285    /// Returns the bottom border color of the current view.
286    pub fn border_bottom_color(&self) -> Color {
287        self.style
288            .border_bottom_color
289            .get_resolved(self.current, &self.style.custom_color_props)
290            .map(|c| Color::rgba(c.r(), c.g(), c.b(), c.a()))
291            .unwrap_or(Color::rgba(0, 0, 0, 0))
292    }
293
294    /// Returns the left border color of the current view.
295    pub fn border_left_color(&self) -> Color {
296        self.style
297            .border_left_color
298            .get_resolved(self.current, &self.style.custom_color_props)
299            .map(|c| Color::rgba(c.r(), c.g(), c.b(), c.a()))
300            .unwrap_or(Color::rgba(0, 0, 0, 0))
301    }
302
303    /// Returns the top border style of the current view.
304    pub fn border_top_style(&self) -> BorderStyleKeyword {
305        self.style.border_top_style.get(self.current).copied().unwrap_or_default()
306    }
307
308    /// Returns the right border style of the current view.
309    pub fn border_right_style(&self) -> BorderStyleKeyword {
310        self.style.border_right_style.get(self.current).copied().unwrap_or_default()
311    }
312
313    /// Returns the bottom border style of the current view.
314    pub fn border_bottom_style(&self) -> BorderStyleKeyword {
315        self.style.border_bottom_style.get(self.current).copied().unwrap_or_default()
316    }
317
318    /// Returns the left border style of the current view.
319    pub fn border_left_style(&self) -> BorderStyleKeyword {
320        self.style.border_left_style.get(self.current).copied().unwrap_or_default()
321    }
322
323    /// Returns the outline color of the current view.
324    pub fn outline_color(&self) -> Color {
325        if let Some(col) =
326            self.style.outline_color.get_resolved(self.current, &self.style.custom_color_props)
327        {
328            Color::rgba(col.r(), col.g(), col.b(), col.a())
329        } else {
330            Color::rgba(0, 0, 0, 0)
331        }
332    }
333
334    /// Returns the outline width of the current view in physical pixels.
335    pub fn outline_width(&self) -> f32 {
336        if let Some(length) =
337            self.style.outline_width.get_resolved(self.current, &self.style.custom_length_props)
338        {
339            let bounds = self.bounds();
340            return length.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round();
341        }
342        0.0
343    }
344
345    /// Returns the outline offset of the current view in physical pixels.
346    pub fn outline_offset(&self) -> f32 {
347        if let Some(length) =
348            self.style.outline_offset.get_resolved(self.current, &self.style.custom_length_props)
349        {
350            let bounds = self.bounds();
351            return length.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round();
352        }
353        0.0
354    }
355
356    /// Returns the corner radius for the top-left corner of the current view.
357    pub fn corner_top_left_radius(&self) -> f32 {
358        let bounds = self.bounds();
359        let scale = self.scale_factor();
360        self.style
361            .corner_top_left_radius
362            .get_resolved(self.current, &self.style.custom_length_props)
363            .map(|l| l.to_pixels(bounds.w.min(bounds.h), scale).round())
364            .unwrap_or(0.0)
365    }
366
367    /// Returns the corner radius for the top-right corner of the current view.
368    pub fn corner_top_right_radius(&self) -> f32 {
369        let bounds = self.bounds();
370        let scale = self.scale_factor();
371        self.style
372            .corner_top_right_radius
373            .get_resolved(self.current, &self.style.custom_length_props)
374            .map(|l| l.to_pixels(bounds.w.min(bounds.h), scale).round())
375            .unwrap_or(0.0)
376    }
377
378    /// Returns the corner radius for the bottom-left corner of the current view.
379    pub fn corner_bottom_left_radius(&self) -> f32 {
380        let bounds = self.bounds();
381        let scale = self.scale_factor();
382        self.style
383            .corner_bottom_left_radius
384            .get_resolved(self.current, &self.style.custom_length_props)
385            .map(|l| l.to_pixels(bounds.w.min(bounds.h), scale).round())
386            .unwrap_or(0.0)
387    }
388
389    /// Returns the corner radius for the bottom-right corner of the current view.
390    pub fn corner_bottom_right_radius(&self) -> f32 {
391        let bounds = self.bounds();
392        let scale = self.scale_factor();
393        self.style
394            .corner_bottom_right_radius
395            .get_resolved(self.current, &self.style.custom_length_props)
396            .map(|l| l.to_pixels(bounds.w.min(bounds.h), scale).round())
397            .unwrap_or(0.0)
398    }
399
400    /// Returns the corner shape for the top-left corner of the current view.
401    pub fn corner_top_left_shape(&self) -> CornerShape {
402        self.style.corner_top_left_shape.get(self.current).copied().unwrap_or_default()
403    }
404
405    /// Returns the corner shape for the top-left corner of the current view.
406    pub fn corner_top_right_shape(&self) -> CornerShape {
407        self.style.corner_top_right_shape.get(self.current).copied().unwrap_or_default()
408    }
409
410    /// Returns the corner shape for the top-left corner of the current view.
411    pub fn corner_bottom_left_shape(&self) -> CornerShape {
412        self.style.corner_bottom_left_shape.get(self.current).copied().unwrap_or_default()
413    }
414
415    /// Returns the corner shape for the top-left corner of the current view.
416    pub fn corner_bottom_right_shape(&self) -> CornerShape {
417        self.style.corner_bottom_right_shape.get(self.current).copied().unwrap_or_default()
418    }
419
420    /// Returns the corner smoothing for the top-left corner of the current view.
421    pub fn corner_top_left_smoothing(&self) -> f32 {
422        self.style.corner_top_left_smoothing.get(self.current).copied().unwrap_or_default()
423    }
424
425    /// Returns the corner shape for the top-left corner of the current view.
426    pub fn corner_top_right_smoothing(&self) -> f32 {
427        self.style.corner_top_right_smoothing.get(self.current).copied().unwrap_or_default()
428    }
429
430    /// Returns the corner shape for the top-left corner of the current view.
431    pub fn corner_bottom_left_smoothing(&self) -> f32 {
432        self.style.corner_bottom_left_smoothing.get(self.current).copied().unwrap_or_default()
433    }
434
435    /// Returns the corner shape for the top-left corner of the current view.
436    pub fn corner_bottom_right_smoothing(&self) -> f32 {
437        self.style.corner_bottom_right_smoothing.get(self.current).copied().unwrap_or_default()
438    }
439
440    get_units_property!(
441        /// Returns the padding-left space of the current view.
442        padding_left
443    );
444
445    get_units_property!(
446        /// Returns the padding-right space of the current view.
447        padding_right
448    );
449
450    get_units_property!(
451        /// Returns the padding-top space of the current view.
452        padding_top
453    );
454
455    get_units_property!(
456        /// Returns the padding-bottom space of the current view.
457        padding_bottom
458    );
459
460    /// Returns the alignment of the current view.
461    pub fn alignment(&self) -> Alignment {
462        self.style.alignment.get(self.current).copied().unwrap_or_default()
463    }
464
465    /// Returns the background color of the current view.
466    pub fn background_color(&self) -> Color {
467        if let Some(col) =
468            self.style.background_color.get_resolved(self.current, &self.style.custom_color_props)
469        {
470            Color::rgba(col.r(), col.g(), col.b(), col.a())
471        } else {
472            Color::rgba(0, 0, 0, 0)
473        }
474    }
475
476    /// Returns the border color of the current view.
477    /// This returns the top border color; use side-specific getters for per-side access.
478    pub fn border_color(&self) -> Color {
479        self.border_top_color()
480    }
481
482    /// Returns the border style of the current view.
483    /// This returns the top border style; use side-specific getters for per-side access.
484    pub fn border_style(&self) -> BorderStyleKeyword {
485        self.border_top_style()
486    }
487
488    /// Returns the border width of the current view in physical pixels.
489    /// This returns the top border width; use side-specific getters for per-side access.
490    pub fn border_width(&self) -> f32 {
491        self.border_top_width()
492    }
493
494    /// Returns the text selection color for the current view.
495    pub fn selection_color(&self) -> Color {
496        if let Some(col) =
497            self.style.selection_color.get_resolved(self.current, &self.style.custom_color_props)
498        {
499            Color::rgba(col.r(), col.g(), col.b(), col.a())
500        } else {
501            Color::rgba(0, 0, 0, 0)
502        }
503    }
504
505    /// Returns the text caret color for the current view.
506    pub fn caret_color(&self) -> Color {
507        if let Some(col) =
508            self.style.caret_color.get_resolved(self.current, &self.style.custom_color_props)
509        {
510            Color::rgba(col.r(), col.g(), col.b(), col.a())
511        } else {
512            Color::rgba(0, 0, 0, 0)
513        }
514    }
515
516    /// Returns the font color for the current view.
517    pub fn font_color(&self) -> Color {
518        if let Some(col) =
519            self.style.font_color.get_resolved(self.current, &self.style.custom_color_props)
520        {
521            Color::rgba(col.r(), col.g(), col.b(), col.a())
522        } else {
523            Color::rgba(0, 0, 0, 0)
524        }
525    }
526
527    /// Returns whether the current view should have its text wrapped.
528    pub fn text_wrap(&self) -> bool {
529        self.style.text_wrap.get(self.current).copied().unwrap_or(true)
530    }
531
532    /// Returns the text alignment of the current view.
533    pub fn text_align(&self) -> TextAlign {
534        self.style.text_align.get(self.current).copied().unwrap_or_default()
535    }
536
537    /// Returns the text overflow preference of the current view.
538    pub fn text_overflow(&self) -> TextOverflow {
539        self.style.text_overflow.get(self.current).copied().unwrap_or_default()
540    }
541
542    /// Returns the line clamp Of the current view.
543    pub fn line_clamp(&self) -> Option<usize> {
544        self.style.line_clamp.get(self.current).copied().map(|lc| lc.0 as usize)
545    }
546
547    /// Returns the resolved shadows of the current view.
548    pub fn shadows(&self) -> Option<Vec<Shadow>> {
549        self.style.shadow.get_resolved(self.current, &self.style.custom_shadow_props)
550    }
551
552    /// Returns a reference to any filter applied to the current view.
553    pub fn filter(&self) -> Option<&Filter> {
554        self.style.filter.get(self.current)
555    }
556
557    /// Return to reference to any filter applied to the current view.
558    pub fn backdrop_filter(&self) -> Option<&Filter> {
559        self.style.backdrop_filter.get(self.current)
560    }
561
562    /// Returns a reference to any images of the current view.
563    pub fn background_images(&self) -> Option<&Vec<ImageOrGradient>> {
564        self.style.background_image.get(self.current)
565    }
566
567    ///  Returns a list of background sizes for the current view.
568    pub fn background_size(&self) -> Vec<BackgroundSize> {
569        self.style.background_size.get(self.current).cloned().unwrap_or_default()
570    }
571
572    /// Returns a list of background positions for the current view.
573    pub fn background_position(&self) -> Vec<Position> {
574        self.style.background_position.get(self.current).cloned().unwrap_or_default()
575    }
576
577    /// Returns a list of background repeat modes for the current view.
578    pub fn background_repeat(&self) -> Vec<BackgroundRepeat> {
579        self.style.background_repeat.get(self.current).cloned().unwrap_or_default()
580    }
581
582    pub fn path(&mut self) -> Path {
583        if self.cache.path.get(self.current).is_none() {
584            self.cache.path.insert(self.current, self.build_path(self.bounds(), (0.0, 0.0)));
585        }
586        let bounds = self.bounds();
587        self.cache.path.get(self.current).unwrap().make_offset(bounds.top_left())
588    }
589
590    /// Get the vector path of the current view.
591    pub fn build_path(&self, bounds: BoundingBox, outset: (f32, f32)) -> Path {
592        self.build_path_with_corners(
593            bounds,
594            outset,
595            (
596                self.corner_top_left_radius(),
597                self.corner_top_right_radius(),
598                self.corner_bottom_right_radius(),
599                self.corner_bottom_left_radius(),
600            ),
601            (
602                self.corner_top_left_shape(),
603                self.corner_top_right_shape(),
604                self.corner_bottom_right_shape(),
605                self.corner_bottom_left_shape(),
606            ),
607            (
608                self.corner_top_left_smoothing(),
609                self.corner_top_right_smoothing(),
610                self.corner_bottom_right_smoothing(),
611                self.corner_bottom_left_smoothing(),
612            ),
613        )
614    }
615
616    fn build_path_with_corners(
617        &self,
618        bounds: BoundingBox,
619        outset: (f32, f32),
620        corner_radii: (f32, f32, f32, f32),
621        corner_shapes: (CornerShape, CornerShape, CornerShape, CornerShape),
622        corner_smoothing: (f32, f32, f32, f32),
623    ) -> Path {
624        let (
625            corner_top_left_radius,
626            corner_top_right_radius,
627            corner_bottom_right_radius,
628            corner_bottom_left_radius,
629        ) = corner_radii;
630        let (
631            corner_top_left_shape,
632            corner_top_right_shape,
633            corner_bottom_right_shape,
634            corner_bottom_left_shape,
635        ) = corner_shapes;
636        let (
637            corner_top_left_smoothing,
638            corner_top_right_smoothing,
639            corner_bottom_right_smoothing,
640            corner_bottom_left_smoothing,
641        ) = corner_smoothing;
642
643        let bounds = BoundingBox::from_min_max(0.0, 0.0, bounds.w, bounds.h);
644
645        let rect: Rect = bounds.into();
646
647        let mut rr = RRect::new_rect_radii(
648            rect,
649            &[
650                Point::new(corner_top_left_radius, corner_top_left_radius),
651                Point::new(corner_top_right_radius, corner_top_right_radius),
652                Point::new(corner_bottom_right_radius, corner_bottom_right_radius),
653                Point::new(corner_bottom_left_radius, corner_bottom_left_radius),
654            ],
655        );
656
657        rr = rr.with_outset(outset);
658
659        let x = rr.bounds().x();
660        let y = rr.bounds().y();
661        let width = rr.width();
662        let height = rr.height();
663        let mut should_offset = true;
664
665        let mut path = PathBuilder::new();
666
667        if width == height
668            && corner_bottom_left_radius == width / 2.0
669            && corner_bottom_right_radius == width / 2.0
670            && corner_top_left_radius == height / 2.0
671            && corner_top_right_radius == height / 2.0
672        {
673            path.add_circle((width / 2.0, height / 2.0), width / 2.0, Some(PathDirection::CW));
674        } else if corner_top_left_radius == corner_top_right_radius
675            && corner_top_right_radius == corner_bottom_right_radius
676            && corner_bottom_right_radius == corner_bottom_left_radius
677            && corner_top_left_smoothing == 0.0
678            && corner_top_left_smoothing == corner_top_right_smoothing
679            && corner_top_right_smoothing == corner_bottom_right_smoothing
680            && corner_bottom_right_smoothing == corner_bottom_left_smoothing
681            && corner_top_left_shape == CornerShape::Round
682            && corner_top_left_shape == corner_top_right_shape
683            && corner_top_right_shape == corner_bottom_right_shape
684            && corner_bottom_right_shape == corner_bottom_left_shape
685        {
686            path.add_rrect(rr, None, None);
687            should_offset = false;
688        } else {
689            let top_right = rr.radii(Corner::UpperRight).x;
690
691            if top_right > 0.0 {
692                let (a, b, c, d, l, p, radius) =
693                    compute_smooth_corner(top_right, corner_top_right_smoothing, width, height);
694
695                path.move_to((f32::max(width / 2.0, width - p), 0.0));
696                if corner_top_right_shape == CornerShape::Round {
697                    path.cubic_to(
698                        (width - (p - a), 0.0),
699                        (width - (p - a - b), 0.0),
700                        (width - (p - a - b - c), d),
701                    )
702                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (l, l))
703                    .cubic_to(
704                        (width, p - a - b),
705                        (width, p - a),
706                        (width, f32::min(height / 2.0, p)),
707                    );
708                } else {
709                    path.line_to((width, f32::min(height / 2.0, p)));
710                }
711            } else {
712                path.move_to((width / 2.0, 0.0))
713                    .line_to((width, 0.0))
714                    .line_to((width, height / 2.0));
715            }
716
717            let bottom_right = rr.radii(Corner::LowerRight).x;
718            if bottom_right > 0.0 {
719                let (a, b, c, d, l, p, radius) = compute_smooth_corner(
720                    bottom_right,
721                    corner_bottom_right_smoothing,
722                    width,
723                    height,
724                );
725
726                path.line_to((width, f32::max(height / 2.0, height - p)));
727                if corner_bottom_right_shape == CornerShape::Round {
728                    path.cubic_to(
729                        (width, height - (p - a)),
730                        (width, height - (p - a - b)),
731                        (width - d, height - (p - a - b - c)),
732                    )
733                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (-l, l))
734                    .cubic_to(
735                        (width - (p - a - b), height),
736                        (width - (p - a), height),
737                        (f32::max(width / 2.0, width - p), height),
738                    );
739                } else {
740                    path.line_to((f32::max(width / 2.0, width - p), height));
741                }
742            } else {
743                path.line_to((width, height)).line_to((width / 2.0, height));
744            }
745
746            let bottom_left = rr.radii(Corner::LowerLeft).x;
747            if bottom_left > 0.0 {
748                let (a, b, c, d, l, p, radius) =
749                    compute_smooth_corner(bottom_left, corner_bottom_left_smoothing, width, height);
750
751                path.line_to((f32::min(width / 2.0, p), height));
752                if corner_bottom_left_shape == CornerShape::Round {
753                    path.cubic_to(
754                        (p - a, height),
755                        (p - a - b, height),
756                        (p - a - b - c, height - d),
757                    )
758                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (-l, -l))
759                    .cubic_to(
760                        (0.0, height - (p - a - b)),
761                        (0.0, height - (p - a)),
762                        (0.0, f32::max(height / 2.0, height - p)),
763                    );
764                } else {
765                    path.line_to((0.0, f32::max(height / 2.0, height - p)));
766                }
767            } else {
768                path.line_to((0.0, height)).line_to((0.0, height / 2.0));
769            }
770
771            let top_left = rr.radii(Corner::UpperLeft).x;
772            if top_left > 0.0 {
773                let (a, b, c, d, l, p, radius) =
774                    compute_smooth_corner(top_left, corner_top_left_smoothing, width, height);
775
776                path.line_to((0.0, f32::min(height / 2.0, p)));
777                if corner_top_left_shape == CornerShape::Round {
778                    path.cubic_to((0.0, p - a), (0.0, p - a - b), (d, p - a - b - c))
779                        .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (l, -l))
780                        .cubic_to((p - a - b, 0.0), (p - a, 0.0), (f32::min(width / 2.0, p), 0.0));
781                } else {
782                    path.line_to((f32::min(width / 2.0, p), 0.0));
783                }
784            } else {
785                path.line_to((0.0, 0.0));
786            }
787
788            path.close();
789        }
790
791        let path = path.detach();
792        if should_offset { path.make_offset((x, y)) } else { path }
793    }
794
795    fn corner_oval(center_x: f32, center_y: f32, radius: f32) -> Rect {
796        Rect::from_xywh(center_x - radius, center_y - radius, radius * 2.0, radius * 2.0)
797    }
798
799    fn round_corner_side_path(
800        side_ix: usize,
801        bounds: BoundingBox,
802        outer_radii: (f32, f32, f32, f32),
803        inner_radii: (f32, f32, f32, f32),
804        widths: (f32, f32, f32, f32),
805    ) -> Option<Path> {
806        let bx = bounds.x;
807        let by = bounds.y;
808        let bw = bounds.w;
809        let bh = bounds.h;
810
811        let (r_tl, r_tr, r_br, r_bl) = outer_radii;
812        let (ir_tl, ir_tr, ir_br, ir_bl) = inner_radii;
813        let (top_width, right_width, bottom_width, left_width) = widths;
814
815        let mut path = PathBuilder::new();
816        let diag = SQRT_2.recip();
817
818        let outer_tl_center = Point::new(bx + r_tl, by + r_tl);
819        let outer_tr_center = Point::new(bx + bw - r_tr, by + r_tr);
820        let outer_br_center = Point::new(bx + bw - r_br, by + bh - r_br);
821        let outer_bl_center = Point::new(bx + r_bl, by + bh - r_bl);
822
823        let inner_tl_center = Point::new(bx + left_width + ir_tl, by + top_width + ir_tl);
824        let inner_tr_center = Point::new(bx + bw - right_width - ir_tr, by + top_width + ir_tr);
825        let inner_br_center =
826            Point::new(bx + bw - right_width - ir_br, by + bh - bottom_width - ir_br);
827        let inner_bl_center = Point::new(bx + left_width + ir_bl, by + bh - bottom_width - ir_bl);
828
829        match side_ix {
830            0 => {
831                let outer_start = if r_tl > 0.0 {
832                    Point::new(outer_tl_center.x - r_tl * diag, outer_tl_center.y - r_tl * diag)
833                } else {
834                    Point::new(bx, by)
835                };
836                path.move_to(outer_start);
837                if r_tl > 0.0 {
838                    path.arc_to(
839                        Self::corner_oval(outer_tl_center.x, outer_tl_center.y, r_tl),
840                        225.0,
841                        45.0,
842                        false,
843                    );
844                }
845                path.line_to((bx + bw - r_tr, by));
846                if r_tr > 0.0 {
847                    path.arc_to(
848                        Self::corner_oval(outer_tr_center.x, outer_tr_center.y, r_tr),
849                        270.0,
850                        45.0,
851                        false,
852                    );
853                }
854
855                let inner_split = if ir_tr > 0.0 {
856                    Point::new(inner_tr_center.x + ir_tr * diag, inner_tr_center.y - ir_tr * diag)
857                } else {
858                    Point::new(bx + bw - right_width, by + top_width)
859                };
860                path.line_to(inner_split);
861                if ir_tr > 0.0 {
862                    path.arc_to(
863                        Self::corner_oval(inner_tr_center.x, inner_tr_center.y, ir_tr),
864                        315.0,
865                        -45.0,
866                        false,
867                    );
868                }
869                path.line_to((bx + left_width + ir_tl, by + top_width));
870                if ir_tl > 0.0 {
871                    path.arc_to(
872                        Self::corner_oval(inner_tl_center.x, inner_tl_center.y, ir_tl),
873                        270.0,
874                        -45.0,
875                        false,
876                    );
877                }
878            }
879            1 => {
880                let outer_start = if r_tr > 0.0 {
881                    Point::new(outer_tr_center.x + r_tr * diag, outer_tr_center.y - r_tr * diag)
882                } else {
883                    Point::new(bx + bw, by)
884                };
885                path.move_to(outer_start);
886                if r_tr > 0.0 {
887                    path.arc_to(
888                        Self::corner_oval(outer_tr_center.x, outer_tr_center.y, r_tr),
889                        315.0,
890                        45.0,
891                        false,
892                    );
893                }
894                path.line_to((bx + bw, by + bh - r_br));
895                if r_br > 0.0 {
896                    path.arc_to(
897                        Self::corner_oval(outer_br_center.x, outer_br_center.y, r_br),
898                        0.0,
899                        45.0,
900                        false,
901                    );
902                }
903
904                let inner_split = if ir_br > 0.0 {
905                    Point::new(inner_br_center.x + ir_br * diag, inner_br_center.y + ir_br * diag)
906                } else {
907                    Point::new(bx + bw - right_width, by + bh - bottom_width)
908                };
909                path.line_to(inner_split);
910                if ir_br > 0.0 {
911                    path.arc_to(
912                        Self::corner_oval(inner_br_center.x, inner_br_center.y, ir_br),
913                        45.0,
914                        -45.0,
915                        false,
916                    );
917                }
918                path.line_to((bx + bw - right_width, by + top_width + ir_tr));
919                if ir_tr > 0.0 {
920                    path.arc_to(
921                        Self::corner_oval(inner_tr_center.x, inner_tr_center.y, ir_tr),
922                        0.0,
923                        -45.0,
924                        false,
925                    );
926                }
927            }
928            2 => {
929                let outer_start = if r_br > 0.0 {
930                    Point::new(outer_br_center.x + r_br * diag, outer_br_center.y + r_br * diag)
931                } else {
932                    Point::new(bx + bw, by + bh)
933                };
934                path.move_to(outer_start);
935                if r_br > 0.0 {
936                    path.arc_to(
937                        Self::corner_oval(outer_br_center.x, outer_br_center.y, r_br),
938                        45.0,
939                        45.0,
940                        false,
941                    );
942                }
943                path.line_to((bx + r_bl, by + bh));
944                if r_bl > 0.0 {
945                    path.arc_to(
946                        Self::corner_oval(outer_bl_center.x, outer_bl_center.y, r_bl),
947                        90.0,
948                        45.0,
949                        false,
950                    );
951                }
952
953                let inner_split = if ir_bl > 0.0 {
954                    Point::new(inner_bl_center.x - ir_bl * diag, inner_bl_center.y + ir_bl * diag)
955                } else {
956                    Point::new(bx + left_width, by + bh - bottom_width)
957                };
958                path.line_to(inner_split);
959                if ir_bl > 0.0 {
960                    path.arc_to(
961                        Self::corner_oval(inner_bl_center.x, inner_bl_center.y, ir_bl),
962                        135.0,
963                        -45.0,
964                        false,
965                    );
966                }
967                path.line_to((bx + bw - right_width - ir_br, by + bh - bottom_width));
968                if ir_br > 0.0 {
969                    path.arc_to(
970                        Self::corner_oval(inner_br_center.x, inner_br_center.y, ir_br),
971                        90.0,
972                        -45.0,
973                        false,
974                    );
975                }
976            }
977            3 => {
978                let outer_start = if r_bl > 0.0 {
979                    Point::new(outer_bl_center.x - r_bl * diag, outer_bl_center.y + r_bl * diag)
980                } else {
981                    Point::new(bx, by + bh)
982                };
983                path.move_to(outer_start);
984                if r_bl > 0.0 {
985                    path.arc_to(
986                        Self::corner_oval(outer_bl_center.x, outer_bl_center.y, r_bl),
987                        135.0,
988                        45.0,
989                        false,
990                    );
991                }
992                path.line_to((bx, by + r_tl));
993                if r_tl > 0.0 {
994                    path.arc_to(
995                        Self::corner_oval(outer_tl_center.x, outer_tl_center.y, r_tl),
996                        180.0,
997                        45.0,
998                        false,
999                    );
1000                }
1001
1002                let inner_split = if ir_tl > 0.0 {
1003                    Point::new(inner_tl_center.x - ir_tl * diag, inner_tl_center.y - ir_tl * diag)
1004                } else {
1005                    Point::new(bx + left_width, by + top_width)
1006                };
1007                path.line_to(inner_split);
1008                if ir_tl > 0.0 {
1009                    path.arc_to(
1010                        Self::corner_oval(inner_tl_center.x, inner_tl_center.y, ir_tl),
1011                        225.0,
1012                        -45.0,
1013                        false,
1014                    );
1015                }
1016                path.line_to((bx + left_width, by + bh - bottom_width - ir_bl));
1017                if ir_bl > 0.0 {
1018                    path.arc_to(
1019                        Self::corner_oval(inner_bl_center.x, inner_bl_center.y, ir_bl),
1020                        180.0,
1021                        -45.0,
1022                        false,
1023                    );
1024                }
1025            }
1026            _ => return None,
1027        }
1028
1029        path.close();
1030        Some(path.detach())
1031    }
1032
1033    fn bevel_corner_side_path(
1034        side_ix: usize,
1035        bounds: BoundingBox,
1036        outer_radii: (f32, f32, f32, f32),
1037        inner_radii: (f32, f32, f32, f32),
1038        widths: (f32, f32, f32, f32),
1039    ) -> Option<Path> {
1040        let bx = bounds.x;
1041        let by = bounds.y;
1042        let bw = bounds.w;
1043        let bh = bounds.h;
1044
1045        let (r_tl, r_tr, r_br, r_bl) = outer_radii;
1046        let (ir_tl, ir_tr, ir_br, ir_bl) = inner_radii;
1047        let (top_width, right_width, bottom_width, left_width) = widths;
1048
1049        let otl = r_tl.min(bw * 0.5).min(bh * 0.5);
1050        let otr = r_tr.min(bw * 0.5).min(bh * 0.5);
1051        let obr = r_br.min(bw * 0.5).min(bh * 0.5);
1052        let obl = r_bl.min(bw * 0.5).min(bh * 0.5);
1053
1054        let itl = ir_tl
1055            .max(0.0)
1056            .min((bw - left_width - right_width).max(0.0) * 0.5)
1057            .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1058        let itr = ir_tr
1059            .max(0.0)
1060            .min((bw - left_width - right_width).max(0.0) * 0.5)
1061            .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1062        let ibr = ir_br
1063            .max(0.0)
1064            .min((bw - left_width - right_width).max(0.0) * 0.5)
1065            .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1066        let ibl = ir_bl
1067            .max(0.0)
1068            .min((bw - left_width - right_width).max(0.0) * 0.5)
1069            .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1070
1071        let outer_tl_top = Point::new(bx + otl, by);
1072        let outer_tl_left = Point::new(bx, by + otl);
1073        let outer_tr_top = Point::new(bx + bw - otr, by);
1074        let outer_tr_right = Point::new(bx + bw, by + otr);
1075        let outer_br_right = Point::new(bx + bw, by + bh - obr);
1076        let outer_br_bottom = Point::new(bx + bw - obr, by + bh);
1077        let outer_bl_bottom = Point::new(bx + obl, by + bh);
1078        let outer_bl_left = Point::new(bx, by + bh - obl);
1079
1080        let inner_tl_top = Point::new(bx + left_width + itl, by + top_width);
1081        let inner_tl_left = Point::new(bx + left_width, by + top_width + itl);
1082        let inner_tr_top = Point::new(bx + bw - right_width - itr, by + top_width);
1083        let inner_tr_right = Point::new(bx + bw - right_width, by + top_width + itr);
1084        let inner_br_right = Point::new(bx + bw - right_width, by + bh - bottom_width - ibr);
1085        let inner_br_bottom = Point::new(bx + bw - right_width - ibr, by + bh - bottom_width);
1086        let inner_bl_bottom = Point::new(bx + left_width + ibl, by + bh - bottom_width);
1087        let inner_bl_left = Point::new(bx + left_width, by + bh - bottom_width - ibl);
1088
1089        let mut path = PathBuilder::new();
1090        match side_ix {
1091            0 => {
1092                path.move_to(outer_tl_left);
1093                path.line_to(outer_tl_top);
1094                path.line_to(outer_tr_top);
1095                path.line_to(outer_tr_right);
1096                path.line_to(inner_tr_right);
1097                path.line_to(inner_tr_top);
1098                path.line_to(inner_tl_top);
1099                path.line_to(inner_tl_left);
1100            }
1101            1 => {
1102                path.move_to(outer_tr_top);
1103                path.line_to(outer_tr_right);
1104                path.line_to(outer_br_right);
1105                path.line_to(outer_br_bottom);
1106                path.line_to(inner_br_bottom);
1107                path.line_to(inner_br_right);
1108                path.line_to(inner_tr_right);
1109                path.line_to(inner_tr_top);
1110            }
1111            2 => {
1112                path.move_to(outer_br_right);
1113                path.line_to(outer_br_bottom);
1114                path.line_to(outer_bl_bottom);
1115                path.line_to(outer_bl_left);
1116                path.line_to(inner_bl_left);
1117                path.line_to(inner_bl_bottom);
1118                path.line_to(inner_br_bottom);
1119                path.line_to(inner_br_right);
1120            }
1121            3 => {
1122                path.move_to(outer_bl_bottom);
1123                path.line_to(outer_bl_left);
1124                path.line_to(outer_tl_left);
1125                path.line_to(outer_tl_top);
1126                path.line_to(inner_tl_top);
1127                path.line_to(inner_tl_left);
1128                path.line_to(inner_bl_left);
1129                path.line_to(inner_bl_bottom);
1130            }
1131            _ => return None,
1132        }
1133
1134        path.close();
1135        Some(path.detach())
1136    }
1137
1138    fn round_corner_side_stroke_path(
1139        side_ix: usize,
1140        bounds: BoundingBox,
1141        outer_radii: (f32, f32, f32, f32),
1142    ) -> Option<Path> {
1143        let bx = bounds.x;
1144        let by = bounds.y;
1145        let bw = bounds.w;
1146        let bh = bounds.h;
1147
1148        let (r_tl, r_tr, r_br, r_bl) = outer_radii;
1149
1150        let mut path = PathBuilder::new();
1151        let diag = SQRT_2.recip();
1152
1153        let outer_tl_center = Point::new(bx + r_tl, by + r_tl);
1154        let outer_tr_center = Point::new(bx + bw - r_tr, by + r_tr);
1155        let outer_br_center = Point::new(bx + bw - r_br, by + bh - r_br);
1156        let outer_bl_center = Point::new(bx + r_bl, by + bh - r_bl);
1157
1158        match side_ix {
1159            0 => {
1160                let outer_start = if r_tl > 0.0 {
1161                    Point::new(outer_tl_center.x - r_tl * diag, outer_tl_center.y - r_tl * diag)
1162                } else {
1163                    Point::new(bx, by)
1164                };
1165                path.move_to(outer_start);
1166                if r_tl > 0.0 {
1167                    path.arc_to(
1168                        Self::corner_oval(outer_tl_center.x, outer_tl_center.y, r_tl),
1169                        225.0,
1170                        45.0,
1171                        false,
1172                    );
1173                }
1174                path.line_to((bx + bw - r_tr, by));
1175                if r_tr > 0.0 {
1176                    path.arc_to(
1177                        Self::corner_oval(outer_tr_center.x, outer_tr_center.y, r_tr),
1178                        270.0,
1179                        45.0,
1180                        false,
1181                    );
1182                }
1183            }
1184            1 => {
1185                let outer_start = if r_tr > 0.0 {
1186                    Point::new(outer_tr_center.x + r_tr * diag, outer_tr_center.y - r_tr * diag)
1187                } else {
1188                    Point::new(bx + bw, by)
1189                };
1190                path.move_to(outer_start);
1191                if r_tr > 0.0 {
1192                    path.arc_to(
1193                        Self::corner_oval(outer_tr_center.x, outer_tr_center.y, r_tr),
1194                        315.0,
1195                        45.0,
1196                        false,
1197                    );
1198                }
1199                path.line_to((bx + bw, by + bh - r_br));
1200                if r_br > 0.0 {
1201                    path.arc_to(
1202                        Self::corner_oval(outer_br_center.x, outer_br_center.y, r_br),
1203                        0.0,
1204                        45.0,
1205                        false,
1206                    );
1207                }
1208            }
1209            2 => {
1210                let outer_start = if r_br > 0.0 {
1211                    Point::new(outer_br_center.x + r_br * diag, outer_br_center.y + r_br * diag)
1212                } else {
1213                    Point::new(bx + bw, by + bh)
1214                };
1215                path.move_to(outer_start);
1216                if r_br > 0.0 {
1217                    path.arc_to(
1218                        Self::corner_oval(outer_br_center.x, outer_br_center.y, r_br),
1219                        45.0,
1220                        45.0,
1221                        false,
1222                    );
1223                }
1224                path.line_to((bx + r_bl, by + bh));
1225                if r_bl > 0.0 {
1226                    path.arc_to(
1227                        Self::corner_oval(outer_bl_center.x, outer_bl_center.y, r_bl),
1228                        90.0,
1229                        45.0,
1230                        false,
1231                    );
1232                }
1233            }
1234            3 => {
1235                let outer_start = if r_bl > 0.0 {
1236                    Point::new(outer_bl_center.x - r_bl * diag, outer_bl_center.y + r_bl * diag)
1237                } else {
1238                    Point::new(bx, by + bh)
1239                };
1240                path.move_to(outer_start);
1241                if r_bl > 0.0 {
1242                    path.arc_to(
1243                        Self::corner_oval(outer_bl_center.x, outer_bl_center.y, r_bl),
1244                        135.0,
1245                        45.0,
1246                        false,
1247                    );
1248                }
1249                path.line_to((bx, by + r_tl));
1250                if r_tl > 0.0 {
1251                    path.arc_to(
1252                        Self::corner_oval(outer_tl_center.x, outer_tl_center.y, r_tl),
1253                        180.0,
1254                        45.0,
1255                        false,
1256                    );
1257                }
1258            }
1259            _ => return None,
1260        }
1261
1262        Some(path.detach())
1263    }
1264
1265    fn bevel_corner_side_stroke_path(
1266        side_ix: usize,
1267        bounds: BoundingBox,
1268        outer_radii: (f32, f32, f32, f32),
1269    ) -> Option<Path> {
1270        let bx = bounds.x;
1271        let by = bounds.y;
1272        let bw = bounds.w;
1273        let bh = bounds.h;
1274
1275        let (r_tl, r_tr, r_br, r_bl) = outer_radii;
1276
1277        let otl = r_tl.min(bw * 0.5).min(bh * 0.5);
1278        let otr = r_tr.min(bw * 0.5).min(bh * 0.5);
1279        let obr = r_br.min(bw * 0.5).min(bh * 0.5);
1280        let obl = r_bl.min(bw * 0.5).min(bh * 0.5);
1281
1282        let outer_tl_top = Point::new(bx + otl, by);
1283        let outer_tl_left = Point::new(bx, by + otl);
1284        let outer_tr_top = Point::new(bx + bw - otr, by);
1285        let outer_tr_right = Point::new(bx + bw, by + otr);
1286        let outer_br_right = Point::new(bx + bw, by + bh - obr);
1287        let outer_br_bottom = Point::new(bx + bw - obr, by + bh);
1288        let outer_bl_bottom = Point::new(bx + obl, by + bh);
1289        let outer_bl_left = Point::new(bx, by + bh - obl);
1290
1291        let mut path = PathBuilder::new();
1292        match side_ix {
1293            0 => {
1294                path.move_to(outer_tl_left);
1295                path.line_to(outer_tl_top);
1296                path.line_to(outer_tr_top);
1297                path.line_to(outer_tr_right);
1298            }
1299            1 => {
1300                path.move_to(outer_tr_top);
1301                path.line_to(outer_tr_right);
1302                path.line_to(outer_br_right);
1303                path.line_to(outer_br_bottom);
1304            }
1305            2 => {
1306                path.move_to(outer_br_right);
1307                path.line_to(outer_br_bottom);
1308                path.line_to(outer_bl_bottom);
1309                path.line_to(outer_bl_left);
1310            }
1311            3 => {
1312                path.move_to(outer_bl_bottom);
1313                path.line_to(outer_bl_left);
1314                path.line_to(outer_tl_left);
1315                path.line_to(outer_tl_top);
1316            }
1317            _ => return None,
1318        }
1319
1320        Some(path.detach())
1321    }
1322
1323    fn smooth_corner_side_path(
1324        side_ix: usize,
1325        bounds: BoundingBox,
1326        outer_radii: (f32, f32, f32, f32),
1327        inner_radii: (f32, f32, f32, f32),
1328        corner_shapes: (CornerShape, CornerShape, CornerShape, CornerShape),
1329        corner_smoothing: (f32, f32, f32, f32),
1330        widths: (f32, f32, f32, f32),
1331    ) -> Option<Path> {
1332        let bx = bounds.x;
1333        let by = bounds.y;
1334        let bw = bounds.w;
1335        let bh = bounds.h;
1336
1337        let (r_tl, r_tr, r_br, r_bl) = outer_radii;
1338        let (ir_tl, ir_tr, ir_br, ir_bl) = inner_radii;
1339        let (corner_tl_shape, corner_tr_shape, corner_br_shape, corner_bl_shape) = corner_shapes;
1340        let (corner_tl_smoothing, corner_tr_smoothing, corner_br_smoothing, corner_bl_smoothing) =
1341            corner_smoothing;
1342        let (top_width, right_width, bottom_width, left_width) = widths;
1343
1344        let mut path = PathBuilder::new();
1345
1346        match side_ix {
1347            0 => {
1348                // Top side: top-left to top-right
1349                let otl = r_tl.min(bw * 0.5).min(bh * 0.5);
1350                let otr = r_tr.min(bw * 0.5).min(bh * 0.5);
1351                let itl = ir_tl
1352                    .max(0.0)
1353                    .min((bw - left_width - right_width).max(0.0) * 0.5)
1354                    .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1355                let itr = ir_tr
1356                    .max(0.0)
1357                    .min((bw - left_width - right_width).max(0.0) * 0.5)
1358                    .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1359
1360                path.move_to((bx + left_width, by + top_width + itl));
1361
1362                // Outer top-left corner
1363                if otl > 0.0 && corner_tl_shape == CornerShape::Round {
1364                    let (a, b, c, d, l, p, radius) =
1365                        compute_smooth_corner(otl, corner_tl_smoothing, bw, bh);
1366                    let start_x = bx + f32::max(left_width * 0.5, left_width + p - left_width);
1367                    let start_y = by + top_width;
1368                    path.line_to((start_x, start_y));
1369                    path.cubic_to(
1370                        (bx + left_width + (p - a), by),
1371                        (bx + left_width + (p - a - b), by),
1372                        (bx + left_width + (p - a - b - c), d + by),
1373                    )
1374                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (l, l))
1375                    .cubic_to(
1376                        (bx + left_width + (p - a - b), by + top_width),
1377                        (bx + left_width + (p - a), by + top_width),
1378                        (bx + left_width + p.min(bw * 0.5), by + top_width),
1379                    );
1380                } else if otl > 0.0 {
1381                    let p = otl.min((bw * 0.5).min(bh * 0.5));
1382                    path.line_to((bx + left_width + p, by + top_width));
1383                } else {
1384                    path.line_to((bx + left_width, by + top_width));
1385                }
1386
1387                // Outer top-right corner
1388                if otr > 0.0 {
1389                    let p = otr.min((bw * 0.5).min(bh * 0.5));
1390                    path.line_to((bx + bw - right_width - p, by + top_width));
1391                    if corner_tr_shape == CornerShape::Round {
1392                        let (a, b, c, d, l, radius_val, _) =
1393                            compute_smooth_corner(otr, corner_tr_smoothing, bw, bh);
1394                        path.cubic_to(
1395                            (bx + bw - right_width - (p - a), by),
1396                            (bx + bw - right_width - (p - a - b), by),
1397                            (bx + bw - right_width - (p - a - b - c) + d, by),
1398                        )
1399                        .r_arc_to(
1400                            (radius_val, radius_val),
1401                            0.0,
1402                            ArcSize::Small,
1403                            PathDirection::CW,
1404                            (l, l),
1405                        )
1406                        .cubic_to(
1407                            (bx + bw - right_width - (p - a - b), by + top_width),
1408                            (bx + bw - right_width - (p - a), by + top_width),
1409                            (bx + bw - right_width - p.min(bw * 0.5), by + top_width),
1410                        );
1411                    }
1412                } else {
1413                    path.line_to((bx + bw - right_width, by + top_width));
1414                }
1415
1416                path.line_to((bx + bw - right_width, by + top_width + itr));
1417
1418                // Inner top-right corner
1419                if itr > 0.0 && corner_tr_shape == CornerShape::Round {
1420                    let (a, b, c, d, l, p, radius) = compute_smooth_corner(
1421                        itr,
1422                        corner_tr_smoothing,
1423                        bw - left_width - right_width,
1424                        bh - top_width - bottom_width,
1425                    );
1426                    path.cubic_to(
1427                        (bx + bw - right_width - (p - a), by + top_width),
1428                        (bx + bw - right_width - (p - a - b), by + top_width),
1429                        (bx + bw - right_width - (p - a - b - c), by + top_width + d),
1430                    )
1431                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (-l, l))
1432                    .cubic_to(
1433                        (bx + bw - right_width, by + top_width + (p - a - b)),
1434                        (bx + bw - right_width, by + top_width + (p - a)),
1435                        (
1436                            bx + bw - right_width,
1437                            by + top_width + p.min((bh - top_width - bottom_width) * 0.5),
1438                        ),
1439                    );
1440                } else if itr > 0.0 {
1441                    let p = itr.min(
1442                        ((bh - top_width - bottom_width) * 0.5)
1443                            .min((bw - left_width - right_width) * 0.5),
1444                    );
1445                    path.line_to((bx + bw - right_width, by + top_width + p));
1446                }
1447
1448                // Inner top-left corner
1449                if itl > 0.0 && corner_tl_shape == CornerShape::Round {
1450                    let (a, b, c, d, l, p, radius) = compute_smooth_corner(
1451                        itl,
1452                        corner_tl_smoothing,
1453                        bw - left_width - right_width,
1454                        bh - top_width - bottom_width,
1455                    );
1456                    path.line_to((bx + left_width + p, by + top_width));
1457                    path.cubic_to(
1458                        (bx + left_width + (p - a), by + top_width),
1459                        (bx + left_width + (p - a - b), by + top_width),
1460                        (bx + left_width + (p - a - b - c), by + top_width + d),
1461                    )
1462                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (-l, -l))
1463                    .cubic_to(
1464                        (bx + left_width, by + top_width + (p - a - b)),
1465                        (bx + left_width, by + top_width + (p - a)),
1466                        (
1467                            bx + left_width,
1468                            by + top_width + p.min((bh - top_width - bottom_width) * 0.5),
1469                        ),
1470                    );
1471                } else if itl > 0.0 {
1472                    let p = itl.min(
1473                        ((bh - top_width - bottom_width) * 0.5)
1474                            .min((bw - left_width - right_width) * 0.5),
1475                    );
1476                    path.line_to((bx + left_width, by + top_width + p));
1477                }
1478            }
1479            1 => {
1480                // Right side: top-right to bottom-right
1481                let otr = r_tr.min(bw * 0.5).min(bh * 0.5);
1482                let obr = r_br.min(bw * 0.5).min(bh * 0.5);
1483                let itr = ir_tr
1484                    .max(0.0)
1485                    .min((bw - left_width - right_width).max(0.0) * 0.5)
1486                    .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1487                let ibr = ir_br
1488                    .max(0.0)
1489                    .min((bw - left_width - right_width).max(0.0) * 0.5)
1490                    .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1491
1492                path.move_to((bx + bw - right_width - itr, by + top_width));
1493
1494                // Outer top-right corner
1495                if otr > 0.0 && corner_tr_shape == CornerShape::Round {
1496                    let (a, b, c, d, l, p, radius) =
1497                        compute_smooth_corner(otr, corner_tr_smoothing, bw, bh);
1498                    let start_x = bx + bw - right_width;
1499                    path.line_to((start_x, by + top_width + p));
1500                    path.cubic_to(
1501                        (start_x, by + (p - a)),
1502                        (start_x, by + (p - a - b)),
1503                        (start_x - d, by + (p - a - b - c)),
1504                    )
1505                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (-l, l))
1506                    .cubic_to(
1507                        (start_x - (p - a - b), by),
1508                        (start_x - (p - a), by),
1509                        (start_x - p.min(bw * 0.5), by),
1510                    );
1511                } else if otr > 0.0 {
1512                    let p = otr.min((bw * 0.5).min(bh * 0.5));
1513                    path.line_to((bx + bw - right_width, by + top_width + p));
1514                } else {
1515                    path.line_to((bx + bw - right_width, by + top_width));
1516                }
1517
1518                // Outer bottom-right corner
1519                if obr > 0.0 {
1520                    let p = obr.min((bw * 0.5).min(bh * 0.5));
1521                    path.line_to((bx + bw - right_width, by + bh - bottom_width - p));
1522                    if corner_br_shape == CornerShape::Round {
1523                        let (a, b, c, d, l, radius_val, _) =
1524                            compute_smooth_corner(obr, corner_br_smoothing, bw, bh);
1525                        path.cubic_to(
1526                            (bx + bw, by + bh - bottom_width - (p - a)),
1527                            (bx + bw, by + bh - bottom_width - (p - a - b)),
1528                            (bx + bw, by + bh - bottom_width - (p - a - b - c) + d),
1529                        )
1530                        .r_arc_to(
1531                            (radius_val, radius_val),
1532                            0.0,
1533                            ArcSize::Small,
1534                            PathDirection::CW,
1535                            (l, l),
1536                        )
1537                        .cubic_to(
1538                            (bx + bw - (p - a - b), by + bh),
1539                            (bx + bw - (p - a), by + bh),
1540                            (bx + bw - p.min(bw * 0.5), by + bh),
1541                        );
1542                    }
1543                } else {
1544                    path.line_to((bx + bw - right_width, by + bh - bottom_width));
1545                }
1546
1547                path.line_to((bx + bw - right_width - ibr, by + bh - bottom_width));
1548
1549                // Inner bottom-right corner
1550                if ibr > 0.0 && corner_br_shape == CornerShape::Round {
1551                    let (a, b, c, d, l, p, radius) = compute_smooth_corner(
1552                        ibr,
1553                        corner_br_smoothing,
1554                        bw - left_width - right_width,
1555                        bh - top_width - bottom_width,
1556                    );
1557                    path.cubic_to(
1558                        (bx + bw - right_width - (p - a), by + bh - bottom_width),
1559                        (bx + bw - right_width - (p - a - b), by + bh - bottom_width),
1560                        (bx + bw - right_width - (p - a - b - c), by + bh - bottom_width + d),
1561                    )
1562                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (-l, -l))
1563                    .cubic_to(
1564                        (bx + bw - right_width, by + bh - bottom_width - (p - a - b)),
1565                        (bx + bw - right_width, by + bh - bottom_width - (p - a)),
1566                        (
1567                            bx + bw - right_width,
1568                            by + bh - bottom_width - p.min((bh - top_width - bottom_width) * 0.5),
1569                        ),
1570                    );
1571                } else if ibr > 0.0 {
1572                    let p = ibr.min(
1573                        ((bh - top_width - bottom_width) * 0.5)
1574                            .min((bw - left_width - right_width) * 0.5),
1575                    );
1576                    path.line_to((bx + bw - right_width, by + bh - bottom_width - p));
1577                }
1578
1579                // Inner top-right corner
1580                if itr > 0.0 && corner_tr_shape == CornerShape::Round {
1581                    let (a, b, c, d, l, p, radius) = compute_smooth_corner(
1582                        itr,
1583                        corner_tr_smoothing,
1584                        bw - left_width - right_width,
1585                        bh - top_width - bottom_width,
1586                    );
1587                    path.line_to((bx + bw - right_width, by + top_width + p));
1588                    path.cubic_to(
1589                        (bx + bw - right_width, by + top_width + (p - a)),
1590                        (bx + bw - right_width, by + top_width + (p - a - b)),
1591                        (bx + bw - right_width + d, by + top_width + (p - a - b - c)),
1592                    )
1593                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (l, -l))
1594                    .cubic_to(
1595                        (bx + bw - right_width - (p - a - b), by + top_width),
1596                        (bx + bw - right_width - (p - a), by + top_width),
1597                        (
1598                            bx + bw - right_width - p.min((bw - left_width - right_width) * 0.5),
1599                            by + top_width,
1600                        ),
1601                    );
1602                } else if itr > 0.0 {
1603                    let p = itr.min(
1604                        ((bh - top_width - bottom_width) * 0.5)
1605                            .min((bw - left_width - right_width) * 0.5),
1606                    );
1607                    path.line_to((bx + bw - right_width, by + top_width + p));
1608                }
1609            }
1610            2 => {
1611                // Bottom side: bottom-right to bottom-left
1612                let obr = r_br.min(bw * 0.5).min(bh * 0.5);
1613                let obl = r_bl.min(bw * 0.5).min(bh * 0.5);
1614                let ibr = ir_br
1615                    .max(0.0)
1616                    .min((bw - left_width - right_width).max(0.0) * 0.5)
1617                    .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1618                let ibl = ir_bl
1619                    .max(0.0)
1620                    .min((bw - left_width - right_width).max(0.0) * 0.5)
1621                    .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1622
1623                path.move_to((bx + bw - right_width - ibr, by + bh - bottom_width));
1624
1625                // Outer bottom-right corner
1626                if obr > 0.0 && corner_br_shape == CornerShape::Round {
1627                    let (a, b, c, d, l, p, radius) =
1628                        compute_smooth_corner(obr, corner_br_smoothing, bw, bh);
1629                    let end_x = bx + bw - right_width;
1630                    path.line_to((end_x - p, by + bh - bottom_width));
1631                    path.cubic_to(
1632                        (end_x - (p - a), by + bh),
1633                        (end_x - (p - a - b), by + bh),
1634                        (end_x - (p - a - b - c) + d, by + bh),
1635                    )
1636                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (-l, l))
1637                    .cubic_to(
1638                        (end_x - (p - a - b), by + bh - bottom_width),
1639                        (end_x - (p - a), by + bh - bottom_width),
1640                        (end_x - p.min(bw * 0.5), by + bh - bottom_width),
1641                    );
1642                } else if obr > 0.0 {
1643                    let p = obr.min((bw * 0.5).min(bh * 0.5));
1644                    path.line_to((bx + bw - right_width - p, by + bh - bottom_width));
1645                } else {
1646                    path.line_to((bx + bw - right_width, by + bh - bottom_width));
1647                }
1648
1649                // Outer bottom-left corner
1650                if obl > 0.0 {
1651                    let p = obl.min((bw * 0.5).min(bh * 0.5));
1652                    path.line_to((bx + left_width + p, by + bh - bottom_width));
1653                    if corner_bl_shape == CornerShape::Round {
1654                        let (a, b, c, d, l, radius_val, _) =
1655                            compute_smooth_corner(obl, corner_bl_smoothing, bw, bh);
1656                        path.cubic_to(
1657                            (bx + left_width + (p - a), by + bh),
1658                            (bx + left_width + (p - a - b), by + bh),
1659                            (bx + left_width + (p - a - b - c) - d, by + bh),
1660                        )
1661                        .r_arc_to(
1662                            (radius_val, radius_val),
1663                            0.0,
1664                            ArcSize::Small,
1665                            PathDirection::CW,
1666                            (-l, -l),
1667                        )
1668                        .cubic_to(
1669                            (bx + left_width + (p - a - b), by + bh - bottom_width),
1670                            (bx + left_width + (p - a), by + bh - bottom_width),
1671                            (bx + left_width + p.min(bw * 0.5), by + bh - bottom_width),
1672                        );
1673                    }
1674                } else {
1675                    path.line_to((bx + left_width, by + bh - bottom_width));
1676                }
1677
1678                path.line_to((bx + left_width + ibl, by + bh - bottom_width));
1679
1680                // Inner bottom-left corner
1681                if ibl > 0.0 && corner_bl_shape == CornerShape::Round {
1682                    let (a, b, c, d, l, p, radius) = compute_smooth_corner(
1683                        ibl,
1684                        corner_bl_smoothing,
1685                        bw - left_width - right_width,
1686                        bh - top_width - bottom_width,
1687                    );
1688                    path.cubic_to(
1689                        (bx + left_width + (p - a), by + bh - bottom_width),
1690                        (bx + left_width + (p - a - b), by + bh - bottom_width),
1691                        (bx + left_width + (p - a - b - c), by + bh - bottom_width + d),
1692                    )
1693                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (l, -l))
1694                    .cubic_to(
1695                        (bx + left_width, by + bh - bottom_width - (p - a - b)),
1696                        (bx + left_width, by + bh - bottom_width - (p - a)),
1697                        (
1698                            bx + left_width,
1699                            by + bh - bottom_width - p.min((bh - top_width - bottom_width) * 0.5),
1700                        ),
1701                    );
1702                } else if ibl > 0.0 {
1703                    let p = ibl.min(
1704                        ((bh - top_width - bottom_width) * 0.5)
1705                            .min((bw - left_width - right_width) * 0.5),
1706                    );
1707                    path.line_to((bx + left_width, by + bh - bottom_width - p));
1708                }
1709
1710                // Inner bottom-right corner
1711                if ibr > 0.0 && corner_br_shape == CornerShape::Round {
1712                    let (a, b, c, d, l, p, radius) = compute_smooth_corner(
1713                        ibr,
1714                        corner_br_smoothing,
1715                        bw - left_width - right_width,
1716                        bh - top_width - bottom_width,
1717                    );
1718                    path.line_to((bx + bw - right_width - p, by + bh - bottom_width));
1719                    path.cubic_to(
1720                        (bx + bw - right_width - (p - a), by + bh - bottom_width),
1721                        (bx + bw - right_width - (p - a - b), by + bh - bottom_width),
1722                        (bx + bw - right_width - (p - a - b - c), by + bh - bottom_width + d),
1723                    )
1724                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (l, l))
1725                    .cubic_to(
1726                        (bx + bw - right_width, by + bh - bottom_width - (p - a - b)),
1727                        (bx + bw - right_width, by + bh - bottom_width - (p - a)),
1728                        (
1729                            bx + bw - right_width,
1730                            by + bh - bottom_width - p.min((bh - top_width - bottom_width) * 0.5),
1731                        ),
1732                    );
1733                } else if ibr > 0.0 {
1734                    let p = ibr.min(
1735                        ((bh - top_width - bottom_width) * 0.5)
1736                            .min((bw - left_width - right_width) * 0.5),
1737                    );
1738                    path.line_to((bx + bw - right_width, by + bh - bottom_width - p));
1739                }
1740            }
1741            3 => {
1742                // Left side: bottom-left to top-left
1743                let obl = r_bl.min(bw * 0.5).min(bh * 0.5);
1744                let otl = r_tl.min(bw * 0.5).min(bh * 0.5);
1745                let ibl = ir_bl
1746                    .max(0.0)
1747                    .min((bw - left_width - right_width).max(0.0) * 0.5)
1748                    .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1749                let itl = ir_tl
1750                    .max(0.0)
1751                    .min((bw - left_width - right_width).max(0.0) * 0.5)
1752                    .min((bh - top_width - bottom_width).max(0.0) * 0.5);
1753
1754                path.move_to((bx + left_width + ibl, by + bh - bottom_width));
1755
1756                // Outer bottom-left corner
1757                if obl > 0.0 && corner_bl_shape == CornerShape::Round {
1758                    let (a, b, c, d, l, p, radius) =
1759                        compute_smooth_corner(obl, corner_bl_smoothing, bw, bh);
1760                    path.line_to((bx + left_width, by + bh - bottom_width - p));
1761                    path.cubic_to(
1762                        (bx, by + bh - bottom_width - (p - a)),
1763                        (bx, by + bh - bottom_width - (p - a - b)),
1764                        (bx, by + bh - bottom_width - (p - a - b - c) - d),
1765                    )
1766                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (l, -l))
1767                    .cubic_to(
1768                        (bx + (p - a - b), by),
1769                        (bx + (p - a), by),
1770                        (bx + p.min(bw * 0.5), by),
1771                    );
1772                } else if obl > 0.0 {
1773                    let p = obl.min((bw * 0.5).min(bh * 0.5));
1774                    path.line_to((bx + left_width, by + bh - bottom_width - p));
1775                } else {
1776                    path.line_to((bx + left_width, by + bh - bottom_width));
1777                }
1778
1779                // Outer top-left corner
1780                if otl > 0.0 {
1781                    let p = otl.min((bw * 0.5).min(bh * 0.5));
1782                    path.line_to((bx + left_width, by + top_width + p));
1783                    if corner_tl_shape == CornerShape::Round {
1784                        let (a, b, c, d, l, radius_val, _) =
1785                            compute_smooth_corner(otl, corner_tl_smoothing, bw, bh);
1786                        path.cubic_to(
1787                            (bx, by + top_width + (p - a)),
1788                            (bx, by + top_width + (p - a - b)),
1789                            (bx, by + top_width + (p - a - b - c) - d),
1790                        )
1791                        .r_arc_to(
1792                            (radius_val, radius_val),
1793                            0.0,
1794                            ArcSize::Small,
1795                            PathDirection::CW,
1796                            (l, l),
1797                        )
1798                        .cubic_to(
1799                            (bx + (p - a - b), by),
1800                            (bx + (p - a), by),
1801                            (bx + p.min(bw * 0.5), by),
1802                        );
1803                    }
1804                } else {
1805                    path.line_to((bx + left_width, by + top_width));
1806                }
1807
1808                path.line_to((bx + left_width + itl, by + top_width));
1809
1810                // Inner top-left corner
1811                if itl > 0.0 && corner_tl_shape == CornerShape::Round {
1812                    let (a, b, c, d, l, p, radius) = compute_smooth_corner(
1813                        itl,
1814                        corner_tl_smoothing,
1815                        bw - left_width - right_width,
1816                        bh - top_width - bottom_width,
1817                    );
1818                    path.cubic_to(
1819                        (bx + left_width + (p - a), by + top_width),
1820                        (bx + left_width + (p - a - b), by + top_width),
1821                        (bx + left_width + (p - a - b - c), by + top_width + d),
1822                    )
1823                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (l, l))
1824                    .cubic_to(
1825                        (bx + left_width, by + top_width + (p - a - b)),
1826                        (bx + left_width, by + top_width + (p - a)),
1827                        (
1828                            bx + left_width,
1829                            by + top_width + p.min((bh - top_width - bottom_width) * 0.5),
1830                        ),
1831                    );
1832                } else if itl > 0.0 {
1833                    let p = itl.min(
1834                        ((bh - top_width - bottom_width) * 0.5)
1835                            .min((bw - left_width - right_width) * 0.5),
1836                    );
1837                    path.line_to((bx + left_width, by + top_width + p));
1838                }
1839
1840                // Inner bottom-left corner
1841                if ibl > 0.0 && corner_bl_shape == CornerShape::Round {
1842                    let (a, b, c, d, l, p, radius) = compute_smooth_corner(
1843                        ibl,
1844                        corner_bl_smoothing,
1845                        bw - left_width - right_width,
1846                        bh - top_width - bottom_width,
1847                    );
1848                    path.line_to((bx + left_width, by + bh - bottom_width - p));
1849                    path.cubic_to(
1850                        (bx + left_width, by + bh - bottom_width - (p - a)),
1851                        (bx + left_width, by + bh - bottom_width - (p - a - b)),
1852                        (bx + left_width - d, by + bh - bottom_width - (p - a - b - c)),
1853                    )
1854                    .r_arc_to((radius, radius), 0.0, ArcSize::Small, PathDirection::CW, (-l, -l))
1855                    .cubic_to(
1856                        (bx + left_width + (p - a - b), by + bh),
1857                        (bx + left_width + (p - a), by + bh),
1858                        (bx + left_width + p.min((bw - left_width - right_width) * 0.5), by + bh),
1859                    );
1860                } else if ibl > 0.0 {
1861                    let p = ibl.min(
1862                        ((bh - top_width - bottom_width) * 0.5)
1863                            .min((bw - left_width - right_width) * 0.5),
1864                    );
1865                    path.line_to((bx + left_width, by + bh - bottom_width - p));
1866                }
1867            }
1868            _ => return None,
1869        }
1870
1871        path.close();
1872        Some(path.detach())
1873    }
1874
1875    /// Draw background color or background image (including gradients) for the current view.
1876    pub fn draw_background(&mut self, canvas: &Canvas) {
1877        let background_color = self.background_color();
1878        if background_color.a() > 0 {
1879            let path = self.path();
1880
1881            let mut paint = Paint::default();
1882            paint.set_color(skia_safe::Color::from_argb(
1883                background_color.a(),
1884                background_color.r(),
1885                background_color.g(),
1886                background_color.b(),
1887            ));
1888            paint.set_anti_alias(true);
1889            canvas.draw_path(&path, &paint);
1890        }
1891
1892        self.draw_background_images(canvas);
1893    }
1894
1895    /// Draw the border of the current view.
1896    ///
1897    /// Supports individual side widths, colors, and styles. Borders are rendered from a
1898    /// ring path (outer minus inner), then split into per-side ownership paths using path
1899    /// intersections. Dashed/dotted use side-local center-line strokes clipped by the
1900    /// corresponding side ownership path.
1901    pub fn draw_border(&mut self, canvas: &Canvas) {
1902        let top_width = self.border_top_width();
1903        let right_width = self.border_right_width();
1904        let bottom_width = self.border_bottom_width();
1905        let left_width = self.border_left_width();
1906
1907        let top_color = self.border_top_color();
1908        let right_color = self.border_right_color();
1909        let bottom_color = self.border_bottom_color();
1910        let left_color = self.border_left_color();
1911
1912        let top_style = self.border_top_style();
1913        let right_style = self.border_right_style();
1914        let bottom_style = self.border_bottom_style();
1915        let left_style = self.border_left_style();
1916
1917        let top_vis = top_width > 0.0 && top_color.a() > 0 && top_style != BorderStyleKeyword::None;
1918        let right_vis =
1919            right_width > 0.0 && right_color.a() > 0 && right_style != BorderStyleKeyword::None;
1920        let bottom_vis =
1921            bottom_width > 0.0 && bottom_color.a() > 0 && bottom_style != BorderStyleKeyword::None;
1922        let left_vis =
1923            left_width > 0.0 && left_color.a() > 0 && left_style != BorderStyleKeyword::None;
1924
1925        if !top_vis && !right_vis && !bottom_vis && !left_vis {
1926            return;
1927        }
1928
1929        let bounds = self.bounds();
1930        let bx = bounds.x;
1931        let by = bounds.y;
1932        let bw = bounds.w;
1933        let bh = bounds.h;
1934
1935        // Outer corner radii (from style).
1936        let r_tl = self.corner_top_left_radius();
1937        let r_tr = self.corner_top_right_radius();
1938        let r_br = self.corner_bottom_right_radius();
1939        let r_bl = self.corner_bottom_left_radius();
1940
1941        // Inner corner radii: outer radius minus the thicker adjacent border, clamped to 0.
1942        let ir_tl = (r_tl - top_width.max(left_width)).max(0.0);
1943        let ir_tr = (r_tr - top_width.max(right_width)).max(0.0);
1944        let ir_br = (r_br - bottom_width.max(right_width)).max(0.0);
1945        let ir_bl = (r_bl - bottom_width.max(left_width)).max(0.0);
1946
1947        // Inner rect dimensions.
1948        let inner_w = (bw - left_width - right_width).max(0.0);
1949        let inner_h = (bh - top_width - bottom_width).max(0.0);
1950        let inner_is_empty = inner_w <= 0.0 || inner_h <= 0.0;
1951
1952        let corner_shapes = (
1953            self.corner_top_left_shape(),
1954            self.corner_top_right_shape(),
1955            self.corner_bottom_right_shape(),
1956            self.corner_bottom_left_shape(),
1957        );
1958        let corner_smoothing = (
1959            self.corner_top_left_smoothing(),
1960            self.corner_top_right_smoothing(),
1961            self.corner_bottom_right_smoothing(),
1962            self.corner_bottom_left_smoothing(),
1963        );
1964        let uniform_borders = top_width == right_width
1965            && right_width == bottom_width
1966            && bottom_width == left_width
1967            && top_color == right_color
1968            && right_color == bottom_color
1969            && bottom_color == left_color
1970            && top_style == right_style
1971            && right_style == bottom_style
1972            && bottom_style == left_style;
1973        let uniform_corners = r_tl == r_tr
1974            && r_tr == r_br
1975            && r_br == r_bl
1976            && corner_shapes.0 == corner_shapes.1
1977            && corner_shapes.1 == corner_shapes.2
1978            && corner_shapes.2 == corner_shapes.3
1979            && corner_smoothing.0 == corner_smoothing.1
1980            && corner_smoothing.1 == corner_smoothing.2
1981            && corner_smoothing.2 == corner_smoothing.3;
1982        let exact_round_solid = !inner_is_empty
1983            && corner_shapes.0 == CornerShape::Round
1984            && corner_shapes.1 == CornerShape::Round
1985            && corner_shapes.2 == CornerShape::Round
1986            && corner_shapes.3 == CornerShape::Round
1987            && corner_smoothing.0 == 0.0
1988            && corner_smoothing.1 == 0.0
1989            && corner_smoothing.2 == 0.0
1990            && corner_smoothing.3 == 0.0;
1991        let exact_bevel_solid = !inner_is_empty
1992            && corner_shapes.0 == CornerShape::Bevel
1993            && corner_shapes.1 == CornerShape::Bevel
1994            && corner_shapes.2 == CornerShape::Bevel
1995            && corner_shapes.3 == CornerShape::Bevel
1996            && corner_smoothing.0 == 0.0
1997            && corner_smoothing.1 == 0.0
1998            && corner_smoothing.2 == 0.0
1999            && corner_smoothing.3 == 0.0;
2000        let exact_smooth_solid = !inner_is_empty
2001            && corner_shapes.0 == CornerShape::Round
2002            && corner_shapes.1 == CornerShape::Round
2003            && corner_shapes.2 == CornerShape::Round
2004            && corner_shapes.3 == CornerShape::Round
2005            && (corner_smoothing.0 > 0.0
2006                || corner_smoothing.1 > 0.0
2007                || corner_smoothing.2 > 0.0
2008                || corner_smoothing.3 > 0.0);
2009
2010        // Outer path in world coordinates.
2011        let outer_path = self
2012            .build_path_with_corners(
2013                bounds,
2014                (0.0, 0.0),
2015                (r_tl, r_tr, r_br, r_bl),
2016                corner_shapes,
2017                corner_smoothing,
2018            )
2019            .make_offset(bounds.top_left());
2020
2021        // Inner path in world coordinates uses the same corner shape/smoothing pipeline as outer.
2022        let inner_path = if inner_is_empty {
2023            None
2024        } else {
2025            let inner_bounds = BoundingBox::from_min_max(0.0, 0.0, inner_w, inner_h);
2026            Some(
2027                self.build_path_with_corners(
2028                    inner_bounds,
2029                    (0.0, 0.0),
2030                    (ir_tl, ir_tr, ir_br, ir_bl),
2031                    corner_shapes,
2032                    corner_smoothing,
2033                )
2034                .make_offset((bx + left_width, by + top_width)),
2035            )
2036        };
2037
2038        let ring_path = if let Some(inner) = inner_path.as_ref() {
2039            outer_path
2040                .op(inner, skia_safe::PathOp::Difference)
2041                .unwrap_or_else(|| outer_path.clone())
2042        } else {
2043            outer_path.clone()
2044        };
2045
2046        if uniform_borders && uniform_corners {
2047            match top_style {
2048                BorderStyleKeyword::Dashed | BorderStyleKeyword::Dotted => {
2049                    canvas.save();
2050                    canvas.clip_path(&ring_path, ClipOp::Intersect, true);
2051
2052                    let half = top_width * 0.5;
2053                    let stroke_path = self
2054                        .build_path_with_corners(
2055                            bounds,
2056                            (-half, -half),
2057                            (r_tl, r_tr, r_br, r_bl),
2058                            corner_shapes,
2059                            corner_smoothing,
2060                        )
2061                        .make_offset(bounds.top_left());
2062
2063                    let mut paint = Paint::default();
2064                    paint.set_style(PaintStyle::Stroke);
2065                    paint.set_color(top_color);
2066                    paint.set_stroke_width(top_width);
2067                    if top_style == BorderStyleKeyword::Dashed {
2068                        paint.set_path_effect(PathEffect::dash(&[top_width * 2.0, top_width], 0.0));
2069                    } else {
2070                        paint.set_path_effect(PathEffect::dash(&[0.0, top_width * 2.0], 0.0));
2071                        paint.set_stroke_cap(skia_safe::PaintCap::Round);
2072                    }
2073                    paint.set_anti_alias(true);
2074                    canvas.draw_path(&stroke_path, &paint);
2075                    canvas.restore();
2076                    return;
2077                }
2078
2079                _ => {
2080                    let mut paint = Paint::default();
2081                    paint.set_color(top_color);
2082                    paint.set_anti_alias(true);
2083                    canvas.draw_path(&ring_path, &paint);
2084                    return;
2085                }
2086            }
2087        }
2088
2089        // Side ownership masks split corner ownership at the outer->inner corner join line.
2090        let mask_top: [Point; 4] = [
2091            Point::new(bx, by),
2092            Point::new(bx + bw, by),
2093            Point::new(bx + bw - right_width, by + top_width),
2094            Point::new(bx + left_width, by + top_width),
2095        ];
2096        let mask_right: [Point; 4] = [
2097            Point::new(bx + bw - right_width, by + top_width),
2098            Point::new(bx + bw, by),
2099            Point::new(bx + bw, by + bh),
2100            Point::new(bx + bw - right_width, by + bh - bottom_width),
2101        ];
2102        let mask_bottom: [Point; 4] = [
2103            Point::new(bx + left_width, by + bh - bottom_width),
2104            Point::new(bx + bw - right_width, by + bh - bottom_width),
2105            Point::new(bx + bw, by + bh),
2106            Point::new(bx, by + bh),
2107        ];
2108        let mask_left: [Point; 4] = [
2109            Point::new(bx, by),
2110            Point::new(bx + left_width, by + top_width),
2111            Point::new(bx + left_width, by + bh - bottom_width),
2112            Point::new(bx, by + bh),
2113        ];
2114
2115        let sides: [(usize, bool, Color, BorderStyleKeyword, f32, [Point; 4]); 4] = [
2116            (0, top_vis, top_color, top_style, top_width, mask_top),
2117            (1, right_vis, right_color, right_style, right_width, mask_right),
2118            (2, bottom_vis, bottom_color, bottom_style, bottom_width, mask_bottom),
2119            (3, left_vis, left_color, left_style, left_width, mask_left),
2120        ];
2121
2122        for (side_ix, vis, color, style, side_width, mask_pts) in sides {
2123            if !vis {
2124                continue;
2125            }
2126
2127            let mut side_mask = PathBuilder::new();
2128            side_mask.move_to(mask_pts[0]);
2129            side_mask.line_to(mask_pts[1]);
2130            side_mask.line_to(mask_pts[2]);
2131            side_mask.line_to(mask_pts[3]);
2132            side_mask.close();
2133            let side_mask = side_mask.detach();
2134            let Some(side_region) = ring_path.op(&side_mask, skia_safe::PathOp::Intersect) else {
2135                continue;
2136            };
2137
2138            match style {
2139                BorderStyleKeyword::Dashed | BorderStyleKeyword::Dotted => {
2140                    canvas.save();
2141                    canvas.clip_path(&side_region, ClipOp::Intersect, true);
2142
2143                    let stroke_path = if exact_round_solid {
2144                        Self::round_corner_side_stroke_path(
2145                            side_ix,
2146                            bounds,
2147                            (r_tl, r_tr, r_br, r_bl),
2148                        )
2149                        .unwrap_or_else(|| {
2150                            let mut side_path = PathBuilder::new();
2151                            let (sx, sy, ex, ey) = match side_ix {
2152                                0 => (
2153                                    bx + left_width * 0.5,
2154                                    by + top_width * 0.5,
2155                                    bx + bw - right_width * 0.5,
2156                                    by + top_width * 0.5,
2157                                ),
2158                                1 => (
2159                                    bx + bw - right_width * 0.5,
2160                                    by + top_width * 0.5,
2161                                    bx + bw - right_width * 0.5,
2162                                    by + bh - bottom_width * 0.5,
2163                                ),
2164                                2 => (
2165                                    bx + left_width * 0.5,
2166                                    by + bh - bottom_width * 0.5,
2167                                    bx + bw - right_width * 0.5,
2168                                    by + bh - bottom_width * 0.5,
2169                                ),
2170                                _ => (
2171                                    bx + left_width * 0.5,
2172                                    by + top_width * 0.5,
2173                                    bx + left_width * 0.5,
2174                                    by + bh - bottom_width * 0.5,
2175                                ),
2176                            };
2177                            side_path.move_to((sx, sy));
2178                            side_path.line_to((ex, ey));
2179                            side_path.detach()
2180                        })
2181                    } else if exact_bevel_solid {
2182                        Self::bevel_corner_side_stroke_path(
2183                            side_ix,
2184                            bounds,
2185                            (r_tl, r_tr, r_br, r_bl),
2186                        )
2187                        .unwrap_or_else(|| {
2188                            let mut side_path = PathBuilder::new();
2189                            let (sx, sy, ex, ey) = match side_ix {
2190                                0 => (
2191                                    bx + left_width * 0.5,
2192                                    by + top_width * 0.5,
2193                                    bx + bw - right_width * 0.5,
2194                                    by + top_width * 0.5,
2195                                ),
2196                                1 => (
2197                                    bx + bw - right_width * 0.5,
2198                                    by + top_width * 0.5,
2199                                    bx + bw - right_width * 0.5,
2200                                    by + bh - bottom_width * 0.5,
2201                                ),
2202                                2 => (
2203                                    bx + left_width * 0.5,
2204                                    by + bh - bottom_width * 0.5,
2205                                    bx + bw - right_width * 0.5,
2206                                    by + bh - bottom_width * 0.5,
2207                                ),
2208                                _ => (
2209                                    bx + left_width * 0.5,
2210                                    by + top_width * 0.5,
2211                                    bx + left_width * 0.5,
2212                                    by + bh - bottom_width * 0.5,
2213                                ),
2214                            };
2215                            side_path.move_to((sx, sy));
2216                            side_path.line_to((ex, ey));
2217                            side_path.detach()
2218                        })
2219                    } else if exact_smooth_solid {
2220                        self.build_path_with_corners(
2221                            bounds,
2222                            (-side_width * 0.5, -side_width * 0.5),
2223                            (r_tl, r_tr, r_br, r_bl),
2224                            corner_shapes,
2225                            corner_smoothing,
2226                        )
2227                        .make_offset(bounds.top_left())
2228                    } else {
2229                        let mut side_path = PathBuilder::new();
2230                        let (sx, sy, ex, ey) = match side_ix {
2231                            0 => (
2232                                bx + left_width * 0.5,
2233                                by + top_width * 0.5,
2234                                bx + bw - right_width * 0.5,
2235                                by + top_width * 0.5,
2236                            ),
2237                            1 => (
2238                                bx + bw - right_width * 0.5,
2239                                by + top_width * 0.5,
2240                                bx + bw - right_width * 0.5,
2241                                by + bh - bottom_width * 0.5,
2242                            ),
2243                            2 => (
2244                                bx + left_width * 0.5,
2245                                by + bh - bottom_width * 0.5,
2246                                bx + bw - right_width * 0.5,
2247                                by + bh - bottom_width * 0.5,
2248                            ),
2249                            _ => (
2250                                bx + left_width * 0.5,
2251                                by + top_width * 0.5,
2252                                bx + left_width * 0.5,
2253                                by + bh - bottom_width * 0.5,
2254                            ),
2255                        };
2256                        side_path.move_to((sx, sy));
2257                        side_path.line_to((ex, ey));
2258                        side_path.detach()
2259                    };
2260                    let mut paint = Paint::default();
2261                    paint.set_style(PaintStyle::Stroke);
2262                    paint.set_color(color);
2263                    paint.set_stroke_width(side_width);
2264                    if style == BorderStyleKeyword::Dashed {
2265                        paint.set_path_effect(PathEffect::dash(
2266                            &[side_width * 2.0, side_width],
2267                            0.0,
2268                        ));
2269                    } else {
2270                        paint.set_path_effect(PathEffect::dash(&[0.0, side_width * 2.0], 0.0));
2271                        paint.set_stroke_cap(skia_safe::PaintCap::Round);
2272                    }
2273                    paint.set_anti_alias(true);
2274                    canvas.draw_path(&stroke_path, &paint);
2275                    canvas.restore();
2276                }
2277                _ => {
2278                    let exact_side_path = if exact_round_solid {
2279                        Self::round_corner_side_path(
2280                            side_ix,
2281                            bounds,
2282                            (r_tl, r_tr, r_br, r_bl),
2283                            (ir_tl, ir_tr, ir_br, ir_bl),
2284                            (top_width, right_width, bottom_width, left_width),
2285                        )
2286                    } else if exact_bevel_solid {
2287                        Self::bevel_corner_side_path(
2288                            side_ix,
2289                            bounds,
2290                            (r_tl, r_tr, r_br, r_bl),
2291                            (ir_tl, ir_tr, ir_br, ir_bl),
2292                            (top_width, right_width, bottom_width, left_width),
2293                        )
2294                    } else if exact_smooth_solid {
2295                        Self::smooth_corner_side_path(
2296                            side_ix,
2297                            bounds,
2298                            (r_tl, r_tr, r_br, r_bl),
2299                            (ir_tl, ir_tr, ir_br, ir_bl),
2300                            corner_shapes,
2301                            corner_smoothing,
2302                            (top_width, right_width, bottom_width, left_width),
2303                        )
2304                    } else {
2305                        None
2306                    };
2307                    // Solid and any other opaque style: fill this side's owned part of the ring.
2308                    let mut paint = Paint::default();
2309                    paint.set_color(color);
2310                    paint.set_anti_alias(true);
2311                    if let Some(path) = exact_side_path {
2312                        let Some(constrained_path) =
2313                            path.op(&ring_path, skia_safe::PathOp::Intersect)
2314                        else {
2315                            continue;
2316                        };
2317                        canvas.draw_path(&constrained_path, &paint);
2318                    } else {
2319                        canvas.draw_path(&side_region, &paint);
2320                    }
2321                }
2322            }
2323        }
2324    }
2325
2326    /// Draw the outline of the current view.
2327    pub fn draw_outline(&mut self, canvas: &Canvas) {
2328        let outline_width = self.outline_width();
2329        let outline_color = self.outline_color();
2330
2331        if outline_width > 0.0 && outline_color.a() != 0 {
2332            let outline_offset = self.outline_offset();
2333
2334            let bounds = self.bounds();
2335
2336            let half_outline_width = outline_width / 2.0;
2337            let mut outline_path = self.build_path(
2338                bounds,
2339                (half_outline_width + outline_offset, half_outline_width + outline_offset),
2340            );
2341
2342            outline_path = outline_path.make_offset(self.bounds().top_left());
2343
2344            let mut outline_paint = Paint::default();
2345            outline_paint.set_color(outline_color);
2346            outline_paint.set_stroke_width(outline_width);
2347            outline_paint.set_style(PaintStyle::Stroke);
2348            outline_paint.set_anti_alias(true);
2349            canvas.draw_path(&outline_path, &outline_paint);
2350        }
2351    }
2352
2353    /// Draw shadows for the current view.
2354    pub fn draw_shadows(&mut self, canvas: &Canvas) {
2355        if let Some(shadows) = self.shadows() {
2356            if shadows.is_empty() {
2357                return;
2358            }
2359
2360            let bounds = self.bounds();
2361
2362            let path = self.build_path(bounds, (0.0, 0.0)).make_offset(bounds.top_left());
2363
2364            for shadow in shadows.iter().rev() {
2365                let shadow_color = shadow.color.unwrap_or_default();
2366
2367                let shadow_x_offset = shadow.x_offset.to_px().unwrap_or(0.0) * self.scale_factor();
2368                let shadow_y_offset = shadow.y_offset.to_px().unwrap_or(0.0) * self.scale_factor();
2369                let spread_radius =
2370                    shadow.spread_radius.as_ref().and_then(|l| l.to_px()).unwrap_or(0.0)
2371                        * self.scale_factor();
2372
2373                let blur_radius =
2374                    shadow.blur_radius.as_ref().and_then(|br| br.to_px()).unwrap_or(0.0);
2375
2376                if shadow_color.a() == 0
2377                    || (shadow_x_offset == 0.0
2378                        && shadow_y_offset == 0.0
2379                        && spread_radius == 0.0
2380                        && blur_radius == 0.0)
2381                {
2382                    continue;
2383                }
2384
2385                let mut shadow_paint = Paint::default();
2386
2387                let outset = if shadow.inset { -spread_radius } else { spread_radius };
2388
2389                shadow_paint.set_style(PaintStyle::Fill);
2390
2391                let mut shadow_path = self.build_path(bounds, (outset, outset));
2392                shadow_path = shadow_path.make_offset(bounds.top_left());
2393
2394                shadow_paint.set_color(shadow_color);
2395
2396                if blur_radius > 0.0 {
2397                    shadow_paint.set_mask_filter(MaskFilter::blur(
2398                        BlurStyle::Normal,
2399                        blur_radius / 2.0,
2400                        false,
2401                    ));
2402                }
2403
2404                shadow_path = shadow_path.make_offset((shadow_x_offset, shadow_y_offset));
2405
2406                if shadow.inset {
2407                    shadow_path = path.op(&shadow_path, skia_safe::PathOp::Difference).unwrap();
2408                }
2409
2410                canvas.save();
2411                canvas.clip_path(
2412                    &path,
2413                    if shadow.inset { ClipOp::Intersect } else { ClipOp::Difference },
2414                    true,
2415                );
2416                canvas.draw_path(&shadow_path, &shadow_paint);
2417                canvas.restore();
2418            }
2419        }
2420    }
2421
2422    /// Draw background images (including gradients) for the current view.
2423    fn draw_background_images(&mut self, canvas: &Canvas) {
2424        let bounds = self.bounds();
2425
2426        if self.background_images().is_some() {
2427            let path = self.path();
2428            if let Some(images) = self.background_images() {
2429                let image_sizes = self.background_size();
2430                let image_positions = self.background_position();
2431                let image_repeats = self.background_repeat();
2432
2433                for (index, image) in images.iter().enumerate() {
2434                    match image {
2435                        ImageOrGradient::Gradient(gradient) => match gradient {
2436                            Gradient::Linear(linear_gradient) => {
2437                                let (start, end, parent_length) = match linear_gradient.direction {
2438                                    LineDirection::Horizontal(horizontal_keyword) => {
2439                                        match horizontal_keyword {
2440                                            HorizontalPositionKeyword::Left => (
2441                                                bounds.center_right(),
2442                                                bounds.center_left(),
2443                                                bounds.width(),
2444                                            ),
2445
2446                                            HorizontalPositionKeyword::Right => (
2447                                                bounds.center_left(),
2448                                                bounds.center_right(),
2449                                                bounds.width(),
2450                                            ),
2451                                        }
2452                                    }
2453
2454                                    LineDirection::Vertical(vertical_keyword) => {
2455                                        match vertical_keyword {
2456                                            VerticalPositionKeyword::Top => (
2457                                                bounds.center_bottom(),
2458                                                bounds.center_top(),
2459                                                bounds.height(),
2460                                            ),
2461
2462                                            VerticalPositionKeyword::Bottom => (
2463                                                bounds.center_top(),
2464                                                bounds.center_bottom(),
2465                                                bounds.height(),
2466                                            ),
2467                                        }
2468                                    }
2469
2470                                    LineDirection::Corner { horizontal, vertical } => {
2471                                        match (horizontal, vertical) {
2472                                            (
2473                                                HorizontalPositionKeyword::Right,
2474                                                VerticalPositionKeyword::Bottom,
2475                                            ) => (
2476                                                bounds.top_left(),
2477                                                bounds.bottom_right(),
2478                                                bounds.diagonal(),
2479                                            ),
2480
2481                                            (
2482                                                HorizontalPositionKeyword::Right,
2483                                                VerticalPositionKeyword::Top,
2484                                            ) => (
2485                                                bounds.bottom_left(),
2486                                                bounds.top_right(),
2487                                                bounds.diagonal(),
2488                                            ),
2489
2490                                            _ => (bounds.top_left(), bounds.bottom_right(), 0.0),
2491                                        }
2492                                    }
2493
2494                                    LineDirection::Angle(angle) => {
2495                                        let angle_rad = angle.to_radians();
2496                                        let start_x = bounds.x
2497                                            + ((angle_rad.sin() * bounds.w) - bounds.w) / -2.0;
2498                                        let end_x = bounds.x
2499                                            + ((angle_rad.sin() * bounds.w) + bounds.w) / 2.0;
2500                                        let start_y = bounds.y
2501                                            + ((angle_rad.cos() * bounds.h) + bounds.h) / 2.0;
2502                                        let end_y = bounds.y
2503                                            + ((angle_rad.cos() * bounds.h) - bounds.h) / -2.0;
2504
2505                                        let x = (end_x - start_x).abs();
2506                                        let y = (end_y - start_y).abs();
2507
2508                                        let dist = (x * x + y * y).sqrt();
2509
2510                                        ((start_x, start_y), (end_x, end_y), dist)
2511                                    }
2512                                };
2513
2514                                let num_stops = linear_gradient.stops.len();
2515
2516                                let mut stops = linear_gradient
2517                                    .stops
2518                                    .iter()
2519                                    .enumerate()
2520                                    .map(|(index, stop)| {
2521                                        let pos = if let Some(pos) = &stop.position {
2522                                            pos.to_pixels(parent_length, self.scale_factor())
2523                                                / parent_length
2524                                        } else {
2525                                            index as f32 / (num_stops - 1) as f32
2526                                        };
2527                                        (pos, skia_safe::Color::from(stop.color))
2528                                    })
2529                                    .collect::<Vec<_>>();
2530
2531                                // Insert a stop at the front if the first stop is not at 0.
2532                                if let Some(first) = stops.first() {
2533                                    if first.0 != 0.0 {
2534                                        stops.insert(0, (0.0, first.1));
2535                                    }
2536                                }
2537
2538                                // Insert a stop at the end if the last stop is not at 1.0.
2539                                if let Some(last) = stops.last() {
2540                                    if last.0 != 1.0 {
2541                                        stops.push((1.0, last.1));
2542                                    }
2543                                }
2544
2545                                let (offsets, colors): (Vec<f32>, Vec<skia_safe::Color>) =
2546                                    stops.into_iter().unzip();
2547                                let colors4f: Vec<skia_safe::Color4f> =
2548                                    colors.iter().copied().map(Into::into).collect();
2549
2550                                let gradient_colors =
2551                                    skia_safe::gradient_shader::GradientColors::new(
2552                                        &colors4f,
2553                                        Some(&offsets[..]),
2554                                        TileMode::Clamp,
2555                                        None,
2556                                    );
2557                                let gradient = skia_safe::gradient_shader::Gradient::new(
2558                                    gradient_colors,
2559                                    skia_safe::gradient_shader::Interpolation::default(),
2560                                );
2561                                let shader = skia_safe::shaders::linear_gradient(
2562                                    (Point::from(start), Point::from(end)),
2563                                    &gradient,
2564                                    None,
2565                                );
2566
2567                                let mut paint = Paint::default();
2568                                paint.set_shader(shader);
2569                                paint.set_anti_alias(true);
2570
2571                                canvas.draw_path(&path, &paint);
2572                            }
2573
2574                            Gradient::Radial(radial_gradient) => {
2575                                let num_stops = radial_gradient.stops.len();
2576
2577                                let mut stops = radial_gradient
2578                                    .stops
2579                                    .iter()
2580                                    .enumerate()
2581                                    .map(|(index, stop)| {
2582                                        let pos = if let Some(pos) = &stop.position {
2583                                            pos.to_pixels(bounds.width(), self.scale_factor())
2584                                                / bounds.width()
2585                                        } else {
2586                                            index as f32 / (num_stops - 1) as f32
2587                                        };
2588
2589                                        (pos, skia_safe::Color::from(stop.color))
2590                                    })
2591                                    .collect::<Vec<_>>();
2592
2593                                // Insert a stop at the front if the first stop is not at 0.
2594                                if let Some(first) = stops.first() {
2595                                    if first.0 != 0.0 {
2596                                        stops.insert(0, (0.0, first.1));
2597                                    }
2598                                }
2599
2600                                // Insert a stop at the end if the last stop is not at 1.0.
2601                                if let Some(last) = stops.last() {
2602                                    if last.0 != 1.0 {
2603                                        stops.push((1.0, last.1));
2604                                    }
2605                                }
2606
2607                                let (offsets, colors): (Vec<f32>, Vec<skia_safe::Color>) =
2608                                    stops.into_iter().unzip();
2609                                let colors4f: Vec<skia_safe::Color4f> =
2610                                    colors.iter().copied().map(Into::into).collect();
2611
2612                                let gradient_colors =
2613                                    skia_safe::gradient_shader::GradientColors::new(
2614                                        &colors4f,
2615                                        Some(&offsets[..]),
2616                                        TileMode::Clamp,
2617                                        None,
2618                                    );
2619                                let gradient = skia_safe::gradient_shader::Gradient::new(
2620                                    gradient_colors,
2621                                    skia_safe::gradient_shader::Interpolation::default(),
2622                                );
2623                                let shader = skia_safe::shaders::radial_gradient(
2624                                    (Point::from(bounds.center()), bounds.w.max(bounds.h)),
2625                                    &gradient,
2626                                    None,
2627                                );
2628
2629                                let mut paint = Paint::default();
2630                                paint.set_shader(shader);
2631                                paint.set_anti_alias(true);
2632
2633                                canvas.draw_path(&path, &paint);
2634                            }
2635
2636                            _ => {}
2637                        },
2638
2639                        ImageOrGradient::Image(image_name) => {
2640                            if let Some(image_id) = self.resource_manager.image_ids.get(image_name)
2641                            {
2642                                if let Some(image) = self.resource_manager.images.get(image_id) {
2643                                    match &image.image {
2644                                        ImageOrSvg::Image(image) => {
2645                                            let image_width = image.width();
2646                                            let image_height = image.height();
2647                                            let (width, height) = if let Some(background_size) =
2648                                                image_sizes.get(index)
2649                                            {
2650                                                match background_size {
2651                                                    BackgroundSize::Explicit { width, height } => {
2652                                                        let w = match width {
2653                                                LengthPercentageOrAuto::LengthPercentage(
2654                                                    length,
2655                                                ) => {
2656                                                    length.to_pixels(bounds.w, self.scale_factor())
2657                                                }
2658                                                LengthPercentageOrAuto::Auto => image_width as f32,
2659                                            };
2660
2661                                                        let h = match height {
2662                                                LengthPercentageOrAuto::LengthPercentage(
2663                                                    length,
2664                                                ) => {
2665                                                    length.to_pixels(bounds.h, self.scale_factor())
2666                                                }
2667                                                LengthPercentageOrAuto::Auto => image_height as f32,
2668                                            };
2669
2670                                                        (w, h)
2671                                                    }
2672
2673                                                    BackgroundSize::Contain => {
2674                                                        let image_ratio = image_width as f32
2675                                                            / image_height as f32;
2676                                                        let container_ratio = bounds.w / bounds.h;
2677
2678                                                        let (w, h) =
2679                                                            if image_ratio > container_ratio {
2680                                                                (bounds.w, bounds.w / image_ratio)
2681                                                            } else {
2682                                                                (bounds.h * image_ratio, bounds.h)
2683                                                            };
2684
2685                                                        (w, h)
2686                                                    }
2687
2688                                                    BackgroundSize::Cover => {
2689                                                        let image_ratio = image_width as f32
2690                                                            / image_height as f32;
2691                                                        let container_ratio = bounds.w / bounds.h;
2692
2693                                                        let (w, h) =
2694                                                            if image_ratio < container_ratio {
2695                                                                (bounds.w, bounds.w / image_ratio)
2696                                                            } else {
2697                                                                (bounds.h * image_ratio, bounds.h)
2698                                                            };
2699
2700                                                        (w, h)
2701                                                    }
2702                                                }
2703                                            } else {
2704                                                (image_width as f32, image_height as f32)
2705                                            };
2706
2707                                            let position = image_positions
2708                                                .get(index)
2709                                                .cloned()
2710                                                .or_else(|| image_positions.last().cloned())
2711                                                .unwrap_or_default();
2712
2713                                            let posx =
2714                                                position.x.to_length_or_percentage().to_pixels(
2715                                                    bounds.width() - width,
2716                                                    self.scale_factor(),
2717                                                );
2718                                            let posy =
2719                                                position.y.to_length_or_percentage().to_pixels(
2720                                                    bounds.height() - height,
2721                                                    self.scale_factor(),
2722                                                );
2723                                            let repeat = image_repeats
2724                                                .get(index)
2725                                                .copied()
2726                                                .or_else(|| image_repeats.last().copied())
2727                                                .unwrap_or(BackgroundRepeat::Repeat);
2728
2729                                            if width <= 0.0 || height <= 0.0 {
2730                                                continue;
2731                                            }
2732
2733                                            let mut paint = Paint::default();
2734                                            paint.set_anti_alias(true);
2735
2736                                            let origin_x = bounds.left() + posx;
2737                                            let origin_y = bounds.top() + posy;
2738
2739                                            let mut start_x = origin_x;
2740                                            let mut start_y = origin_y;
2741
2742                                            if matches!(
2743                                                repeat,
2744                                                BackgroundRepeat::Repeat
2745                                                    | BackgroundRepeat::RepeatX
2746                                            ) {
2747                                                let tiles_to_left =
2748                                                    ((bounds.left() - origin_x) / width).floor();
2749                                                start_x = origin_x + tiles_to_left * width;
2750                                                if start_x > bounds.left() {
2751                                                    start_x -= width;
2752                                                }
2753                                            }
2754
2755                                            if matches!(
2756                                                repeat,
2757                                                BackgroundRepeat::Repeat
2758                                                    | BackgroundRepeat::RepeatY
2759                                            ) {
2760                                                let tiles_to_top =
2761                                                    ((bounds.top() - origin_y) / height).floor();
2762                                                start_y = origin_y + tiles_to_top * height;
2763                                                if start_y > bounds.top() {
2764                                                    start_y -= height;
2765                                                }
2766                                            }
2767
2768                                            canvas.save();
2769                                            canvas.clip_path(&path, ClipOp::Intersect, true);
2770
2771                                            match repeat {
2772                                                BackgroundRepeat::NoRepeat => {
2773                                                    let dst = Rect::new(
2774                                                        origin_x,
2775                                                        origin_y,
2776                                                        origin_x + width,
2777                                                        origin_y + height,
2778                                                    );
2779                                                    canvas.draw_image_rect_with_sampling_options(
2780                                                        image,
2781                                                        None,
2782                                                        dst,
2783                                                        SamplingOptions::default(),
2784                                                        &paint,
2785                                                    );
2786                                                }
2787
2788                                                BackgroundRepeat::RepeatX => {
2789                                                    let mut x = start_x;
2790                                                    while x < bounds.right() {
2791                                                        let dst = Rect::new(
2792                                                            x,
2793                                                            origin_y,
2794                                                            x + width,
2795                                                            origin_y + height,
2796                                                        );
2797                                                        canvas
2798                                                            .draw_image_rect_with_sampling_options(
2799                                                                image,
2800                                                                None,
2801                                                                dst,
2802                                                                SamplingOptions::default(),
2803                                                                &paint,
2804                                                            );
2805                                                        x += width;
2806                                                    }
2807                                                }
2808
2809                                                BackgroundRepeat::RepeatY => {
2810                                                    let mut y = start_y;
2811                                                    while y < bounds.bottom() {
2812                                                        let dst = Rect::new(
2813                                                            origin_x,
2814                                                            y,
2815                                                            origin_x + width,
2816                                                            y + height,
2817                                                        );
2818                                                        canvas
2819                                                            .draw_image_rect_with_sampling_options(
2820                                                                image,
2821                                                                None,
2822                                                                dst,
2823                                                                SamplingOptions::default(),
2824                                                                &paint,
2825                                                            );
2826                                                        y += height;
2827                                                    }
2828                                                }
2829
2830                                                BackgroundRepeat::Repeat => {
2831                                                    let mut y = start_y;
2832                                                    while y < bounds.bottom() {
2833                                                        let mut x = start_x;
2834                                                        while x < bounds.right() {
2835                                                            let dst = Rect::new(
2836                                                                x,
2837                                                                y,
2838                                                                x + width,
2839                                                                y + height,
2840                                                            );
2841                                                            canvas.draw_image_rect_with_sampling_options(
2842                                                                image,
2843                                                                None,
2844                                                                dst,
2845                                                                SamplingOptions::default(),
2846                                                                &paint,
2847                                                            );
2848                                                            x += width;
2849                                                        }
2850                                                        y += height;
2851                                                    }
2852                                                }
2853                                            }
2854
2855                                            canvas.restore();
2856                                        }
2857
2858                                        ImageOrSvg::Svg(svg) => {
2859                                            canvas.save_layer(&SaveLayerRec::default());
2860                                            canvas.translate((bounds.x, bounds.y));
2861                                            let (scale_x, scale_y) = (
2862                                                bounds.width() / svg.inner().fContainerSize.fWidth,
2863                                                bounds.height()
2864                                                    / svg.inner().fContainerSize.fHeight,
2865                                            );
2866
2867                                            if scale_x.is_finite() && scale_y.is_finite() {
2868                                                canvas.scale((scale_x, scale_y));
2869                                            } else {
2870                                                svg.clone().set_container_size((
2871                                                    bounds.width(),
2872                                                    bounds.height(),
2873                                                ));
2874                                            }
2875
2876                                            svg.render(canvas);
2877
2878                                            if let Some(color) = self.style.fill.get_resolved(
2879                                                self.current,
2880                                                &self.style.custom_color_props,
2881                                            ) {
2882                                                // Escape hatch for multi-color SVGs (logos,
2883                                                // illustrations) that would otherwise be flooded
2884                                                // by the root-level `fill: var(--foreground)`
2885                                                // tint in the default theme. Setting
2886                                                // `fill: transparent` on such elements makes the
2887                                                // SrcIn pass a no-op, so the SVG's own path
2888                                                // fills render untouched. Icon SVGs that want
2889                                                // the tint still work — they set a
2890                                                // non-transparent fill explicitly. See #636.
2891                                                if color.a() != 0 {
2892                                                    let mut paint = Paint::default();
2893                                                    paint.set_anti_alias(true);
2894                                                    paint.set_blend_mode(
2895                                                        skia_safe::BlendMode::SrcIn,
2896                                                    );
2897                                                    paint.set_color(color);
2898                                                    canvas.draw_paint(&paint);
2899                                                }
2900                                            }
2901                                            canvas.restore();
2902                                        }
2903                                    }
2904                                }
2905                            }
2906                        }
2907                    }
2908                }
2909            }
2910        }
2911    }
2912
2913    /// Draw any text for the current view.
2914    pub fn draw_text(&mut self, canvas: &Canvas) {
2915        if let Some(paragraph) = self.text_context.text_paragraphs.get(self.current) {
2916            let bounds = self.bounds();
2917
2918            let alignment = self.alignment();
2919
2920            let (mut top, _) = match alignment {
2921                Alignment::TopLeft => (0.0, 0.0),
2922                Alignment::TopCenter => (0.0, 0.5),
2923                Alignment::TopRight => (0.0, 1.0),
2924                Alignment::Left => (0.5, 0.0),
2925                Alignment::Center => (0.5, 0.5),
2926                Alignment::Right => (0.5, 1.0),
2927                Alignment::BottomLeft => (1.0, 0.0),
2928                Alignment::BottomCenter => (1.0, 0.5),
2929                Alignment::BottomRight => (1.0, 1.0),
2930            };
2931
2932            let padding_top = match self.padding_top() {
2933                Units::Pixels(val) => val,
2934                _ => 0.0,
2935            };
2936
2937            let padding_bottom = match self.padding_bottom() {
2938                Units::Pixels(val) => val,
2939                _ => 0.0,
2940            };
2941
2942            top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
2943
2944            let mut padding_left = match self.padding_left() {
2945                Units::Pixels(val) => val,
2946                _ => 0.0,
2947            };
2948
2949            let mut padding_right = match self.padding_right() {
2950                Units::Pixels(val) => val,
2951                _ => 0.0,
2952            };
2953
2954            if resolved_text_direction(self.style, self.current) == Direction::RightToLeft {
2955                std::mem::swap(&mut padding_left, &mut padding_right);
2956            }
2957
2958            paragraph.paint(
2959                canvas,
2960                ((bounds.x + padding_left).round(), (bounds.y + padding_top + top).round()),
2961            );
2962        }
2963    }
2964}
2965
2966impl DataContext for DrawContext<'_> {
2967    fn try_data<T: 'static>(&self) -> Option<&T> {
2968        // Return data for the static model.
2969        if let Some(t) = <dyn Any>::downcast_ref::<T>(&()) {
2970            return Some(t);
2971        }
2972
2973        for entity in self.current.parent_iter(self.tree) {
2974            // Return model data.
2975            if let Some(models) = self.models.get(&entity) {
2976                if let Some(model) = models.get(&TypeId::of::<T>()) {
2977                    return model.downcast_ref::<T>();
2978                }
2979            }
2980
2981            // Return view data.
2982            if let Some(view_handler) = self.views.get(&entity) {
2983                if let Some(data) = view_handler.downcast_ref::<T>() {
2984                    return Some(data);
2985                }
2986            }
2987        }
2988
2989        None
2990    }
2991}
2992
2993// Helper function for computing a rounded corner with variable smoothing
2994fn compute_smooth_corner(
2995    corner_radius: f32,
2996    smoothing: f32,
2997    width: f32,
2998    height: f32,
2999) -> (f32, f32, f32, f32, f32, f32, f32) {
3000    let max_p = f32::min(width, height) / 2.0;
3001    let corner_radius = f32::min(corner_radius, max_p);
3002
3003    let p = f32::min((1.0 + smoothing) * corner_radius, max_p);
3004
3005    let angle_alpha: f32;
3006    let angle_beta: f32;
3007
3008    if corner_radius <= max_p / 2.0 {
3009        angle_alpha = 45.0 * smoothing;
3010        angle_beta = 90.0 * (1.0 - smoothing);
3011    } else {
3012        let diff_ratio = (corner_radius - max_p / 2.0) / (max_p / 2.0);
3013
3014        angle_alpha = 45.0 * smoothing * (1.0 - diff_ratio);
3015        angle_beta = 90.0 * (1.0 - smoothing * (1.0 - diff_ratio));
3016    }
3017
3018    let angle_theta = (90.0 - angle_beta) / 2.0;
3019    let dist_p3_p4 = corner_radius * (angle_theta / 2.0).to_radians().tan();
3020
3021    let l = (angle_beta / 2.0).to_radians().sin() * corner_radius * SQRT_2;
3022    let c = dist_p3_p4 * angle_alpha.to_radians().cos();
3023    let d = c * angle_alpha.to_radians().tan();
3024    let b = (p - l - c - d) / 3.0;
3025    let a = 2.0 * b;
3026
3027    (a, b, c, d, l, p, corner_radius)
3028}