Skip to main content

cranpose_ui_graphics/
geometry.rs

1//! Geometric primitives: Point, Size, Rect, Insets, Path
2
3use std::{ops::AddAssign, rc::Rc};
4
5use crate::{
6    ArcRecordArgs, Brush, Color, ColorFilter, CommandRecorder, CommandRecording, DrawGroup,
7    DrawTransform, GroupMarker, ImageBitmap, ImageSampling, PathFillRule, Trapezoid,
8    normalized_band,
9    path::{DrawStyle, Path},
10    stroke::{LineGeometry, Stroke},
11    typography::{
12        DrawTextMeasurer, DrawTextStyle, TextAlign, TextMeasurement, TextVerticalAlign,
13        estimate_text_measurement,
14    },
15};
16
17const VECTOR_PATH_MASK_CACHE_ENTRIES: usize = 96;
18const VECTOR_PATH_MASK_CACHE_BYTES: usize = 8 * 1024 * 1024;
19
20pub(crate) struct VectorPathMaskCache {
21    entries: Vec<(u64, ImageBitmap)>,
22    bytes: usize,
23}
24
25impl VectorPathMaskCache {
26    pub(crate) const fn new() -> Self {
27        Self {
28            entries: Vec::new(),
29            bytes: 0,
30        }
31    }
32
33    fn get(&mut self, key: u64) -> Option<ImageBitmap> {
34        let index = self.entries.iter().position(|(seen, _)| *seen == key)?;
35        let entry = self.entries.remove(index);
36        let image = entry.1.clone();
37        self.entries.push(entry);
38        Some(image)
39    }
40
41    fn put(&mut self, key: u64, image: ImageBitmap) {
42        let bytes = image.pixels().len();
43        if bytes > VECTOR_PATH_MASK_CACHE_BYTES {
44            return;
45        }
46        self.bytes += bytes;
47        self.entries.push((key, image));
48        while self.entries.len() > VECTOR_PATH_MASK_CACHE_ENTRIES
49            || self.bytes > VECTOR_PATH_MASK_CACHE_BYTES
50        {
51            let (_, dropped) = self.entries.remove(0);
52            self.bytes = self.bytes.saturating_sub(dropped.pixels().len());
53        }
54    }
55}
56
57/// The key of `path`'s raster: a gradient's colors depend on the rect it
58/// resolves against, a solid color's do not.
59/// Which side of a path's edge a coverage mask covers: its fill, or the
60/// rest of the mask's rect, as a clip's mask erases it.
61#[derive(Clone, Copy, PartialEq, Eq)]
62enum PathSide {
63    Inside,
64    Outside,
65}
66
67fn vector_path_mask_key(
68    path: &crate::VectorPath,
69    origin: Point,
70    mask_size: (usize, usize),
71    brush: &Brush,
72    brush_rect: Rect,
73    side: PathSide,
74) -> u64 {
75    use std::hash::Hasher;
76    let mut hasher = crate::fx_hash::FxHasher::default();
77    hasher.write_u8(path.fill_rule() as u8);
78    hasher.write_u8(side as u8);
79    hasher.write_u32(origin.x.to_bits());
80    hasher.write_u32(origin.y.to_bits());
81    hasher.write_usize(mask_size.0);
82    hasher.write_usize(mask_size.1);
83    crate::render_hash::hash_brush(brush, &mut hasher);
84    if !matches!(brush, Brush::Solid(_)) {
85        for value in [
86            brush_rect.x,
87            brush_rect.y,
88            brush_rect.width,
89            brush_rect.height,
90        ] {
91            hasher.write_u32(value.to_bits());
92        }
93    }
94    for subpath in path.subpaths() {
95        hasher.write_usize(subpath.len());
96        for point in subpath {
97            hasher.write_u32(point.x.to_bits());
98            hasher.write_u32(point.y.to_bits());
99        }
100    }
101    hasher.finish()
102}
103
104/// How many raster pixels a vector path's fill gets per logical pixel along
105/// each axis.
106const VECTOR_PATH_SUPERSAMPLE: f32 = 2.0;
107
108/// `path`'s fill, or with [`PathSide::Outside`] the rest of the mask, over
109/// `size` raster pixels from `origin`: one alpha byte of a solid color per
110/// pixel, or for a gradient the color it paints at each pixel's center with
111/// the pixel's coverage.
112fn rasterize_vector_path(
113    path: &crate::VectorPath,
114    brush: &Brush,
115    brush_rect: Rect,
116    origin: Point,
117    (width, height): (usize, usize),
118    side: PathSide,
119) -> Option<ImageBitmap> {
120    let byte = |value: f32| (value.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
121    let covered = |opacity: f32| {
122        let mut mask = path.coverage_mask(width, height, origin, VECTOR_PATH_SUPERSAMPLE, 1.0);
123        for value in &mut mask {
124            let share = match side {
125                PathSide::Inside => *value,
126                PathSide::Outside => u8::MAX - *value,
127            };
128            *value = (f32::from(share) * opacity + 0.5) as u8;
129        }
130        mask
131    };
132    if let Brush::Solid(color) = brush {
133        let mask = match side {
134            PathSide::Inside => path.coverage_mask(
135                width,
136                height,
137                origin,
138                VECTOR_PATH_SUPERSAMPLE,
139                color.3.clamp(0.0, 1.0),
140            ),
141            PathSide::Outside => covered(color.3.clamp(0.0, 1.0)),
142        };
143        let rgb = [byte(color.0), byte(color.1), byte(color.2)];
144        return ImageBitmap::from_alpha8(width as u32, height as u32, rgb, mask).ok();
145    }
146    let coverage = covered(1.0);
147    let mut pixels = Vec::with_capacity(coverage.len() * 4);
148    for (row, line) in coverage.chunks_exact(width).enumerate() {
149        let y = origin.y + (row as f32 + 0.5) / VECTOR_PATH_SUPERSAMPLE;
150        for (column, &covered) in line.iter().enumerate() {
151            if covered == 0 {
152                pixels.extend_from_slice(&[0; 4]);
153                continue;
154            }
155            let x = origin.x + (column as f32 + 0.5) / VECTOR_PATH_SUPERSAMPLE;
156            let color = brush.color_at(brush_rect, Point::new(x, y));
157            let alpha = (color.3.clamp(0.0, 1.0) * f32::from(covered) + 0.5) as u8;
158            pixels.extend_from_slice(&[byte(color.0), byte(color.1), byte(color.2), alpha]);
159        }
160    }
161    ImageBitmap::from_rgba8(width as u32, height as u32, pixels).ok()
162}
163
164fn vector_path_mask_cache_get(key: u64) -> Option<ImageBitmap> {
165    crate::thread_state::with(|state| state.vector_path_masks.borrow_mut().get(key))
166}
167
168fn vector_path_mask_cache_put(key: u64, image: ImageBitmap) {
169    crate::thread_state::with(|state| state.vector_path_masks.borrow_mut().put(key, image));
170}
171
172/// A position in two dimensions, in logical pixels. Compose's `Offset`.
173#[derive(Clone, Copy, Debug, PartialEq, Default)]
174pub struct Point {
175    /// Horizontal coordinate, in logical pixels, growing to the right.
176    pub x: f32,
177    /// Vertical coordinate, in logical pixels, growing downward.
178    pub y: f32,
179}
180
181impl Point {
182    /// Creates a point from its coordinates.
183    pub const fn new(x: f32, y: f32) -> Self {
184        Self { x, y }
185    }
186
187    /// The origin point `(0.0, 0.0)`.
188    pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
189}
190
191/// A width and a height, in logical pixels.
192#[derive(Clone, Copy, Debug, PartialEq, Default)]
193pub struct Size {
194    /// Width, in logical pixels.
195    pub width: f32,
196    /// Height, in logical pixels.
197    pub height: f32,
198}
199
200impl Size {
201    /// Creates a size from a width and a height.
202    pub const fn new(width: f32, height: f32) -> Self {
203        Self { width, height }
204    }
205
206    /// A size of zero width and zero height.
207    pub const ZERO: Size = Size {
208        width: 0.0,
209        height: 0.0,
210    };
211}
212
213/// An axis-aligned rectangle given by its top-left corner and its size, in logical pixels.
214#[derive(Clone, Copy, Debug, PartialEq)]
215pub struct Rect {
216    /// Left edge, in logical pixels.
217    pub x: f32,
218    /// Top edge, in logical pixels.
219    pub y: f32,
220    /// Width, in logical pixels.
221    pub width: f32,
222    /// Height, in logical pixels.
223    pub height: f32,
224}
225
226impl Rect {
227    /// The rect that holds nothing: what two clips that do not overlap
228    /// resolve to, so a clip that meets nothing stays a clip instead of
229    /// lifting.
230    pub const EMPTY: Rect = Rect {
231        x: 0.0,
232        y: 0.0,
233        width: 0.0,
234        height: 0.0,
235    };
236
237    /// Creates a rectangle with its top-left corner at `origin` and the given `size`.
238    pub fn from_origin_size(origin: Point, size: Size) -> Self {
239        Self {
240            x: origin.x,
241            y: origin.y,
242            width: size.width,
243            height: size.height,
244        }
245    }
246
247    /// Creates a rectangle of `size` with its top-left corner at the origin.
248    pub fn from_size(size: Size) -> Self {
249        Self {
250            x: 0.0,
251            y: 0.0,
252            width: size.width,
253            height: size.height,
254        }
255    }
256
257    /// Returns the rectangle moved by `dx` horizontally and `dy` vertically.
258    pub fn translate(&self, dx: f32, dy: f32) -> Self {
259        Self {
260            x: self.x + dx,
261            y: self.y + dy,
262            width: self.width,
263            height: self.height,
264        }
265    }
266
267    /// Whether the point `(x, y)` lies inside the rectangle, edges included.
268    pub fn contains(&self, x: f32, y: f32) -> bool {
269        x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
270    }
271
272    /// Whether the rect covers no area, so nothing clipped to it can paint.
273    pub fn is_empty(&self) -> bool {
274        self.width <= 0.0 || self.height <= 0.0
275    }
276
277    /// Returns the intersection of two rectangles, or `None` if they don't overlap.
278    pub fn intersect(&self, other: Rect) -> Option<Rect> {
279        let left = self.x.max(other.x);
280        let top = self.y.max(other.y);
281        let right = (self.x + self.width).min(other.x + other.width);
282        let bottom = (self.y + self.height).min(other.y + other.height);
283        let width = right - left;
284        let height = bottom - top;
285        if width <= 0.0 || height <= 0.0 {
286            None
287        } else {
288            Some(Rect {
289                x: left,
290                y: top,
291                width,
292                height,
293            })
294        }
295    }
296
297    /// Returns the smallest rectangle that contains both rectangles.
298    pub fn union(&self, other: Rect) -> Rect {
299        let left = self.x.min(other.x);
300        let top = self.y.min(other.y);
301        let right = (self.x + self.width).max(other.x + other.width);
302        let bottom = (self.y + self.height).max(other.y + other.height);
303        Rect {
304            x: left,
305            y: top,
306            width: (right - left).max(0.0),
307            height: (bottom - top).max(0.0),
308        }
309    }
310}
311
312/// Padding values for each edge of a rectangle.
313#[derive(Clone, Copy, Debug, Default, PartialEq)]
314pub struct EdgeInsets {
315    /// Inset on the left edge, in logical pixels.
316    pub left: f32,
317    /// Inset on the top edge, in logical pixels.
318    pub top: f32,
319    /// Inset on the right edge, in logical pixels.
320    pub right: f32,
321    /// Inset on the bottom edge, in logical pixels.
322    pub bottom: f32,
323}
324
325impl EdgeInsets {
326    /// Creates insets with `all` on every edge.
327    pub fn uniform(all: f32) -> Self {
328        Self {
329            left: all,
330            top: all,
331            right: all,
332            bottom: all,
333        }
334    }
335
336    /// Creates insets with `horizontal` on the left and right and 0.0 on the top and bottom.
337    pub fn horizontal(horizontal: f32) -> Self {
338        Self {
339            left: horizontal,
340            right: horizontal,
341            ..Self::default()
342        }
343    }
344
345    /// Creates insets with `vertical` on the top and bottom and 0.0 on the left and right.
346    pub fn vertical(vertical: f32) -> Self {
347        Self {
348            top: vertical,
349            bottom: vertical,
350            ..Self::default()
351        }
352    }
353
354    /// Creates insets with `horizontal` on the left and right and `vertical` on the top and bottom.
355    pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
356        Self {
357            left: horizontal,
358            right: horizontal,
359            top: vertical,
360            bottom: vertical,
361        }
362    }
363
364    /// Creates insets from separate left, top, right and bottom values.
365    pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
366        Self {
367            left,
368            top,
369            right,
370            bottom,
371        }
372    }
373
374    /// Whether all four insets are 0.0.
375    pub fn is_zero(&self) -> bool {
376        self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
377    }
378
379    /// The sum of the left and right insets.
380    pub fn horizontal_sum(&self) -> f32 {
381        self.left + self.right
382    }
383
384    /// The sum of the top and bottom insets.
385    pub fn vertical_sum(&self) -> f32 {
386        self.top + self.bottom
387    }
388
389    /// The part of a `size` rect these insets leave, at the leading insets;
390    /// it never shrinks below zero.
391    pub fn inset_rect(&self, size: Size) -> Rect {
392        Rect {
393            x: self.left,
394            y: self.top,
395            width: (size.width - self.horizontal_sum()).max(0.0),
396            height: (size.height - self.vertical_sum()).max(0.0),
397        }
398    }
399}
400
401impl AddAssign for EdgeInsets {
402    fn add_assign(&mut self, rhs: Self) {
403        self.left += rhs.left;
404        self.top += rhs.top;
405        self.right += rhs.right;
406        self.bottom += rhs.bottom;
407    }
408}
409
410/// The radius of each corner of a rounded rectangle, in logical pixels.
411#[derive(Clone, Copy, Debug, Default, PartialEq)]
412pub struct CornerRadii {
413    /// Radius of the top-left corner, in logical pixels.
414    pub top_left: f32,
415    /// Radius of the top-right corner, in logical pixels.
416    pub top_right: f32,
417    /// Radius of the bottom-right corner, in logical pixels.
418    pub bottom_right: f32,
419    /// Radius of the bottom-left corner, in logical pixels.
420    pub bottom_left: f32,
421}
422
423impl CornerRadii {
424    /// Creates radii with the same value on all four corners.
425    pub fn uniform(radius: f32) -> Self {
426        Self {
427            top_left: radius,
428            top_right: radius,
429            bottom_right: radius,
430            bottom_left: radius,
431        }
432    }
433}
434
435/// A rectangle shape with rounded corners. Compose's `RoundedCornerShape`.
436#[derive(Clone, Copy, Debug, PartialEq)]
437pub struct RoundedCornerShape {
438    radii: CornerRadii,
439}
440
441impl RoundedCornerShape {
442    /// Creates a shape with a separate radius for each corner, in logical pixels.
443    pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
444        Self {
445            radii: CornerRadii {
446                top_left,
447                top_right,
448                bottom_right,
449                bottom_left,
450            },
451        }
452    }
453
454    /// Creates a shape with the same radius on all four corners.
455    pub fn uniform(radius: f32) -> Self {
456        Self {
457            radii: CornerRadii::uniform(radius),
458        }
459    }
460
461    /// Creates a shape from a `CornerRadii` value.
462    pub fn with_radii(radii: CornerRadii) -> Self {
463        Self { radii }
464    }
465
466    /// Returns the radii for a rectangle of `width` by `height`, with each radius limited to half
467    /// the width and half the height so the corners fit.
468    pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
469        let mut resolved = self.radii;
470        let max_width = (width / 2.0).max(0.0);
471        let max_height = (height / 2.0).max(0.0);
472        resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
473        resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
474        resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
475        resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
476        resolved
477    }
478
479    /// The radii as given, before any clamping to a size.
480    pub fn radii(&self) -> CornerRadii {
481        self.radii
482    }
483}
484
485/// The pivot point for scaling and rotating a layer, given as fractions of its size. Compose's
486/// `TransformOrigin`.
487#[derive(Clone, Copy, Debug, PartialEq)]
488pub struct TransformOrigin {
489    /// Horizontal pivot as a fraction of the layer's width, from 0.0 (left) to 1.0 (right).
490    pub pivot_fraction_x: f32,
491    /// Vertical pivot as a fraction of the layer's height, from 0.0 (top) to 1.0 (bottom).
492    pub pivot_fraction_y: f32,
493}
494
495impl TransformOrigin {
496    /// Creates an origin from fractions of the layer's width and height.
497    pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
498        Self {
499            pivot_fraction_x,
500            pivot_fraction_y,
501        }
502    }
503
504    /// Pivot at the center of the layer.
505    pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
506}
507
508impl Default for TransformOrigin {
509    fn default() -> Self {
510        Self::CENTER
511    }
512}
513
514/// The outline of a graphics layer, used for clipping and for its shadow.
515#[derive(Clone, Copy, Debug, Default, PartialEq)]
516pub enum LayerShape {
517    /// Square corners.
518    #[default]
519    Rectangle,
520    /// Corners rounded by the given shape.
521    Rounded(RoundedCornerShape),
522}
523
524/// The transform, opacity, clip, shadow and effect properties of a layer. Compose's
525/// `GraphicsLayerScope`.
526#[derive(Clone, Debug, PartialEq)]
527pub struct GraphicsLayer {
528    /// Opacity of the layer, from 0.0 (transparent) to 1.0 (opaque).
529    pub alpha: f32,
530    /// Uniform scale factor for both axes. 1.0 leaves the size unchanged.
531    pub scale: f32,
532    /// Horizontal scale factor, multiplied with `scale`. 1.0 leaves the width unchanged.
533    pub scale_x: f32,
534    /// Vertical scale factor, multiplied with `scale`. 1.0 leaves the height unchanged.
535    pub scale_y: f32,
536    /// Rotation around the x axis, in degrees.
537    pub rotation_x: f32,
538    /// Rotation around the y axis, in degrees.
539    pub rotation_y: f32,
540    /// Rotation around the z axis, in degrees. Positive values turn clockwise on screen.
541    pub rotation_z: f32,
542    /// Distance of the camera from the layer plane. It sets the strength of the perspective for
543    /// `rotation_x` and `rotation_y`; larger values flatten it.
544    pub camera_distance: f32,
545    /// The point that scaling and rotation turn around, as fractions of the layer's size.
546    pub transform_origin: TransformOrigin,
547    /// Horizontal translation, in layout coordinates.
548    pub translation_x: f32,
549    /// Vertical translation, in layout coordinates.
550    pub translation_y: f32,
551    /// Where the layer sits in 3D: it shows a scene, or its content is drawn
552    /// on a mesh in its parent's scene. `None` keeps it flat. The scale,
553    /// rotation and translation above do not apply to a mesh surface, which
554    /// its own transform places.
555    pub space: Option<std::sync::Arc<crate::space::LayerSpace>>,
556    /// Shadow elevation in layout units. A value of 0.0 or less draws no shadow.
557    pub shadow_elevation: f32,
558    /// Color of the ambient shadow.
559    pub ambient_shadow_color: Color,
560    /// Color of the spot shadow.
561    pub spot_shadow_color: Color,
562    /// Outline used for clipping and for the shadow.
563    pub shape: LayerShape,
564    /// Whether content is clipped to `shape`.
565    pub clip: bool,
566    /// Whether the layer renders offscreen before it is composited.
567    pub compositing_strategy: CompositingStrategy,
568    /// How the layer's content combines with what is already drawn behind it.
569    pub blend_mode: BlendMode,
570    /// Color filter applied to the layer's content, or `None` for none.
571    pub color_filter: Option<ColorFilter>,
572    /// Filter applied to the layer's rendered content as a whole, such as a blur. `None` applies
573    /// none.
574    pub render_effect: Option<crate::render_effect::RenderEffect>,
575    /// Filters the pixels behind this layer. The result shares the layer's
576    /// group opacity and blend mode with its content; `ModulateAlpha` applies
577    /// opacity independently to the backdrop and each content draw.
578    pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
579}
580
581impl GraphicsLayer {
582    /// Default properties for an untransformed, opaque layer without effects.
583    pub const DEFAULT: Self = Self {
584        alpha: 1.0,
585        scale: 1.0,
586        scale_x: 1.0,
587        scale_y: 1.0,
588        rotation_x: 0.0,
589        rotation_y: 0.0,
590        rotation_z: 0.0,
591        camera_distance: 8.0,
592        transform_origin: TransformOrigin::CENTER,
593        translation_x: 0.0,
594        translation_y: 0.0,
595        space: None,
596        shadow_elevation: 0.0,
597        ambient_shadow_color: Color::BLACK,
598        spot_shadow_color: Color::BLACK,
599        shape: LayerShape::Rectangle,
600        clip: false,
601        compositing_strategy: CompositingStrategy::Auto,
602        blend_mode: BlendMode::SrcOver,
603        color_filter: None,
604        render_effect: None,
605        backdrop_effect: None,
606    };
607
608    /// The alpha an isolated layer is composited at: an **eight-bit** one,
609    /// truncated.
610    ///
611    /// The platform never composites a layer at a float alpha. HWUI hands an
612    /// isolated `RenderNode` to the rasterizer as
613    /// `canvas->saveLayerAlpha(&bounds, (int)(properties.getAlpha() * 255))`
614    /// (`frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp`,
615    /// `setViewProperties`), and `(int)` truncates — 0.5 composites at 127/255,
616    /// not at 128/255. The fraction below that byte is gone before a single pixel
617    /// is blended, so anything that keeps it lands a level out wherever the byte
618    /// and the float fall on opposite sides of a half.
619    ///
620    /// The sibling branch is a float on purpose: where `getHasOverlappingRendering()`
621    /// is false HWUI takes `*alphaMultiplier = properties.getAlpha()` and folds it
622    /// into each draw without ever making a byte of it. That is what
623    /// `CompositingStrategy::ModulateAlpha` names.
624    ///
625    /// Truncating here and **rounding** in [`Color::srgb_8bit`] is not an
626    /// inconsistency: they are different call sites in the platform. A colour's own
627    /// alpha is snapped by `Color`'s constructor, which adds the half; a layer's
628    /// alpha is snapped by HWUI's cast, which does not. Anything modelling a faded
629    /// layer without allocating one — a canvas drawing a list row's fade by hand,
630    /// say — wants this rule and not the other.
631    pub fn composite_alpha_8bit(alpha: f32) -> f32 {
632        (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
633    }
634}
635
636impl Default for GraphicsLayer {
637    fn default() -> Self {
638        Self::DEFAULT
639    }
640}
641
642/// Blend mode used for draw primitives.
643///
644/// This mirrors Jetpack Compose's blend-mode vocabulary while the renderer
645/// currently guarantees `SrcOver` and `DstOut` behavior.
646#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
647pub enum BlendMode {
648    /// Clears the destination to transparent where the source draws.
649    Clear,
650    /// Replaces the destination with the source.
651    Src,
652    /// Keeps the destination and discards the source.
653    Dst,
654    /// Draws the source over the destination. This is the default.
655    #[default]
656    SrcOver,
657    /// Draws the destination over the source.
658    DstOver,
659    /// Keeps the source only where the destination is opaque.
660    SrcIn,
661    /// Keeps the destination only where the source is opaque.
662    DstIn,
663    /// Keeps the source only where the destination is transparent.
664    SrcOut,
665    /// Keeps the destination only where the source is transparent.
666    DstOut,
667    /// Draws the source over the destination, only where the destination is opaque.
668    SrcAtop,
669    /// Draws the destination over the source, only where the source is opaque.
670    DstAtop,
671    /// Keeps the source and the destination where the other is transparent.
672    Xor,
673    /// Adds the source and destination colors and alphas.
674    Plus,
675    /// Multiplies the source and destination colors and alphas.
676    Modulate,
677    /// Inverts, multiplies and inverts again, which lightens the result.
678    Screen,
679    /// Multiplies or screens depending on the destination color, which raises contrast.
680    Overlay,
681    /// Keeps the darker color of each channel.
682    Darken,
683    /// Keeps the lighter color of each channel.
684    Lighten,
685    /// Brightens the destination by the source color.
686    ColorDodge,
687    /// Darkens the destination by the source color.
688    ColorBurn,
689    /// Multiplies or screens depending on the source color.
690    HardLight,
691    /// A softer version of `HardLight`: darkens or lightens depending on the source color.
692    SoftLight,
693    /// Subtracts the darker color from the lighter one in each channel.
694    Difference,
695    /// Like `Difference` with lower contrast.
696    Exclusion,
697    /// Multiplies the source and destination colors, which darkens the result.
698    Multiply,
699    /// Takes the hue of the source with the saturation and luminosity of the destination.
700    Hue,
701    /// Takes the saturation of the source with the hue and luminosity of the destination.
702    Saturation,
703    /// Takes the hue and saturation of the source with the luminosity of the destination.
704    Color,
705    /// Takes the luminosity of the source with the hue and saturation of the destination.
706    Luminosity,
707}
708
709impl BlendMode {
710    /// Every mode in declaration order, so `mode as u32` indexes it.
711    pub const ALL: [BlendMode; 29] = [
712        BlendMode::Clear,
713        BlendMode::Src,
714        BlendMode::Dst,
715        BlendMode::SrcOver,
716        BlendMode::DstOver,
717        BlendMode::SrcIn,
718        BlendMode::DstIn,
719        BlendMode::SrcOut,
720        BlendMode::DstOut,
721        BlendMode::SrcAtop,
722        BlendMode::DstAtop,
723        BlendMode::Xor,
724        BlendMode::Plus,
725        BlendMode::Modulate,
726        BlendMode::Screen,
727        BlendMode::Overlay,
728        BlendMode::Darken,
729        BlendMode::Lighten,
730        BlendMode::ColorDodge,
731        BlendMode::ColorBurn,
732        BlendMode::HardLight,
733        BlendMode::SoftLight,
734        BlendMode::Difference,
735        BlendMode::Exclusion,
736        BlendMode::Multiply,
737        BlendMode::Hue,
738        BlendMode::Saturation,
739        BlendMode::Color,
740        BlendMode::Luminosity,
741    ];
742}
743
744/// How a [`GraphicsLayer`] is composited into its parent. Compose's `CompositingStrategy`.
745#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
746pub enum CompositingStrategy {
747    /// Use renderer heuristics (default).
748    #[default]
749    Auto,
750    /// Render this layer to an offscreen target, then composite.
751    Offscreen,
752    /// Multiply alpha on source colors without allocating an offscreen layer.
753    ModulateAlpha,
754}
755
756/// One recorded draw operation, in scope coordinates. Renderers consume these in draw order.
757#[derive(Clone, Debug, PartialEq)]
758pub enum DrawPrimitive {
759    /// Marker emitted by `draw_content()` inside `draw_with_content`.
760    /// This is consumed by the modifier pipeline and never rendered directly.
761    Content,
762    /// Wrapper to associate a draw primitive with a non-default blend mode.
763    Blend {
764        /// The primitive drawn with `blend_mode`.
765        primitive: Box<DrawPrimitive>,
766        /// How the wrapped primitive combines with what is already drawn.
767        blend_mode: BlendMode,
768    },
769    /// A rectangle, filled or stroked.
770    Rect {
771        /// The rectangle in scope coordinates.
772        rect: Rect,
773        /// Paint for the fill or the stroke.
774        brush: Brush,
775        /// `None` fills the rect; `Some` strokes its outline, centered on the
776        /// edge (so it bleeds `width / 2` outside `rect`).
777        stroke: Option<Stroke>,
778    },
779    /// A rectangle with rounded corners, filled or stroked.
780    RoundRect {
781        /// The rectangle in scope coordinates.
782        rect: Rect,
783        /// Paint for the fill or the stroke.
784        brush: Brush,
785        /// Corner radii of the rectangle, in logical pixels.
786        radii: CornerRadii,
787        /// `None` fills the rounded rect; `Some` strokes its outline, centered
788        /// on the edge.
789        stroke: Option<Stroke>,
790    },
791    /// A circular band: a stroked arc, or a filled annular sector / pie wedge.
792    ///
793    /// Angles are radians, `0` = +X, increasing **clockwise** on screen (see
794    /// `crate::stroke` for the full convention).
795    ///
796    /// * `stroke = Some(_)` — the band is `radius ± width/2`, its ends shaped
797    ///   by the stroke cap. `inner_radius` is ignored.
798    /// * `stroke = None` — the band is `inner_radius ..= radius` with flat
799    ///   (butt) radial ends; `inner_radius = 0` is a filled pie wedge.
800    Arc {
801        /// Tight bounding box of the rendered band, caps included. Kept as the
802        /// first field (like every other variant) so bbox/culling/clip logic
803        /// treats an arc exactly like any other primitive.
804        rect: Rect,
805        /// Paint for the arc.
806        brush: Brush,
807        /// Center of the circle the arc lies on, in scope coordinates.
808        center: Point,
809        /// Radius of the stroked centerline, or the outer radius of a filled sector, in logical
810        /// pixels.
811        radius: f32,
812        /// Where the arc starts, in radians. `0` points along +X and angles increase clockwise on
813        /// screen.
814        start_angle: f32,
815        /// Angle swept from `start_angle`, in radians. A negative value sweeps counter-clockwise.
816        sweep_angle: f32,
817        /// `Some` strokes the arc; `None` fills the sector between `inner_radius` and `radius`.
818        stroke: Option<Stroke>,
819        /// `> 0` turns a filled wedge into an annular sector.
820        inner_radius: f32,
821    },
822    /// A stroked straight segment from `start` to `end`, its ends shaped by
823    /// the stroke's cap: Compose's `drawLine`.
824    Line {
825        /// The box of the two ends; the stroke reaches half its width past
826        /// it, as a stroked rect's does past its rect.
827        rect: Rect,
828        /// Paint for the line.
829        brush: Brush,
830        /// Start of the segment, in scope coordinates.
831        start: Point,
832        /// End of the segment, in scope coordinates.
833        end: Point,
834        /// Width and cap of the line.
835        stroke: Stroke,
836    },
837    /// One slice of a filled path, which the draw scope cuts a fill into
838    /// so the GPU draws it: see [`Trapezoid`].
839    Trapezoid {
840        /// The rect `brush` resolves against: the draw scope's own, as a
841        /// path's brush resolves in Compose.
842        rect: Rect,
843        /// Paint for the slice.
844        brush: Brush,
845        /// The slice to fill.
846        trapezoid: Trapezoid,
847    },
848    /// An image drawn into a destination rectangle.
849    Image {
850        /// Destination rectangle in scope coordinates. The image is stretched to fill it.
851        rect: Rect,
852        /// The image to draw.
853        image: ImageBitmap,
854        /// Opacity from 0.0 (transparent) to 1.0 (opaque).
855        alpha: f32,
856        /// Color filter applied to the image, or `None` for none.
857        color_filter: Option<ColorFilter>,
858        /// How pixels are sampled when the image is scaled.
859        sampling: ImageSampling,
860        /// Optional source rectangle in image-pixel coordinates.
861        /// When `None`, the entire image is drawn. When `Some`, only the
862        /// specified sub-region of the source image is sampled.
863        src_rect: Option<Rect>,
864    },
865    /// A laid-out run of text. See [`TextPrimitive`].
866    Text(Box<TextPrimitive>),
867    /// Shadow that requires blur processing. The renderer decides technique
868    /// (GPU blur, CPU approximation, etc.).
869    Shadow(ShadowPrimitive),
870    /// Where a group of draws that [`DrawScope::with_transform`] places starts
871    /// or ends. The primitives between are in the group's coordinates.
872    Group(GroupMarker),
873}
874
875/// A run of text, positioned and ready to rasterize.
876///
877/// `rect` is *already resolved*: [`DrawScope::draw_text_at`] measures the
878/// string, applies [`DrawTextStyle::align`] / [`DrawTextStyle::vertical_align`] inside
879/// the requested box, and stores the result here. Renderers therefore lay the
880/// glyphs out from `rect`'s top-left and never re-align — which is what keeps
881/// what [`DrawScope::measure_text`] reported and what lands on screen the same
882/// geometry.
883#[derive(Clone, Debug, PartialEq)]
884pub struct TextPrimitive {
885    /// Tight block box: origin is the top-left of the first line's slot, size
886    /// is the measured size.
887    pub rect: Rect,
888    /// Shared so redrawing an unchanged string each frame clones a pointer
889    /// rather than the characters.
890    pub text: std::rc::Rc<str>,
891    /// Font, size, alignment and other styling the glyphs are laid out and drawn with.
892    pub style: DrawTextStyle,
893    /// Paint for the glyphs: a solid colour, or a gradient laid over the
894    /// text block, as Compose's `drawText(brush)` lays it over the layout.
895    pub brush: Brush,
896}
897
898/// Returns a shared `Rc<str>` for `text`, reusing the copy made on an earlier
899/// frame when the content matches.
900///
901/// Apps hand `draw_text*` a `&str` every frame, and a score counter or label
902/// is the same characters frame after frame — without this pool every call
903/// copied them into a fresh `Rc<str>` anyway, defeating the sharing
904/// [`TextPrimitive::text`] exists for. Hits are verified by content, so a hash
905/// collision costs one fresh copy, never the wrong text. The pool clears
906/// itself when full; a live scene re-warms within one frame.
907fn shared_text_str(text: &str) -> Rc<str> {
908    use std::hash::{Hash, Hasher};
909
910    const POOL_CAPACITY: usize = 256;
911
912    let mut hasher = crate::FxHasher::default();
913    text.hash(&mut hasher);
914    let key = hasher.finish();
915
916    crate::thread_state::with(|state| {
917        let mut pool = state.shared_texts.borrow_mut();
918        if let Some(shared) = pool.get(&key)
919            && &**shared == text
920        {
921            return Rc::clone(shared);
922        }
923        let shared: Rc<str> = Rc::from(text);
924        if pool.len() >= POOL_CAPACITY {
925            pool.clear();
926        }
927        pool.insert(key, Rc::clone(&shared));
928        shared
929    })
930}
931
932impl DrawPrimitive {
933    /// The primitive moved by `dx`, `dy`. Brush geometry is relative to the
934    /// primitive's rect, so it moves along.
935    pub fn translate(self, dx: f32, dy: f32) -> Self {
936        match self {
937            DrawPrimitive::Content => DrawPrimitive::Content,
938            DrawPrimitive::Blend {
939                primitive,
940                blend_mode,
941            } => DrawPrimitive::Blend {
942                primitive: Box::new(primitive.translate(dx, dy)),
943                blend_mode,
944            },
945            DrawPrimitive::Rect {
946                rect,
947                brush,
948                stroke,
949            } => DrawPrimitive::Rect {
950                rect: rect.translate(dx, dy),
951                brush,
952                stroke,
953            },
954            DrawPrimitive::RoundRect {
955                rect,
956                brush,
957                radii,
958                stroke,
959            } => DrawPrimitive::RoundRect {
960                rect: rect.translate(dx, dy),
961                brush,
962                radii,
963                stroke,
964            },
965            DrawPrimitive::Arc {
966                rect,
967                brush,
968                center,
969                radius,
970                start_angle,
971                sweep_angle,
972                stroke,
973                inner_radius,
974            } => DrawPrimitive::Arc {
975                rect: rect.translate(dx, dy),
976                brush,
977                center: Point::new(center.x + dx, center.y + dy),
978                radius,
979                start_angle,
980                sweep_angle,
981                stroke,
982                inner_radius,
983            },
984            DrawPrimitive::Line {
985                rect,
986                brush,
987                start,
988                end,
989                stroke,
990            } => DrawPrimitive::Line {
991                rect: rect.translate(dx, dy),
992                brush,
993                start: Point::new(start.x + dx, start.y + dy),
994                end: Point::new(end.x + dx, end.y + dy),
995                stroke,
996            },
997            DrawPrimitive::Trapezoid {
998                rect,
999                brush,
1000                trapezoid,
1001            } => DrawPrimitive::Trapezoid {
1002                rect: rect.translate(dx, dy),
1003                brush,
1004                trapezoid: trapezoid.translate(dx, dy),
1005            },
1006            DrawPrimitive::Image {
1007                rect,
1008                image,
1009                alpha,
1010                color_filter,
1011                sampling,
1012                src_rect,
1013            } => DrawPrimitive::Image {
1014                rect: rect.translate(dx, dy),
1015                image,
1016                alpha,
1017                color_filter,
1018                sampling,
1019                src_rect,
1020            },
1021            DrawPrimitive::Text(mut text) => {
1022                text.rect = text.rect.translate(dx, dy);
1023                DrawPrimitive::Text(text)
1024            }
1025            DrawPrimitive::Shadow(ShadowPrimitive::Drop {
1026                shape,
1027                cutout,
1028                blur_radius,
1029                blend_mode,
1030            }) => DrawPrimitive::Shadow(ShadowPrimitive::Drop {
1031                shape: Box::new(shape.translate(dx, dy)),
1032                cutout: cutout.map(|cutout| Box::new(cutout.translate(dx, dy))),
1033                blur_radius,
1034                blend_mode,
1035            }),
1036            DrawPrimitive::Shadow(ShadowPrimitive::Inner {
1037                fill,
1038                cutout,
1039                blur_radius,
1040                blend_mode,
1041                clip_rect,
1042            }) => DrawPrimitive::Shadow(ShadowPrimitive::Inner {
1043                fill: Box::new(fill.translate(dx, dy)),
1044                cutout: Box::new(cutout.translate(dx, dy)),
1045                blur_radius,
1046                blend_mode,
1047                clip_rect: clip_rect.translate(dx, dy),
1048            }),
1049            DrawPrimitive::Group(GroupMarker::Start(group)) => {
1050                DrawPrimitive::Group(GroupMarker::Start(group.translate(dx, dy)))
1051            }
1052            DrawPrimitive::Group(GroupMarker::End) => DrawPrimitive::Group(GroupMarker::End),
1053        }
1054    }
1055}
1056
1057/// A shadow that needs blur processing. Each renderer chooses how to blur. See
1058/// [`DrawPrimitive::Shadow`].
1059#[derive(Clone, Debug, PartialEq)]
1060pub enum ShadowPrimitive {
1061    /// Drop shadow: render shape behind content, blurred. `cutout` knocks
1062    /// the element's own (unoffset) shape out of the silhouette before the
1063    /// blur so translucent surfaces never sample their own shadow.
1064    Drop {
1065        /// The shape drawn and blurred behind the content.
1066        shape: Box<DrawPrimitive>,
1067        /// `Some` cuts the element's own unoffset shape out of the silhouette before the blur.
1068        cutout: Option<Box<DrawPrimitive>>,
1069        /// Blur radius of the shadow, in logical pixels.
1070        blur_radius: f32,
1071        /// How the shadow combines with what is already drawn.
1072        blend_mode: BlendMode,
1073    },
1074    /// Inner shadow: render fill + cutout to offscreen, blur, clip to bounds.
1075    Inner {
1076        /// The shape drawn and blurred to make the shadow.
1077        fill: Box<DrawPrimitive>,
1078        /// The shape cut out of the shadow's silhouette, so it appears only outside that shape.
1079        cutout: Box<DrawPrimitive>,
1080        /// Blur radius of the shadow, in logical pixels.
1081        blur_radius: f32,
1082        /// How the shadow combines with what is already drawn.
1083        blend_mode: BlendMode,
1084        /// Element bounds — blurred result must be clipped here.
1085        clip_rect: Rect,
1086    },
1087}
1088
1089/// The drawing surface passed to draw modifiers. Compose's `DrawScope`. Every method records a
1090/// primitive in scope coordinates, with the origin at the top-left corner of the node, and the
1091/// `_blend` forms composite with a given blend mode instead of `SrcOver`.
1092pub trait DrawScope {
1093    /// Size of the drawing area, in logical pixels.
1094    fn size(&self) -> Size;
1095    /// Marks where the node's own content draws, as Compose's `drawContent()` does in
1096    /// `drawWithContent`. Draws recorded before the marker paint under the content and draws after
1097    /// it paint over it.
1098    fn draw_content(&mut self);
1099    /// Fills the whole scope rect with `brush`.
1100    fn draw_rect(&mut self, brush: Brush);
1101    /// As `draw_rect`, composited with `blend_mode` instead of `SrcOver`.
1102    fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
1103    /// Draws a rectangle at the specified position and size.
1104    fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
1105    /// As `draw_rect_at`, composited with `blend_mode` instead of `SrcOver`.
1106    fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
1107    /// Fills the whole scope rect with rounded corners of `radii`.
1108    fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
1109    /// As `draw_round_rect`, composited with `blend_mode` instead of `SrcOver`.
1110    fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
1111    /// Draws a rounded rectangle at the specified position and size.
1112    fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
1113    /// Fills a circle of `radius` around `center`, in scope coordinates.
1114    fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
1115    /// As `draw_circle`, composited with `blend_mode` instead of `SrcOver`.
1116    fn draw_circle_blend(
1117        &mut self,
1118        brush: Brush,
1119        center: Point,
1120        radius: f32,
1121        blend_mode: BlendMode,
1122    );
1123
1124    /// Strokes the outline of the whole scope rect.
1125    fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
1126    /// As `draw_rect_stroked`, composited with `blend_mode` instead of `SrcOver`.
1127    fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
1128    /// Strokes the outline of `rect`.
1129    fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
1130    /// As `draw_rect_at_stroked`, composited with `blend_mode` instead of `SrcOver`.
1131    fn draw_rect_at_stroked_blend(
1132        &mut self,
1133        rect: Rect,
1134        brush: Brush,
1135        stroke: Stroke,
1136        blend_mode: BlendMode,
1137    );
1138    /// Strokes the outline of the whole scope rect with rounded corners.
1139    fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
1140    /// As `draw_round_rect_stroked`, composited with `blend_mode` instead of `SrcOver`.
1141    fn draw_round_rect_stroked_blend(
1142        &mut self,
1143        brush: Brush,
1144        radii: CornerRadii,
1145        stroke: Stroke,
1146        blend_mode: BlendMode,
1147    );
1148    /// Strokes the outline of `rect` with rounded corners.
1149    fn draw_round_rect_at_stroked(
1150        &mut self,
1151        rect: Rect,
1152        brush: Brush,
1153        radii: CornerRadii,
1154        stroke: Stroke,
1155    );
1156    /// As `draw_round_rect_at_stroked`, composited with `blend_mode` instead of `SrcOver`.
1157    fn draw_round_rect_at_stroked_blend(
1158        &mut self,
1159        rect: Rect,
1160        brush: Brush,
1161        radii: CornerRadii,
1162        stroke: Stroke,
1163        blend_mode: BlendMode,
1164    );
1165    /// Strokes a circle outline. Lowers to a stroked rounded rect, so it shares
1166    /// the fill pipeline and batches with every other shape.
1167    fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
1168    /// As `draw_circle_stroked`, composited with `blend_mode` instead of `SrcOver`.
1169    fn draw_circle_stroked_blend(
1170        &mut self,
1171        brush: Brush,
1172        center: Point,
1173        radius: f32,
1174        stroke: Stroke,
1175        blend_mode: BlendMode,
1176    );
1177
1178    /// Strokes a circular arc.
1179    ///
1180    /// Angles are in **radians**, `0` points along **+X**, and increasing
1181    /// angles sweep **clockwise on screen** (Cranpose uses y-down device
1182    /// coordinates, so this matches `atan2(dy, dx)` and the sweep-gradient
1183    /// brush). A negative `sweep_angle` sweeps counter-clockwise; `|sweep| >=
1184    /// 2Ï€` draws a closed ring.
1185    ///
1186    /// The stroke is centered on `radius`, so the band covers
1187    /// `radius ± width/2`. [`StrokeCap`](crate::StrokeCap) shapes the two ends.
1188    /// Nothing is drawn for a zero sweep, a non-positive width, or non-finite
1189    /// input.
1190    fn draw_arc(
1191        &mut self,
1192        brush: Brush,
1193        center: Point,
1194        radius: f32,
1195        start_angle: f32,
1196        sweep_angle: f32,
1197        stroke: Stroke,
1198    );
1199    /// As `draw_arc`, composited with `blend_mode` instead of `SrcOver`.
1200    #[expect(clippy::too_many_arguments)]
1201    fn draw_arc_blend(
1202        &mut self,
1203        brush: Brush,
1204        center: Point,
1205        radius: f32,
1206        start_angle: f32,
1207        sweep_angle: f32,
1208        stroke: Stroke,
1209        blend_mode: BlendMode,
1210    );
1211
1212    /// Strokes the straight segment from `start` to `end`, centred on it and
1213    /// `stroke.width` wide, its ends shaped by `stroke.cap`: Compose's
1214    /// `drawLine(brush, start, end, strokeWidth, cap)`. Nothing is drawn
1215    /// for a non-positive width, a non-finite end, or a segment of no length
1216    /// between butt caps; round and square caps draw a dot there.
1217    fn draw_line(&mut self, brush: Brush, start: Point, end: Point, stroke: Stroke);
1218    /// As `draw_line`, composited with `blend_mode` instead of `SrcOver`.
1219    fn draw_line_blend(
1220        &mut self,
1221        brush: Brush,
1222        start: Point,
1223        end: Point,
1224        stroke: Stroke,
1225        blend_mode: BlendMode,
1226    );
1227
1228    /// Fills or strokes `path` with `brush`: Compose's
1229    /// `drawPath(path, brush, style)`. A fill closes every contour and
1230    /// rasterizes it as [`draw_vector_path`](Self::draw_vector_path) does. A
1231    /// stroke draws each flattened edge as a line on the GPU, capped at an
1232    /// open contour's ends and joined at its corners as
1233    /// [`crate::for_each_stroke_line`] describes; a dashed stroke draws the
1234    /// dashes its effect cuts. Where edges meet, a translucent stroke is
1235    /// painted twice.
1236    fn draw_path(&mut self, path: &Path, brush: Brush, style: DrawStyle);
1237    /// As `draw_path`, composited with `blend_mode` instead of `SrcOver`.
1238    fn draw_path_blend(
1239        &mut self,
1240        path: &Path,
1241        brush: Brush,
1242        style: DrawStyle,
1243        blend_mode: BlendMode,
1244    );
1245
1246    /// Fills an annular sector — the region between `inner_radius` and
1247    /// `outer_radius`, limited to an angular sweep, with **flat radial ends**.
1248    ///
1249    /// This is the shape a stroked arc cannot express: its ends are straight
1250    /// lines through the center, not caps. `inner_radius = 0` fills a pie
1251    /// wedge. Angle convention is identical to [`draw_arc`](Self::draw_arc).
1252    /// Nothing is drawn when `inner_radius >= outer_radius`, the sweep is zero,
1253    /// or any input is non-finite.
1254    fn draw_annular_sector(
1255        &mut self,
1256        brush: Brush,
1257        center: Point,
1258        inner_radius: f32,
1259        outer_radius: f32,
1260        start_angle: f32,
1261        sweep_angle: f32,
1262    );
1263    /// As `draw_annular_sector`, composited with `blend_mode` instead of `SrcOver`.
1264    #[expect(clippy::too_many_arguments)]
1265    fn draw_annular_sector_blend(
1266        &mut self,
1267        brush: Brush,
1268        center: Point,
1269        inner_radius: f32,
1270        outer_radius: f32,
1271        start_angle: f32,
1272        sweep_angle: f32,
1273        blend_mode: BlendMode,
1274    );
1275
1276    /// Draws `image` stretched over the whole scope rect.
1277    fn draw_image(&mut self, image: ImageBitmap);
1278    /// As `draw_image`, composited with `blend_mode` instead of `SrcOver`.
1279    fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
1280    /// Draws `image` stretched into `rect`, with `alpha` from 0.0 to 1.0 and an optional
1281    /// `color_filter`.
1282    fn draw_image_at(
1283        &mut self,
1284        rect: Rect,
1285        image: ImageBitmap,
1286        alpha: f32,
1287        color_filter: Option<ColorFilter>,
1288    );
1289    /// As `draw_image_at`, sampling the image with `sampling` instead of nearest-neighbor.
1290    fn draw_image_at_sampled(
1291        &mut self,
1292        rect: Rect,
1293        image: ImageBitmap,
1294        alpha: f32,
1295        color_filter: Option<ColorFilter>,
1296        sampling: ImageSampling,
1297    );
1298    /// As `draw_image_at`, composited with `blend_mode` instead of `SrcOver`.
1299    fn draw_image_at_blend(
1300        &mut self,
1301        rect: Rect,
1302        image: ImageBitmap,
1303        alpha: f32,
1304        color_filter: Option<ColorFilter>,
1305        blend_mode: BlendMode,
1306    );
1307    /// Draws a sub-region of an image. `src_rect` is in image-pixel
1308    /// coordinates; `dst_rect` is in scope coordinates.
1309    fn draw_image_src(
1310        &mut self,
1311        image: ImageBitmap,
1312        src_rect: Rect,
1313        dst_rect: Rect,
1314        alpha: f32,
1315        color_filter: Option<ColorFilter>,
1316    );
1317    /// As `draw_image_src`, sampling the source region with `sampling` instead of nearest-neighbor.
1318    fn draw_image_src_sampled(
1319        &mut self,
1320        image: ImageBitmap,
1321        src_rect: Rect,
1322        dst_rect: Rect,
1323        alpha: f32,
1324        color_filter: Option<ColorFilter>,
1325        sampling: ImageSampling,
1326    );
1327    /// As `draw_image_src_sampled`, composited with `blend_mode` instead of `SrcOver`.
1328    fn draw_image_src_blend(
1329        &mut self,
1330        image: ImageBitmap,
1331        src_rect: Rect,
1332        dst_rect: Rect,
1333        alpha: f32,
1334        color_filter: Option<ColorFilter>,
1335        blend_mode: BlendMode,
1336    );
1337    /// Fills a parsed SVG path in scope coordinates (path units are dp).
1338    ///
1339    /// The fill is rasterized on the CPU into a supersampled, anti-aliased
1340    /// bitmap covering the path bounds and drawn as an image primitive, so
1341    /// it works on every render backend. Parse the path once with
1342    /// [`crate::VectorPath::parse`] and redraw it per frame. A gradient brush
1343    /// resolves against the scope's size, as other fills do.
1344    fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
1345    /// Parses SVG path data (the `d` attribute syntax: `M/m L/l H/h V/v
1346    /// C/c S/s Q/q T/t A/a Z/z`) and fills it. Invalid path data draws
1347    /// nothing. Prefer [`crate::VectorPath::parse`] +
1348    /// [`draw_vector_path`](Self::draw_vector_path) to avoid re-parsing
1349    /// and to surface parse errors.
1350    fn draw_svg_path(&mut self, d: &str, brush: Brush) {
1351        if let Ok(path) = crate::VectorPath::parse(d) {
1352            self.draw_vector_path(&path, brush);
1353        }
1354    }
1355
1356    /// The block size, line height and first baseline `text` would occupy in
1357    /// `style`.
1358    ///
1359    /// Free to call repeatedly: the underlying text stack caches metrics on
1360    /// `(text, style)`, so a game can measure every label every frame to center
1361    /// it without touching a font file more than once.
1362    fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement;
1363
1364    /// Draws `text` inside the whole scope rect, positioned by
1365    /// [`DrawTextStyle::align`] and [`DrawTextStyle::vertical_align`].
1366    fn draw_text(&mut self, brush: Brush, text: &str, style: &DrawTextStyle) {
1367        self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
1368    }
1369
1370    /// Draws `text` inside `rect`, positioned by [`DrawTextStyle::align`] and
1371    /// [`DrawTextStyle::vertical_align`].
1372    ///
1373    /// The glyphs are *not* clipped to `rect` — it is an alignment box, not a
1374    /// viewport. A `rect` narrower than the measured text overflows in the
1375    /// direction the alignment implies; clip the layer if that matters.
1376    fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle);
1377
1378    /// Draws `text` with the top-left corner of its block at `top_left`.
1379    ///
1380    /// Alignment is a no-op here because the box is the measurement — this is
1381    /// the "I already know where it goes" form, and the one to pair with
1382    /// [`measure_text`](Self::measure_text) for hand-rolled centering.
1383    fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &DrawTextStyle) {
1384        if text.is_empty() {
1385            return;
1386        }
1387        let measurement = self.measure_text(text, style);
1388        self.draw_text_at(
1389            Rect::from_origin_size(top_left, measurement.size),
1390            brush,
1391            text,
1392            &DrawTextStyle {
1393                align: TextAlign::Left,
1394                vertical_align: TextVerticalAlign::Top,
1395                ..style.clone()
1396            },
1397        );
1398    }
1399
1400    /// Draws `block` with every primitive it records, text and images
1401    /// included, placed by `transform`'s steps in order, each inside the
1402    /// ones before it: Compose's `withTransform`. The block sees the scope
1403    /// size the steps leave. Content that `draw_content` draws inside the
1404    /// block keeps its place: only the block's own draws move.
1405    fn with_transform(
1406        &mut self,
1407        transform: &[DrawTransform<'_>],
1408        block: &mut dyn FnMut(&mut dyn DrawScope),
1409    );
1410
1411    /// Draws `block` moved by `dx`, `dy`. Compose's `translate`.
1412    fn translate(&mut self, dx: f32, dy: f32, block: &mut dyn FnMut(&mut dyn DrawScope)) {
1413        self.with_transform(&[DrawTransform::Translate { dx, dy }], block);
1414    }
1415
1416    /// Draws `block` scaled by `scale_x`, `scale_y` about `pivot`, which
1417    /// stays where it is. Compose's `scale`.
1418    fn scale(
1419        &mut self,
1420        scale_x: f32,
1421        scale_y: f32,
1422        pivot: Point,
1423        block: &mut dyn FnMut(&mut dyn DrawScope),
1424    ) {
1425        self.with_transform(
1426            &[DrawTransform::Scale {
1427                scale_x,
1428                scale_y,
1429                pivot,
1430            }],
1431            block,
1432        );
1433    }
1434
1435    /// Draws `block` turned clockwise by `degrees` about `pivot`. Compose's
1436    /// `rotate`.
1437    fn rotate(&mut self, degrees: f32, pivot: Point, block: &mut dyn FnMut(&mut dyn DrawScope)) {
1438        self.with_transform(&[DrawTransform::Rotate { degrees, pivot }], block);
1439    }
1440
1441    /// Draws `block` in the scope shrunk by the four insets: it sees the
1442    /// smaller size and draws moved by `left`, `top`. Compose's `inset`.
1443    fn inset(
1444        &mut self,
1445        left: f32,
1446        top: f32,
1447        right: f32,
1448        bottom: f32,
1449        block: &mut dyn FnMut(&mut dyn DrawScope),
1450    ) {
1451        self.with_transform(
1452            &[DrawTransform::Inset {
1453                left,
1454                top,
1455                right,
1456                bottom,
1457            }],
1458            block,
1459        );
1460    }
1461
1462    /// Draws `block` cut to `rect`. Compose's `clipRect`.
1463    fn clip_rect(&mut self, rect: Rect, block: &mut dyn FnMut(&mut dyn DrawScope)) {
1464        self.with_transform(&[DrawTransform::ClipRect(rect)], block);
1465    }
1466
1467    /// Draws `block` cut to the fill of `path`, its edge anti-aliased.
1468    /// Compose's `clipPath`.
1469    fn clip_path(&mut self, path: &Path, block: &mut dyn FnMut(&mut dyn DrawScope)) {
1470        self.with_transform(&[DrawTransform::ClipPath(path)], block);
1471    }
1472
1473    /// Consumes the scope and returns the recorded primitives in draw order, including the
1474    /// `Content` markers.
1475    fn into_primitives(self) -> Vec<DrawPrimitive>;
1476}
1477
1478/// Resolves the top-left corner a text block of `measurement` gets when it is
1479/// aligned inside `rect`.
1480///
1481/// Split out so the placement rule is stated once and can be unit-tested
1482/// against the measurement it is derived from.
1483pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
1484    let x = match style.align {
1485        TextAlign::Left => rect.x,
1486        TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
1487        TextAlign::Right => rect.x + rect.width - measurement.size.width,
1488    };
1489    let y = match style.vertical_align {
1490        TextVerticalAlign::Top => rect.y,
1491        TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
1492        TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
1493        TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
1494    };
1495    Point::new(x, y)
1496}
1497
1498/// The rect a path's coverage mask covers: its bounds out to whole pixels and
1499/// one pixel more each way, so the anti-aliased edge fits.
1500fn vector_mask_rect(bounds: Rect) -> Rect {
1501    let x = bounds.x.floor() - 1.0;
1502    let y = bounds.y.floor() - 1.0;
1503    Rect {
1504        x,
1505        y,
1506        width: (bounds.x + bounds.width).ceil() - x + 1.0,
1507        height: (bounds.y + bounds.height).ceil() - y + 1.0,
1508    }
1509}
1510
1511/// The `DrawScope` the framework passes to draw closures. It records the primitives drawn in draw
1512/// order, in place, into a [`CommandRecording`] its caller holds: a recording
1513/// kept from an earlier frame is drawn again in the storage it has.
1514pub struct DrawScopeDefault<'a> {
1515    size: Size,
1516    recording: CommandRecorder<'a>,
1517    text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1518}
1519
1520impl<'a> DrawScopeDefault<'a> {
1521    /// Creates a scope of `size` recording into `recording` anew, without a
1522    /// text measurer, so text is measured by an estimate.
1523    pub fn new(size: Size, recording: &'a mut CommandRecording) -> Self {
1524        Self::with_storage(size, None, recording)
1525    }
1526
1527    /// A scope that measures text with the app's fonts.
1528    ///
1529    /// The framework calls this for every draw closure it runs; `new` exists
1530    /// for callers that never draw text.
1531    pub fn with_text_measurer(
1532        size: Size,
1533        text_measurer: Rc<dyn DrawTextMeasurer>,
1534        recording: &'a mut CommandRecording,
1535    ) -> Self {
1536        Self::with_storage(size, Some(text_measurer), recording)
1537    }
1538
1539    fn with_storage(
1540        size: Size,
1541        text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1542        recording: &'a mut CommandRecording,
1543    ) -> Self {
1544        Self {
1545            size,
1546            recording: recording.record(),
1547            text_measurer,
1548        }
1549    }
1550
1551    /// How many `draw_content` markers this scope has recorded.
1552    pub fn content_marker_count(&self) -> u32 {
1553        self.recording.content_markers()
1554    }
1555
1556    /// Runs `draw` in this scope's rect shrunk by `insets`, where a draw
1557    /// modifier placed after layout padding draws: `draw` sees the inner
1558    /// size, and what it records lands offset by the leading insets.
1559    pub fn draw_inset(&mut self, insets: EdgeInsets, draw: impl FnOnce(&mut DrawScopeDefault<'_>)) {
1560        let inner = insets.inset_rect(self.size);
1561        let mut recording = CommandRecording::default();
1562        let mut scope = DrawScopeDefault::with_storage(
1563            Size {
1564                width: inner.width,
1565                height: inner.height,
1566            },
1567            self.text_measurer.clone(),
1568            &mut recording,
1569        );
1570        draw(&mut scope);
1571        scope.finish();
1572        self.push_recorded(crate::translate_recorded(
1573            recording.primitives_with_markers(),
1574            inner.x,
1575            inner.y,
1576        ));
1577    }
1578
1579    /// Records primitives already built, as if each had been drawn here.
1580    pub fn push_recorded(&mut self, primitives: impl IntoIterator<Item = DrawPrimitive>) {
1581        for primitive in primitives {
1582            self.recording.push_primitive(primitive);
1583        }
1584    }
1585
1586    /// Completes the recording in the storage the scope records into.
1587    pub fn finish(self) {
1588        self.recording.finish();
1589    }
1590
1591    fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1592        if blend_mode != BlendMode::SrcOver {
1593            self.recording.push_other(DrawPrimitive::Blend {
1594                primitive: Box::new(primitive),
1595                blend_mode,
1596            });
1597            return;
1598        }
1599        self.recording.push_other(primitive);
1600    }
1601
1602    #[expect(clippy::too_many_arguments)]
1603    #[inline]
1604    fn push_arc(
1605        &mut self,
1606        brush: Brush,
1607        center: Point,
1608        radius: f32,
1609        start_angle: f32,
1610        sweep_angle: f32,
1611        stroke: Option<Stroke>,
1612        inner_radius: f32,
1613        blend_mode: BlendMode,
1614    ) {
1615        let args = ArcRecordArgs {
1616            brush: &brush,
1617            center,
1618            radius,
1619            start_angle,
1620            sweep_angle,
1621            stroke,
1622            inner_radius,
1623            blend_mode,
1624        };
1625        let geometry = normalized_band(&args);
1626        if geometry.is_degenerate() {
1627            return;
1628        }
1629        self.recording.push_scope_arc(&args, &geometry);
1630    }
1631
1632    /// Records the fill of `path` as the slices the GPU draws, its brush
1633    /// resolved against the scope's rect; a path that cannot be sliced is
1634    /// rasterized into a mask instead.
1635    fn push_path_fill(&mut self, path: &Path, brush: &Brush, blend_mode: BlendMode) {
1636        let brush_rect = Rect::from_size(self.size);
1637        let recording = &mut self.recording;
1638        let sliced = crate::thread_state::with(|state| {
1639            let mut slicer = state.path_slicer.borrow_mut();
1640            let contours = path
1641                .contours()
1642                .iter()
1643                .map(|contour| contour.points.as_slice());
1644            slicer
1645                .slice(contours, PathFillRule::NonZero)
1646                .map(|slices| recording.push_trapezoids(brush_rect, slices, brush, blend_mode))
1647                .is_some()
1648        });
1649        if !sliced {
1650            self.push_vector_path(
1651                &path.to_vector_path(PathFillRule::NonZero),
1652                brush,
1653                brush_rect,
1654                blend_mode,
1655                PathSide::Inside,
1656            );
1657        }
1658    }
1659
1660    /// Draws `block` under the `rest` of its steps in a group that `mask`'s
1661    /// fill cuts. The group ends with a mask that erases what lies outside
1662    /// the fill: a blend every renderer draws, where keeping what lies inside
1663    /// is not.
1664    fn with_path_mask(
1665        &mut self,
1666        mask: &crate::VectorPath,
1667        rest: &[DrawTransform<'_>],
1668        block: &mut dyn FnMut(&mut dyn DrawScope),
1669    ) {
1670        let rect = vector_mask_rect(mask.bounds());
1671        self.recording.push_group_start(DrawGroup::masked(rect));
1672        self.with_transform(rest, block);
1673        self.push_vector_path(
1674            mask,
1675            &Brush::solid(Color::BLACK),
1676            rect,
1677            BlendMode::DstOut,
1678            PathSide::Outside,
1679        );
1680        self.recording.push_group_end();
1681    }
1682
1683    /// Rasterizes the `side` of `path`'s edge into a cached image and
1684    /// records it, blended by `blend_mode`. A gradient `brush` resolves
1685    /// against `brush_rect`.
1686    fn push_vector_path(
1687        &mut self,
1688        path: &crate::VectorPath,
1689        brush: &Brush,
1690        brush_rect: Rect,
1691        blend_mode: BlendMode,
1692        side: PathSide,
1693    ) {
1694        const MAX_MASK_PIXELS: f32 = 4096.0;
1695
1696        if path.is_empty() || !brush.paints() {
1697            return;
1698        }
1699        let bounds = path.bounds();
1700        if bounds.width <= 0.0 || bounds.height <= 0.0 {
1701            return;
1702        }
1703
1704        let rect = vector_mask_rect(bounds);
1705        let origin = Point::new(rect.x, rect.y);
1706        let mask_width = (rect.width * VECTOR_PATH_SUPERSAMPLE)
1707            .ceil()
1708            .clamp(1.0, MAX_MASK_PIXELS) as usize;
1709        let mask_height = (rect.height * VECTOR_PATH_SUPERSAMPLE)
1710            .ceil()
1711            .clamp(1.0, MAX_MASK_PIXELS) as usize;
1712        let mask_size = (mask_width, mask_height);
1713
1714        let key = vector_path_mask_key(path, origin, mask_size, brush, brush_rect, side);
1715        let image = match vector_path_mask_cache_get(key) {
1716            Some(image) => image,
1717            None => {
1718                let Some(image) =
1719                    rasterize_vector_path(path, brush, brush_rect, origin, mask_size, side)
1720                else {
1721                    return;
1722                };
1723                vector_path_mask_cache_put(key, image.clone());
1724                image
1725            }
1726        };
1727
1728        let image = DrawPrimitive::Image {
1729            rect,
1730            image,
1731            alpha: 1.0,
1732            color_filter: None,
1733            sampling: ImageSampling::Linear,
1734            src_rect: None,
1735        };
1736        self.recording
1737            .push_other(if blend_mode == BlendMode::SrcOver {
1738                image
1739            } else {
1740                DrawPrimitive::Blend {
1741                    primitive: Box::new(image),
1742                    blend_mode,
1743                }
1744            });
1745    }
1746}
1747
1748impl DrawScope for DrawScopeDefault<'_> {
1749    fn size(&self) -> Size {
1750        self.size
1751    }
1752
1753    fn with_transform(
1754        &mut self,
1755        transform: &[DrawTransform<'_>],
1756        block: &mut dyn FnMut(&mut dyn DrawScope),
1757    ) {
1758        let Some((step, rest)) = transform.split_first() else {
1759            block(self);
1760            return;
1761        };
1762        let outer = self.size;
1763        let (group, size) = match *step {
1764            DrawTransform::Translate { dx, dy } => (DrawGroup::moved(dx, dy, outer), outer),
1765            DrawTransform::Scale {
1766                scale_x,
1767                scale_y,
1768                pivot,
1769            } => (DrawGroup::about(pivot, scale_x, scale_y, 0.0, outer), outer),
1770            DrawTransform::Rotate { degrees, pivot } => {
1771                (DrawGroup::about(pivot, 1.0, 1.0, degrees, outer), outer)
1772            }
1773            DrawTransform::Inset {
1774                left,
1775                top,
1776                right,
1777                bottom,
1778            } => {
1779                let inner = Size::new(
1780                    (outer.width - left - right).max(0.0),
1781                    (outer.height - top - bottom).max(0.0),
1782                );
1783                (DrawGroup::moved(left, top, inner), inner)
1784            }
1785            DrawTransform::ClipRect(rect) => (DrawGroup::plain(rect, true), outer),
1786            DrawTransform::ClipPath(path) => {
1787                let mask = path.to_vector_path(PathFillRule::NonZero);
1788                if !mask.is_empty() {
1789                    self.with_path_mask(&mask, rest, block);
1790                    return;
1791                }
1792                // A path with no fill leaves nothing of the block.
1793                (DrawGroup::plain(Rect::EMPTY, true), outer)
1794            }
1795        };
1796        self.recording.push_group_start(group);
1797        self.size = size;
1798        self.with_transform(rest, block);
1799        self.size = outer;
1800        self.recording.push_group_end();
1801    }
1802
1803    fn draw_content(&mut self) {
1804        self.recording.push_content();
1805    }
1806
1807    fn draw_rect(&mut self, brush: Brush) {
1808        self.draw_rect_blend(brush, BlendMode::SrcOver);
1809    }
1810
1811    fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1812        self.recording
1813            .push_rect(Rect::from_size(self.size), &brush, None, blend_mode);
1814    }
1815
1816    fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1817        self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1818    }
1819
1820    fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1821        self.recording.push_rect(rect, &brush, None, blend_mode);
1822    }
1823
1824    fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1825        self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1826    }
1827
1828    fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1829        self.recording
1830            .push_round_rect(Rect::from_size(self.size), &brush, radii, None, blend_mode);
1831    }
1832
1833    fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1834        self.recording
1835            .push_round_rect(rect, &brush, radii, None, BlendMode::SrcOver);
1836    }
1837
1838    fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1839        self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1840    }
1841
1842    fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1843        self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1844    }
1845
1846    fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1847        self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1848    }
1849
1850    fn draw_rect_at_stroked_blend(
1851        &mut self,
1852        rect: Rect,
1853        brush: Brush,
1854        stroke: Stroke,
1855        blend_mode: BlendMode,
1856    ) {
1857        if !stroke.is_visible() {
1858            return;
1859        }
1860        self.recording
1861            .push_rect(rect, &brush, Some(stroke), blend_mode);
1862    }
1863
1864    fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1865        self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1866    }
1867
1868    fn draw_round_rect_stroked_blend(
1869        &mut self,
1870        brush: Brush,
1871        radii: CornerRadii,
1872        stroke: Stroke,
1873        blend_mode: BlendMode,
1874    ) {
1875        self.draw_round_rect_at_stroked_blend(
1876            Rect::from_size(self.size),
1877            brush,
1878            radii,
1879            stroke,
1880            blend_mode,
1881        );
1882    }
1883
1884    fn draw_round_rect_at_stroked(
1885        &mut self,
1886        rect: Rect,
1887        brush: Brush,
1888        radii: CornerRadii,
1889        stroke: Stroke,
1890    ) {
1891        self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1892    }
1893
1894    fn draw_round_rect_at_stroked_blend(
1895        &mut self,
1896        rect: Rect,
1897        brush: Brush,
1898        radii: CornerRadii,
1899        stroke: Stroke,
1900        blend_mode: BlendMode,
1901    ) {
1902        if !stroke.is_visible() {
1903            return;
1904        }
1905        self.recording
1906            .push_round_rect(rect, &brush, radii, Some(stroke), blend_mode);
1907    }
1908
1909    fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1910        self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1911    }
1912
1913    fn draw_circle_stroked_blend(
1914        &mut self,
1915        brush: Brush,
1916        center: Point,
1917        radius: f32,
1918        stroke: Stroke,
1919        blend_mode: BlendMode,
1920    ) {
1921        if !stroke.is_visible() || !radius.is_finite() {
1922            return;
1923        }
1924        let radius = radius.max(0.0);
1925        let diameter = radius * 2.0;
1926        self.draw_round_rect_at_stroked_blend(
1927            Rect {
1928                x: center.x - radius,
1929                y: center.y - radius,
1930                width: diameter,
1931                height: diameter,
1932            },
1933            brush,
1934            CornerRadii::uniform(radius),
1935            stroke,
1936            blend_mode,
1937        );
1938    }
1939
1940    fn draw_arc(
1941        &mut self,
1942        brush: Brush,
1943        center: Point,
1944        radius: f32,
1945        start_angle: f32,
1946        sweep_angle: f32,
1947        stroke: Stroke,
1948    ) {
1949        self.draw_arc_blend(
1950            brush,
1951            center,
1952            radius,
1953            start_angle,
1954            sweep_angle,
1955            stroke,
1956            BlendMode::SrcOver,
1957        );
1958    }
1959
1960    fn draw_arc_blend(
1961        &mut self,
1962        brush: Brush,
1963        center: Point,
1964        radius: f32,
1965        start_angle: f32,
1966        sweep_angle: f32,
1967        stroke: Stroke,
1968        blend_mode: BlendMode,
1969    ) {
1970        if !stroke.is_visible() {
1971            return;
1972        }
1973        self.push_arc(
1974            brush,
1975            center,
1976            radius,
1977            start_angle,
1978            sweep_angle,
1979            Some(stroke),
1980            0.0,
1981            blend_mode,
1982        );
1983    }
1984
1985    fn draw_line(&mut self, brush: Brush, start: Point, end: Point, stroke: Stroke) {
1986        self.draw_line_blend(brush, start, end, stroke, BlendMode::SrcOver);
1987    }
1988
1989    fn draw_line_blend(
1990        &mut self,
1991        brush: Brush,
1992        start: Point,
1993        end: Point,
1994        stroke: Stroke,
1995        blend_mode: BlendMode,
1996    ) {
1997        self.recording
1998            .line_run(&brush, stroke, blend_mode)
1999            .push(start, end, stroke.cap);
2000    }
2001
2002    fn draw_path(&mut self, path: &Path, brush: Brush, style: DrawStyle) {
2003        self.draw_path_blend(path, brush, style, BlendMode::SrcOver);
2004    }
2005
2006    fn draw_path_blend(
2007        &mut self,
2008        path: &Path,
2009        brush: Brush,
2010        style: DrawStyle,
2011        blend_mode: BlendMode,
2012    ) {
2013        let (stroke, dash) = match style {
2014            DrawStyle::Fill => {
2015                self.push_path_fill(path, &brush, blend_mode);
2016                return;
2017            }
2018            DrawStyle::Stroke(stroke) => (stroke, None),
2019            DrawStyle::DashedStroke(stroke, dash) => (stroke, Some(dash)),
2020        };
2021        if !stroke.is_visible() {
2022            return;
2023        }
2024        let mut run = self.recording.line_run(&brush, stroke, blend_mode);
2025        let mut push = |line: LineGeometry| run.push(line.start, line.end, line.cap);
2026        for contour in path.contours() {
2027            let Some(dash) = dash else {
2028                crate::for_each_stroke_line(&contour.points, contour.closed, stroke, &mut push);
2029                continue;
2030            };
2031            let closing = contour
2032                .closed
2033                .then(|| contour.points.first().copied())
2034                .flatten();
2035            let points: std::borrow::Cow<'_, [Point]> = match closing {
2036                Some(first) => {
2037                    std::borrow::Cow::Owned(contour.points.iter().copied().chain([first]).collect())
2038                }
2039                None => std::borrow::Cow::Borrowed(&contour.points),
2040            };
2041            dash.for_each_dash(&points, |piece| {
2042                crate::for_each_stroke_line(piece, false, stroke, &mut push);
2043            });
2044        }
2045    }
2046
2047    fn draw_annular_sector(
2048        &mut self,
2049        brush: Brush,
2050        center: Point,
2051        inner_radius: f32,
2052        outer_radius: f32,
2053        start_angle: f32,
2054        sweep_angle: f32,
2055    ) {
2056        self.draw_annular_sector_blend(
2057            brush,
2058            center,
2059            inner_radius,
2060            outer_radius,
2061            start_angle,
2062            sweep_angle,
2063            BlendMode::SrcOver,
2064        );
2065    }
2066
2067    fn draw_annular_sector_blend(
2068        &mut self,
2069        brush: Brush,
2070        center: Point,
2071        inner_radius: f32,
2072        outer_radius: f32,
2073        start_angle: f32,
2074        sweep_angle: f32,
2075        blend_mode: BlendMode,
2076    ) {
2077        self.push_arc(
2078            brush,
2079            center,
2080            outer_radius,
2081            start_angle,
2082            sweep_angle,
2083            None,
2084            inner_radius,
2085            blend_mode,
2086        );
2087    }
2088
2089    fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
2090        self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
2091    }
2092
2093    fn draw_circle_blend(
2094        &mut self,
2095        brush: Brush,
2096        center: Point,
2097        radius: f32,
2098        blend_mode: BlendMode,
2099    ) {
2100        let radius = radius.max(0.0);
2101        let diameter = radius * 2.0;
2102        self.recording.push_round_rect(
2103            Rect {
2104                x: center.x - radius,
2105                y: center.y - radius,
2106                width: diameter,
2107                height: diameter,
2108            },
2109            &brush,
2110            CornerRadii::uniform(radius),
2111            None,
2112            blend_mode,
2113        );
2114    }
2115
2116    fn draw_image(&mut self, image: ImageBitmap) {
2117        self.draw_image_blend(image, BlendMode::SrcOver);
2118    }
2119
2120    fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
2121        self.push_blended_primitive(
2122            DrawPrimitive::Image {
2123                rect: Rect::from_size(self.size),
2124                image,
2125                alpha: 1.0,
2126                color_filter: None,
2127                sampling: ImageSampling::Nearest,
2128                src_rect: None,
2129            },
2130            blend_mode,
2131        );
2132    }
2133
2134    fn draw_image_at(
2135        &mut self,
2136        rect: Rect,
2137        image: ImageBitmap,
2138        alpha: f32,
2139        color_filter: Option<ColorFilter>,
2140    ) {
2141        self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
2142    }
2143
2144    fn draw_image_at_sampled(
2145        &mut self,
2146        rect: Rect,
2147        image: ImageBitmap,
2148        alpha: f32,
2149        color_filter: Option<ColorFilter>,
2150        sampling: ImageSampling,
2151    ) {
2152        self.push_blended_primitive(
2153            DrawPrimitive::Image {
2154                rect,
2155                image,
2156                alpha: alpha.clamp(0.0, 1.0),
2157                color_filter,
2158                sampling,
2159                src_rect: None,
2160            },
2161            BlendMode::SrcOver,
2162        );
2163    }
2164
2165    fn draw_image_at_blend(
2166        &mut self,
2167        rect: Rect,
2168        image: ImageBitmap,
2169        alpha: f32,
2170        color_filter: Option<ColorFilter>,
2171        blend_mode: BlendMode,
2172    ) {
2173        self.push_blended_primitive(
2174            DrawPrimitive::Image {
2175                rect,
2176                image,
2177                alpha: alpha.clamp(0.0, 1.0),
2178                color_filter,
2179                sampling: ImageSampling::Nearest,
2180                src_rect: None,
2181            },
2182            blend_mode,
2183        );
2184    }
2185
2186    fn draw_image_src(
2187        &mut self,
2188        image: ImageBitmap,
2189        src_rect: Rect,
2190        dst_rect: Rect,
2191        alpha: f32,
2192        color_filter: Option<ColorFilter>,
2193    ) {
2194        self.draw_image_src_blend(
2195            image,
2196            src_rect,
2197            dst_rect,
2198            alpha,
2199            color_filter,
2200            BlendMode::SrcOver,
2201        );
2202    }
2203
2204    fn draw_image_src_sampled(
2205        &mut self,
2206        image: ImageBitmap,
2207        src_rect: Rect,
2208        dst_rect: Rect,
2209        alpha: f32,
2210        color_filter: Option<ColorFilter>,
2211        sampling: ImageSampling,
2212    ) {
2213        self.push_blended_primitive(
2214            DrawPrimitive::Image {
2215                rect: dst_rect,
2216                image,
2217                alpha: alpha.clamp(0.0, 1.0),
2218                color_filter,
2219                sampling,
2220                src_rect: Some(src_rect),
2221            },
2222            BlendMode::SrcOver,
2223        );
2224    }
2225
2226    fn draw_image_src_blend(
2227        &mut self,
2228        image: ImageBitmap,
2229        src_rect: Rect,
2230        dst_rect: Rect,
2231        alpha: f32,
2232        color_filter: Option<ColorFilter>,
2233        blend_mode: BlendMode,
2234    ) {
2235        self.push_blended_primitive(
2236            DrawPrimitive::Image {
2237                rect: dst_rect,
2238                image,
2239                alpha: alpha.clamp(0.0, 1.0),
2240                color_filter,
2241                sampling: ImageSampling::Nearest,
2242                src_rect: Some(src_rect),
2243            },
2244            blend_mode,
2245        );
2246    }
2247
2248    fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
2249        self.push_vector_path(
2250            path,
2251            &brush,
2252            Rect::from_size(self.size),
2253            BlendMode::SrcOver,
2254            PathSide::Inside,
2255        );
2256    }
2257
2258    fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement {
2259        match &self.text_measurer {
2260            Some(measurer) => measurer.measure_text(text, style),
2261            None => estimate_text_measurement(text, style),
2262        }
2263    }
2264
2265    fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle) {
2266        if text.is_empty() || !brush.paints() {
2267            return;
2268        }
2269        let measurement = self.measure_text(text, style);
2270        if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
2271            return;
2272        }
2273        let origin = align_text_block(rect, measurement, style);
2274        if !origin.x.is_finite() || !origin.y.is_finite() {
2275            return;
2276        }
2277        self.recording
2278            .push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
2279                rect: Rect::from_origin_size(origin, measurement.size),
2280                text: shared_text_str(text),
2281                style: style.clone(),
2282                brush,
2283            })));
2284    }
2285
2286    fn into_primitives(self) -> Vec<DrawPrimitive> {
2287        self.recording.into_primitives()
2288    }
2289}
2290
2291#[cfg(test)]
2292#[path = "tests/geometry_tests.rs"]
2293mod tests;