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