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