Skip to main content

swiftui_native/views/
typography.rs

1//! Typography.
2//!
3//! * Typography
4
5use super::*;
6use crate::abi;
7use crate::resolve;
8use core::ffi::c_void;
9use crate::mangle::{self, swift_type};
10
11// ── Typography ──
12
13/// Build a `SwiftUI.Font` from `Font.system(size:weight:design:)`.
14///
15/// `design` is `Optional<Font.Design>`, which is passed indirectly; its `nil`
16/// representation is produced through the value witness table rather than
17/// assumed, since it depends on how many cases the enum has.
18pub(crate) unsafe fn system_font(size: f64, bold: bool) -> u64 {
19    let design_meta = unsafe { crate::type_by_name!(swift_type!(opt enum SwiftUI.Font.Design).as_bytes()) };
20    assert!(!design_meta.is_null(), "Optional<Font.Design> metadata");
21    let mut design = vec![0u8; unsafe { abi::type_bytes(design_meta) }];
22    unsafe {
23        abi::store_optional_tag(&mut design, design_payload_meta(), false);
24    }
25
26    // Optional<Font.Weight>: payload in x0 (Font.Weight is a Double), tag in x1.
27    // `Font.Weight` wraps a Double, so its static getters return in d0 — reading
28    // x0 instead yields a stale register and the weight is silently ignored.
29    let (weight_payload, weight_tag) = if bold {
30        let w = unsafe { static_getter_f64(resolve::font_weight_bold(), resolve::font_weight_meta()) };
31        (w.to_bits(), 0u64)
32    } else {
33        (0u64, 1u64)
34    };
35
36    let font: u64;
37    unsafe {
38        core::arch::asm!("blr {f}", f = in(reg) resolve::font_system(),
39            in("d0") size,
40            in("x0") weight_payload,
41            in("x1") weight_tag,
42            in("x2") design.as_mut_ptr(),
43            in("x20") resolve::font_meta(),
44            lateout("x0") font,
45            lateout("x1") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
46            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
47            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
48            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
49            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
50        );
51    }
52    font
53}
54
55/// Apply `.font(.system(size:weight:))`.
56pub fn font(view: &ViewHandle, size: f64, bold: bool) -> ViewHandle {
57    unsafe {
58        let f = system_font(size, bold);
59        let (_, _, out_size) = FONT_T.applied_to(view.meta);
60        let mut result = vec![0u8; out_size];
61        let self_storage = view.value_ptr();
62        // font(_: Font?) — x0 = Font? (8 bytes, nil == 0)
63        core::arch::asm!("blr {fun}", fun = in(reg) resolve::font_fn(),
64            in("x0") f,
65            in("x1") view.meta,
66            in("x2") view.wt,
67            in("x8") result.as_mut_ptr(),
68            in("x20") self_storage,
69            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
70            lateout("x9") _, lateout("x10") _, lateout("x11") _,
71            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
72            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
73            clobber_abi("C"),
74        );
75        wrap_modified(result, &FONT_T, view)
76    }
77}
78
79/// Apply `.font(.system(size:))`.
80pub fn font_size(view: &ViewHandle, size: f64) -> ViewHandle {
81    font(view, size, false)
82}
83
84/// Apply `.bold()`.
85pub fn bold(view: &ViewHandle) -> ViewHandle {
86    unsafe {
87        let (_, _, size) = BOLD_T.applied_to(view.meta);
88        let mut result = vec![0u8; size];
89        let self_storage = view.value_ptr();
90        core::arch::asm!("blr {f}", f = in(reg) resolve::bold_fn(),
91            in("x0") 1u64,
92            in("x1") view.meta,
93            in("x2") view.wt,
94            in("x8") result.as_mut_ptr(),
95            in("x20") self_storage,
96            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
97            lateout("x9") _, lateout("x10") _, lateout("x11") _,
98            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
99            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
100            clobber_abi("C"),
101        );
102        wrap_modified(result, &BOLD_T, view)
103    }
104}
105
106/// Apply `.foregroundStyle(_:)` with a colour.
107///
108/// Named for what it does, not for `.foregroundColor(_:)` — which is deprecated
109/// and a different thing: it writes an environment key, producing
110/// `_EnvironmentKeyWritingModifier<Color?>` where this produces
111/// `_ForegroundStyleModifier<Color>`. The two render alike, so only a
112/// type-level comparison tells them apart. See [`foreground_color`] for the
113/// other one.
114pub fn foreground_style_color(view: &ViewHandle, r: f64, g: f64, b: f64, a: f64) -> ViewHandle {
115    unsafe { foreground_style_value(view, color_value(r, g, b, a)) }
116}
117
118/// Apply `.foregroundStyle(_:_:)` — a primary and a secondary style.
119///
120/// What `symbolRenderingMode(.palette)` needs: a palette symbol takes its
121/// layers from successive styles, and with only one it renders as monochrome.
122pub fn foreground_style_pair(view: &ViewHandle, primary: u64, secondary: u64) -> ViewHandle {
123    unsafe {
124        let (_, _, size) = FOREGROUND2_T.applied_to(view.meta);
125        let mut result = vec![0u8; size];
126        let self_storage = view.value_ptr();
127        let mut a: u64 = primary;
128        let mut b: u64 = secondary;
129        // foregroundStyle<S1: ShapeStyle, S2: ShapeStyle>(_:_:)
130        //   x0/x1 = the two styles by address, then Self.Type, S1.Type,
131        //   S2.Type, and the three witness tables. Eight words exactly, so
132        //   nothing spills.
133        core::arch::asm!("blr {f}", f = in(reg) resolve::foreground_style2_fn(),
134            in("x0") &mut a as *mut u64,
135            in("x1") &mut b as *mut u64,
136            in("x2") view.meta,
137            in("x3") resolve::color_meta(),
138            in("x4") resolve::color_meta(),
139            in("x5") view.wt,
140            in("x6") resolve::color_shapestyle_wt(),
141            in("x7") resolve::color_shapestyle_wt(),
142            in("x8") result.as_mut_ptr(),
143            in("x20") self_storage,
144            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
145            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
146            lateout("x17") _, lateout("lr") _, lateout("x21") _,
147            clobber_abi("C"),
148        );
149        wrap_modified(result, &FOREGROUND2_T, view)
150    }
151}
152
153/// Apply `.foregroundStyle(_:_:_:)` — three styles.
154///
155/// A palette symbol takes its layers from successive styles.
156/// [`foreground_style_pair`] covers the two-layer symbols; the three-layer
157/// ones — and there are plenty in SF Symbols — rendered their third layer in
158/// the secondary colour, because there was no way to say what it should be.
159///
160/// Eleven words of arguments, so three spill: the three style addresses and
161/// `Self.Type` fill x0–x3, the three style types and the `Self: View` witness
162/// take x4–x7, and all *three* `ShapeStyle` witnesses go to the stack.
163///
164/// Spilling only two of them — which is easy to do, since the pair version next
165/// door fits in eight words exactly and spills none — left the callee reading a
166/// stale word as a witness table. It did not fault there: it faulted much later
167/// inside `swift::MetadataCacheKey::operator==`, recursing on a type built from
168/// the garbage, which is what an argument-count mistake looks like from the
169/// outside.
170pub fn foreground_style_triple(
171    view: &ViewHandle,
172    primary: u64,
173    secondary: u64,
174    tertiary: u64,
175) -> ViewHandle {
176    unsafe {
177        let (_, _, size) = FOREGROUND3_T.applied_to(view.meta);
178        let mut result = vec![0u8; size];
179        let self_storage = view.value_ptr();
180        let mut a: u64 = primary;
181        let mut b: u64 = secondary;
182        let mut c: u64 = tertiary;
183        let color = resolve::color_meta();
184        let style_wt = resolve::color_shapestyle_wt();
185        core::arch::asm!(
186            "sub sp, sp, #32",
187            "stp {w1}, {w2}, [sp]",
188            "str {w3}, [sp, #16]",
189            "blr {f}",
190            "add sp, sp, #32",
191            f = in(reg) resolve::foreground_style3_fn(),
192            w1 = in(reg) style_wt,
193            w2 = in(reg) style_wt,
194            w3 = in(reg) style_wt,
195            in("x0") &mut a as *mut u64,
196            in("x1") &mut b as *mut u64,
197            in("x2") &mut c as *mut u64,
198            in("x3") view.meta,
199            in("x4") color,
200            in("x5") color,
201            in("x6") color,
202            in("x7") view.wt,
203            in("x8") result.as_mut_ptr(),
204            in("x20") self_storage,
205            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
206            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
207            lateout("x17") _, lateout("lr") _, lateout("x21") _,
208            clobber_abi("C"),
209        );
210        wrap_modified(result, &FOREGROUND3_T, view)
211    }
212}
213
214/// Apply `.foregroundStyle(_:)` to a `Color` that is already built.
215///
216/// The components overload above cannot express `Color.blue`: SwiftUI's named
217/// colours are semantic, not literal, and resolve per appearance — the aqua
218/// `Color.blue` is nowhere near `(0, 0, 1)`. This takes the value a static
219/// getter handed back instead. See [`super::system_color`].
220pub fn foreground_style_value(view: &ViewHandle, color: u64) -> ViewHandle {
221    let bytes = color.to_le_bytes();
222    unsafe {
223        foreground_style_of(
224            view,
225            &bytes,
226            resolve::color_meta(),
227            resolve::color_shapestyle_wt(),
228            &FOREGROUND_T,
229        )
230    }
231}
232
233/// Apply `.foregroundStyle(.linearGradient(colors:startPoint:endPoint:))`.
234///
235/// The shorthand in Swift source is a protocol-extension static marked
236/// `@inlinable`, so it has no symbol; the `LinearGradient` it builds does.
237/// Apply `.foregroundStyle(_:)` with one of the hierarchical levels —
238/// `.secondary` and friends.
239pub fn foreground_style_hierarchical(
240    view: &ViewHandle,
241    level: super::Hierarchical,
242) -> ViewHandle {
243    let (style, meta, wt) = super::hierarchical_style(level);
244    unsafe { foreground_style_of(view, &style, meta, wt, &FOREGROUND_HIERARCHICAL_T) }
245}
246
247pub fn foreground_style_linear_gradient(
248    view: &ViewHandle,
249    colors: &[u64],
250    start: (f64, f64),
251    end: (f64, f64),
252) -> ViewHandle {
253    let (style, meta, wt) = super::linear_gradient_style_of(colors, start, end);
254    unsafe { foreground_style_of(view, &style, meta, wt, &FOREGROUND_GRADIENT_T) }
255}
256
257/// Apply `.foregroundStyle(_:)` with any `ShapeStyle`.
258///
259/// `.linearGradient(colors:startPoint:endPoint:)` and its neighbours are
260/// protocol-extension statics marked `@inlinable`, so none of them has a
261/// symbol — but the concrete type each one builds does. Passing a
262/// `LinearGradient` here is the same call the shorthand would have made.
263///
264/// `result` names the `ModifiedContent<AnyView, _ForegroundStyleModifier<S>>`
265/// the call produces, which depends on `S` and so cannot be fixed here.
266/// # Safety
267/// `style` must hold an initialised value of the type `style_meta` describes,
268/// `style_wt` must be that type's `ShapeStyle` conformance, and `result` must
269/// name the `ModifiedContent` the call actually produces.
270pub unsafe fn foreground_style_of(
271    view: &ViewHandle,
272    style: &[u8],
273    style_meta: *const c_void,
274    style_wt: *const c_void,
275    modifier: &ModifierType,
276) -> ViewHandle {
277    unsafe {
278        let (_, _, size) = modifier.applied_to(view.meta);
279        let mut result = vec![0u8; size];
280        let self_storage = view.value_ptr();
281        let mut arg = style.to_vec();
282        arg.resize(abi::type_bytes(style_meta).max(style.len()).max(1), 0);
283        // foregroundStyle<S: ShapeStyle>(_:)
284        //   x0 = @in style, x1 = Self.Type, x2 = S.Type,
285        //   x3 = Self: View WT, x4 = S: ShapeStyle WT, x8 = @out, x20 = self
286        core::arch::asm!("blr {f}", f = in(reg) resolve::foreground_style_fn(),
287            in("x0") arg.as_mut_ptr(),
288            in("x1") view.meta,
289            in("x2") style_meta,
290            in("x3") view.wt,
291            in("x4") style_wt,
292            in("x8") result.as_mut_ptr(),
293            in("x20") self_storage,
294            lateout("x5") _, lateout("x6") _, lateout("x7") _,
295            lateout("x9") _, lateout("x10") _, lateout("x11") _,
296            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
297            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
298            clobber_abi("C"),
299        );
300        wrap_modified(result, modifier, view)
301    }
302}
303
304// ── Typography ──
305
306// `.font(_:)` writes `Font?` into the environment; `.italic()`, `.bold()`,
307// `.fontWeight()`, `.fontDesign()` and `.monospaced()` all funnel through the
308// same environment-transform modifier.
309mangle::modifier_types! {
310    /// `.kerning(_:)`, `.tracking(_:)` and `.lineSpacing(_:)` share a result type.
311    /// `ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<CoreGraphics.CGFloat>>`
312    pub(crate) CGFLOAT_ENV_T = ty("_EnvironmentKeyWritingModifier").of(vec![Type::Nominal { module: "CoreGraphics", path: vec!["CGFloat"], kind: Kind::Struct }]);
313    /// `.lineLimit(_:)`
314    /// `ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<Int?>>`
315    pub(crate) LINE_LIMIT_T = ty("_EnvironmentKeyWritingModifier").of(vec![opt(Type::Verbatim("Si".into()))]);
316    /// `.multilineTextAlignment(_:)`
317    /// `ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<TextAlignment>>`
318    pub(crate) TEXT_ALIGN_T = ty("_EnvironmentKeyWritingModifier").of(vec![ty_enum("TextAlignment")]);
319}
320
321
322
323/// `SwiftUI.Font.Weight`. The values are Doubles behind the scenes; these are
324/// the named steps SwiftUI exposes.
325#[derive(Clone, Copy, PartialEq, Eq, Debug)]
326pub enum Weight {
327    UltraLight,
328    Thin,
329    Light,
330    Regular,
331    Medium,
332    Semibold,
333    Bold,
334    Heavy,
335    Black,
336}
337
338impl Weight {
339    pub(crate) fn getter(self) -> &'static core::ffi::CStr {
340        match self {
341            Weight::UltraLight => c"$s7SwiftUI4FontV6WeightV10ultraLightAEvgZ",
342            Weight::Thin => c"$s7SwiftUI4FontV6WeightV4thinAEvgZ",
343            Weight::Light => c"$s7SwiftUI4FontV6WeightV5lightAEvgZ",
344            Weight::Regular => c"$s7SwiftUI4FontV6WeightV7regularAEvgZ",
345            Weight::Medium => c"$s7SwiftUI4FontV6WeightV6mediumAEvgZ",
346            Weight::Semibold => c"$s7SwiftUI4FontV6WeightV8semiboldAEvgZ",
347            Weight::Bold => c"$s7SwiftUI4FontV6WeightV4boldAEvgZ",
348            Weight::Heavy => c"$s7SwiftUI4FontV6WeightV5heavyAEvgZ",
349            Weight::Black => c"$s7SwiftUI4FontV6WeightV5blackAEvgZ",
350        }
351    }
352
353    /// The raw `Font.Weight` payload (a Double, returned in d0).
354    pub fn value(self) -> f64 {
355        unsafe { static_getter_f64(resolve::require(self.getter()), resolve::font_weight_meta()) }
356    }
357}
358
359/// `SwiftUI.Font.Design`, in declaration order.
360#[derive(Clone, Copy, PartialEq, Eq, Debug)]
361#[repr(u8)]
362pub enum Design {
363    Default = 0,
364    Serif = 1,
365    Rounded = 2,
366    Monospaced = 3,
367}
368
369/// `SwiftUI.Font.TextStyle` presets, reached through their static getters.
370#[derive(Clone, Copy, PartialEq, Eq, Debug)]
371pub enum TextStyle {
372    LargeTitle,
373    Title,
374    Title2,
375    Title3,
376    Headline,
377    Subheadline,
378    Body,
379    Callout,
380    Footnote,
381    Caption,
382    Caption2,
383}
384
385impl TextStyle {
386    pub(crate) fn getter(self) -> &'static core::ffi::CStr {
387        match self {
388            TextStyle::LargeTitle => c"$s7SwiftUI4FontV10largeTitleACvgZ",
389            TextStyle::Title => c"$s7SwiftUI4FontV5titleACvgZ",
390            TextStyle::Title2 => c"$s7SwiftUI4FontV6title2ACvgZ",
391            TextStyle::Title3 => c"$s7SwiftUI4FontV6title3ACvgZ",
392            TextStyle::Headline => c"$s7SwiftUI4FontV8headlineACvgZ",
393            TextStyle::Subheadline => c"$s7SwiftUI4FontV11subheadlineACvgZ",
394            TextStyle::Body => c"$s7SwiftUI4FontV4bodyACvgZ",
395            TextStyle::Callout => c"$s7SwiftUI4FontV7calloutACvgZ",
396            TextStyle::Footnote => c"$s7SwiftUI4FontV8footnoteACvgZ",
397            TextStyle::Caption => c"$s7SwiftUI4FontV7captionACvgZ",
398            TextStyle::Caption2 => c"$s7SwiftUI4FontV8caption2ACvgZ",
399        }
400    }
401}
402
403/// Build `Font.system(size:weight:design:)`.
404///
405/// `design` is `Optional<Font.Design>`, passed indirectly; its `nil`
406/// representation comes from the value witness table rather than being assumed,
407/// since it depends on how many cases the enum has.
408/// # Safety
409/// SwiftUI must be loaded. The returned word is a `+1` `Font`.
410pub unsafe fn system_font_full(size: f64, weight: Option<Weight>, design: Option<Design>) -> u64 {
411    let mut design_buf = unsafe { optional_design(design) };
412
413    // Optional<Font.Weight>: payload in x0 (Font.Weight wraps a Double), tag in x1.
414    let (payload, tag) = match weight {
415        Some(w) => (w.value().to_bits(), 0u64),
416        None => (0u64, 1u64),
417    };
418
419    let font: u64;
420    unsafe {
421        core::arch::asm!("blr {f}", f = in(reg) resolve::font_system(),
422            in("d0") size,
423            in("x0") payload,
424            in("x1") tag,
425            in("x2") design_buf.as_mut_ptr(),
426            in("x20") resolve::font_meta(),
427            lateout("x0") font,
428            lateout("x1") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
429            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
430            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
431            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
432            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
433        );
434    }
435    font
436}
437
438/// Build `Font.custom(_:size:)`.
439/// # Safety
440/// As [`system_font_full`].
441pub unsafe fn custom_font(name: &str, size: f64) -> u64 {
442    let ss = unsafe { abi::swift_string(name) };
443    let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
444    let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
445    let font: u64;
446    unsafe {
447        core::arch::asm!("blr {f}", f = in(reg) resolve::font_custom(),
448            in("x0") s0, in("x1") s1, in("d0") size,
449            in("x20") resolve::font_meta(),
450            lateout("x0") font,
451            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
452            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
453            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
454            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
455            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
456        );
457    }
458    font
459}
460
461/// A reusable `SwiftUI.Font`.
462///
463/// Building a font is expensive — SwiftUI resolves it through the text system,
464/// which costs tens of microseconds — while *applying* one costs a few hundred
465/// nanoseconds. Building it once and reusing the handle is the difference
466/// between roughly 66 µs and 0.4 µs per view.
467///
468/// ```ignore
469/// let title = Font::system(28.0, Some(Weight::Bold), None);
470/// let ui = vstack![
471///     text("Heading").font_ref(&title),
472///     text("Another").font_ref(&title),
473/// ];
474/// ```
475pub struct Font(u64);
476
477impl Font {
478    /// `Font.system(size:weight:design:)`
479    pub fn system(size: f64, weight: Option<Weight>, design: Option<Design>) -> Self {
480        Self(unsafe { system_font_full(size, weight, design) })
481    }
482
483    /// `Font.custom(_:size:)`
484    pub fn custom(name: &str, size: f64) -> Self {
485        Self(unsafe { custom_font(name, size) })
486    }
487
488    /// One of the built-in text styles.
489    pub fn text_style(style: TextStyle) -> Self {
490        Self(unsafe {
491            let (f, _) = static_getter(resolve::require(style.getter()), resolve::font_meta());
492            f
493        })
494    }
495
496    /// The raw `Font` word, for the `font_apply` form that takes one.
497    pub fn raw(&self) -> u64 {
498        self.0
499    }
500
501}
502
503impl Clone for Font {
504    fn clone(&self) -> Self {
505        unsafe { swift_runtime_sys::RuntimeRaw::swift_retain(self.0 as *mut c_void) };
506        Self(self.0)
507    }
508}
509
510impl Drop for Font {
511    fn drop(&mut self) {
512        unsafe { swift_runtime_sys::RuntimeRaw::swift_release(self.0 as *mut c_void) };
513    }
514}
515
516/// Apply a prebuilt [`Font`] with `.font(_:)`.
517pub fn font_ref(view: &ViewHandle, font: &Font) -> ViewHandle {
518    unsafe { apply_font(view, font.raw()) }
519}
520
521/// Apply an already-built raw `Font` value with `.font(_:)`.
522pub fn font_apply(view: &ViewHandle, font: u64) -> ViewHandle {
523    unsafe { apply_font(view, font) }
524}
525
526/// Apply an already-built `Font` value with `.font(_:)`.
527pub(crate) unsafe fn apply_font(view: &ViewHandle, font: u64) -> ViewHandle {
528    unsafe {
529        let (_, _, size) = FONT_T.applied_to(view.meta);
530        let r = abi::call_modifier(resolve::font_fn(), view.value_ptr(), view.meta, view.wt, &[font], [0.0; 4], size);
531        wrap_modified(r, &FONT_T, view)
532    }
533}
534
535/// Apply `.font(.system(size:weight:design:))`.
536pub fn font_system(
537    view: &ViewHandle,
538    size: f64,
539    weight: Option<Weight>,
540    design: Option<Design>,
541) -> ViewHandle {
542    unsafe { apply_font(view, system_font_full(size, weight, design)) }
543}
544
545/// Apply `.font(.custom(_:size:))`.
546pub fn font_custom(view: &ViewHandle, name: &str, size: f64) -> ViewHandle {
547    unsafe { apply_font(view, custom_font(name, size)) }
548}
549
550/// Apply `.font(_:)` with one of the built-in text styles.
551pub fn font_text_style(view: &ViewHandle, style: TextStyle) -> ViewHandle {
552    unsafe {
553        let (f, _) = static_getter(resolve::require(style.getter()), resolve::font_meta());
554        apply_font(view, f)
555    }
556}
557
558/// Apply `.italic()`.
559pub fn italic(view: &ViewHandle) -> ViewHandle {
560    unsafe {
561        let (_, _, size) = BOLD_T.applied_to(view.meta);
562        let r = abi::call_modifier(resolve::italic_fn(), view.value_ptr(), view.meta, view.wt, &[1], [0.0; 4], size);
563        wrap_modified(r, &BOLD_T, view)
564    }
565}
566
567/// Apply `.monospaced()`.
568pub fn monospaced(view: &ViewHandle) -> ViewHandle {
569    unsafe {
570        let (_, _, size) = BOLD_T.applied_to(view.meta);
571        let r = abi::call_modifier(resolve::monospaced_fn(), view.value_ptr(), view.meta, view.wt, &[1], [0.0; 4], size);
572        wrap_modified(r, &BOLD_T, view)
573    }
574}
575
576/// Apply `.fontWeight(_:)`.
577pub fn font_weight(view: &ViewHandle, weight: Weight) -> ViewHandle {
578    unsafe {
579        let (_, _, size) = BOLD_T.applied_to(view.meta);
580        // Optional<Font.Weight>: payload then tag (0 == some).
581        let r = abi::call_modifier(resolve::font_weight_fn(), view.value_ptr(), view.meta, view.wt,
582            &[weight.value().to_bits(), 0],
583            [0.0; 4],
584            size,
585        );
586        wrap_modified(r, &BOLD_T, view)
587    }
588}
589
590/// Build an `Optional<Font.Design>` buffer.
591///
592/// `Font.Design` is a resilient enum from another module, so it is address-only
593/// — every API that takes one takes it *indirectly*, by pointer.
594pub(crate) unsafe fn optional_design(design: Option<Design>) -> Vec<u8> {
595    let meta = unsafe { crate::type_by_name!(swift_type!(opt enum SwiftUI.Font.Design).as_bytes()) };
596    assert!(!meta.is_null(), "Optional<Font.Design> metadata");
597    let mut buf = vec![0u8; unsafe { abi::type_bytes(meta) }];
598    match design {
599        Some(d) => buf[0] = d as u8,
600        None => unsafe { abi::store_optional_tag(&mut buf, design_payload_meta(), false) },
601    }
602    buf
603}
604
605/// `Font.Design`'s own metadata — the payload of `Optional<Font.Design>`.
606pub(crate) fn design_payload_meta() -> *const c_void {
607    let meta = unsafe { crate::type_by_name!(swift_type!(enum SwiftUI.Font.Design).as_bytes()) };
608    assert!(!meta.is_null(), "Font.Design metadata");
609    meta
610}
611
612/// Apply `.fontDesign(_:)`.
613pub fn font_design(view: &ViewHandle, design: Design) -> ViewHandle {
614    unsafe {
615        let (_, _, size) = BOLD_T.applied_to(view.meta);
616        let mut buf = optional_design(Some(design));
617        let r = abi::call_modifier(resolve::font_design_fn(), view.value_ptr(), view.meta, view.wt,
618            &[buf.as_mut_ptr() as u64],
619            [0.0; 4],
620            size,
621        );
622        wrap_modified(r, &BOLD_T, view)
623    }
624}
625
626/// Apply `.kerning(_:)`.
627pub fn kerning(view: &ViewHandle, amount: f64) -> ViewHandle {
628    unsafe {
629        let (_, _, size) = CGFLOAT_ENV_T.applied_to(view.meta);
630        let r = abi::call_modifier(resolve::kerning_fn(), view.value_ptr(), view.meta, view.wt, &[], [amount, 0.0, 0.0, 0.0], size);
631        wrap_modified(r, &CGFLOAT_ENV_T, view)
632    }
633}
634
635/// Apply `.tracking(_:)`.
636pub fn tracking(view: &ViewHandle, amount: f64) -> ViewHandle {
637    unsafe {
638        let (_, _, size) = CGFLOAT_ENV_T.applied_to(view.meta);
639        let r = abi::call_modifier(resolve::tracking_fn(), view.value_ptr(), view.meta, view.wt, &[], [amount, 0.0, 0.0, 0.0], size);
640        wrap_modified(r, &CGFLOAT_ENV_T, view)
641    }
642}
643
644/// Apply `.lineSpacing(_:)`.
645pub fn line_spacing(view: &ViewHandle, amount: f64) -> ViewHandle {
646    unsafe {
647        let (_, _, size) = CGFLOAT_ENV_T.applied_to(view.meta);
648        let r =
649            abi::call_modifier(resolve::line_spacing_fn(), view.value_ptr(), view.meta, view.wt, &[], [amount, 0.0, 0.0, 0.0], size);
650        wrap_modified(r, &CGFLOAT_ENV_T, view)
651    }
652}
653
654/// Apply `.lineLimit(_:)`.
655pub fn line_limit(view: &ViewHandle, limit: Option<i64>) -> ViewHandle {
656    unsafe {
657        let (_, _, size) = LINE_LIMIT_T.applied_to(view.meta);
658        // Optional<Int>: payload then tag (0 == some).
659        let ints = match limit {
660            Some(n) => [n as u64, 0],
661            None => [0, 1],
662        };
663        let r = abi::call_modifier(resolve::line_limit_fn(), view.value_ptr(), view.meta, view.wt, &ints, [0.0; 4], size);
664        wrap_modified(r, &LINE_LIMIT_T, view)
665    }
666}
667
668/// `SwiftUI.TextAlignment`, in declaration order.
669#[derive(Clone, Copy, PartialEq, Eq, Debug)]
670#[repr(u8)]
671pub enum TextAlign {
672    Leading = 0,
673    Center = 1,
674    Trailing = 2,
675}
676
677/// Apply `.multilineTextAlignment(_:)`.
678pub fn multiline_alignment(view: &ViewHandle, align: TextAlign) -> ViewHandle {
679    unsafe {
680        let (_, _, size) = TEXT_ALIGN_T.applied_to(view.meta);
681        let r = abi::call_modifier(resolve::multiline_align_fn(), view.value_ptr(), view.meta, view.wt,
682            &[align as u64],
683            [0.0; 4],
684            size,
685        );
686        wrap_modified(r, &TEXT_ALIGN_T, view)
687    }
688}
689
690/// `Locale.Language(languageCode: .thai)`.
691///
692/// `Locale.LanguageCode.thai` has no symbol — it is `@inlinable`, and so is the
693/// `init(stringLiteral:)` it reduces to. But the value it produces is plain:
694/// dumping `.thai` gives thirty-two bytes that are the small string `"th"`
695/// twice over, so the code can be laid out here without calling anything. The
696/// `Language` initialiser around it *is* exported, and takes all three of its
697/// optionals by address.
698pub fn locale_language_thai() -> Vec<u8> {
699    unsafe {
700        let code_meta = abi::metadata0(resolve::language_code_ma());
701        assert!(!code_meta.is_null(), "Locale.LanguageCode metadata");
702        let opt_code = abi::resolve_type_by_mangled_name(b"10Foundation6LocaleV12LanguageCodeVSg");
703        assert!(!opt_code.is_null(), "Optional<Locale.LanguageCode>");
704
705        let th = abi::swift_string("th");
706        let mut code = vec![0u8; abi::type_bytes(opt_code)];
707        code[..16].copy_from_slice(&th);
708        code[16..32].copy_from_slice(&th);
709
710        let mut script = nil_of(b"10Foundation6LocaleV6ScriptVSg");
711        let mut region = nil_of(b"10Foundation6LocaleV6RegionVSg");
712
713        let lang_meta = abi::metadata0(resolve::locale_language_ma());
714        assert!(!lang_meta.is_null(), "Locale.Language metadata");
715        let mut out = vec![0u8; abi::type_bytes(lang_meta)];
716        core::arch::asm!("blr {f}", f = in(reg) resolve::locale_language_init(),
717            in("x0") code.as_mut_ptr(),
718            in("x1") script.as_mut_ptr(),
719            in("x2") region.as_mut_ptr(),
720            in("x8") out.as_mut_ptr(),
721            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
722            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
723            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
724            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
725            lateout("x21") _, clobber_abi("C"),
726        );
727        out
728    }
729}
730
731/// A `nil` of the optional named by `mangled`, using its own enum witness.
732unsafe fn nil_of(mangled: &[u8]) -> Vec<u8> {
733    unsafe {
734        let meta = abi::resolve_type_by_mangled_name(mangled);
735        assert!(!meta.is_null(), "{}", crate::mangle::demangle(&String::from_utf8_lossy(mangled)));
736        let mut buf = vec![0u8; abi::type_bytes(meta)];
737        abi::inject_enum_tag(&mut buf, meta, 1);
738        buf
739    }
740}
741
742/// `Text(verbatim:)` — the string as written, with no localisation lookup.
743pub fn text_verbatim(s: &str) -> ViewHandle {
744    unsafe { wrap_text(text_verbatim_words(s)) }
745}
746
747/// The four words a verbatim `Text` occupies.
748/// # Safety
749/// SwiftUI must be loaded. The four words are a `+1` `Text` and must be given
750/// to something that takes ownership, or released.
751pub unsafe fn text_verbatim_words(s: &str) -> [u64; 4] {
752    unsafe {
753        let ss = abi::swift_string(s);
754        let s0 = u64::from_le_bytes(ss[..8].try_into().expect("lo"));
755        let s1 = u64::from_le_bytes(ss[8..].try_into().expect("hi"));
756        // A `Text` is `{ i64, i64, i8, ptr }` — four registers out, which is
757        // one more than `call_regs` reports.
758        let (t0, t1, t2, t3): (u64, u64, u64, u64);
759        core::arch::asm!("blr {f}", f = in(reg) resolve::text_verbatim_init(),
760            inout("x0") s0 => t0, inout("x1") s1 => t1,
761            lateout("x2") t2, lateout("x3") t3,
762            in("x20") resolve::text_meta(),
763            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
764            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
765            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
766            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
767            clobber_abi("C"),
768        );
769        [t0, t1, t2, t3]
770    }
771}
772
773/// `Text.typesettingLanguage(_:isEnabled:)` with a `Locale.Language`.
774///
775/// The language goes by address in x0, `isEnabled` in x1, and the receiving
776/// `Text`'s four words follow in x2 through x5 — a `Text` is not `self` in x20
777/// here, because this is a method on a struct that returns a new one and the
778/// compiler passes it like any other argument.
779pub fn text_typesetting_language(words: [u64; 4], language: &[u8], enabled: bool) -> ViewHandle {
780    unsafe { wrap_text(typesetting_call(resolve::text_typesetting_language_fn(), words, language, enabled)) }
781}
782
783/// `Text.typesettingLanguage(_:isEnabled:)` with `.explicit(_:)`.
784pub fn text_typesetting_explicit(words: [u64; 4], language: &[u8], enabled: bool) -> ViewHandle {
785    unsafe {
786        let meta = abi::metadata0(resolve::typesetting_language_ma());
787        assert!(!meta.is_null(), "TypesettingLanguage metadata");
788        let mut lang = language.to_vec();
789        let mut explicit = vec![0u8; abi::type_bytes(meta)];
790        core::arch::asm!("blr {f}", f = in(reg) resolve::typesetting_explicit_of(),
791            in("x0") lang.as_mut_ptr(),
792            in("x8") explicit.as_mut_ptr(),
793            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
794            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
795            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
796            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
797            lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
798        );
799        wrap_text(typesetting_call(
800            resolve::text_typesetting_explicit_fn(),
801            words,
802            &explicit,
803            enabled,
804        ))
805    }
806}
807
808unsafe fn typesetting_call(
809    func: *const c_void,
810    words: [u64; 4],
811    value: &[u8],
812    enabled: bool,
813) -> [u64; 4] {
814    unsafe {
815        let mut arg = value.to_vec();
816        let (t0, t1, t2, t3): (u64, u64, u64, u64);
817        core::arch::asm!("blr {f}", f = in(reg) func,
818            inout("x0") arg.as_mut_ptr() => t0,
819            inout("x1") u64::from(enabled) => t1,
820            inout("x2") words[0] => t2,
821            inout("x3") words[1] => t3,
822            in("x4") words[2], in("x5") words[3],
823            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
824            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
825            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
826            lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
827        );
828        [t0, t1, t2, t3]
829    }
830}
831
832/// A `Text`'s four words, as a view.
833pub(crate) unsafe fn wrap_text(words: [u64; 4]) -> ViewHandle {
834    unsafe {
835        ViewHandle::wrap(
836            words.as_ptr() as *const c_void,
837            resolve::text_meta(),
838            resolve::text_wt(),
839        )
840    }
841}
842
843// ── `Text`'s own modifiers ───────────────────────────────────────────────────
844//
845// SwiftUI declares `bold`, `italic`, `font` and their neighbours twice: once on
846// `Text`, returning `Text`, and once as a `View` extension that writes the
847// environment for everything below it. The two are not interchangeable — the
848// Swift in Apple's own documentation reaches the first and produces `Text`,
849// while going through the second produces
850// `ModifiedContent<Text, _EnvironmentKeyTransformModifier<…>>`. Same pixels,
851// different type, and type identity is the check that notices.
852//
853// `Text` lowers to `{ i64, i64, i8, ptr }` and travels in registers rather than
854// indirectly, so these are register-in/register-out calls with the arguments
855// first and `self` last — read off the IR rather than assumed.
856
857/// The four words of a `Text` value.
858///
859/// # Safety
860/// `view` must hold a `SwiftUI.Text`.
861unsafe fn text_words(view: &ViewHandle) -> (u64, u64, u64, u64) {
862    unsafe {
863        let p = view.value_ptr() as *const u8;
864        (
865            (p as *const u64).read_unaligned(),
866            (p.add(8) as *const u64).read_unaligned(),
867            u64::from(p.add(16).read()),
868            (p.add(24) as *const u64).read_unaligned(),
869        )
870    }
871}
872
873/// Put four returned words back into a `Text`-shaped buffer.
874///
875/// # Safety
876/// `view` must be the `Text` the call was made on, so its metadata describes the
877/// result too — every one of these returns `Text`.
878unsafe fn text_from_words(view: &ViewHandle, w: (u64, u64, u64, u64)) -> ViewHandle {
879    unsafe {
880        let mut out = vec![0u8; abi::type_bytes(view.meta)];
881        let p: *mut u8 = out.as_mut_ptr();
882        (p as *mut u64).write_unaligned(w.0);
883        (p.add(8) as *mut u64).write_unaligned(w.1);
884        p.add(16).write(w.2 as u8);
885        (p.add(24) as *mut u64).write_unaligned(w.3);
886        super::wrap_owned(out, view.meta, view.wt)
887    }
888}
889
890/// Is this handle a `SwiftUI.Text`?
891///
892/// Every function below reinterprets the handle's bytes as `Text`'s four fields,
893/// so handing one anything else would read whatever happened to be there.
894fn is_text(view: &ViewHandle) -> bool {
895    crate::diag::type_name(view.meta) == "SwiftUI.Text"
896}
897
898/// Call a `Text` method that takes no arguments.
899///
900/// `self` is the four fields in x0-x3 and the result comes back the same way.
901///
902/// # Safety
903/// `f` must be a `Text` method of this shape and `view` must hold a `Text`.
904unsafe fn text_call0(view: &ViewHandle, f: *const c_void) -> ViewHandle {
905    unsafe {
906        let (a, b, c, d) = text_words(view);
907        let (r0, r1, r2, r3): (u64, u64, u64, u64);
908        core::arch::asm!("blr {f}", f = in(reg) f,
909            inout("x0") a => r0, inout("x1") b => r1,
910            inout("x2") c => r2, inout("x3") d => r3,
911            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
912            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
913            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
914            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
915            lateout("x21") _, clobber_abi("C"),
916        );
917        text_from_words(view, (r0, r1, r2, r3))
918    }
919}
920
921/// Call a `Text` method taking one word, which lands ahead of `self`.
922///
923/// # Safety
924/// As [`text_call0`], and `w` must be the argument that method expects.
925unsafe fn text_call1(view: &ViewHandle, f: *const c_void, w: u64) -> ViewHandle {
926    unsafe {
927        let (a, b, c, d) = text_words(view);
928        let (r0, r1, r2, r3): (u64, u64, u64, u64);
929        core::arch::asm!("blr {f}", f = in(reg) f,
930            inout("x0") w => r0, inout("x1") a => r1,
931            inout("x2") b => r2, inout("x3") c => r3,
932            inout("x4") d => _,
933            lateout("x5") _, lateout("x6") _, lateout("x7") _,
934            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
935            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
936            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
937            lateout("x21") _, clobber_abi("C"),
938        );
939        text_from_words(view, (r0, r1, r2, r3))
940    }
941}
942
943/// Call a `Text` method taking one `CGFloat`, which travels in d0 while `self`
944/// stays in x0-x3.
945///
946/// # Safety
947/// As [`text_call0`].
948unsafe fn text_call_f64(view: &ViewHandle, f: *const c_void, x: f64) -> ViewHandle {
949    unsafe {
950        let (a, b, c, d) = text_words(view);
951        let (r0, r1, r2, r3): (u64, u64, u64, u64);
952        core::arch::asm!("blr {f}", f = in(reg) f,
953            in("d0") x,
954            inout("x0") a => r0, inout("x1") b => r1,
955            inout("x2") c => r2, inout("x3") d => r3,
956            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
957            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
958            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
959            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
960            lateout("x21") _, clobber_abi("C"),
961        );
962        text_from_words(view, (r0, r1, r2, r3))
963    }
964}
965
966/// `Text.bold()` — the one that returns `Text`.
967///
968/// Returns the `View` extension's result if this is not a `Text`: that is what a
969/// caller wants there, and it is what every one of these used to do.
970pub fn text_bold(view: &ViewHandle) -> ViewHandle {
971    if !is_text(view) {
972        return super::bold(view);
973    }
974    unsafe { text_call0(view, resolve::text_bold()) }
975}
976
977/// `Text.italic()`
978pub fn text_italic(view: &ViewHandle) -> ViewHandle {
979    if !is_text(view) {
980        return super::italic(view);
981    }
982    unsafe { text_call0(view, resolve::text_italic()) }
983}
984
985/// `Text.monospaced(_:)`
986pub fn text_monospaced(view: &ViewHandle) -> ViewHandle {
987    if !is_text(view) {
988        return super::monospaced(view);
989    }
990    unsafe { text_call1(view, resolve::text_monospaced(), 1) }
991}
992
993/// `Text.foregroundColor(_:)` — the overload that returns `Text`.
994pub fn text_foreground_color(view: &ViewHandle, color: crate::Color) -> ViewHandle {
995    if !is_text(view) {
996        return super::foreground_style_color(view, color.r, color.g, color.b, color.a);
997    }
998    unsafe { text_call1(view, resolve::text_fg_color(), super::color_value_of(color)) }
999}
1000
1001/// `Text.kerning(_:)` — space added between every pair of characters.
1002pub fn text_kerning(view: &ViewHandle, amount: f64) -> ViewHandle {
1003    if !is_text(view) {
1004        return super::kerning(view, amount);
1005    }
1006    unsafe { text_call_f64(view, resolve::text_kerning(), amount) }
1007}
1008
1009/// `Text.tracking(_:)` — like kerning, but not suppressed between the pairs the
1010/// font has a ligature for.
1011pub fn text_tracking(view: &ViewHandle, amount: f64) -> ViewHandle {
1012    if !is_text(view) {
1013        return super::tracking(view, amount);
1014    }
1015    unsafe { text_call_f64(view, resolve::text_tracking(), amount) }
1016}
1017
1018/// `Text.baselineOffset(_:)`
1019pub fn text_baseline_offset(view: &ViewHandle, amount: f64) -> ViewHandle {
1020    if !is_text(view) {
1021        return view.clone();
1022    }
1023    unsafe { text_call_f64(view, resolve::text_baseline(), amount) }
1024}
1025/// `Text.font(_:)` — the overload that returns `Text`.
1026///
1027/// `Text("x").font(.body).bold()` stays a `Text` all the way through in Swift
1028/// precisely because this returns one; going through the `View` extension for
1029/// the font is what made the rest of the chain fall back to the environment
1030/// forms. `None` clears the font.
1031pub fn text_font(view: &ViewHandle, font: Option<&super::Font>) -> ViewHandle {
1032    if !is_text(view) {
1033        return match font {
1034            Some(f) => super::font_ref(view, f),
1035            None => view.clone(),
1036        };
1037    }
1038    unsafe { text_call1(view, resolve::text_font(), font.map_or(0, super::Font::raw)) }
1039}
1040
1041/// `Text.fontWeight(_:)` — the overload that returns `Text`.
1042///
1043/// `Font.Weight?` lowers to two words, a payload and a tag, which land ahead of
1044/// `self` — so this is the one shape the single-word helper cannot serve.
1045/// `None` clears the weight.
1046pub fn text_font_weight(view: &ViewHandle, weight: Option<super::Weight>) -> ViewHandle {
1047    if !is_text(view) {
1048        return match weight {
1049            Some(w) => super::font_weight(view, w),
1050            None => view.clone(),
1051        };
1052    }
1053    unsafe {
1054        let (a, b, c, d) = text_words(view);
1055        // The payload is the weight's own value; the tag is 0 for `some` and 1
1056        // for `none`, which is how a single-payload optional of a non-pointer
1057        // type is spelled.
1058        let (w0, w1) = match weight {
1059            // `Font.Weight` is a wrapper around a `CGFloat`, which is what the
1060            // `View` extension next door passes too.
1061            Some(w) => (w.value().to_bits(), 0u64),
1062            None => (0, 1),
1063        };
1064        let (r0, r1, r2, r3): (u64, u64, u64, u64);
1065        core::arch::asm!("blr {f}", f = in(reg) resolve::text_font_weight(),
1066            inout("x0") w0 => r0, inout("x1") w1 => r1,
1067            inout("x2") a => r2, inout("x3") b => r3,
1068            inout("x4") c => _, inout("x5") d => _,
1069            lateout("x6") _, lateout("x7") _,
1070            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
1071            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
1072            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
1073            lateout("x21") _, clobber_abi("C"),
1074        );
1075        text_from_words(view, (r0, r1, r2, r3))
1076    }
1077}
1078
1079/// `Text.foregroundStyle(_:)` — the overload that returns `Text`.
1080///
1081/// Generic over `ShapeStyle`, so the style travels `@in` with its metadata and
1082/// witness after `self`: `(@in S, self…, S.Type, S: ShapeStyle)` — read off the
1083/// IR. This is what a gradient-filled `Text` needs; the `View` extension next
1084/// door wraps it in a `_ForegroundStyleModifier` instead.
1085///
1086/// # Safety
1087/// `style` must be a value of the type `style_meta` describes, and `style_wt`
1088/// its `ShapeStyle` conformance.
1089pub unsafe fn text_foreground_style(
1090    view: &ViewHandle,
1091    style: &[u8],
1092    style_meta: *const c_void,
1093    style_wt: *const c_void,
1094) -> ViewHandle {
1095    if !is_text(view) {
1096        return unsafe { foreground_style_of(view, style, style_meta, style_wt, &FOREGROUND_GRADIENT_T) };
1097    }
1098    unsafe {
1099        let (a, b, c, d) = text_words(view);
1100        let mut buf = style.to_vec();
1101        let (r0, r1, r2, r3): (u64, u64, u64, u64);
1102        core::arch::asm!("blr {f}", f = in(reg) resolve::text_fg_style(),
1103            inout("x0") buf.as_mut_ptr() as u64 => r0,
1104            inout("x1") a => r1, inout("x2") b => r2, inout("x3") c => r3,
1105            inout("x4") d => _,
1106            in("x5") style_meta, in("x6") style_wt,
1107            lateout("x7") _,
1108            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
1109            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
1110            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
1111            lateout("x21") _, clobber_abi("C"),
1112        );
1113        text_from_words(view, (r0, r1, r2, r3))
1114    }
1115}
1116
1117/// `Text.foregroundStyle(LinearGradient(...))`.
1118pub fn text_foreground_linear_gradient(
1119    view: &ViewHandle,
1120    colors: &[u64],
1121    start: (f64, f64),
1122    end: (f64, f64),
1123) -> ViewHandle {
1124    let (style, meta, wt) = super::linear_gradient_style_of(colors, start, end);
1125    unsafe { text_foreground_style(view, &style, meta, wt) }
1126}
1127
1128/// `Text.foregroundStyle(_:)` with one of the hierarchical styles.
1129pub fn text_foreground_hierarchical(
1130    view: &ViewHandle,
1131    level: super::Hierarchical,
1132) -> ViewHandle {
1133    let (style, meta, wt) = super::hierarchical_style(level);
1134    unsafe { text_foreground_style(view, &style, meta, wt) }
1135}