Skip to main content

swiftui_native/views/
shapes.rs

1//! Shapes.
2//!
3//! * Gradients
4
5use super::*;
6use crate::abi;
7use crate::resolve;
8use crate::mangle::{self, swift_type};
9
10// ── Shapes ──
11
12/// Wrap a shape value as a view. Shapes conform to `View` and fill themselves
13/// with the current foreground style, so `foreground_style_color` acts as `.fill(_:)`.
14pub(crate) unsafe fn shape_view(bytes: &[u8], mangled: &[u8]) -> ViewHandle {
15    let meta = unsafe { abi::resolve_type_by_mangled_name(mangled) };
16    assert!(!meta.is_null(), "shape metadata: {}", crate::diag::demangle_type(mangled));
17    let wt = unsafe { abi::get_view_wt(meta) };
18    assert!(!wt.is_null(), "{} !: View", crate::diag::demangle_type(mangled));
19    unsafe { wrap_buf(bytes, meta, wt) }
20}
21
22/// `Rectangle()`
23pub fn rectangle() -> ViewHandle {
24    unsafe { shape_view(&[], swift_type!(SwiftUI.Rectangle).as_bytes()) }
25}
26
27/// `Circle()`
28pub fn circle() -> ViewHandle {
29    unsafe { shape_view(&[], swift_type!(SwiftUI.Circle).as_bytes()) }
30}
31
32/// `Ellipse()`
33pub fn ellipse() -> ViewHandle {
34    unsafe { shape_view(&[], swift_type!(SwiftUI.Ellipse).as_bytes()) }
35}
36
37/// `Capsule()` — `RoundedCornerStyle.circular` is case 0.
38pub fn capsule() -> ViewHandle {
39    unsafe { shape_view(&[0u8], swift_type!(SwiftUI.Capsule).as_bytes()) }
40}
41
42/// `RoundedRectangle(cornerRadius:style:)`.
43///
44/// The layout is a `CGSize` of corner radii followed by the corner style, so
45/// the radius is written into both the width and height slots.
46///
47/// The style is not optional here the way it reads as optional in Swift. Source
48/// that writes `RoundedRectangle(cornerRadius: 10)` gets `.continuous`, not
49/// `.circular` — the two round the corners differently enough to disagree on a
50/// couple of hundred pixels — so a binding that always wrote case 0 quietly
51/// built the other shape. Callers say which one they mean.
52pub fn rounded_rectangle_styled(radius: f64, continuous: bool) -> ViewHandle {
53    let mut buf = [0u8; 17];
54    buf[..8].copy_from_slice(&radius.to_le_bytes());
55    buf[8..16].copy_from_slice(&radius.to_le_bytes());
56    buf[16] = u8::from(continuous);
57    unsafe { shape_view(&buf, swift_type!(SwiftUI.RoundedRectangle).as_bytes()) }
58}
59
60/// `RoundedRectangle(cornerRadius:)`, with the `.continuous` style Swift
61/// source gets when it leaves the style out.
62pub fn rounded_rectangle(radius: f64) -> ViewHandle {
63    rounded_rectangle_styled(radius, true)
64}
65
66// ── Gradients ──
67
68mangle::modifier_types! {
69    /// `.background(_: LinearGradient)`
70    /// `ModifiedContent<AnyView, _BackgroundStyleModifier<LinearGradient>>`
71    pub(crate) LINEAR_GRADIENT_BG_T = ty("_BackgroundStyleModifier").of(vec![ty("LinearGradient")]);
72    /// `.background(_: RadialGradient)`
73    /// `ModifiedContent<AnyView, _BackgroundStyleModifier<RadialGradient>>`
74    pub(crate) RADIAL_GRADIENT_BG_T = ty("_BackgroundStyleModifier").of(vec![ty("RadialGradient")]);
75}
76
77
78/// Build a Swift `Array<Color>` from raw `Color` references.
79///
80/// `Array._allocateUninitialized(_:)` hands back the array value together with a
81/// pointer to its uninitialised buffer, which is the only practical way to build
82/// a Swift array from outside Swift.
83pub(crate) unsafe fn color_array(colors: &[u64]) -> u64 {
84    let (array, buf): (u64, u64);
85    unsafe {
86        // `_allocateUninitializedArray<Element>(_ count:)` is what an array
87        // literal compiles down to: x0 = count, x1 = Element.Type, returning
88        // (Array, buffer) in x0/x1. The element metadata is a normal argument,
89        // not the `self` register.
90        core::arch::asm!("blr {f}", f = in(reg) resolve::array_alloc_uninit(),
91            in("x0") colors.len() as u64,
92            in("x1") resolve::color_meta(),
93            lateout("x0") array, lateout("x1") buf,
94            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
95            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
96            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
97            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
98            lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
99        );
100        let dst = buf as *mut u64;
101        for (i, c) in colors.iter().enumerate() {
102            *dst.add(i) = *c;
103        }
104    }
105    array
106}
107
108/// Build a `Gradient` from a list of colours.
109pub(crate) unsafe fn gradient(colors: &[crate::Color]) -> u64 {
110    let raw: Vec<u64> =
111        colors.iter().map(|c| unsafe { color_value(c.r, c.g, c.b, c.a) }).collect();
112    unsafe { gradient_of(&raw) }
113}
114
115/// Build a `Gradient` from `Color` values that are already built.
116///
117/// The components form cannot express `.yellow` or `.blue`: SwiftUI's named
118/// colours are semantic and a literal triple is a visibly different colour —
119/// 1371 of 1377 drawn pixels different, in the case that found this.
120pub(crate) unsafe fn gradient_of(raw: &[u64]) -> u64 {
121    let array = unsafe { color_array(raw) };
122    let g: u64;
123    unsafe {
124        core::arch::asm!("blr {f}", f = in(reg) resolve::gradient_init(),
125            in("x0") array,
126            in("x20") resolve::gradient_meta(),
127            lateout("x0") g,
128            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
129            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
130            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
131            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
132            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
133        );
134    }
135    g
136}
137
138/// `Color`'s metadata and `ShapeStyle` witness, for a call taking a style.
139pub fn color_meta_for_style() -> *const c_void {
140    resolve::color_meta()
141}
142
143/// See [`color_meta_for_style`].
144pub fn color_shapestyle_wt_for_style() -> *const c_void {
145    resolve::color_shapestyle_wt()
146}
147
148/// Which of the five hierarchical levels a style names.
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150pub enum Hierarchical {
151    Primary,
152    Secondary,
153    Tertiary,
154    Quaternary,
155    Quinary,
156}
157
158/// `HierarchicalShapeStyle.primary` and friends, as a `ShapeStyle`.
159///
160/// `.primary` in a `ShapeStyle` position is *not* `Color.primary`: it is one of
161/// these, four bytes wide, and the two render differently against a tinted
162/// foreground. The shorthand that reaches them from `ShapeStyle` is inlinable;
163/// these concrete statics are exported.
164pub fn hierarchical_style(level: Hierarchical) -> (Vec<u8>, *const c_void, *const c_void) {
165    unsafe {
166        let getter = match level {
167            Hierarchical::Primary => resolve::hierarchical_primary(),
168            Hierarchical::Secondary => resolve::hierarchical_secondary(),
169            Hierarchical::Tertiary => resolve::hierarchical_tertiary(),
170            Hierarchical::Quaternary => resolve::hierarchical_quaternary(),
171            Hierarchical::Quinary => resolve::hierarchical_quinary(),
172        };
173        let meta = abi::metadata0(resolve::hierarchical_ma());
174        assert!(!meta.is_null(), "HierarchicalShapeStyle metadata");
175        let r = abi::static_regs(getter, meta);
176        let mut bytes = vec![0u8; abi::type_bytes(meta)];
177        let n = bytes.len().min(8);
178        bytes[..n].copy_from_slice(&r[0].to_le_bytes()[..n]);
179        // Looked up rather than named: the witness table's own mangling
180        // compresses `ShapeStyle` against the words in
181        // `HierarchicalShapeStyle`, and guessing the substitution letters is
182        // how this file has gone wrong before.
183        let wt = abi::get_view_wt_for_proto(meta, resolve::shape_style_proto());
184        assert!(!wt.is_null(), "HierarchicalShapeStyle !: ShapeStyle");
185        (bytes, meta, wt)
186    }
187}
188
189/// `.opacity(_:)` applied to a style that is already built.
190///
191/// `_OpacityShapeStyle<Base>` stores the base then the `Float`, so unlike the
192/// `_ImplicitShapeStyle` case the base's bytes have to go in first.
193pub fn opacity_of_style(
194    base: &[u8],
195    base_ty: mangle::Type,
196    opacity: f64,
197) -> (Vec<u8>, *const c_void, *const c_void) {
198    unsafe {
199        let ty = mangle::ty("_OpacityShapeStyle").of(vec![base_ty]).mangle();
200        let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
201        assert!(!meta.is_null(), "_OpacityShapeStyle metadata: {ty}");
202        let wt = abi::get_view_wt_for_proto(meta, resolve::shape_style_proto());
203        assert!(!wt.is_null(), "_OpacityShapeStyle !: ShapeStyle");
204        let mut bytes = vec![0u8; abi::type_bytes(meta)];
205        bytes[..base.len()].copy_from_slice(base);
206        let at = base.len().next_multiple_of(4);
207        bytes[at..at + 4].copy_from_slice(&(opacity as f32).to_le_bytes());
208        (bytes, meta, wt)
209    }
210}
211
212/// `ShapeStyle.opacity(_:)` — the current foreground style, faded.
213///
214/// `.opacity(0.5)` where a `ShapeStyle` is expected is a static on a protocol
215/// extension, `@inlinable`, with no symbol. Its body builds one struct:
216///
217/// ```text
218/// %6 = struct $_ImplicitShapeStyle ()
219/// %7 = struct $_OpacityShapeStyle<_ImplicitShapeStyle> (%6, %5)
220/// ```
221///
222/// `_ImplicitShapeStyle` has no stored properties, so the whole value is the
223/// `Float` — which means it can be built here without calling anything.
224pub fn implicit_opacity_style(opacity: f64) -> (Vec<u8>, *const c_void, *const c_void) {
225    unsafe {
226        let ty = mangle::ty("_OpacityShapeStyle")
227            .of(vec![mangle::ty("_ImplicitShapeStyle")])
228            .mangle();
229        let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
230        assert!(!meta.is_null(), "_OpacityShapeStyle<_ImplicitShapeStyle> metadata");
231        let wt = abi::get_view_wt_for_proto(meta, resolve::shape_style_proto());
232        assert!(!wt.is_null(), "_OpacityShapeStyle !: ShapeStyle");
233        let mut bytes = vec![0u8; abi::type_bytes(meta)];
234        bytes[..4].copy_from_slice(&(opacity as f32).to_le_bytes());
235        (bytes, meta, wt)
236    }
237}
238
239/// A `ShadowStyle` — `.drop(...)` or `.inner(...)`.
240///
241/// Both are exported statics: a `Color`, three `CGFloat`s and the metatype in,
242/// the style out through x8. Only the *shorthand* that drops the receiver is
243/// inlinable, and only the default arguments live in generator thunks — which
244/// is why a search for what the SIL references reports these as missing when
245/// they are not.
246///
247/// `color` defaults to `Color(.sRGBLinear, white: 0, opacity: _)` in Swift —
248/// and the two are *not* the same default: a drop shadow gets 0.33 and an
249/// inner one 0.55. Pass `None` to take whichever applies.
250pub fn shadow_style(
251    drop: bool,
252    color: Option<u64>,
253    radius: f64,
254    x: f64,
255    y: f64,
256) -> Vec<u8> {
257    unsafe {
258        let meta = abi::metadata0(resolve::shadow_style_ma());
259        assert!(!meta.is_null(), "ShadowStyle metadata");
260        let colour = color.unwrap_or_else(|| default_shadow_colour(if drop { 0.33 } else { 0.55 }));
261        let mut out = vec![0u8; abi::type_bytes(meta)];
262        let f = if drop { resolve::shadow_style_drop() } else { resolve::shadow_style_inner() };
263        core::arch::asm!("blr {f}", f = in(reg) f,
264            in("x0") colour,
265            in("d0") radius, in("d1") x, in("d2") y,
266            in("x8") out.as_mut_ptr(),
267            in("x20") meta,
268            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
269            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
270            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
271            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
272            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
273        );
274        out
275    }
276}
277
278/// `Color(.sRGBLinear, white: 0, opacity: _)` — Swift's default shadow colour.
279///
280/// `RGBColorSpace` is a frozen enum passed by address; `.sRGBLinear` is case 1.
281fn default_shadow_colour(opacity: f64) -> u64 {
282    unsafe {
283        let mut space: u8 = 1;
284        let out: u64;
285        core::arch::asm!("blr {f}", f = in(reg) resolve::color_white_init(),
286            in("x0") &mut space as *mut u8,
287            in("d0") 0.0f64, in("d1") opacity,
288            in("x20") resolve::color_meta(),
289            lateout("x0") out,
290            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
291            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
292            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
293            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
294            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
295        );
296        out
297    }
298}
299
300/// `.shadow(_:)` applied to the *implicit* foreground style.
301///
302/// `_ShadowShapeStyle<_ImplicitShapeStyle>` again stores an empty base and the
303/// argument, so the whole value is the `ShadowStyle`.
304pub fn implicit_shadow_style(shadow: &[u8]) -> (Vec<u8>, *const c_void, *const c_void) {
305    unsafe {
306        let ty = mangle::ty("_ShadowShapeStyle")
307            .of(vec![mangle::ty("_ImplicitShapeStyle")])
308            .mangle();
309        let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
310        assert!(!meta.is_null(), "_ShadowShapeStyle<_ImplicitShapeStyle> metadata");
311        let wt = abi::get_view_wt_for_proto(meta, resolve::shape_style_proto());
312        assert!(!wt.is_null(), "_ShadowShapeStyle !: ShapeStyle");
313        let mut bytes = vec![0u8; abi::type_bytes(meta)];
314        let n = shadow.len().min(bytes.len());
315        bytes[..n].copy_from_slice(&shadow[..n]);
316        (bytes, meta, wt)
317    }
318}
319
320/// `Color.shadow(_:)` — the generic `ShapeStyle.shadow`, with `Self == Color`.
321pub fn color_shadow_style(color: u64, shadow: &[u8]) -> (Vec<u8>, *const c_void, *const c_void) {
322    unsafe {
323        let subst = [resolve::color_meta(), resolve::color_shapestyle_wt()];
324        let (meta, _, size) =
325            super::opaque_result_type(resolve::shape_style_shadow_oret(), &subst);
326        let wt = abi::get_view_wt_for_proto(meta, resolve::shape_style_proto());
327        assert!(!wt.is_null(), "shadowed style !: ShapeStyle");
328
329        let mut out = vec![0u8; size];
330        let mut arg = shadow.to_vec();
331        let mut receiver = color;
332        core::arch::asm!("blr {f}", f = in(reg) resolve::shape_style_shadow_fn(),
333            in("x0") arg.as_mut_ptr(),
334            in("x1") subst[0], in("x2") subst[1],
335            in("x8") out.as_mut_ptr(),
336            in("x20") &mut receiver as *mut u64,
337            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
338            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
339            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
340            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
341            clobber_abi("C"),
342        );
343        (out, meta, wt)
344    }
345}
346
347/// `ShapeStyle.blendMode(_:)` — the current foreground style, blended.
348///
349/// The same shape as [`implicit_opacity_style`]: an inlinable static whose body
350/// stores an empty `_ImplicitShapeStyle` and the mode into a
351/// `_BlendModeShapeStyle<_ImplicitShapeStyle>`. `BlendMode` is frozen, so the
352/// case number is the whole of it.
353pub fn implicit_blend_mode_style(mode: u8) -> (Vec<u8>, *const c_void, *const c_void) {
354    unsafe {
355        let ty = mangle::ty("_BlendModeShapeStyle")
356            .of(vec![mangle::ty("_ImplicitShapeStyle")])
357            .mangle();
358        let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
359        assert!(!meta.is_null(), "_BlendModeShapeStyle<_ImplicitShapeStyle> metadata");
360        let wt = abi::get_view_wt_for_proto(meta, resolve::shape_style_proto());
361        assert!(!wt.is_null(), "_BlendModeShapeStyle !: ShapeStyle");
362        let mut bytes = vec![0u8; abi::type_bytes(meta)];
363        bytes[0] = mode;
364        (bytes, meta, wt)
365    }
366}
367
368swift_statics! {
369    /// `SwiftUI.Gradient.ColorSpace` — which space a gradient interpolates in.
370    ///
371    /// `Device` walks the colour components as stored, which is what most
372    /// systems do and what makes a red-to-green ramp pass through a muddy
373    /// brown; `Perceptual` interpolates so that equal steps look equal, which
374    /// is Swift's default wherever this is a defaulted argument.
375    pub enum GradientColorSpace {
376        Device = c"$s7SwiftUI8GradientV10ColorSpaceV6deviceAEvgZ",
377        Perceptual = c"$s7SwiftUI8GradientV10ColorSpaceV10perceptualAEvgZ",
378    }
379}
380
381/// `Gradient.colorSpace(_:)` — the gradient interpolated in another space.
382///
383/// Returns an `AnyGradient`, which is the `ShapeStyle` [`Shape::fill_style`]
384/// wants. `Gradient.ColorSpace` is resilient, so `.perceptual` arrives through
385/// x8 and is then handed over by address, with the gradient itself in x1.
386pub fn gradient_color_space_perceptual(
387    colors: &[u64],
388) -> (Vec<u8>, *const c_void, *const c_void) {
389    gradient_color_space(colors, GradientColorSpace::Perceptual)
390}
391
392/// The same, in whichever space is named.
393pub fn gradient_color_space(
394    colors: &[u64],
395    space: GradientColorSpace,
396) -> (Vec<u8>, *const c_void, *const c_void) {
397    unsafe {
398        let g = gradient_of(colors);
399        let space_meta = abi::metadata0(resolve::gradient_color_space_ma());
400        assert!(!space_meta.is_null(), "Gradient.ColorSpace metadata");
401        let mut space = super::static_getter_indirect(
402            space.resolved(),
403            space_meta,
404            abi::type_bytes(space_meta),
405        );
406        let out: u64;
407        core::arch::asm!("blr {f}", f = in(reg) resolve::gradient_color_space_fn(),
408            in("x0") space.as_mut_ptr(),
409            in("x1") g,
410            lateout("x0") out,
411            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
412            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
413            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
414            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
415            lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
416        );
417        (out.to_le_bytes().to_vec(), resolve::any_gradient_meta(), resolve::any_gradient_wt())
418    }
419}
420
421/// Build a `LinearGradient` value, with its metadata and `ShapeStyle` witness.
422///
423/// What [`Shape::fill_style`] wants: `.fill(.linearGradient(...))` is the same
424/// call as `.fill(Color.red)` with a different style, so the style is passed as
425/// bytes rather than as one of a fixed set.
426pub fn linear_gradient_style(
427    colors: &[crate::Color],
428    start: (f64, f64),
429    end: (f64, f64),
430) -> (Vec<u8>, *const c_void, *const c_void) {
431    let raw: Vec<u64> = colors.iter().map(|c| super::color_value_of(*c)).collect();
432    linear_gradient_style_of(&raw, start, end)
433}
434
435/// `AngularGradient(gradient:center:angle:)` — a sweep around a point.
436///
437/// The angle is where the sweep starts, in degrees; SwiftUI's default is zero,
438/// which is what Swift's own defaulted argument passes.
439pub fn angular_gradient_style(
440    colors: &[crate::Color],
441    center: (f64, f64),
442    start_degrees: f64,
443) -> (Vec<u8>, *const c_void, *const c_void) {
444    let raw: Vec<u64> = colors.iter().map(|c| super::color_value_of(*c)).collect();
445    unsafe {
446        let g = gradient_of(&raw);
447        let meta = crate::type_by_name!(swift_type!(SwiftUI.AngularGradient).as_bytes());
448        assert!(!meta.is_null(), "AngularGradient metadata");
449        let mut value = vec![0u8; abi::type_bytes(meta)];
450        // `(sret, gradient, cx, cy, angle)` — the `Angle` is one `Double` of
451        // radians, so degrees are converted here as they are for `rotate`.
452        core::arch::asm!("blr {f}", f = in(reg) resolve::angular_gradient_init(),
453            in("x0") g,
454            in("d0") center.0, in("d1") center.1,
455            in("d2") start_degrees * core::f64::consts::PI / 180.0,
456            in("x8") value.as_mut_ptr(),
457            in("x20") meta,
458            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
459            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
460            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
461            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
462            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
463        );
464        (value, meta, resolve::angular_gradient_wt())
465    }
466}
467
468/// `EllipticalGradient(colors:center:startRadiusFraction:endRadiusFraction:)`.
469///
470/// Takes the colours directly rather than a built `Gradient` — a different
471/// initialiser shape from the three gradients beside it, so there is no
472/// `gradient_of` call here.
473pub fn elliptical_gradient_style(
474    colors: &[crate::Color],
475    center: (f64, f64),
476    start_fraction: f64,
477    end_fraction: f64,
478) -> (Vec<u8>, *const c_void, *const c_void) {
479    let raw: Vec<u64> = colors.iter().map(|c| super::color_value_of(*c)).collect();
480    unsafe {
481        let array = color_array(&raw);
482        let meta = crate::type_by_name!(swift_type!(SwiftUI.EllipticalGradient).as_bytes());
483        assert!(!meta.is_null(), "EllipticalGradient metadata");
484        let mut value = vec![0u8; abi::type_bytes(meta)];
485        core::arch::asm!("blr {f}", f = in(reg) resolve::elliptical_gradient_init(),
486            in("x0") array,
487            in("d0") center.0, in("d1") center.1,
488            in("d2") start_fraction, in("d3") end_fraction,
489            in("x8") value.as_mut_ptr(),
490            in("x20") meta,
491            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
492            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
493            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
494            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
495            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
496        );
497        (value, meta, resolve::elliptical_gradient_wt())
498    }
499}
500
501/// As [`linear_gradient_style`], from `Color` values that are already built.
502pub fn linear_gradient_style_of(
503    colors: &[u64],
504    start: (f64, f64),
505    end: (f64, f64),
506) -> (Vec<u8>, *const c_void, *const c_void) {
507    unsafe {
508        let g = gradient_of(colors);
509        let meta = crate::type_by_name!(swift_type!(SwiftUI.LinearGradient).as_bytes());
510        assert!(!meta.is_null(), "LinearGradient metadata");
511        let mut value = vec![0u8; abi::type_bytes(meta)];
512        core::arch::asm!("blr {f}", f = in(reg) resolve::linear_gradient_init(),
513            in("x0") g,
514            in("d0") start.0, in("d1") start.1, in("d2") end.0, in("d3") end.1,
515            in("x8") value.as_mut_ptr(),
516            in("x20") meta,
517            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
518            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
519            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
520            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
521            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
522        );
523        (value, meta, resolve::linear_gradient_wt())
524    }
525}
526
527/// Apply `.background(LinearGradient(colors:startPoint:endPoint:))`.
528pub fn background_linear_gradient(
529    view: &ViewHandle,
530    colors: &[crate::Color],
531    start: (f64, f64),
532    end: (f64, f64),
533) -> ViewHandle {
534    unsafe {
535        let g = gradient(colors);
536        let meta = crate::type_by_name!(swift_type!(SwiftUI.LinearGradient).as_bytes());
537        assert!(!meta.is_null(), "LinearGradient metadata");
538        let mut value = vec![0u8; abi::type_bytes(meta)];
539        // init(gradient:startPoint:endPoint:) — x0 = Gradient, UnitPoints in d0-d3
540        core::arch::asm!("blr {f}", f = in(reg) resolve::linear_gradient_init(),
541            in("x0") g,
542            in("d0") start.0, in("d1") start.1, in("d2") end.0, in("d3") end.1,
543            in("x8") value.as_mut_ptr(),
544            in("x20") meta,
545            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
546            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
547            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
548            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
549            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
550        );
551        background_style(
552            view,
553            value.as_mut_ptr(),
554            meta,
555            resolve::linear_gradient_wt(),
556            &LINEAR_GRADIENT_BG_T,
557        )
558    }
559}
560
561/// Apply `.background(RadialGradient(colors:center:startRadius:endRadius:))`.
562pub fn background_radial_gradient(
563    view: &ViewHandle,
564    colors: &[crate::Color],
565    center: (f64, f64),
566    start_radius: f64,
567    end_radius: f64,
568) -> ViewHandle {
569    unsafe {
570        let g = gradient(colors);
571        let meta = crate::type_by_name!(swift_type!(SwiftUI.RadialGradient).as_bytes());
572        assert!(!meta.is_null(), "RadialGradient metadata");
573        let mut value = vec![0u8; abi::type_bytes(meta)];
574        core::arch::asm!("blr {f}", f = in(reg) resolve::radial_gradient_init(),
575            in("x0") g,
576            in("d0") center.0, in("d1") center.1, in("d2") start_radius, in("d3") end_radius,
577            in("x8") value.as_mut_ptr(),
578            in("x20") meta,
579            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
580            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
581            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
582            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
583            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
584        );
585        background_style(
586            view,
587            value.as_mut_ptr(),
588            meta,
589            resolve::radial_gradient_wt(),
590            &RADIAL_GRADIENT_BG_T,
591        )
592    }
593}
594
595// ── Shapes kept as shapes ───────────────────────────────────────────────────
596
597/// A shape value, not yet erased to a view.
598///
599/// [`circle`] and its neighbours hand back a `ViewHandle`, because a shape
600/// conforms to `View` and that is all most callers want. `Shape.fill(_:style:)`
601/// and `.stroke(_:lineWidth:)` are not view modifiers, though — they are
602/// extension methods on `Shape` whose receiver is `@in_guaranteed Self` with
603/// `Self: Shape`, and an `AnyView` satisfies neither. So reaching them needs
604/// the value, its metadata and its `Shape` witness table kept together, which
605/// is what this is.
606///
607/// The bytes are the shape's own storage: empty for `Circle`, `Rectangle` and
608/// `Ellipse`, which have no stored properties at all.
609pub struct Shape {
610    bytes: Vec<u8>,
611    meta: *const c_void,
612    wt: *const c_void,
613    /// What this shape *is*, so a wrapping shape can name itself.
614    ///
615    /// `.offset(x:y:)` on a `Circle` gives an `OffsetShape<Circle>` — still a
616    /// `Shape`, and still fillable — so each transform has to spell a type one
617    /// level deeper than the last. Keeping the `mangle::Type` rather than the
618    /// finished string is what makes that possible: the mangling of
619    /// `OffsetShape<Circle>` is not the mangling of `Circle` with something
620    /// glued on, because the substitution table depends on the whole.
621    ty: mangle::Type,
622}
623
624impl Shape {
625    /// A shape whose metadata and conformance are already in hand.
626    ///
627    /// For [`crate::views::custom_shape_value`], whose type was *minted at run
628    /// time*: there is no name for it that `swift_getTypeByMangledName` can
629    /// resolve, because a synthesised nominal descriptor is in no image's
630    /// metadata section. The `ty` is carried anyway so the error message from a
631    /// method that composes names says which shape it was — those methods cannot
632    /// work for a synthesised type, and failing with its name beats failing with
633    /// a null.
634    ///
635    /// # Safety
636    /// `bytes` must hold a value of `meta`'s type, and `wt` must be that type's
637    /// `Shape` conformance.
638    pub(crate) unsafe fn of_metadata(
639        bytes: Vec<u8>,
640        meta: *const c_void,
641        wt: *const c_void,
642        ty: mangle::Type,
643    ) -> Self {
644        Self { bytes, meta, wt, ty }
645    }
646
647    unsafe fn of(bytes: Vec<u8>, ty: mangle::Type) -> Self {
648        unsafe {
649            let mangled = ty.mangle();
650            let meta = abi::resolve_type_by_mangled_name(mangled.as_bytes());
651            assert!(!meta.is_null(), "shape metadata: {}", mangle::demangle(&mangled));
652            let wt = abi::get_view_wt_for_proto(meta, resolve::shape_proto());
653            assert!(!wt.is_null(), "{} !: Shape", mangle::demangle(&mangled));
654            Self { bytes, meta, wt, ty }
655        }
656    }
657
658    /// `Circle()`
659    pub fn circle() -> Self {
660        unsafe { Self::empty(mangle::ty("Circle"), resolve::circle_init()) }
661    }
662
663    /// `Rectangle()`
664    pub fn rectangle() -> Self {
665        unsafe { Self::empty(mangle::ty("Rectangle"), resolve::rectangle_init()) }
666    }
667
668    /// `Ellipse()`
669    pub fn ellipse() -> Self {
670        unsafe { Self::empty(mangle::ty("Ellipse"), resolve::ellipse_init()) }
671    }
672
673    /// A shape with no stored properties, built by calling its initialiser.
674    ///
675    /// These three used to be an empty `Vec`, which is right for as long as
676    /// they stay empty and says nothing if they stop. Calling the initialiser
677    /// is what source does — the IR has one `Rectangle()` call per rectangle —
678    /// and it writes whatever the type actually holds.
679    ///
680    /// # Safety
681    /// `init` must be `ty`'s zero-argument initialiser.
682    unsafe fn empty(ty: mangle::Type, init: *const c_void) -> Self {
683        unsafe {
684            let mut me = Self::of(Vec::new(), ty);
685            let size = abi::type_bytes(me.meta);
686            let mut bytes = vec![0u8; size.max(1)];
687            core::arch::asm!("blr {f}", f = in(reg) init,
688                in("x8") bytes.as_mut_ptr(),
689                in("x20") me.meta,
690                lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
691                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
692                lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
693                lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
694                lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
695            );
696            bytes.truncate(size);
697            me.bytes = bytes;
698            me
699        }
700    }
701
702    /// `Capsule()` — one byte of `RoundedCornerStyle`.
703    ///
704    /// Case **1**, `.continuous`, which is what `Capsule()` with no argument
705    /// means. This said case 0 and called it `.circular` until a listing needed
706    /// a capsule's corner style to match; nothing had compared one before, and
707    /// [`Self::rounded_rectangle`] next door had the numbering right all along.
708    pub fn capsule() -> Self {
709        unsafe { Self::of(vec![1u8], mangle::ty("Capsule")) }
710    }
711
712    /// `RoundedRectangle(cornerRadius:style:)` — a `CGSize` then the style.
713    ///
714    /// `style` is `RoundedCornerStyle`: `.circular` is case 0 and
715    /// `.continuous` is case 1. The one-argument spelling in Swift source does
716    /// *not* mean circular — the declared default is `.continuous`, and the two
717    /// differ by about two hundred pixels around the corners of a 200x50 box.
718    pub fn rounded_rectangle(radius: f64, continuous: bool) -> Self {
719        let mut bytes = Vec::with_capacity(24);
720        bytes.extend_from_slice(&radius.to_le_bytes());
721        bytes.extend_from_slice(&radius.to_le_bytes());
722        bytes.push(u8::from(continuous));
723        unsafe { Self::of(bytes, mangle::ty("RoundedRectangle")) }
724    }
725
726    /// The shape on its own, as a view.
727    /// The shape's own type, for spelling a type it appears inside.
728    ///
729    /// `fill_then_stroke` names its result `StrokeShapeView<Shape, Color,
730    /// _ShapeView<Shape, Color>>`, which cannot be written without knowing what
731    /// the shape is — and after `.inset` or `.rotation` that is no longer the
732    /// shape it started as.
733    pub(crate) fn ty(&self) -> &mangle::Type {
734        &self.ty
735    }
736
737    pub fn view(&self) -> ViewHandle {
738        unsafe {
739            let wt = abi::get_view_wt(self.meta);
740            ViewHandle::wrap(self.bytes.as_ptr() as _, self.meta, wt)
741        }
742    }
743
744    /// A `Path`, built by the four mutating methods below.
745    ///
746    /// `Path` is 33 bytes — a reference plus a discriminator — so it is neither
747    /// one word nor a neat multiple of one, and every method takes it as
748    /// `inout` through x20 while the points travel in d0 onwards.
749    pub fn path() -> PathBuilder {
750        let mut bytes = vec![0u8; 40]; // stride, not size: the callee may write padding
751        unsafe {
752            core::arch::asm!("blr {f}", f = in(reg) resolve::path_init(),
753                in("x8") bytes.as_mut_ptr(),
754                lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
755                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
756                lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
757                lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
758                lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
759                clobber_abi("C"),
760            );
761        }
762        PathBuilder { bytes }
763    }
764
765    /// The shape's own name, when it is a plain nominal type.
766    fn base_name(&self) -> Option<&'static str> {
767        match &self.ty {
768            mangle::Type::Nominal { path, .. } => path.last().copied(),
769            _ => None,
770        }
771    }
772
773    /// `.inset(by:)` — the shape drawn inside its own bounds.
774    ///
775    /// `inset(by:)` is an `InsettableShape` *requirement*, so it is exported
776    /// once per conforming type rather than once on the protocol: there is a
777    /// `Circle.inset`, a `Rectangle.inset` and so on, and no
778    /// `InsettableShape.inset` at all. The result is `some InsettableShape`,
779    /// whose first conformance is `InsettableShape` — so the `Shape` witness
780    /// comes from the concrete metadata, as with `stroke(lineWidth:)`.
781    pub fn inset(&self, amount: f64) -> Self {
782        let name = self.base_name().expect("inset needs a plain shape");
783        let symbol: &core::ffi::CStr = match name {
784            "Circle" => c"$s7SwiftUI6CircleV5inset2byQr12CoreGraphics7CGFloatV_tF",
785            "Capsule" => c"$s7SwiftUI7CapsuleV5inset2byQr12CoreGraphics7CGFloatV_tF",
786            "Ellipse" => c"$s7SwiftUI7EllipseV5inset2byQr12CoreGraphics7CGFloatV_tF",
787            "Rectangle" => c"$s7SwiftUI9RectangleV5inset2byQr12CoreGraphics7CGFloatV_tF",
788            "RoundedRectangle" => c"$s7SwiftUI16RoundedRectangleV5inset2byQr12CoreGraphics7CGFloatV_tF",
789            other => panic!("no exported inset(by:) for {other}"),
790        };
791        let oret: &core::ffi::CStr = match name {
792            "Circle" => c"$s7SwiftUI6CircleV5inset2byQr12CoreGraphics7CGFloatV_tFQOMQ",
793            "Capsule" => c"$s7SwiftUI7CapsuleV5inset2byQr12CoreGraphics7CGFloatV_tFQOMQ",
794            "Ellipse" => c"$s7SwiftUI7EllipseV5inset2byQr12CoreGraphics7CGFloatV_tFQOMQ",
795            "RoundedRectangle" => c"$s7SwiftUI16RoundedRectangleV5inset2byQr12CoreGraphics7CGFloatV_tFQOMQ",
796            _ => c"$s7SwiftUI9RectangleV5inset2byQr12CoreGraphics7CGFloatV_tFQOMQ",
797        };
798        unsafe {
799            let (meta, _, size) = super::opaque_result_type(resolve::sym(oret), &[]);
800            let wt = abi::get_view_wt_for_proto(meta, resolve::shape_proto());
801            assert!(!wt.is_null(), "inset shape !: Shape");
802            let mut bytes = vec![0u8; size.max(1)];
803            let mut receiver = self.receiver();
804            core::arch::asm!("blr {f}", f = in(reg) resolve::sym(symbol),
805                in("d0") amount,
806                in("x8") bytes.as_mut_ptr(),
807                in("x20") receiver.as_mut_ptr(),
808                lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
809                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
810                lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
811                lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
812                lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
813            );
814            // `inset(by:)` returns `some InsettableShape`, so the call site has
815            // no name — but the type behind it is always `<Shape>._Inset`, and
816            // keeping the shape this started as is what made `fill_then_stroke`
817            // spell `StrokeShapeView<Circle, …>` for an inset circle: a type
818            // that exists, is the wrong one, and is laid out differently, which
819            // is what its field-offset assertion was catching.
820            //
821            // Spelled as a nested path rather than pasted in as a verbatim
822            // name: this crate's mangler numbers its own substitutions, and a
823            // full `7SwiftUI6CircleV6_InsetV` dropped into the middle shifts
824            // every `AA` after it, so the composed name resolves to nothing.
825            //
826            // The runtime knows the real name, so the guess is checked against
827            // it rather than trusted.
828            let ty = mangle::Type::Nominal {
829                module: "SwiftUI",
830                path: vec![name, "_Inset"],
831                kind: mangle::Kind::Struct,
832            };
833            debug_assert_eq!(
834                abi::mangled_type_name(meta).as_deref(),
835                Some(ty.mangle().as_bytes()),
836                "{name}.inset(by:) does not return {name}._Inset",
837            );
838            Self { bytes, meta, wt, ty }
839        }
840    }
841
842    /// `.offset(x:y:)` — an `OffsetShape` around this one.
843    ///
844    /// A concrete wrapper, not an opaque return, so there is no substitution
845    /// list to build: the result's size comes from its own metadata and the
846    /// call writes it through x8.
847    pub fn offset(&self, x: f64, y: f64) -> Self {
848        let ty = mangle::ty("OffsetShape").of(vec![self.ty.clone()]);
849        unsafe { self.wrapped(resolve::shape_offset_fn(), ty, [x, y, 0.0, 0.0]) }
850    }
851
852    /// `.rotation(_:anchor:)` — a `RotatedShape` around this one.
853    ///
854    /// `Angle` stores radians, and `.center` is `UnitPoint(0.5, 0.5)`.
855    pub fn rotation(&self, degrees: f64) -> Self {
856        let ty = mangle::ty("RotatedShape").of(vec![self.ty.clone()]);
857        let radians = degrees * std::f64::consts::PI / 180.0;
858        unsafe { self.wrapped(resolve::shape_rotation_fn(), ty, [radians, 0.5, 0.5, 0.0]) }
859    }
860
861    /// Call a `Shape` extension method that returns a concrete wrapper.
862    ///
863    /// The floats go in d0 onwards, the receiver is `@in_guaranteed` in x20,
864    /// and the two generic words — `Self` and its `Shape` witness — follow in
865    /// x0 and x1 because no formal argument occupies an integer register.
866    unsafe fn wrapped(&self, func: *const c_void, ty: mangle::Type, floats: [f64; 4]) -> Self {
867        unsafe {
868            let mangled = ty.mangle();
869            let meta = abi::resolve_type_by_mangled_name(mangled.as_bytes());
870            assert!(!meta.is_null(), "shape metadata: {}", mangle::demangle(&mangled));
871            let wt = abi::get_view_wt_for_proto(meta, resolve::shape_proto());
872            assert!(!wt.is_null(), "{} !: Shape", mangle::demangle(&mangled));
873
874            let mut bytes = vec![0u8; abi::type_bytes(meta).max(1)];
875            let mut receiver = self.receiver();
876            core::arch::asm!("blr {f}", f = in(reg) func,
877                in("x0") self.meta,
878                in("x1") self.wt,
879                in("d0") floats[0], in("d1") floats[1],
880                in("d2") floats[2], in("d3") floats[3],
881                in("x8") bytes.as_mut_ptr(),
882                in("x20") receiver.as_mut_ptr(),
883                lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
884                lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
885                lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
886                lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
887                lateout("x21") _, clobber_abi("C"),
888            );
889            Self { bytes, meta, wt, ty }
890        }
891    }
892
893    /// `.fill(_:style:)` with any `ShapeStyle` — its value, metadata and
894    /// witness table.
895    /// # Safety
896    /// `style` must hold an initialised value of the type `meta` describes, and
897    /// `wt` must be that type's `ShapeStyle` conformance.
898    pub unsafe fn fill_style(&self, style: &[u8], meta: *const c_void, wt: *const c_void) -> ViewHandle {
899        unsafe { self.filled(style, meta, wt) }
900    }
901
902    /// `.fill(_:style:)`, by building the `_ShapeView` it reduces to.
903    ///
904    /// `Shape.fill` has two overloads — one returning `some View`, one
905    /// returning `_ShapeView<Self, S>` — and source picks the second. Neither
906    /// helps directly: the concrete one is `@inlinable` and not exported, and
907    /// the opaque one needs its result type resolved through a descriptor.
908    /// What the inlined body does is call `_ShapeView.init`, which *is*
909    /// exported, so that is what this calls.
910    ///
911    /// The `style:` default stays a constant here. SwiftUI's own generator for
912    /// it — `…FfA0_` in the IR — is emitted into the client rather than
913    /// exported, so there is nothing to call: `FillStyle()` is
914    /// `(eoFill: false, antialiased: true)`, two bytes, and `0x0100` is them.
915    ///
916    /// # Safety
917    /// `style` must be a live value of `meta` conforming through `wt`.
918    unsafe fn filled(
919        &self,
920        style: &[u8],
921        style_meta: *const c_void,
922        style_wt: *const c_void,
923    ) -> ViewHandle {
924        unsafe {
925            let meta = abi::generic_metadata_from_descriptor(
926                resolve::shape_view_desc(),
927                &[self.meta, style_meta, self.wt, style_wt],
928            );
929            assert!(!meta.is_null(), "_ShapeView metadata");
930            let wt = abi::get_view_wt(meta);
931            assert!(!wt.is_null(), "_ShapeView !: View");
932
933            let mut arg = style.to_vec();
934            arg.resize(abi::type_bytes(style_meta).max(style.len()).max(1), 0);
935            let mut receiver = self.receiver();
936            let mut result = vec![0u8; abi::type_bytes(meta)];
937            // `(sret, @in shape, @in style, i16 fillStyle, Self.Type, S.Type,
938            //   Self: Shape WT, S: ShapeStyle WT)` — an initialiser, so the
939            // shape is an ordinary argument rather than `self` in x20.
940            core::arch::asm!("blr {f}", f = in(reg) resolve::shape_view_init(),
941                in("x0") receiver.as_mut_ptr(),
942                in("x1") arg.as_mut_ptr(),
943                in("x2") FILL_STYLE_DEFAULT,
944                in("x3") self.meta,
945                in("x4") style_meta,
946                in("x5") self.wt,
947                in("x6") style_wt,
948                in("x8") result.as_mut_ptr(),
949                lateout("x7") _, lateout("x9") _, lateout("x10") _,
950                lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
951                lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
952                lateout("x20") _, lateout("x21") _, clobber_abi("C"),
953            );
954            wrap_owned(result, meta, wt)
955        }
956    }
957
958    /// `.fill(_:)`'s value as raw bytes plus its type, for composing into a
959    /// larger one rather than erasing it to a view.
960    ///
961    /// `ShapeView.stroke(_:lineWidth:)` needs this: it is
962    /// `@_alwaysEmitIntoClient`, and what it builds is a `StrokeShapeView`
963    /// holding *two* filled shape views — the stroke in front, the original fill
964    /// behind it as a background — so both have to survive as values.
965    pub(crate) fn fill_parts(
966        &self,
967        style: &[u8],
968        meta: *const c_void,
969        wt: *const c_void,
970    ) -> (Vec<u8>, *const c_void) {
971        unsafe {
972            let subst = [self.meta, meta, self.wt, wt];
973            let (ty, _, size) = super::opaque_result_type(resolve::shape_fill_oret(), &subst);
974            let mut result = vec![0u8; size];
975            let mut arg = style.to_vec();
976            arg.resize(abi::type_bytes(meta).max(style.len()).max(1), 0);
977            let mut receiver = self.receiver();
978            core::arch::asm!("blr {f}", f = in(reg) resolve::shape_fill_fn(),
979                in("x0") arg.as_mut_ptr(),
980                in("x1") FILL_STYLE_DEFAULT as *const c_void,
981                in("x2") subst[0], in("x3") subst[1], in("x4") subst[2], in("x5") subst[3],
982                in("d0") 0.0f64,
983                in("x8") result.as_mut_ptr(),
984                in("x20") receiver.as_mut_ptr(),
985                lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
986                lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
987                lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
988                lateout("x21") _, clobber_abi("C"),
989            );
990            (result, ty)
991        }
992    }
993
994    /// `.stroke(_:lineWidth:)` with any `ShapeStyle`.
995    /// # Safety
996    /// `style` must hold an initialised value of the type `meta` describes, and
997    /// `wt` must be that type's `ShapeStyle` conformance.
998    pub unsafe fn stroke_style(
999        &self,
1000        style: &[u8],
1001        meta: *const c_void,
1002        wt: *const c_void,
1003        line_width: f64,
1004    ) -> ViewHandle {
1005        unsafe {
1006            let inner = self.styled(
1007                resolve::shape_stroke_fn(),
1008                resolve::shape_stroke_oret(),
1009                style,
1010                meta,
1011                wt,
1012                false,
1013                line_width,
1014            );
1015            // Every stroke, not just the colour-taking one: Swift's
1016            // `.stroke(_:lineWidth:)` returns a `StrokeShapeView` whatever the
1017            // style is.
1018            self.as_stroke_shape_view(inner, meta, wt)
1019        }
1020    }
1021
1022    /// `.fill(_:style:)` with a `Color`.
1023    pub fn fill(&self, color: u64) -> ViewHandle {
1024        let bytes = color.to_le_bytes();
1025        // SAFETY: a `Color` word with `Color`'s metadata and conformance.
1026        unsafe { self.fill_style(&bytes, resolve::color_meta(), resolve::color_shapestyle_wt()) }
1027    }
1028
1029    /// `.stroke(lineWidth:)`, kept as a `Shape` rather than erased to a view.
1030    ///
1031    /// What `overlay(_:in:)` wants: its `in:` argument is a `Shape`, and a
1032    /// stroked shape still is one.
1033    pub fn stroked(&self, line_width: f64) -> Self {
1034        unsafe {
1035            let subst = [self.meta, self.wt];
1036            let (meta, _, size) =
1037                super::opaque_result_type(resolve::shape_stroke_plain_oret(), &subst);
1038            let wt = abi::get_view_wt_for_proto(meta, resolve::shape_proto());
1039            assert!(!wt.is_null(), "stroked shape !: Shape");
1040            let mut bytes = vec![0u8; size.max(1)];
1041            let mut receiver = self.receiver();
1042            core::arch::asm!("blr {f}", f = in(reg) resolve::shape_stroke_plain_fn(),
1043                in("x0") subst[0], in("x1") subst[1],
1044                in("d0") line_width,
1045                in("x8") bytes.as_mut_ptr(),
1046                in("x20") receiver.as_mut_ptr(),
1047                lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
1048                lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
1049                lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
1050                lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
1051                lateout("x21") _, clobber_abi("C"),
1052            );
1053            Self { bytes, meta, wt, ty: self.ty.clone() }
1054        }
1055    }
1056
1057    /// The value, metadata and `Shape` witness, for a call that takes a shape.
1058    pub fn parts(&self) -> (Vec<u8>, *const c_void, *const c_void) {
1059        (self.bytes.clone(), self.meta, self.wt)
1060    }
1061
1062    /// `.stroke(lineWidth:)` — no style, so it strokes with the foreground.
1063    ///
1064    /// One generic parameter rather than two, so the substitution list is just
1065    /// `Self` and its `Shape` witness, and the line width is the only formal
1066    /// argument.
1067    pub fn stroke_default(&self, line_width: f64) -> ViewHandle {
1068        unsafe {
1069            let subst = [self.meta, self.wt];
1070            // `-> some Shape`, not `-> some View`. The opaque type carries one
1071            // conformance and it is `Shape`, so asking for the second one hands
1072            // back whatever follows it in memory; the value builds, reports the
1073            // right type, and faults when something tries to draw it. The
1074            // `View` witness comes from the concrete metadata instead.
1075            let (meta, _, size) =
1076                super::opaque_result_type(resolve::shape_stroke_plain_oret(), &subst);
1077            let wt = abi::get_view_wt(meta);
1078            assert!(!wt.is_null(), "stroked shape !: View");
1079            let mut result = vec![0u8; size];
1080            let mut receiver = self.receiver();
1081            core::arch::asm!("blr {f}", f = in(reg) resolve::shape_stroke_plain_fn(),
1082                in("x0") subst[0], in("x1") subst[1],
1083                in("d0") line_width,
1084                in("x8") result.as_mut_ptr(),
1085                in("x20") receiver.as_mut_ptr(),
1086                lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
1087                lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
1088                lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
1089                lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
1090                lateout("x21") _, clobber_abi("C"),
1091            );
1092            wrap_owned(result, meta, wt)
1093        }
1094    }
1095
1096    /// `.stroke(_:lineWidth:)` with a `Color`.
1097    pub fn stroke(&self, color: u64, line_width: f64) -> ViewHandle {
1098        let bytes = color.to_le_bytes();
1099        // SAFETY: as [`Shape::fill`].
1100        unsafe {
1101            self.stroke_style(&bytes, resolve::color_meta(), resolve::color_shapestyle_wt(), line_width)
1102        }
1103    }
1104
1105    /// Present a stroked shape as the `StrokeShapeView` Swift builds.
1106    ///
1107    /// `Circle().stroke(.blue, lineWidth: 4)` in Swift is a
1108    /// `StrokeShapeView<Circle, Color, EmptyView>`, and dumping one shows what
1109    /// that is: a newtype over
1110    /// `ModifiedContent<_ShapeView<_StrokedShape<Circle>, Color>,
1111    /// _BackgroundModifier<EmptyView>>` — one stored property, so identical in
1112    /// layout to what it wraps. The bindings built the inner `_ShapeView` and
1113    /// stopped there, which rendered the same and typed differently.
1114    ///
1115    /// `.stroke` is `@inlinable`, so there is no initialiser to call and no
1116    /// instantiation to read a descriptor off; the descriptor itself is exported
1117    /// and is what this instantiates from. A wrong argument vector would build
1118    /// some *other* type rather than fail, so the result is checked and anything
1119    /// unexpected keeps the inner view, which is what this always returned.
1120    ///
1121    /// # Safety
1122    /// `inner` must be the `_ShapeView` this shape's stroke produced.
1123    unsafe fn as_stroke_shape_view(
1124        &self,
1125        inner: ViewHandle,
1126        style_meta: *const c_void,
1127        style_wt: *const c_void,
1128    ) -> ViewHandle {
1129        unsafe {
1130            let empty = super::empty_view();
1131            let bg = super::background_content(&inner, &empty, super::Alignment::Center);
1132
1133            let desc = resolve::stroke_shape_view_desc();
1134            if desc.is_null() {
1135                return inner;
1136            }
1137            // Parameters first — Content, Style, Background — then their
1138            // conformances: Shape, ShapeStyle, View.
1139            let args = [
1140                self.meta,
1141                style_meta,
1142                resolve::emptyview_meta(),
1143                self.wt,
1144                style_wt,
1145                resolve::emptyview_view_wt(),
1146            ];
1147            let meta = abi::generic_metadata_from_descriptor(desc, &args);
1148            if meta.is_null() {
1149                return inner;
1150            }
1151            // `StrokeShapeView` is a newtype over the `ModifiedContent` below
1152            // it: one stored property at offset zero, which is what lets the
1153            // bytes carry over unchanged. Read that from the metadata rather
1154            // than inferring it from the sizes agreeing.
1155            if abi::struct_field_offsets(meta).as_deref() != Some(&[0][..])
1156                || abi::type_bytes(meta) != abi::type_bytes(bg.meta)
1157            {
1158                return inner;
1159            }
1160            if !crate::diag::type_name(meta).starts_with("SwiftUI.StrokeShapeView<") {
1161                return inner;
1162            }
1163            let wt = abi::get_view_wt(meta);
1164            if wt.is_null() {
1165                return inner;
1166            }
1167            // Same layout, so the bytes carry over as they are — copied through
1168            // the value witness so anything refcounted inside is retained.
1169            let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(bg.meta);
1170            let mut out = vec![0u8; abi::type_bytes(meta)];
1171            if vwt.size > 0 {
1172                (vwt.initialize_with_copy)(
1173                    out.as_mut_ptr() as *mut c_void,
1174                    bg.value_ptr() as *mut c_void,
1175                    bg.meta,
1176                );
1177            }
1178            super::wrap_owned(out, meta, wt)
1179        }
1180    }
1181
1182    /// Both calls have the same shape and differ by one register.
1183    ///
1184    /// `fill(_ content: S, style: FillStyle)` puts the `FillStyle` — two
1185    /// `Bool`s, so loadable — in x1, which pushes the four generic words to
1186    /// x2. `stroke(_ content: S, lineWidth: CGFloat)` sends its second
1187    /// argument in d0 instead, so nothing occupies x1 and the generic words
1188    /// start there. Reading the style metadata out of a line width faults
1189    /// inside the runtime.
1190    #[allow(clippy::too_many_arguments)]
1191    unsafe fn styled(
1192        &self,
1193        func: *const c_void,
1194        oret: *const c_void,
1195        style: &[u8],
1196        style_meta: *const c_void,
1197        style_wt: *const c_void,
1198        filling: bool,
1199        line_width: f64,
1200    ) -> ViewHandle {
1201        unsafe {
1202            // The substitution list an opaque return wants is every generic
1203            // argument and then every witness table: `Self`, the style,
1204            // `Self: Shape`, and the style's `ShapeStyle`.
1205            let subst = [self.meta, style_meta, self.wt, style_wt];
1206            let (meta, wt, size) = super::opaque_result_type(oret, &subst);
1207
1208            let mut result = vec![0u8; size];
1209            let mut arg = style.to_vec();
1210            arg.resize(abi::type_bytes(style_meta).max(style.len()).max(1), 0);
1211            let mut receiver = self.receiver();
1212            let (x1, x2, x3, x4, x5) = if filling {
1213                (FILL_STYLE_DEFAULT as *const c_void, subst[0], subst[1], subst[2], subst[3])
1214            } else {
1215                (subst[0], subst[1], subst[2], subst[3], core::ptr::null())
1216            };
1217            core::arch::asm!("blr {f}", f = in(reg) func,
1218                in("x0") arg.as_mut_ptr(),
1219                in("x1") x1, in("x2") x2, in("x3") x3, in("x4") x4, in("x5") x5,
1220                in("d0") line_width,
1221                in("x8") result.as_mut_ptr(),
1222                in("x20") receiver.as_mut_ptr(),
1223                lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
1224                lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
1225                lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
1226                lateout("x21") _, clobber_abi("C"),
1227            );
1228            wrap_owned(result, meta, wt)
1229        }
1230    }
1231
1232    /// The shape's storage, with a byte to spare.
1233    ///
1234    /// `Circle` and `Rectangle` have no stored properties, and an empty `Vec`
1235    /// has a dangling pointer — fine where nothing reads it, but not worth
1236    /// relying on across a call whose layout is only inferred.
1237    fn receiver(&self) -> Vec<u8> {
1238        let mut v = self.bytes.clone();
1239        v.push(0);
1240        v
1241    }
1242}
1243
1244/// A `Path` under construction.
1245///
1246/// Every method is `mutating` in Swift, so each takes the path through x20 and
1247/// writes back in place; there is no value to thread through. Finish with
1248/// [`PathBuilder::shape`].
1249pub struct PathBuilder {
1250    bytes: Vec<u8>,
1251}
1252
1253impl PathBuilder {
1254    /// Move the `Path` into an `@out` slot and give up ownership.
1255    ///
1256    /// For a `Shape.path(in:)` witness, whose result slot SwiftUI supplies:
1257    /// copying and then dropping the builder would release the path's storage
1258    /// while SwiftUI still holds it.
1259    ///
1260    /// # Safety
1261    /// `out` must have room for a `Path` — the builder's own buffer is sized for
1262    /// one, and `bytes.len()` is that size.
1263    pub(crate) unsafe fn move_into(self, out: *mut u8) {
1264        let me = core::mem::ManuallyDrop::new(self);
1265        unsafe { core::ptr::copy_nonoverlapping(me.bytes.as_ptr(), out, me.bytes.len()) };
1266    }
1267
1268    /// `move(to:)`
1269    pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
1270        unsafe { self.point(resolve::path_move_to(), x, y, 0.0, 0.0) }
1271    }
1272
1273    /// `addLine(to:)`
1274    pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
1275        unsafe { self.point(resolve::path_add_line(), x, y, 0.0, 0.0) }
1276    }
1277
1278    /// `addQuadCurve(to:control:)` — the destination in d0/d1, the control
1279    /// point in d2/d3.
1280    pub fn quad_curve_to(&mut self, x: f64, y: f64, cx: f64, cy: f64) -> &mut Self {
1281        unsafe { self.point(resolve::path_add_quad_curve(), x, y, cx, cy) }
1282    }
1283
1284    /// `closeSubpath()`
1285    pub fn close(&mut self) -> &mut Self {
1286        unsafe { self.point(resolve::path_close_subpath(), 0.0, 0.0, 0.0, 0.0) }
1287    }
1288
1289    unsafe fn point(&mut self, f: *const c_void, a: f64, b: f64, c: f64, d: f64) -> &mut Self {
1290        unsafe {
1291            core::arch::asm!("blr {f}", f = in(reg) f,
1292                in("d0") a, in("d1") b, in("d2") c, in("d3") d,
1293                in("x20") self.bytes.as_mut_ptr(),
1294                lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
1295                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
1296                lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
1297                lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
1298                lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
1299                clobber_abi("C"),
1300            );
1301        }
1302        self
1303    }
1304
1305    /// The finished path, as a `Shape`.
1306    pub fn shape(&self) -> Shape {
1307        unsafe { Shape::of(self.bytes.clone(), mangle::ty("Path")) }
1308    }
1309}
1310
1311/// `FillStyle()` — two `Bool`s in one register: `isEOFilled` false in byte 0,
1312/// `isAntialiased` true in byte 1.
1313const FILL_STYLE_DEFAULT: u64 = 0x0100;
1314
1315/// `.strokeBorder(_:lineWidth:antialiased:)` on an `InsettableShape`.
1316///
1317/// Not `.stroke`: a stroke straddles the shape's edge, a border is inset so it
1318/// falls entirely inside. The difference is half the line width everywhere.
1319///
1320/// Two overloads share the name. The older returns `some View` and is exported;
1321/// the newer returns a concrete `StrokeBorderShapeView` and is
1322/// `@_alwaysEmitIntoClient`, and it is the one Swift picks — so matching what a
1323/// reference builds means building that type's value rather than calling either.
1324pub fn stroke_border(
1325    shape: super::InsettableShape,
1326    color: u64,
1327    line_width: f64,
1328) -> ViewHandle {
1329    unsafe {
1330        let (ma, _insettable_wt, init) = shape.parts();
1331        let shape_meta = abi::metadata0(ma);
1332        let mut receiver = vec![0u8; abi::type_bytes(shape_meta).max(1)];
1333        core::arch::asm!("blr {f}", f = in(reg) init,
1334            in("x8") receiver.as_mut_ptr(),
1335            in("x20") shape_meta,
1336            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
1337            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
1338            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1339            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1340            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
1341        );
1342
1343        let subst = [shape_meta, resolve::color_meta(), _insettable_wt, resolve::color_shapestyle_wt()];
1344        let (meta, wt, size) =
1345            super::opaque_result_type(resolve::stroke_border_oret(), &subst);
1346        let style = color.to_le_bytes();
1347        let mut result = vec![0u8; size.max(1)];
1348        core::arch::asm!("blr {f}", f = in(reg) resolve::stroke_border_fn(),
1349            in("x0") style.as_ptr(),
1350            in("d0") line_width,
1351            in("x1") 1u64,
1352            in("x2") subst[0], in("x3") subst[1], in("x4") subst[2], in("x5") subst[3],
1353            in("x8") result.as_mut_ptr(),
1354            in("x20") receiver.as_mut_ptr(),
1355            lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
1356            lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
1357            lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
1358            lateout("x21") _, clobber_abi("C"),
1359        );
1360        // The exported call produced the *inner* view. Swift's overload wraps it:
1361        //   StrokeBorderShapeView<Shape, Style, EmptyView> {
1362        //       shape, view: ModifiedContent<that, _BackgroundModifier<EmptyView>>
1363        //   }
1364        // `Circle` and `EmptyView` are both zero bytes, so the whole wrapper adds
1365        // only the background modifier's `Alignment` — which is `.center`.
1366        let outer_mangled = format!(
1367            "7SwiftUI21StrokeBorderShapeViewVy{}7SwiftUI5ColorV7SwiftUI9EmptyViewVG",
1368            shape.type_mangling()
1369        );
1370        let outer = abi::resolve_type_by_mangled_name(outer_mangled.as_bytes());
1371        assert!(!outer.is_null(), "{}", crate::diag::demangle_type(outer_mangled.as_bytes()));
1372        let outer_wt = abi::get_view_wt(outer);
1373        assert!(!outer_wt.is_null(), "StrokeBorderShapeView !: View");
1374
1375        // `Alignment` is two `AlignmentKey`s and comes back in x0/x1, not
1376        // through x8 — reading it as an indirect return left both keys zero,
1377        // which renders the same (centre *is* what an empty background aligns
1378        // to) and only the value comparison saw.
1379        let (ah, av) = super::static_getter(resolve::alignment_center(), resolve::alignment_meta());
1380
1381        let mut bytes = vec![0u8; abi::type_bytes(outer)];
1382        let inner = abi::type_size(meta);
1383        bytes[..inner].copy_from_slice(&result[..inner]);
1384        // Where the alignment sits is read from the metadata, by the same walk
1385        // [`stroke_view_offsets`] does — it is not a field of the outer type at
1386        // all, but of the `_BackgroundModifier` two levels in. The arithmetic
1387        // that used to stand in here padded the inner view to eight, and
1388        // writing at its unpadded 58 instead straddled the two alignment keys
1389        // and read back as one `AlignmentKey(bits: 0x50002)` — which still
1390        // rendered centred, because an empty background has nothing to
1391        // misalign.
1392        let align_at = border_view_offsets(outer)
1393            .map(|(_, _, _, align)| align)
1394            .unwrap_or_else(|| inner.next_multiple_of(8));
1395        bytes[align_at..align_at + 8].copy_from_slice(&ah.to_le_bytes());
1396        bytes[align_at + 8..align_at + 16].copy_from_slice(&av.to_le_bytes());
1397        let _ = wt;
1398        wrap_buf(&bytes, outer, outer_wt)
1399    }
1400}
1401
1402/// `.fill(_:).stroke(_:lineWidth:)` — a shape filled, then outlined.
1403///
1404/// `ShapeView.stroke(_:lineWidth:antialiased:)` is `@_alwaysEmitIntoClient`, so
1405/// there is nothing to call. What it builds is
1406/// `StrokeShapeView<Content, S, Self>`, whose single field is a
1407/// `ModifiedContent` putting the *stroke* in front and the original *fill*
1408/// behind it as a `_BackgroundModifier` — so the fill is drawn as a background
1409/// rather than composited, and both filled shape views have to be built.
1410pub fn fill_then_stroke(
1411    shape: &Shape,
1412    fill: u64,
1413    stroke: u64,
1414    line_width: f64,
1415    result_mangled: &[u8],
1416) -> ViewHandle {
1417    unsafe {
1418        let color_meta = resolve::color_meta();
1419        let color_wt = resolve::color_shapestyle_wt();
1420        let (background, bg_ty) = shape.fill_parts(&fill.to_le_bytes(), color_meta, color_wt);
1421        let (content, content_ty) =
1422            shape.stroked(line_width).fill_parts(&stroke.to_le_bytes(), color_meta, color_wt);
1423
1424        let meta = abi::resolve_type_by_mangled_name(result_mangled);
1425        assert!(!meta.is_null(), "{}", crate::diag::demangle_type(result_mangled));
1426        let wt = abi::get_view_wt(meta);
1427        assert!(!wt.is_null(), "StrokeShapeView !: View");
1428
1429        let (ah, av) = super::static_getter(resolve::alignment_center(), resolve::alignment_meta());
1430        let content_size = abi::type_size(content_ty);
1431        let bg_size = abi::type_size(bg_ty);
1432        let (content_at, bg_at, align_at) =
1433            stroke_view_offsets(meta)
1434                .unwrap_or_else(|| {
1435                    // Every offset padded to eight — right for a bare shape,
1436                    // and an assumption rather than a fact. Only reached if one
1437                    // of the three nested types does not resolve.
1438                    let bg_at = content_size.next_multiple_of(8);
1439                    (0, bg_at, (bg_at + bg_size).next_multiple_of(8))
1440                });
1441
1442        let mut bytes = vec![0u8; abi::type_bytes(meta)];
1443        assert!(
1444            align_at + 16 <= bytes.len() && bg_at + bg_size <= bytes.len(),
1445            "{} lays out its fields differently than expected: content at {content_at} \
1446             ({content_size} bytes), background at {bg_at} ({bg_size}), alignment at \
1447             {align_at} — but the type is only {} bytes.",
1448            crate::diag::type_name(meta),
1449            bytes.len(),
1450        );
1451        bytes[content_at..content_at + content_size].copy_from_slice(&content[..content_size]);
1452        bytes[bg_at..bg_at + bg_size].copy_from_slice(&background[..bg_size]);
1453        bytes[align_at..align_at + 8].copy_from_slice(&ah.to_le_bytes());
1454        bytes[align_at + 8..align_at + 16].copy_from_slice(&av.to_le_bytes());
1455        wrap_buf(&bytes, meta, wt)
1456    }
1457}
1458
1459/// Where a `StrokeShapeView`'s three written fields actually sit.
1460///
1461/// They are not its own fields: it has exactly one, a `ModifiedContent`, whose
1462/// two fields are the stroked shape and a `_BackgroundModifier`, whose two are
1463/// the fill and the alignment. So each offset is a sum down three levels, and
1464/// every level is read from that level's metadata — reached with
1465/// [`abi::field_type_metadata`], since `_BackgroundModifier` exports no
1466/// accessor to ask for by name.
1467///
1468/// Returns `None` if any level does not look as expected, which leaves the
1469/// caller on its padded-to-eight estimate — right for a bare shape, and what
1470/// this replaces.
1471///
1472/// # Safety
1473/// `meta` must be `StrokeShapeView` metadata.
1474unsafe fn stroke_view_offsets(meta: *const c_void) -> Option<(usize, usize, usize)> {
1475    unsafe {
1476        let &[view_at] = abi::struct_field_offsets(meta)?.as_slice() else { return None };
1477        let modified = abi::field_type_metadata(meta, 0);
1478        if modified.is_null() {
1479            return None;
1480        }
1481        let &[content_at, modifier_at] = abi::struct_field_offsets(modified)?.as_slice() else {
1482            return None;
1483        };
1484        let background = abi::field_type_metadata(modified, 1);
1485        if background.is_null() {
1486            return None;
1487        }
1488        let &[bg_at, align_at] = abi::struct_field_offsets(background)?.as_slice() else {
1489            return None;
1490        };
1491        Some((
1492            view_at + content_at,
1493            view_at + modifier_at + bg_at,
1494            view_at + modifier_at + align_at,
1495        ))
1496    }
1497}
1498
1499/// Where a `StrokeBorderShapeView`'s four written fields sit.
1500///
1501/// The same walk as [`stroke_view_offsets`], with the type's own `shape` field
1502/// in front of the `ModifiedContent`.
1503///
1504/// # Safety
1505/// `meta` must be `StrokeBorderShapeView` metadata.
1506unsafe fn border_view_offsets(meta: *const c_void) -> Option<(usize, usize, usize, usize)> {
1507    unsafe {
1508        let &[shape_at, view_at] = abi::struct_field_offsets(meta)?.as_slice() else {
1509            return None;
1510        };
1511        let modified = abi::field_type_metadata(meta, 1);
1512        if modified.is_null() {
1513            return None;
1514        }
1515        let &[content_at, modifier_at] = abi::struct_field_offsets(modified)?.as_slice() else {
1516            return None;
1517        };
1518        let background = abi::field_type_metadata(modified, 1);
1519        if background.is_null() {
1520            return None;
1521        }
1522        let &[bg_at, align_at] = abi::struct_field_offsets(background)?.as_slice() else {
1523            return None;
1524        };
1525        Some((
1526            shape_at,
1527            view_at + content_at,
1528            view_at + modifier_at + bg_at,
1529            view_at + modifier_at + align_at,
1530        ))
1531    }
1532}
1533
1534/// `.fill(_:).strokeBorder(_:style:)` — a shape filled, then bordered inside its
1535/// own edge.
1536///
1537/// The `ShapeView` spelling of `strokeBorder`, and like the `Shape` one it is
1538/// `@_alwaysEmitIntoClient`. What it builds is a `StrokeBorderShapeView` whose
1539/// `shape` field is the original shape and whose `view` field is the same
1540/// stroke-over-fill `ModifiedContent` [`fill_then_stroke`] builds — except the
1541/// stroked shape is first `inset(by:)` half the line width, which is the whole
1542/// difference between a stroke and a border.
1543pub fn fill_then_stroke_border(
1544    shape: &Shape,
1545    fill: u64,
1546    border: u64,
1547    line_width: f64,
1548    result_mangled: &[u8],
1549) -> ViewHandle {
1550    unsafe {
1551        let color_meta = resolve::color_meta();
1552        let color_wt = resolve::color_shapestyle_wt();
1553        let (background, bg_ty) = shape.fill_parts(&fill.to_le_bytes(), color_meta, color_wt);
1554        let (content, content_ty) = shape
1555            .inset(line_width / 2.0)
1556            .stroked(line_width)
1557            .fill_parts(&border.to_le_bytes(), color_meta, color_wt);
1558
1559        let meta = abi::resolve_type_by_mangled_name(result_mangled);
1560        assert!(!meta.is_null(), "{}", crate::diag::demangle_type(result_mangled));
1561        let wt = abi::get_view_wt(meta);
1562        assert!(!wt.is_null(), "StrokeBorderShapeView !: View");
1563
1564        let (ah, av) = super::static_getter(resolve::alignment_center(), resolve::alignment_meta());
1565        let shape_size = abi::type_size(shape.meta);
1566        let content_size = abi::type_size(content_ty);
1567        let bg_size = abi::type_size(bg_ty);
1568        // `StrokeBorderShapeView` has a `shape` of its own in front of the same
1569        // nested `ModifiedContent` the stroke case has, so its offsets are read
1570        // the same way — one level further in.
1571        let (shape_at, content_at, bg_at, align_at) = border_view_offsets(meta)
1572            .unwrap_or_else(|| {
1573                let content_at = shape_size.next_multiple_of(8);
1574                let bg_at = (content_at + content_size).next_multiple_of(8);
1575                (0, content_at, bg_at, (bg_at + bg_size).next_multiple_of(8))
1576            });
1577
1578        let mut bytes = vec![0u8; abi::type_bytes(meta)];
1579        assert!(
1580            align_at + 16 <= bytes.len(),
1581            "{} lays out its fields differently than expected",
1582            crate::diag::type_name(meta),
1583        );
1584        bytes[shape_at..shape_at + shape_size].copy_from_slice(&shape.bytes[..shape_size]);
1585        bytes[content_at..content_at + content_size].copy_from_slice(&content[..content_size]);
1586        bytes[bg_at..bg_at + bg_size].copy_from_slice(&background[..bg_size]);
1587        bytes[align_at..align_at + 8].copy_from_slice(&ah.to_le_bytes());
1588        bytes[align_at + 8..align_at + 16].copy_from_slice(&av.to_le_bytes());
1589        wrap_buf(&bytes, meta, wt)
1590    }
1591}
1592
1593/// A `LinearGradient` built from explicit `Gradient.Stop`s.
1594///
1595/// Not the same as the `colors:` spelling next door: that one spaces its colours
1596/// evenly, this one places each at a location the caller gives. A mask built
1597/// from stops — which is what both of the tvOS Media Catalog's hero views use —
1598/// cannot be expressed as evenly-spaced colours.
1599pub fn linear_gradient_stops(
1600    stops: &[(u64, f64)],
1601    start: (f64, f64),
1602    end: (f64, f64),
1603) -> (Vec<u8>, *const c_void, *const c_void) {
1604    unsafe {
1605        let stop_meta = abi::metadata0(resolve::gradient_stop_ma());
1606        let stride = abi::type_stride(stop_meta);
1607        let mut bytes = vec![0u8; stride * stops.len()];
1608        for (i, (color, location)) in stops.iter().enumerate() {
1609            // Gradient.Stop.init(color:location:) — the Color in x0 and the
1610            // location in d0, and the result comes *back* the same way: a Stop
1611            // is one reference and one CGFloat, so it is returned in x0/d0
1612            // rather than through x8. Handing it an x8 left the buffer zeroed
1613            // and the array of stops full of null Colors, which faulted on the
1614            // first draw.
1615            let (word, loc): (u64, f64);
1616            core::arch::asm!("blr {f}", f = in(reg) resolve::gradient_stop_init(),
1617                inout("x0") *color => word,
1618                inout("d0") *location => loc,
1619                lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
1620                lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
1621                lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1622                lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1623                lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
1624                clobber_abi("C"),
1625            );
1626            bytes[i * stride..i * stride + 8].copy_from_slice(&word.to_le_bytes());
1627            bytes[i * stride + 8..i * stride + 16].copy_from_slice(&loc.to_le_bytes());
1628        }
1629        let array = abi::swift_array(stop_meta, stops.len(), &bytes);
1630
1631        let g: u64;
1632        core::arch::asm!("blr {f}", f = in(reg) resolve::gradient_stops_init(),
1633            in("x0") array,
1634            in("x20") resolve::gradient_meta(),
1635            lateout("x0") g,
1636            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
1637            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
1638            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1639            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1640            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
1641        );
1642
1643        let meta = crate::type_by_name!(swift_type!(SwiftUI.LinearGradient).as_bytes());
1644        assert!(!meta.is_null(), "LinearGradient metadata");
1645        let mut value = vec![0u8; abi::type_bytes(meta)];
1646        core::arch::asm!("blr {f}", f = in(reg) resolve::linear_gradient_init(),
1647            in("x0") g,
1648            in("d0") start.0, in("d1") start.1, in("d2") end.0, in("d3") end.1,
1649            in("x8") value.as_mut_ptr(),
1650            in("x20") meta,
1651            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
1652            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
1653            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
1654            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
1655            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
1656        );
1657        (value, meta, resolve::linear_gradient_wt())
1658    }
1659}
1660
1661/// A `LinearGradient` as a view, for `.mask { … }` and friends.
1662pub fn linear_gradient_stops_view(
1663    stops: &[(u64, f64)],
1664    start: (f64, f64),
1665    end: (f64, f64),
1666) -> ViewHandle {
1667    let (bytes, meta, _) = linear_gradient_stops(stops, start, end);
1668    unsafe {
1669        let wt = abi::get_view_wt(meta);
1670        assert!(!wt.is_null(), "LinearGradient !: View");
1671        wrap_buf(&bytes, meta, wt)
1672    }
1673}
1674
1675/// `.shadow(_:)` applied to any `ShapeStyle` — `_ShadowShapeStyle<Base>`.
1676///
1677/// [`implicit_shadow_style`] is the special case where `Base` is
1678/// `_ImplicitShapeStyle` and so contributes no bytes. A real base — a
1679/// `Material`, say — sits in front of the `ShadowStyle`, which is why this takes
1680/// its bytes and its type rather than assuming both are empty.
1681pub fn shadow_over_style(
1682    base: &[u8],
1683    base_ty: &str,
1684    shadow: &[u8],
1685) -> (Vec<u8>, *const c_void, *const c_void) {
1686    unsafe {
1687        let ty = mangle::ty("_ShadowShapeStyle")
1688            .of(vec![mangle::Type::Verbatim(base_ty.into())])
1689            .mangle();
1690        let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
1691        assert!(!meta.is_null(), "{}", mangle::demangle(&ty));
1692        let wt = abi::get_view_wt_for_proto(meta, resolve::shape_style_proto());
1693        assert!(!wt.is_null(), "_ShadowShapeStyle !: ShapeStyle");
1694        let mut bytes = vec![0u8; abi::type_bytes(meta)];
1695        // Where the two fields sit comes from the metadata: `Base` is whatever
1696        // style this wraps, so padding the first to eight is a guess about a
1697        // type chosen by the caller.
1698        let offsets = abi::struct_field_offsets(meta).expect("two stored fields");
1699        let &[base_at, shadow_at] = offsets.as_slice() else {
1700            panic!("_ShadowShapeStyle is {} fields, not 2", offsets.len());
1701        };
1702        bytes[base_at..base_at + base.len()].copy_from_slice(base);
1703        let n = shadow.len().min(bytes.len() - shadow_at);
1704        bytes[shadow_at..shadow_at + n].copy_from_slice(&shadow[..n]);
1705        (bytes, meta, wt)
1706    }
1707}
1708
1709/// `.opacity(_:)` applied to any `ShapeStyle` — `_OpacityShapeStyle<Base>`.
1710///
1711/// [`implicit_opacity_style`] is the case where `Base` is `_ImplicitShapeStyle`
1712/// and contributes nothing; a real base — `.red.gradient`, say — sits in front
1713/// of the `Float`.
1714pub fn opacity_over_style(
1715    base: &[u8],
1716    base_ty: &str,
1717    opacity: f64,
1718) -> (Vec<u8>, *const c_void, *const c_void) {
1719    unsafe {
1720        let ty = mangle::ty("_OpacityShapeStyle")
1721            .of(vec![mangle::Type::Verbatim(base_ty.into())])
1722            .mangle();
1723        let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
1724        assert!(!meta.is_null(), "{}", mangle::demangle(&ty));
1725        let wt = abi::get_view_wt_for_proto(meta, resolve::shape_style_proto());
1726        assert!(!wt.is_null(), "_OpacityShapeStyle !: ShapeStyle");
1727        let mut bytes = vec![0u8; abi::type_bytes(meta)];
1728        bytes[..base.len()].copy_from_slice(base);
1729        let at = base.len().next_multiple_of(4);
1730        bytes[at..at + 4].copy_from_slice(&(opacity as f32).to_le_bytes());
1731        (bytes, meta, wt)
1732    }
1733}
1734
1735impl Shape {
1736    /// `.trim(from:to:)` — keep a fraction of the shape's path.
1737    ///
1738    /// How a progress ring is drawn: a stroked circle trimmed to the fraction
1739    /// complete. Without it a "ring showing 70%" is a full circle.
1740    ///
1741    /// `.trim` is `@inlinable`, so SwiftUI exports no function for it — but
1742    /// `_TrimmedShape<S>` is just the shape and two `CGFloat`s, its descriptor
1743    /// is exported, and the field offsets come from the metadata rather than
1744    /// being assumed.
1745    pub fn trim(&self, from: f64, to: f64) -> Self {
1746        unsafe {
1747            let desc = resolve::trimmed_shape_desc();
1748            let meta = if desc.is_null() {
1749                core::ptr::null()
1750            } else {
1751                abi::generic_metadata_from_descriptor(desc, &[self.meta, self.wt])
1752            };
1753            let offsets = if meta.is_null() {
1754                None
1755            } else {
1756                abi::struct_field_offsets(meta)
1757            };
1758            // Shape, then the two fractions — anything else is not the type
1759            // this was taken for, and writing into it would be a guess.
1760            let same = || Self {
1761                bytes: self.bytes.clone(),
1762                meta: self.meta,
1763                wt: self.wt,
1764                ty: self.ty.clone(),
1765            };
1766            let Some(offs) = offsets.filter(|o| o.len() == 3) else {
1767                return same();
1768            };
1769            let wt = abi::get_view_wt_for_proto(meta, resolve::shape_proto());
1770            if wt.is_null() {
1771                return same();
1772            }
1773
1774            let mut bytes = vec![0u8; abi::type_bytes(meta)];
1775            let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(self.meta);
1776            if vwt.size > 0 {
1777                (vwt.initialize_with_copy)(
1778                    bytes.as_mut_ptr().add(offs[0]) as *mut c_void,
1779                    self.bytes.as_ptr() as *mut c_void,
1780                    self.meta,
1781                );
1782            }
1783            bytes[offs[1]..offs[1] + 8].copy_from_slice(&from.to_le_bytes());
1784            bytes[offs[2]..offs[2] + 8].copy_from_slice(&to.to_le_bytes());
1785
1786            Self {
1787                bytes,
1788                meta,
1789                wt,
1790                ty: mangle::ty("_TrimmedShape").of(vec![self.ty.clone()]),
1791            }
1792        }
1793    }
1794}