Skip to main content

cranpose_ui_graphics/
geometry.rs

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