Skip to main content

kui_core/
text.rs

1//! Core-owned text stack. Shaping and line layout run through cosmic-text and
2//! are cached across frames keyed by (content, style, scale) — the layout pass
3//! measures through this cache, so shaping survives resizes and static text
4//! costs a hash lookup per frame. Rasterization feeds the shared glyph atlas;
5//! renderers only ever see positioned atlas quads.
6
7use cosmic_text::{
8    Attrs, Buffer, CacheKeyFlags, Ellipsize, EllipsizeHeightLimit, FontSystem, Metrics, Shaping,
9    Style as FontStyle, SwashContent, Weight, Wrap,
10};
11use rustc_hash::FxHashMap;
12
13use crate::atlas::GlyphAtlas;
14use crate::color::Color;
15use crate::display::{Clip, ClipId, Quad, QuadKind};
16use crate::geom::{Rect, Size, Vec2};
17use crate::key::Key;
18use crate::resources::Resources;
19use crate::retain::Kept;
20use crate::spec::{FontFamily, TextStyle, TextWrap, UnderlineStyle};
21use crate::tree::TextId;
22use crate::value::Value;
23
24/// The glyph rasterizer: swash's scaler, as cosmic-text's `SwashCache`
25/// drives it, for plain alpha masks, LCD subpixel masks and color bitmaps
26/// alike — our own so the subpixel format is ours to pick, and so a
27/// variable face is drawn at the `wght` coordinate its axis gives a CSS
28/// weight and a glyph marked for synthetic bold is drawn bold, neither of
29/// which cosmic-text's cache does.
30pub(crate) struct Raster {
31    ctx: swash::scale::ScaleContext,
32    /// Each face's `wght` axis, read once: `None` for a face without one.
33    axes: FxHashMap<cosmic_text::fontdb::ID, Option<crate::weights::WghtAxis>>,
34    /// Rasterize outline glyphs as per-channel subpixel coverage. Set by
35    /// the driver from what its renderer can blend; see
36    /// `Core::set_subpixel_text`.
37    pub(crate) subpixel: bool,
38}
39
40impl Raster {
41    fn new() -> Self {
42        Self {
43            ctx: swash::scale::ScaleContext::new(),
44            axes: FxHashMap::default(),
45            subpixel: false,
46        }
47    }
48
49    /// cosmic-text's `swash_image` in `format`: with `Format::Subpixel`,
50    /// three rasterizations shifted by a third of a pixel land in r, g and
51    /// b. Color sources are tried first so emoji still come out as bitmaps.
52    ///
53    /// A variable face is drawn where its `wght` axis puts the glyph's
54    /// weight ([`WghtAxis`](crate::weights::WghtAxis)) — cosmic-text takes
55    /// the CSS number itself as the coordinate, which draws Berkeley Mono
56    /// Variable's regular at its Bold — and a glyph marked
57    /// [`SYNTHETIC_BOLD`](crate::weights::SYNTHETIC_BOLD) at the axis's
58    /// bold, or, when the face has no heavier instance or no axis, with
59    /// its outline emboldened.
60    fn image(
61        &mut self,
62        fs: &mut FontSystem,
63        key: cosmic_text::CacheKey,
64        format: swash::zeno::Format,
65    ) -> Option<cosmic_text::SwashImage> {
66        use swash::scale::{Render, Source, StrikeWith};
67        use swash::zeno::{Angle, Transform, Vector};
68        let font = fs.get_font(key.font_id, key.font_weight)?;
69        let swash_font = font.as_swash();
70        let axis = self
71            .axes
72            .entry(key.font_id)
73            .or_insert_with(|| {
74                let weight = fs.db().face(key.font_id).map_or(400, |face| face.weight.0);
75                crate::weights::WghtAxis::read(swash_font, weight)
76            })
77            .as_ref();
78        let size = f32::from_bits(key.font_size_bits);
79        let face_weight = f32::from(key.font_weight.0);
80        let bold = key.flags.contains(crate::weights::SYNTHETIC_BOLD);
81        let asked = if bold {
82            f32::from(Weight::BOLD.0)
83        } else {
84            face_weight
85        };
86        let at = axis.map(|axis| axis.coordinate(asked));
87        // Bold that the axis cannot draw heavier than the face's own
88        // weight, or a face with no axis: the outline grown instead.
89        let embolden = bold
90            && match (axis, at) {
91                (Some(axis), Some(at)) => at <= axis.coordinate(face_weight),
92                _ => true,
93            };
94        let mut scaler = self
95            .ctx
96            .builder(swash_font)
97            .size(size)
98            .hint(!key.flags.contains(CacheKeyFlags::DISABLE_HINTING));
99        if let Some(at) = at {
100            scaler = scaler.normalized_coords(
101                swash_font
102                    .variations()
103                    .normalized_coords([(swash::Tag::from_be_bytes(*b"wght"), at)]),
104            );
105        }
106        let mut scaler = scaler.build();
107        // A glyph wider or taller than an atlas page is refused there
108        // (`GlyphAtlas::get_or_insert`), but only once it is drawn, and a
109        // size is a plain number every binding passes through: at 10^6 px
110        // zeno sizes the mask as `w * h * 4` in a `u32`, which overflows,
111        // and the image it would draw is terabytes (backlog FZ5). Past
112        // the size at which any face's glyph still fits a page, its
113        // outline's box is read first; a glyph with no outline (a bitmap
114        // strike) goes by the size.
115        if size > LARGE_GLYPH {
116            let page = crate::atlas::MAX_ATLAS_SIZE as f32;
117            let fits = match scaler.scale_outline(key.glyph_id) {
118                Some(outline) => {
119                    let b = outline.bounds();
120                    b.width() <= page && b.height() <= page
121                }
122                None => size <= page,
123            };
124            if !fits {
125                return None;
126            }
127        }
128        let offset = if key.flags.contains(CacheKeyFlags::PIXEL_FONT) {
129            Vector::new(key.x_bin.as_float().round(), key.y_bin.as_float().round())
130        } else {
131            Vector::new(key.x_bin.as_float(), key.y_bin.as_float())
132        };
133        Render::new(&[
134            Source::ColorOutline(0),
135            Source::ColorBitmap(StrikeWith::BestFit),
136            Source::Outline,
137        ])
138        .format(format)
139        .offset(offset)
140        .embolden(if embolden {
141            crate::weights::embolden_strength(size)
142        } else {
143            0.0
144        })
145        .transform(
146            key.flags
147                .contains(CacheKeyFlags::FAKE_ITALIC)
148                .then(|| Transform::skew(Angle::from_degrees(14.0), Angle::from_degrees(0.0))),
149        )
150        .render(&mut scaler, key.glyph_id)
151    }
152}
153
154/// The size, in physical px, past which a glyph's outline is measured
155/// before it is drawn (backlog FZ5): below it even a glyph three em wide
156/// fits an atlas page.
157const LARGE_GLYPH: f32 = 1024.0;
158
159/// Rasterizes one glyph into the atlas (shared by static text and editors).
160pub(crate) fn raster_glyph(
161    key: cosmic_text::CacheKey,
162    fs: &mut FontSystem,
163    raster: &mut Raster,
164    atlas: &mut crate::atlas::GlyphAtlas,
165) -> Option<crate::atlas::GlyphSlot> {
166    atlas.get_or_insert(key, || {
167        let format = if raster.subpixel {
168            swash::zeno::Format::Subpixel
169        } else {
170            swash::zeno::Format::Alpha
171        };
172        let image = raster.image(fs, key, format)?;
173        if image.placement.width == 0 || image.placement.height == 0 {
174            return None;
175        }
176        let (data, is_color, subpixel) = match image.content {
177            SwashContent::Mask => {
178                let mut rgba = Vec::with_capacity(image.data.len() * 4);
179                for &a in image.data.iter() {
180                    rgba.extend_from_slice(&[255, 255, 255, a]);
181                }
182                (rgba, false, false)
183            }
184            SwashContent::Color => (image.data.to_vec(), true, false),
185            SwashContent::SubpixelMask => {
186                // rgb carry the per-channel coverages; alpha (which zeno
187                // leaves untouched) becomes the union, the value a backend
188                // without per-channel blending falls back to.
189                let mut rgba = image.data.to_vec();
190                for px in rgba.as_chunks_mut::<4>().0 {
191                    px[3] = px[0].max(px[1]).max(px[2]);
192                }
193                (rgba, false, true)
194            }
195        };
196        Some(crate::atlas::RasterGlyph {
197            w: image.placement.width,
198            h: image.placement.height,
199            left: image.placement.left,
200            top: image.placement.top,
201            color: is_color,
202            subpixel,
203            data,
204        })
205    })
206}
207
208/// The quad kind a rasterized glyph draws as.
209pub(crate) fn glyph_kind(slot: &crate::atlas::GlyphSlot) -> QuadKind {
210    if slot.color_glyph {
211        QuadKind::GlyphColor
212    } else if slot.subpixel {
213        QuadKind::GlyphSubpixel
214    } else {
215        QuadKind::GlyphMask
216    }
217}
218
219/// The default byte budget for the shaped-text cache. Sized
220/// so a screenful of code never meets it — two panes of 55 highlighted
221/// lines are ~900 short entries, a few megabytes — and a pane streaming
222/// new text meets it within seconds, which is when the clock alone let the
223/// cache reach gigabytes. `Core::set_text_cache_budget` changes it.
224pub const DEFAULT_TEXT_CACHE_BYTES: usize = 64 << 20;
225
226/// What one cache entry costs, estimated: a fixed floor (the `Buffer`, its
227/// line, the attrs list, the entry itself) plus a per-glyph rate that
228/// covers cosmic-text's `ShapeGlyph` and `LayoutGlyph`, our
229/// `GlyphTemplate`, and the entry's share of the shape-run cache. Measured
230/// on 2026-09-07 with a counting allocator around a `Core` drawing 50 new
231/// lines a frame: 7.6 KB per 10-glyph line, 22 KB per 40, 92 KB per 200
232/// (cosmic-text's own `Buffer` is 284 B/glyph of that and the shape-run
233/// cache 118 B/glyph). The estimate lands within ~10% of those, on the
234/// high side, so the budget errs toward evicting.
235const ENTRY_BASE_BYTES: usize = 4096;
236const ENTRY_GLYPH_BYTES: usize = 480;
237
238/// A non-wrapping text at least this long is shaped in chunks: a minified
239/// bundle, a log line with a blob in it, a base64 field.
240/// Shorter text takes the path it always took, so nothing below the line
241/// moves. Bytes, not characters, for the same reason a step line carries
242/// integers: every binding can count them.
243pub const LONG_LINE_BYTES: usize = 4096;
244/// How long a chunk is, at most: cut at the last whitespace in the second
245/// half of the window, else at a grapheme boundary. cosmic-text shapes
246/// per whitespace-delimited word and its shape-run cache keys on words, so
247/// a cut at whitespace loses nothing the whole line kept.
248const CHUNK_BYTES: usize = 1024;
249/// Mixed into a long line's key so it never collides with the entry a
250/// short text of the same content would get.
251const LONG_SALT: u64 = 0x5f5f_6c6f_6e67_5f5f;
252/// Mixed into the key of a long line's copies, one for each further node
253/// of a frame drawing the same line ([`TextSystem::claim_long`]).
254const COPY_SALT: u64 = 0x5f5f_636f_7079_5f5f;
255
256/// When the cache is over budget it is evicted down to this fraction of it,
257/// not to the line, so a stream that adds a little every frame walks the
258/// cache once per quarter-budget of new text rather than every frame.
259const EVICT_TO_NUMERATOR: usize = 3;
260const EVICT_TO_DENOMINATOR: usize = 4;
261
262pub(crate) struct CachedText {
263    buffer: Buffer,
264    /// The text itself (spans concatenated, for rich text): what the
265    /// access tree names a control by.
266    content: String,
267    /// Wrap width (physical px) the buffer is currently laid out at.
268    wrap: Option<f32>,
269    /// Unwrapped measurement, physical px.
270    intrinsic: Size,
271    /// Lines past this many are dropped (0 = unlimited): the buffer-wide
272    /// budget; cosmic-text's ellipsize limit is per paragraph.
273    max_lines: usize,
274    /// The content may run past the node's width (no-wrap, ellipsis):
275    /// report the box width and clip glyphs to it.
276    clamp_w: bool,
277    last_used: u64,
278    /// What this entry costs the budget (see `ENTRY_BASE_BYTES`).
279    bytes: usize,
280    /// Positioned glyph quads relative to the text origin, so steady-state
281    /// emission is a memcpy-style walk instead of per-glyph atlas lookups.
282    glyphs: Vec<GlyphTemplate>,
283    /// The decoration rects that go with them: a span's
284    /// background under its glyphs, its underline and strikethrough over
285    /// them, one rect per run of the span per line, so they wrap with it.
286    deco: Vec<DecoTemplate>,
287    /// What each span asked for, by the index its glyphs carry as
288    /// metadata; one entry for plain text.
289    span_deco: Vec<SpanDeco>,
290    /// (wrap, atlas stamp) the template cache was built for.
291    glyphs_built_for: Option<(Option<u32>, u64)>,
292    /// The frame a node last wrapped this run for, and the wrap it asked:
293    /// a second node of that frame asking another width wraps a copy of
294    /// its own ([`TextSystem::own_wrap`]), since the queries read the
295    /// buffer as it was left.
296    claimed: u64,
297    claimed_wrap: Option<f32>,
298    /// A break may fall between any two glyphs (`TextWrap::Glyph`), not
299    /// only where a line may break: what the min-content reads.
300    breaks_anywhere: bool,
301    /// The widest stretch no break falls inside, physical px — CSS's
302    /// min-content — measured the first time a shrink asks and kept for
303    /// the entry's life, since no width changes it.
304    min_content: Option<f32>,
305}
306
307#[derive(Clone, Copy)]
308struct GlyphTemplate {
309    x: f32,
310    y: f32,
311    w: f32,
312    h: f32,
313    uv: [u32; 4],
314    kind: QuadKind,
315    /// Per-span color override (rich text); falls back to the node color.
316    /// A glyph whose ink lies over the places of spans painted apart is
317    /// several templates, a slice of its columns each
318    /// ([`Places::push_painted`]).
319    color: Option<Color>,
320    /// The byte the glyph starts at in the entry's content: what puts it
321    /// on a row when a wrapped long line's chunk is drawn. Every slice of
322    /// a glyph carries the glyph's, so they go to its row together.
323    byte: u32,
324}
325
326/// One decoration rect relative to the text origin, physical px.
327struct DecoTemplate {
328    x: f32,
329    y: f32,
330    w: f32,
331    h: f32,
332    /// The rect's colour; `None` is the text's own (an underline in the
333    /// text colour), which a background never is.
334    color: Option<Color>,
335    /// Painted before the glyphs (a background) rather than after (a line).
336    under: bool,
337    /// The shape, for an underline: the rect is where a solid line goes,
338    /// and a wave or dots are built around it at emission.
339    style: UnderlineStyle,
340    /// A background's radius, logical px; above zero the frame joins it
341    /// with the ones it meets ([`JoinBg`]).
342    radius: f32,
343}
344
345/// A rounded span background emitted this frame: the quad
346/// it is, square for now, which the frame's last pass turns into its part
347/// of one shape once every text has been painted and each can be told the
348/// ones it meets (`crate::join`). The radius is logical px. `outer` is the
349/// clip the text was given, and `own` its own box's physical x-range when
350/// it clips to it, as a no-wrap text does: a piece is no wider than that
351/// box shows, but a fillet past the end of a short line lies outside the
352/// box, beside it, and is clipped only as the text's parent is.
353#[derive(Clone, Copy, Debug)]
354pub(crate) struct JoinBg {
355    pub quad: u32,
356    pub radius: f32,
357    pub outer: ClipId,
358    pub own: Option<(f32, f32)>,
359}
360
361/// What one span (or a plain text's whole content) asked for past its
362/// shaping: where it starts, its colour, and its decorations.
363#[derive(Clone, Copy, Default)]
364struct SpanDeco {
365    /// The byte it starts at in the entry's content.
366    start: u32,
367    /// Its colour as its glyphs carry it ([`shaped_color`]); `None` is
368    /// the text's own.
369    color: Option<Color>,
370    underline: bool,
371    underline_color: Option<Color>,
372    underline_style: UnderlineStyle,
373    strikethrough: bool,
374    bg: Option<Color>,
375    /// The background's radius, logical px: above zero it is joined with
376    /// the backgrounds it meets.
377    bg_radius: f32,
378}
379
380impl SpanDeco {
381    fn of_style(style: &TextStyle) -> Self {
382        Self {
383            start: 0,
384            color: None,
385            underline: style.underline,
386            underline_color: style.underline_color,
387            underline_style: style.underline_style,
388            strikethrough: style.strikethrough,
389            bg: None,
390            bg_radius: 0.0,
391        }
392    }
393
394    fn any(&self) -> bool {
395        self.underline || self.strikethrough || self.bg.is_some()
396    }
397}
398
399/// One styled run inside a rich-text paragraph. Spans are shaped and wrapped
400/// together as a single flow; plain data, so every frontend can build them.
401#[derive(Clone, Copy, Debug)]
402pub struct Span<'a> {
403    pub text: &'a str,
404    pub color: Option<Color>,
405    pub bold: bool,
406    pub italic: bool,
407    /// A line under the span, where the face puts its underline.
408    pub underline: bool,
409    /// The underline's own colour; `None` is the span's.
410    pub underline_color: Option<Color>,
411    /// The underline's shape.
412    pub underline_style: UnderlineStyle,
413    /// A line through the span, where the face puts its strikeout.
414    pub strikethrough: bool,
415    /// A background behind the span's glyphs, one rect per line it spans,
416    /// so it follows the span across a wrap the way a box cannot.
417    pub bg: Option<Color>,
418    /// The background's corner radius, logical px. Above
419    /// zero, every background of the same colour and radius that meets
420    /// another edge to edge on the line above or below — in this text or
421    /// another — is one shape with it: its corners convex where a line
422    /// reaches past its neighbour, concave where it falls short, round
423    /// where nothing meets it. What a selection over lines looks like.
424    pub bg_radius: f32,
425    /// The span's own face, in place of the paragraph's: inline `code` in
426    /// `FontFamily::Mono` inside a sans line. `None` is the paragraph's.
427    /// The span keeps the paragraph's weights for its family (bold is the
428    /// family's bold), and its glyphs, carets and selection are measured
429    /// in the face they are drawn in, so byte positions stay exact across
430    /// the change.
431    pub family: Option<FontFamily>,
432    /// The span's own size in logical px, in place of the paragraph's: a
433    /// heading's larger first word, a smaller footnote mark. Its line
434    /// height scales with it at the paragraph's ratio, and a line is as
435    /// tall as its tallest span. `None` is the paragraph's. A paragraph
436    /// with a sized span is shaped whole, never chunked as a long line.
437    pub size: Option<f32>,
438}
439
440impl<'a> Span<'a> {
441    pub fn new(text: &'a str) -> Self {
442        Self {
443            text,
444            color: None,
445            bold: false,
446            italic: false,
447            underline: false,
448            underline_color: None,
449            underline_style: UnderlineStyle::Solid,
450            strikethrough: false,
451            bg: None,
452            bg_radius: 0.0,
453            family: None,
454            size: None,
455        }
456    }
457
458    /// The span in its own face (see the `family` field).
459    pub fn family(mut self, family: FontFamily) -> Self {
460        self.family = Some(family);
461        self
462    }
463
464    /// The span in the monospace face: inline code.
465    ///
466    /// ```
467    /// # use kui_core::{FontFamily, text::Span};
468    /// let code = Span::new("cargo test").mono();
469    /// assert_eq!(code.family, Some(FontFamily::Mono));
470    /// ```
471    pub fn mono(self) -> Self {
472        self.family(FontFamily::Mono)
473    }
474
475    /// The span at its own size in logical px (see the `size` field); a
476    /// size that is not a positive number is the paragraph's.
477    pub fn size(mut self, px: f32) -> Self {
478        self.size = (px.is_finite() && px > 0.0).then_some(px);
479        self
480    }
481
482    pub fn color(mut self, c: Color) -> Self {
483        self.color = Some(c);
484        self
485    }
486
487    pub fn bold(mut self) -> Self {
488        self.bold = true;
489        self
490    }
491
492    pub fn italic(mut self) -> Self {
493        self.italic = true;
494        self
495    }
496
497    pub fn underline(mut self) -> Self {
498        self.underline = true;
499        self
500    }
501
502    /// An underline in its own colour; turns it on.
503    pub fn underline_color(mut self, c: Color) -> Self {
504        self.underline = true;
505        self.underline_color = Some(c);
506        self
507    }
508
509    /// An underline of this shape; turns it on.
510    pub fn underline_style(mut self, s: UnderlineStyle) -> Self {
511        self.underline = true;
512        self.underline_style = s;
513        self
514    }
515
516    pub fn strikethrough(mut self) -> Self {
517        self.strikethrough = true;
518        self
519    }
520
521    pub fn bg(mut self, c: Color) -> Self {
522        self.bg = Some(c);
523        self
524    }
525
526    /// Rounds the background, joined with the ones it meets (see
527    /// [`Span::bg_radius`](Self#structfield.bg_radius)).
528    pub fn bg_radius(mut self, r: f32) -> Self {
529        self.bg_radius = r.max(0.0);
530        self
531    }
532
533    /// The span without its text: what a long line keeps per span so a
534    /// chunk can be rebuilt from the content and the ranges.
535    fn attrs_only(&self) -> SpanAttrs {
536        SpanAttrs {
537            color: self.color,
538            bold: self.bold,
539            italic: self.italic,
540            underline: self.underline,
541            underline_color: self.underline_color,
542            underline_style: self.underline_style,
543            strikethrough: self.strikethrough,
544            bg: self.bg,
545            bg_radius: self.bg_radius,
546            family: self.family,
547            size: self.size,
548        }
549    }
550
551    fn attrs<'r>(
552        &self,
553        family: cosmic_text::Family<'r>,
554        weights: crate::weights::Weights,
555    ) -> Attrs<'r> {
556        // Pin the family (the paragraph base's) so weight/style variants
557        // stay in one typeface instead of falling back to whatever face
558        // matches first — at weights it has faces for (backlog F100).
559        let mut attrs = weights.apply(Attrs::new().family(family), self.bold);
560        if self.italic {
561            attrs = attrs.style(FontStyle::Italic);
562        }
563        if let Some(c) = self.color {
564            attrs = attrs.color(shaper_color(c));
565        }
566        attrs
567    }
568}
569
570/// A colour as the shaper is handed it, eight bits a channel.
571fn shaper_color(c: Color) -> cosmic_text::Color {
572    cosmic_text::Color::rgba(
573        (c.r * 255.0) as u8,
574        (c.g * 255.0) as u8,
575        (c.b * 255.0) as u8,
576        (c.a * 255.0) as u8,
577    )
578}
579
580/// A colour as a span's glyphs come back carrying it ([`shaper_color`]
581/// and back), so a colour read off a span and one read off its glyphs
582/// compare equal.
583fn shaped_color(c: Color) -> Color {
584    let c = shaper_color(c);
585    Color::rgba8(c.r(), c.g(), c.b(), c.a())
586}
587
588/// A [`Span`]'s attributes without its text, owned: what a long rich
589/// line keeps per span, with the range in [`OwnedSpan`], so a chunk can
590/// be handed the spans that intersect it, sliced.
591#[derive(Clone, Copy)]
592struct SpanAttrs {
593    color: Option<Color>,
594    bold: bool,
595    italic: bool,
596    underline: bool,
597    underline_color: Option<Color>,
598    underline_style: UnderlineStyle,
599    strikethrough: bool,
600    bg: Option<Color>,
601    bg_radius: f32,
602    family: Option<FontFamily>,
603    size: Option<f32>,
604}
605
606impl SpanAttrs {
607    fn span<'a>(&self, text: &'a str) -> Span<'a> {
608        Span {
609            text,
610            color: self.color,
611            bold: self.bold,
612            italic: self.italic,
613            underline: self.underline,
614            underline_color: self.underline_color,
615            underline_style: self.underline_style,
616            strikethrough: self.strikethrough,
617            bg: self.bg,
618            bg_radius: self.bg_radius,
619            family: self.family,
620            size: self.size,
621        }
622    }
623}
624
625/// One span of a long rich line: its byte range in the content and its
626/// attributes. The ranges are contiguous and cover the content.
627#[derive(Clone)]
628struct OwnedSpan {
629    start: usize,
630    end: usize,
631    attrs: SpanAttrs,
632}
633
634/// The spans of `spans` that intersect `start..end` of `content`, sliced
635/// to it and with the empty dropped: what a chunk of a long rich line is
636/// shaped from. A span cut by the chunk boundary becomes two, one a
637/// side, with the same attributes — its background rects meet at the cut.
638fn chunk_spans<'a>(
639    content: &'a str,
640    spans: &[OwnedSpan],
641    start: usize,
642    end: usize,
643) -> Vec<Span<'a>> {
644    let first = spans.partition_point(|s| s.end <= start);
645    spans[first..]
646        .iter()
647        .take_while(|s| s.start < end)
648        .filter_map(|s| {
649            let (a, b) = (s.start.max(start), s.end.min(end));
650            (a < b).then(|| s.attrs.span(&content[a..b]))
651        })
652        .collect()
653}
654
655struct FrameText {
656    cache_key: u64,
657    color: Color,
658}
659
660/// One entry of the text cache: a shaped run, or a long line whose chunks
661/// are runs of their own in the same map. Whether a text is
662/// long is decided once, at [`TextSystem::add`], and lives here as the
663/// variant; every operation after it asks the entry rather than carrying
664/// a flag beside the key. A long line joins a selection scope's
665/// concatenation like any run: being long is how it was shaped, not
666/// something a reader dragging across it should feel.
667pub(crate) enum Entry {
668    Run(CachedText),
669    Long(LongLine),
670}
671
672impl Entry {
673    pub(crate) fn content(&self) -> &str {
674        match self {
675            Entry::Run(e) => &e.content,
676            Entry::Long(l) => &l.content,
677        }
678    }
679
680    fn last_used(&self) -> u64 {
681        match self {
682            Entry::Run(e) => e.last_used,
683            Entry::Long(l) => l.last_used,
684        }
685    }
686
687    fn touch(&mut self, frame_no: u64) {
688        match self {
689            Entry::Run(e) => e.last_used = frame_no,
690            Entry::Long(l) => l.last_used = frame_no,
691        }
692    }
693
694    /// What the entry costs the budget.
695    fn bytes(&self) -> usize {
696        match self {
697            Entry::Run(e) => e.bytes,
698            Entry::Long(l) => l.bytes,
699        }
700    }
701
702    fn run(&self) -> Option<&CachedText> {
703        match self {
704            Entry::Run(e) => Some(e),
705            Entry::Long(_) => None,
706        }
707    }
708
709    fn run_mut(&mut self) -> Option<&mut CachedText> {
710        match self {
711            Entry::Run(e) => Some(e),
712            Entry::Long(_) => None,
713        }
714    }
715
716    fn long(&self) -> Option<&LongLine> {
717        match self {
718            Entry::Long(l) => Some(l),
719            Entry::Run(_) => None,
720        }
721    }
722
723    fn long_mut(&mut self) -> Option<&mut LongLine> {
724        match self {
725            Entry::Long(l) => Some(l),
726            Entry::Run(_) => None,
727        }
728    }
729}
730
731/// One chunk of a long line: its byte range in the content, the cache key
732/// its shaped entry has (an ordinary `CachedText`, budgeted like any), and
733/// its width once shaped.
734#[derive(Clone)]
735struct Chunk {
736    start: usize,
737    end: usize,
738    key: u64,
739    /// The run's own width: its tabs' stops measured from its start.
740    width: Option<f32>,
741    /// For a shaped chunk that starts off the line's tab stops and ends in
742    /// its one tab (`chunk_ranges`): where the tab starts in the run, and
743    /// the stops' interval, physical px. The run's width put that tab on
744    /// a stop of the chunk's own; the line puts it on one of its own, from
745    /// where the chunk starts (`LongLine::reprefix`, backlog RG76).
746    tab: Option<(f32, f32)>,
747    /// While the line is wrapped and this chunk is shaped: where each of
748    /// its rows starts. Empty otherwise (one row, or an estimate).
749    rows: Vec<RowStart>,
750}
751
752/// Where a row of a wrapped long line's chunk starts, relative to the
753/// chunk: the byte, and the x of that glyph in the chunk's unwrapped run,
754/// which the row's glyphs are shifted back by when drawn.
755#[derive(Clone, Copy, Debug)]
756struct RowStart {
757    byte: u32,
758    x: f32,
759}
760
761/// A non-wrapping text past [`LONG_LINE_BYTES`], shaped in chunks on demand.
762/// The line itself holds no buffer: its chunks are cache
763/// entries interned when emission, a hit-test or a caret query lands in
764/// them, and `prefix` is where each chunk starts — an estimate from the
765/// first chunk's mean advance until the chunk shapes, exact after. So a
766/// 100k-character line costs the screenful it shows, a keystroke into it
767/// costs the chunk it lands in, and the width the scrollbar sees can move
768/// a little as chunks fill in, exact under monospace.
769///
770/// Its rows are its own, so a line is laid out for one node a frame: a
771/// second node drawing the same content takes a copy
772/// ([`TextSystem::claim_long`]), which shares the chunks' shaped runs.
773#[derive(Clone)]
774pub(crate) struct LongLine {
775    content: String,
776    /// The spans, for a rich line: each chunk is shaped as a
777    /// rich run of the spans that intersect it, sliced. Empty for a plain
778    /// line, whose chunks are plain runs.
779    spans: Vec<OwnedSpan>,
780    /// The chunks' style: the text's with `wrap` set to `None`, since a
781    /// chunk is always shaped as one run and the line breaks it itself.
782    style: TextStyle,
783    /// The text's own line breaking. `None` is one row; `Word` and
784    /// `Glyph` break the chunks into rows once the line does not fit its
785    /// box (C19's step 5): a one-direction prefix computation over glyph
786    /// positions, each chunk's first row starting where the previous
787    /// chunk's last row ended, so it costs positions and not shaping.
788    wrap: TextWrap,
789    /// The physical width the rows are broken to, while they are; `None`
790    /// is one row — the line fits, or its style never wraps.
791    wrap_w: Option<f32>,
792    /// While wrapped: where each chunk begins, the row and the x on it —
793    /// one more than there are chunks, the last being where the text
794    /// ends. The wrapped counterpart of `prefix`, estimated the same way
795    /// for a chunk that never showed.
796    starts: Vec<(u32, f32)>,
797    chunks: Vec<Chunk>,
798    /// Physical px from the line's origin to each chunk's start, one more
799    /// than there are chunks: the last is the line's width.
800    prefix: Vec<f32>,
801    /// Physical line height, from the first chunk's buffer.
802    line_h: f32,
803    /// Physical px per byte, from the first chunk: the estimate an
804    /// unshaped chunk's width is.
805    avg: f32,
806    last_used: u64,
807    /// The frame a node last took this line to draw, `u64::MAX` before
808    /// one has.
809    claimed: u64,
810    /// The rows layout gave the node this frame, and the rows the last
811    /// frame asked for was owed for (`u32::MAX` before any): emission
812    /// breaks the rows again once the chunks it shapes are known, and
813    /// rows that differ from layout's owe a frame laid out on them —
814    /// once per count, so a cache too small to keep the chunks cannot
815    /// ask forever.
816    laid_rows: u32,
817    asked_rows: u32,
818    bytes: usize,
819}
820
821impl LongLine {
822    /// The chunk `x` (physical, from the line's origin) falls in.
823    fn chunk_at(&self, x: f32) -> usize {
824        match self.prefix[1..].partition_point(|&p| p <= x) {
825            i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
826            i => i,
827        }
828    }
829
830    /// The chunk byte `byte` falls in (the last for the end).
831    fn chunk_of_byte(&self, byte: usize) -> usize {
832        match self.chunks.partition_point(|c| c.end <= byte) {
833            i if i >= self.chunks.len() => self.chunks.len().saturating_sub(1),
834            i => i,
835        }
836    }
837
838    fn width(&self) -> f32 {
839        self.prefix.last().copied().unwrap_or(0.0)
840    }
841
842    /// Rows while wrapped; one otherwise.
843    fn rows(&self) -> u32 {
844        match self.starts.last() {
845            Some(&(r, _)) if self.wrap_w.is_some() => r + 1,
846            _ => 1,
847        }
848    }
849
850    /// The chunks whose rows touch `ra..=rb`, while wrapped: chunk `i`
851    /// spans `starts[i].0..=starts[i + 1].0`.
852    fn chunks_on_rows(&self, ra: u32, rb: u32) -> Option<(usize, usize)> {
853        let n = self.chunks.len();
854        if n == 0 || self.starts.len() != n + 1 {
855            return None;
856        }
857        let first = self.starts[1..].partition_point(|s| s.0 < ra);
858        let last = self.starts[..n].partition_point(|s| s.0 <= rb);
859        (first < last).then_some((first, last - 1))
860    }
861
862    /// Recomputes `prefix` from the chunk widths, estimating the unshaped.
863    /// A chunk ending in a tab it started off the stops for is as wide as
864    /// takes that tab to the line's next stop (backlog RG76), so the chunk
865    /// after it starts on one; the stop is exact once every chunk since
866    /// the line's previous tab is shaped, and otherwise as near as the
867    /// estimate `prefix` already is.
868    fn reprefix(&mut self) {
869        let mut at = 0.0f32;
870        self.prefix.clear();
871        self.prefix.push(0.0);
872        for c in &self.chunks {
873            at = match (c.width, c.tab) {
874                (Some(_), Some((tab_x, every))) => next_tab_stop(at + tab_x, every),
875                (Some(w), None) => at + w,
876                (None, _) => at + (c.end - c.start) as f32 * self.avg,
877            };
878            self.prefix.push(at);
879        }
880    }
881
882    /// How far the line moves chunk `i`'s end from where its run ends:
883    /// nonzero only for a chunk whose tab the line places (`reprefix`).
884    fn tab_shift(&self, i: usize) -> f32 {
885        let c = &self.chunks[i];
886        match (c.width, c.tab) {
887            (Some(w), Some(_)) => self.prefix[i + 1] - self.prefix[i] - w,
888            _ => 0.0,
889        }
890    }
891
892    /// `tab_shift` for a caret `local` bytes into chunk `i`: only the
893    /// chunk's end is past a tab the line places, which is its last
894    /// character, so only the end caret moves — and with it the line's end
895    /// caret, which then agrees with the line's width (backlog RG122).
896    fn end_shift(&self, i: usize, local: usize) -> f32 {
897        let c = &self.chunks[i];
898        match local >= c.end - c.start {
899            true => self.tab_shift(i),
900            false => 0.0,
901        }
902    }
903
904    /// The byte a point at `x` along chunk `i`'s run answers when it is on
905    /// a tab the line places: the run's tab is as wide as the chunk's own
906    /// stops make it and the drawn one as wide as the line's (`reprefix`),
907    /// so the run's hit test splits it at the wrong place. The tab before
908    /// the drawn tab's middle, the byte after it from there (backlog
909    /// RG122). `None` off such a tab.
910    fn placed_tab_hit(&self, i: usize, x: f32) -> Option<usize> {
911        let c = &self.chunks[i];
912        let (tab_x, _) = c.tab?;
913        c.width?;
914        if x < tab_x {
915            return None;
916        }
917        let drawn_end = self.prefix[i + 1] - self.prefix[i];
918        Some(match x < (tab_x + drawn_end) / 2.0 {
919            true => c.end - 1,
920            false => c.end,
921        })
922    }
923}
924
925/// The tab stop after `x` (physical px from the line's start), stops
926/// `every` px apart: cosmic-text's rule, which moves a tab already on a
927/// stop to the next one. `x` is a sum of chunk widths, so a stop within a
928/// sixteenth of a pixel ahead counts as reached — the drift that sum has
929/// over a stretch without a tab, which a whole line's own sum has too.
930fn next_tab_stop(x: f32, every: f32) -> f32 {
931    if every <= 0.0 {
932        return x;
933    }
934    (((x + 1.0 / 16.0) / every).floor() + 1.0) * every
935}
936
937/// Where a long line is cut: after the last tab in the second half of each
938/// window, else after the last whitespace there, else at the last grapheme
939/// boundary inside it — except that a chunk starting anywhere but after a
940/// tab ends after its first tab, wherever in the window that falls. A line
941/// under [`LONG_LINE_BYTES`] — long only by its `break-spaces` — is one
942/// chunk, shaped whole as the run it would otherwise be.
943///
944/// Tab stops are why: cosmic-text measures them from where
945/// the shaped text starts, so a chunk shaped alone put a tab after its
946/// start at a stop measured from there and not from the line's. A chunk
947/// cut just after a tab ends on a stop, so the next one starts on one and
948/// its stops are the line's (RG75). A chunk that starts off the stops —
949/// after a stretch of more than half a window without a tab — holds at
950/// most one tab, its last character, whose advance the line places from
951/// where the chunk starts (`Chunk::tab`, backlog RG76): nothing in the
952/// chunk follows that tab, so its run needs no reshaping for the line's x.
953fn chunk_ranges(content: &str) -> Vec<(usize, usize)> {
954    use unicode_segmentation::UnicodeSegmentation;
955    if content.len() < LONG_LINE_BYTES {
956        return vec![(0, content.len())];
957    }
958    let bytes = content.as_bytes();
959    let mut out = Vec::with_capacity(content.len() / CHUNK_BYTES + 1);
960    let mut start = 0usize;
961    while start < content.len() {
962        // The window's end floored to a char boundary: a multibyte
963        // character straddling `start + CHUNK_BYTES` is the window's, not
964        // the next one's, and slicing inside it was a panic (backlog C44).
965        let mut window_end = (start + CHUNK_BYTES).min(content.len());
966        while !content.is_char_boundary(window_end) {
967            window_end -= 1;
968        }
969        let window = &content[start..window_end];
970        // Where the window's second half starts, on a char boundary.
971        let mut from = (CHUNK_BYTES / 2).min(window.len());
972        while !window.is_char_boundary(from) {
973            from += 1;
974        }
975        // Off the stops (after anything but a tab), a tab in the first
976        // half ends the chunk: `find` is a memchr, so a tab-free line pays
977        // a search per window and not a second walk of it (RG75's cost).
978        let off_stops = start > 0 && bytes[start - 1] != b'\t';
979        let early_tab = match off_stops {
980            true => window[..from].find('\t'),
981            false => None,
982        };
983        let end = if let Some(t) = early_tab {
984            start + t + 1
985        } else if window_end == content.len() && !(off_stops && window[from..].contains('\t')) {
986            window_end
987        } else {
988            // One pass over the second half, noting its first and last
989            // tab and its last whitespace: a line re-cut on every edit
990            // (C19) must not walk each window twice.
991            let (mut first_tab, mut last_tab, mut space) = (None, None, None);
992            for (i, c) in window[from..].char_indices() {
993                if c.is_whitespace() {
994                    let end = from + i + c.len_utf8();
995                    space = Some(end);
996                    if c == '\t' {
997                        first_tab = first_tab.or(Some(end));
998                        last_tab = Some(end);
999                    }
1000                }
1001            }
1002            let tab = if off_stops { first_tab } else { last_tab };
1003            match tab.or(space) {
1004                Some(i) => start + i,
1005                None => {
1006                    // The last grapheme boundary at or before the window's end.
1007                    let mut cut = 0usize;
1008                    for (i, _) in window.grapheme_indices(true) {
1009                        if i == 0 {
1010                            continue;
1011                        }
1012                        cut = i;
1013                    }
1014                    if cut == 0 { window_end } else { start + cut }
1015                }
1016            }
1017        };
1018        out.push((start, end));
1019        start = end;
1020    }
1021    out
1022}
1023
1024/// How many enclosing keys a place remembers: a text run answers to its
1025/// own key and to any of this many ancestors, which is a `line` row, a
1026/// selection wrapper around a run, and two to spare. A text further than
1027/// this below its `line` raises `text-beyond-line`.
1028pub(crate) const PLACE_ANCESTORS: usize = 4;
1029
1030/// The keys above a text node, nearest first, as many as a place
1031/// remembers, and where among them a `role="none"` ancestor sits — what
1032/// `Core::text_ancestors` gathers for [`TextSystem::place`].
1033pub(crate) struct Ancestry {
1034    pub(crate) keys: [Key; PLACE_ANCESTORS],
1035    pub(crate) depth: usize,
1036    pub(crate) none_at: Option<usize>,
1037}
1038
1039/// Where a text node was drawn: what `Core::text_hit` and
1040/// `Core::caret_rect` answer from. Recorded at emission, so
1041/// a node the frame culled — scrolled out of its clip — has no place and
1042/// answers nothing, which is also true of a point nobody can click.
1043pub(crate) struct TextPlace {
1044    pub(crate) key: Key,
1045    /// The keys above it, nearest first, as many as `depth` says.
1046    ancestors: [Key; PLACE_ANCESTORS],
1047    depth: u8,
1048    /// The nearest ancestor (an index into `ancestors`) declaring
1049    /// `role="none"`, or `u8::MAX` for none within reach: a query by a key
1050    /// above it does not reach this run, the way the access tree skips a
1051    /// gutter's text when it reads a `line`; a query by the
1052    /// gutter itself still does.
1053    none_at: u8,
1054    cache_key: u64,
1055    /// The node's origin, logical viewport px.
1056    origin: Vec2,
1057    /// The innermost `selectable` node above this run, when there is one.
1058    /// What `scope_runs` gathers by, and the reason a place
1059    /// is recorded for a run that was never drawn — see `drawn`.
1060    scope: Option<Key>,
1061    /// Whether the run was painted. False for a run inside a selection
1062    /// scope that the frame culled: emission skips a node clipped
1063    /// entirely away, but a selection reaching past the viewport needs
1064    /// that node's content and its place in the order, so a scoped run
1065    /// records where it *would* have been. Hit tests and the public
1066    /// `text_hit` / `caret_rect` queries ignore these, because a point
1067    /// nobody can click still answers nothing.
1068    drawn: bool,
1069}
1070
1071impl TextPlace {
1072    fn answers_to(&self, key: Key) -> bool {
1073        if self.key == key {
1074            return true;
1075        }
1076        // Found among the ancestors, and not through a `role="none"`
1077        // subtree below the key: a gutter's text is not the line's, and
1078        // is still the gutter's own.
1079        self.ancestors[..self.depth as usize]
1080            .iter()
1081            .position(|k| *k == key)
1082            .is_some_and(|d| d <= self.none_at as usize)
1083    }
1084}
1085
1086/// One run inside a selection scope: where it was placed, its text, and
1087/// the byte offset its content starts at in the scope's concatenation.
1088/// The bases count content only — a separator between two runs is a
1089/// decision the *copy* makes (see `TextSystem::scope_slice`), so an
1090/// offset means the same thing whoever asks for it.
1091pub(crate) struct ScopeRun<'a> {
1092    pub place: &'a TextPlace,
1093    /// The run's entry, whichever way it was shaped.
1094    pub text: &'a Entry,
1095    pub base: usize,
1096}
1097
1098impl ScopeRun<'_> {
1099    /// The half-open byte range this run occupies in the concatenation.
1100    /// The run's `[start, end)` in the scope's concatenation.
1101    pub(crate) fn span(&self) -> (usize, usize) {
1102        (self.base, self.base + self.text.content().len())
1103    }
1104}
1105
1106/// Where a point landed in the text a keyed node drew: a byte offset and
1107/// the visual row it is on. `byte` is a caret position: between two
1108/// characters, past the last one at the end, and cosmic-text's rule for
1109/// which side of a glyph the point fell on. For a node holding several
1110/// text runs the offset runs across them in tree order, the way the
1111/// access tree reads a `line`. `line` is the **visual row** within the
1112/// node, 0-based, counted across every run the key covers by where the
1113/// rows sit: a `line` row of three inline runs is one row, a run that
1114/// wrapped is as many as it wrapped to, and two runs stacked are two —
1115/// not the wrapped line within one run's buffer, and not the ordinal `role="line"` node a pointer
1116/// event's `line` names (that one counts rows of the editor, this one
1117/// rows of the text asked about).
1118#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1119pub struct TextHit {
1120    pub byte: usize,
1121    pub line: u32,
1122}
1123
1124impl TextHit {
1125    /// `{byte, line}`.
1126    pub fn to_value(self) -> Value {
1127        Value::map([
1128            ("byte", Value::Int(self.byte as i64)),
1129            ("line", Value::Int(self.line as i64)),
1130        ])
1131    }
1132}
1133
1134/// What a piece of text measures, in logical px at the current scale —
1135/// the same numbers layout uses for a text node with that content and
1136/// style, so a view can size a column to its widest label or pick a tier
1137/// that fits without hand-tuned magic numbers.
1138#[derive(Clone, Copy, Debug, Default, PartialEq)]
1139pub struct TextMetrics {
1140    pub width: f32,
1141    pub height: f32,
1142    /// Lines after wrapping (capped by `max_lines`).
1143    pub lines: u32,
1144}
1145
1146impl TextMetrics {
1147    /// `{width, height, lines}`.
1148    pub fn to_value(self) -> Value {
1149        Value::map([
1150            ("width", Value::float(self.width)),
1151            ("height", Value::float(self.height)),
1152            ("lines", Value::Int(self.lines as i64)),
1153        ])
1154    }
1155}
1156
1157/// The shaping and rasterization state of one window. The font database
1158/// it shapes against is not in here — that is the session's
1159/// ([`crate::session::Session`]), passed in as `fs` — because a font
1160/// registered in one window has to shape in every window of the session.
1161/// What is here is coupled to this window's glyph atlas: the shaped-buffer
1162/// cache stamps its positioned glyphs with the atlas stamp they were
1163/// packed against, and the frame lists are what `TextId` indexes.
1164pub struct TextSystem {
1165    raster: Raster,
1166    /// The rounded span backgrounds this frame emitted, for the pass that
1167    /// joins them once every text is painted.
1168    joins: Vec<JoinBg>,
1169    /// Every shaped run and every long line, by key — a long line's key
1170    /// is salted (`LONG_SALT`) and its chunks are runs beside it.
1171    entries: FxHashMap<u64, Entry>,
1172    /// The sum of the entries' `bytes`, kept exact against inserts and
1173    /// removals so a frame inside its budget costs one comparison.
1174    bytes: usize,
1175    budget: usize,
1176    /// This frame's text nodes, and the previous frame's kept the same way
1177    /// and on the same condition as `Core`'s previous tree: a departing
1178    /// subtree's text nodes carry that frame's `TextId`s, and this is what
1179    /// they index (see [`Self::prev_frame_text`]).
1180    frame: Kept<FrameText>,
1181    /// Where this frame's text nodes were drawn, and where the last
1182    /// frame's were — always kept: a query during a build answers from the
1183    /// frame that finished, which is the layout a click was made against.
1184    places: Kept<TextPlace>,
1185    scale: f32,
1186    frame_no: u64,
1187    /// Emission broke a long line into rows other than the ones layout
1188    /// used: the frame laid out on them is owed. Taken by [`Self::take_owed`].
1189    owed: bool,
1190}
1191
1192/// A style's features in cosmic-text's terms. Empty stays empty, which is
1193/// the shaper's own defaults.
1194pub(crate) fn cosmic_features(f: &crate::spec::FontFeatures) -> cosmic_text::FontFeatures {
1195    let mut out = cosmic_text::FontFeatures::new();
1196    for (tag, value) in f.iter() {
1197        out.set(cosmic_text::FeatureTag::new(tag), value);
1198    }
1199    out
1200}
1201
1202/// The faces the three generic families resolve to, per platform, in
1203/// order of preference. cosmic-text's own defaults are `Open Sans`,
1204/// `DejaVu Serif` and `Noto Sans Mono`, none of which a stock Windows or
1205/// macOS machine has; sans survives that through the platform fallback
1206/// list, but a missing monospace family falls to the *lowest-id*
1207/// monospaced face in the database — style is not in that ranking's key,
1208/// so on a machine whose first monospaced face is an italic instance every
1209/// `Mono` glyph is italic. The first installed name in each
1210/// list wins; when none is, cosmic-text's own name stays so its fallback
1211/// still runs.
1212#[cfg(target_os = "macos")]
1213const DEFAULT_FAMILIES: [&[&str]; 3] = [
1214    &["Helvetica Neue", "Helvetica"],
1215    &["Times New Roman", "Times"],
1216    &["SF Mono", "Menlo", "Monaco"],
1217];
1218#[cfg(target_os = "windows")]
1219const DEFAULT_FAMILIES: [&[&str]; 3] = [
1220    &["Segoe UI", "Arial"],
1221    &["Times New Roman"],
1222    &["Cascadia Mono", "Consolas", "Courier New"],
1223];
1224#[cfg(not(any(target_os = "macos", target_os = "windows")))]
1225const DEFAULT_FAMILIES: [&[&str]; 3] = [
1226    &["DejaVu Sans", "Noto Sans", "Liberation Sans", "Ubuntu"],
1227    &["DejaVu Serif", "Noto Serif", "Liberation Serif"],
1228    &[
1229        "DejaVu Sans Mono",
1230        "Noto Sans Mono",
1231        "Liberation Mono",
1232        "Ubuntu Mono",
1233    ],
1234];
1235
1236/// The first family on `list` that some installed face is a member of,
1237/// matched the way `fontdb::Database::query` matches a name.
1238fn first_installed<'a>(db: &cosmic_text::fontdb::Database, list: &[&'a str]) -> Option<&'a str> {
1239    list.iter().copied().find(|name| {
1240        db.faces()
1241            .any(|face| face.families.iter().any(|(f, _)| f == name))
1242    })
1243}
1244
1245/// The session's font database, set up the way kui shapes against it: the
1246/// generic sans, serif and monospace families pinned to an installed face
1247/// (see [`DEFAULT_FAMILIES`]), so weight and style matching starts from a
1248/// face with real variants rather than whatever the fallback pops.
1249/// Faces whose glyphs cannot be measured are taken out first.
1250///
1251/// The system's faces are scanned and checked once a process
1252/// ([`system_fonts`]); every session after the first starts from a copy of
1253/// that database. The scan opens every font file twice (fontdb's, then
1254/// [`keep_measurable`]'s) — on a Mac 1312 faces in ~850 files, 20–70 ms and
1255/// most of it `open`, paid again by every `Core::new` — where a copy is a
1256/// list of names. A font installed while the process runs is seen when
1257/// something asks for a new scan (`Core::reload_system_fonts`).
1258pub(crate) fn new_font_system() -> (FontSystem, std::sync::Arc<SystemFonts>) {
1259    let system = system_fonts();
1260    let fonts = font_system_over(system.locale.clone(), system.db.clone(), &[]);
1261    (fonts, system)
1262}
1263
1264/// A font system over `db` whose fallback asks `first`'s families before
1265/// the platform's ([`AppFallback`]). cosmic-text reads its fallback lists
1266/// when the system is built and keeps them, so a new list is a new system
1267/// over the same database: the ids stay.
1268pub(crate) fn font_system_over(
1269    locale: String,
1270    db: cosmic_text::fontdb::Database,
1271    first: &[String],
1272) -> FontSystem {
1273    FontSystem::new_with_locale_and_db_and_fallback(locale, db, AppFallback::new(first))
1274}
1275
1276/// The platform's fallback lists with the app's families ahead of them
1277/// (`Core::set_fallback_fonts`, backlog F121): a character the text's own
1278/// family has no glyph for is asked of each of the app's, in order, before
1279/// the script's faces and the platform's common ones — on macOS the
1280/// system's proportional face, which is no editor's second choice.
1281/// With none it is the platform's lists as they are.
1282pub(crate) struct AppFallback {
1283    platform: cosmic_text::PlatformFallback,
1284    first: &'static [&'static str],
1285    common: &'static [&'static str],
1286    /// A script's list with `first` ahead of it, made when first asked
1287    /// for.
1288    scripts: std::sync::Mutex<
1289        rustc_hash::FxHashMap<(unicode_script::Script, String), &'static [&'static str]>,
1290    >,
1291}
1292
1293/// `names` for the life of the process, each spelling and each list kept
1294/// once: cosmic-text's lists are `&'static`, and an app sets a handful.
1295fn keep(names: Vec<&'static str>) -> &'static [&'static str] {
1296    static LISTS: std::sync::Mutex<Vec<&'static [&'static str]>> =
1297        std::sync::Mutex::new(Vec::new());
1298    let mut lists = LISTS.lock().unwrap_or_else(|e| e.into_inner());
1299    if let Some(&kept) = lists.iter().find(|l| ***l == names[..]) {
1300        return kept;
1301    }
1302    let kept: &'static [&'static str] = Box::leak(names.into_boxed_slice());
1303    lists.push(kept);
1304    kept
1305}
1306
1307fn keep_name(name: &str) -> &'static str {
1308    static NAMES: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
1309    let mut names = NAMES.lock().unwrap_or_else(|e| e.into_inner());
1310    if let Some(&kept) = names.iter().find(|n| **n == name) {
1311        return kept;
1312    }
1313    let kept: &'static str = Box::leak(name.to_string().into_boxed_str());
1314    names.push(kept);
1315    kept
1316}
1317
1318impl AppFallback {
1319    pub(crate) fn new(first: &[String]) -> Self {
1320        use cosmic_text::Fallback;
1321        let platform = cosmic_text::PlatformFallback;
1322        let first = keep(first.iter().map(|n| keep_name(n)).collect());
1323        let common = keep(
1324            first
1325                .iter()
1326                .chain(platform.common_fallback())
1327                .copied()
1328                .collect(),
1329        );
1330        Self {
1331            platform,
1332            first,
1333            common,
1334            scripts: Default::default(),
1335        }
1336    }
1337}
1338
1339impl cosmic_text::Fallback for AppFallback {
1340    fn common_fallback(&self) -> &[&'static str] {
1341        self.common
1342    }
1343
1344    fn forbidden_fallback(&self) -> &[&'static str] {
1345        self.platform.forbidden_fallback()
1346    }
1347
1348    fn script_fallback(&self, script: unicode_script::Script, locale: &str) -> &[&'static str] {
1349        let theirs = self.platform.script_fallback(script, locale);
1350        if self.first.is_empty() {
1351            return theirs;
1352        }
1353        let mut scripts = self.scripts.lock().unwrap_or_else(|e| e.into_inner());
1354        scripts
1355            .entry((script, locale.to_string()))
1356            .or_insert_with(|| keep(self.first.iter().chain(theirs).copied().collect()))
1357    }
1358}
1359
1360/// One scan of the system's fonts, checked and pinned the way kui shapes
1361/// against them, and which files' faces it found — what a session started
1362/// from, so a later scan can say what came and went since.
1363pub(crate) struct SystemFonts {
1364    locale: String,
1365    db: cosmic_text::fontdb::Database,
1366    /// Each face the scan kept, by its file and its index in the file.
1367    pub(crate) faces: rustc_hash::FxHashSet<(std::path::PathBuf, u32)>,
1368}
1369
1370/// The process's scan; `None` until the first session asks.
1371static SYSTEM: std::sync::Mutex<Option<std::sync::Arc<SystemFonts>>> = std::sync::Mutex::new(None);
1372
1373/// The process's scan of the system's fonts, made on the first call. Held
1374/// under the lock while it scans, so sessions made at once on several
1375/// threads wait for one scan rather than each making its own.
1376pub(crate) fn system_fonts() -> std::sync::Arc<SystemFonts> {
1377    let mut held = SYSTEM.lock().unwrap_or_else(|e| e.into_inner());
1378    held.get_or_insert_with(|| std::sync::Arc::new(scan_system_fonts()))
1379        .clone()
1380}
1381
1382/// Scans the system's fonts again and makes that the process's scan, so
1383/// sessions made from here on start from it; returns it.
1384pub(crate) fn rescan_system_fonts() -> std::sync::Arc<SystemFonts> {
1385    let fresh = std::sync::Arc::new(scan_system_fonts());
1386    *SYSTEM.lock().unwrap_or_else(|e| e.into_inner()) = Some(fresh.clone());
1387    fresh
1388}
1389
1390fn scan_system_fonts() -> SystemFonts {
1391    let (locale, db) = FontSystem::new().into_locale_and_db();
1392    SystemFonts::from_db(locale, db)
1393}
1394
1395impl SystemFonts {
1396    /// A scan from a database already loaded: the unmeasurable faces taken
1397    /// out and the generic families pinned.
1398    pub(crate) fn from_db(locale: String, mut db: cosmic_text::fontdb::Database) -> Self {
1399        let all: Vec<_> = db.faces().map(|face| face.id).collect();
1400        keep_measurable(&mut db, all);
1401        pin_default_families(&mut db);
1402        let faces = db.faces().filter_map(face_file).collect();
1403        Self { locale, db, faces }
1404    }
1405
1406    /// This scan's database, to build a test's own scan from.
1407    #[cfg(test)]
1408    pub(crate) fn db(&self) -> &cosmic_text::fontdb::Database {
1409        &self.db
1410    }
1411
1412    #[cfg(test)]
1413    pub(crate) fn locale(&self) -> &str {
1414        &self.locale
1415    }
1416}
1417
1418/// The file a face was read from and its index there; `None` for a face
1419/// loaded from bytes.
1420pub(crate) fn face_file(face: &cosmic_text::fontdb::FaceInfo) -> Option<(std::path::PathBuf, u32)> {
1421    use cosmic_text::fontdb::Source;
1422    match &face.source {
1423        Source::File(path) | Source::SharedFile(path, _) => Some((path.clone(), face.index)),
1424        Source::Binary(_) => None,
1425    }
1426}
1427
1428/// Points the generic sans, serif and monospace families at the first
1429/// installed family of [`DEFAULT_FAMILIES`]' lists; a list with none
1430/// installed leaves its generic as it was.
1431pub(crate) fn pin_default_families(db: &mut cosmic_text::fontdb::Database) {
1432    let [sans, serif, mono] = DEFAULT_FAMILIES;
1433    if let Some(name) = first_installed(db, sans) {
1434        db.set_sans_serif_family(name);
1435    }
1436    if let Some(name) = first_installed(db, serif) {
1437        db.set_serif_family(name);
1438    }
1439    if let Some(name) = first_installed(db, mono) {
1440        db.set_monospace_family(name);
1441    }
1442}
1443
1444/// [`new_font_system`] over a database already loaded. The font system is
1445/// built after the unmeasurable faces are out, so cosmic-text's list of
1446/// monospaced faces, which `Mono`'s fallback walks, never names one.
1447#[cfg(test)]
1448fn font_system_with(locale: String, db: cosmic_text::fontdb::Database) -> FontSystem {
1449    let system = SystemFonts::from_db(locale, db);
1450    FontSystem::new_with_locale_and_db(system.locale, system.db)
1451}
1452
1453/// Whether the shaper can say how wide a face's glyphs are:
1454/// a `head` whose units per em are in the range the spec allows
1455/// (16–16384), an `hhea` with at least one horizontal metric, and an
1456/// `hmtx` as long as that says. Every advance is design units over units
1457/// per em, so a face without a readable `head` shapes to infinitely wide
1458/// glyphs — macOS's GB18030 Bitmap, which carries Apple's `bhed` in its
1459/// place — and swash, which reads `hmtx` for the raster, subtracts one
1460/// from a zero count. Read with skrifa, the reader the shaper measures
1461/// with, from the table directory, two fixed-size headers and `hmtx`'s
1462/// length: nothing that can panic on the face it is there to catch.
1463pub(crate) fn measurable(data: &[u8], index: u32) -> bool {
1464    use cosmic_text::skrifa::raw::{FontRef, TableProvider};
1465    let Ok(font) = FontRef::from_index(data, index) else {
1466        return false;
1467    };
1468    font.head()
1469        .is_ok_and(|head| (16..=16384).contains(&head.units_per_em()))
1470        && font.hhea().is_ok_and(|hhea| hhea.number_of_h_metrics() > 0)
1471        && font.hmtx().is_ok()
1472}
1473
1474/// Files per checking thread, below which [`keep_measurable`] checks on
1475/// the caller's thread: a font added from bytes, or a folder of a few.
1476const FILES_PER_CHECKER: usize = 64;
1477
1478/// Takes the faces of `ids` the shaper cannot measure out of `db` (see
1479/// [`measurable`]) and returns the rest, so a face that would shape to
1480/// infinitely wide glyphs is not a family to list, to name or to fall
1481/// back to. A face whose file cannot be read goes too: the
1482/// shaper could not load it either.
1483///
1484/// Each file is opened once for all its faces, and the files are spread
1485/// over a few threads: opening one is most of the cost. Measured over the
1486/// 1312 faces in 850 files of a Mac, the check adds about 4.5 ms to the
1487/// ~20 ms fontdb takes to scan them warm (release build); on one thread
1488/// it took 15–22 ms.
1489pub(crate) fn keep_measurable(
1490    db: &mut cosmic_text::fontdb::Database,
1491    ids: Vec<cosmic_text::fontdb::ID>,
1492) -> Vec<cosmic_text::fontdb::ID> {
1493    use cosmic_text::fontdb::{ID, Source};
1494    // The faces of one file together; bytes already in memory each alone.
1495    let mut by_file = FxHashMap::<&std::path::Path, Vec<ID>>::default();
1496    let mut groups = Vec::new();
1497    for &id in &ids {
1498        match db.face(id).map(|face| &face.source) {
1499            Some(Source::File(path) | Source::SharedFile(path, _)) => {
1500                by_file.entry(path).or_default().push(id);
1501            }
1502            Some(Source::Binary(_)) => groups.push(vec![id]),
1503            None => {}
1504        }
1505    }
1506    groups.extend(by_file.into_values());
1507    let db_ref = &*db;
1508    let unmeasurable_in = |groups: &[Vec<ID>]| -> Vec<ID> {
1509        let mut out = Vec::new();
1510        for group in groups {
1511            let read = db_ref.with_face_data(group[0], |data, _| {
1512                group
1513                    .iter()
1514                    .filter(|&&id| {
1515                        db_ref
1516                            .face(id)
1517                            .is_none_or(|face| !measurable(data, face.index))
1518                    })
1519                    .copied()
1520                    .collect::<Vec<_>>()
1521            });
1522            out.extend(read.unwrap_or_else(|| group.clone()));
1523        }
1524        out
1525    };
1526    let checkers = std::thread::available_parallelism()
1527        .map_or(1, |n| n.get())
1528        .min(groups.len().div_ceil(FILES_PER_CHECKER));
1529    let unmeasurable = if checkers <= 1 {
1530        unmeasurable_in(&groups)
1531    } else {
1532        let per = groups.len().div_ceil(checkers);
1533        std::thread::scope(|scope| {
1534            let parts: Vec<_> = groups
1535                .chunks(per)
1536                .map(|part| {
1537                    let spawned = std::thread::Builder::new()
1538                        .name("kui-font-check".into())
1539                        .spawn_scoped(scope, move || unmeasurable_in(part));
1540                    (part, spawned.ok())
1541                })
1542                .collect();
1543            let mut out = Vec::new();
1544            for (part, checker) in parts {
1545                out.extend(match checker {
1546                    Some(checker) => checker
1547                        .join()
1548                        .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
1549                    // No thread to be had: check that part here.
1550                    None => unmeasurable_in(part),
1551                });
1552            }
1553            out
1554        })
1555    };
1556    for &id in &unmeasurable {
1557        db.remove_face(id);
1558    }
1559    ids.into_iter()
1560        .filter(|id| !unmeasurable.contains(id))
1561        .collect()
1562}
1563
1564/// The family names `Sans`, `Serif` and `Mono` shape with, in that order —
1565/// what [`new_font_system`] pinned, or cosmic-text's own name where nothing
1566/// on the list was installed.
1567pub(crate) fn default_families(fs: &FontSystem) -> [&str; 3] {
1568    let db = fs.db();
1569    [
1570        db.family_name(&cosmic_text::Family::SansSerif),
1571        db.family_name(&cosmic_text::Family::Serif),
1572        db.family_name(&cosmic_text::Family::Monospace),
1573    ]
1574}
1575
1576impl TextSystem {
1577    pub fn new() -> Self {
1578        Self {
1579            raster: Raster::new(),
1580            joins: Vec::new(),
1581            entries: FxHashMap::default(),
1582            bytes: 0,
1583            budget: DEFAULT_TEXT_CACHE_BYTES,
1584            frame: Kept::default(),
1585            places: Kept::default(),
1586            scale: 1.0,
1587            frame_no: 0,
1588            owed: false,
1589        }
1590    }
1591
1592    /// Whether this frame's emission owes another frame,
1593    /// clearing it.
1594    pub(crate) fn take_owed(&mut self) -> bool {
1595        std::mem::take(&mut self.owed)
1596    }
1597
1598    /// The rounded backgrounds emitted since the last call: what the
1599    /// frame's join pass shapes (`crate::join`).
1600    pub(crate) fn take_joins(&mut self) -> Vec<JoinBg> {
1601        std::mem::take(&mut self.joins)
1602    }
1603
1604    /// This window's rasterizer, for glyph raster against its atlas.
1605    pub(crate) fn raster_mut(&mut self) -> &mut Raster {
1606        &mut self.raster
1607    }
1608
1609    pub(crate) fn subpixel(&self) -> bool {
1610        self.raster.subpixel
1611    }
1612
1613    /// Switches outline rasterization between alpha masks and LCD subpixel
1614    /// coverage. Returns whether it changed (the caller resets the atlas).
1615    pub(crate) fn set_subpixel(&mut self, on: bool) -> bool {
1616        let changed = self.raster.subpixel != on;
1617        self.raster.subpixel = on;
1618        changed
1619    }
1620
1621    /// The byte budget the cache is evicted to; see
1622    /// `Core::set_text_cache_budget`.
1623    pub fn budget(&self) -> usize {
1624        self.budget
1625    }
1626
1627    /// Sets the budget. Takes effect at the next frame's start, where
1628    /// eviction runs; nothing is evicted here.
1629    pub fn set_budget(&mut self, bytes: usize) {
1630        self.budget = bytes;
1631    }
1632
1633    /// The estimated bytes the cache holds (see `ENTRY_BASE_BYTES`).
1634    pub fn bytes(&self) -> usize {
1635        self.bytes
1636    }
1637
1638    /// How many shaped texts the cache holds (a long line's chunks each
1639    /// count; the line itself does not).
1640    pub fn len(&self) -> usize {
1641        self.entries.len() - self.long_lines()
1642    }
1643
1644    /// How many long lines are held.
1645    pub fn long_lines(&self) -> usize {
1646        self.entries.values().filter(|e| e.long().is_some()).count()
1647    }
1648
1649    pub fn is_empty(&self) -> bool {
1650        self.len() == 0
1651    }
1652
1653    /// Evicts the least recently used entries until the cache is under
1654    /// three quarters of its budget. Never an entry the frame that just
1655    /// finished drew: what is on screen stays shaped whatever the budget
1656    /// says, the way F26's declared state is never evicted. Ordered by
1657    /// last use and then by key, so the same history evicts the same
1658    /// entries whatever order the map iterated.
1659    fn evict_to_budget(&mut self, fs: &mut FontSystem) {
1660        if self.bytes <= self.budget {
1661            return;
1662        }
1663        let floor = self.budget / EVICT_TO_DENOMINATOR * EVICT_TO_NUMERATOR;
1664        let drawn_last_frame = self.frame_no.saturating_sub(1);
1665        // Oldest first, runs and long lines alike: a long line's record is
1666        // charged to `bytes` like any entry (its shaped chunks are entries
1667        // in their own right), so the budget reaches it too rather than
1668        // leaving the records to the 300-frame sweep alone.
1669        let mut order: Vec<(u64, u64)> = self
1670            .entries
1671            .iter()
1672            .filter(|(_, e)| e.last_used() < drawn_last_frame)
1673            .map(|(k, e)| (e.last_used(), *k))
1674            .collect();
1675        order.sort_unstable();
1676        for (_, key) in order {
1677            if self.bytes <= floor {
1678                break;
1679            }
1680            let freed = self.entries.remove(&key).map(|e| e.bytes());
1681            self.bytes -= freed.unwrap_or(0);
1682        }
1683        // The words those entries shaped are still in cosmic-text's
1684        // shape-run cache, which has no byte budget of its own and ages
1685        // only when trimmed: drop what has not been used since the last
1686        // eviction. Measured with the 2 MB budget in `tests/text_budget.rs`,
1687        // leaving it on its 240-frame clock held nine times the budget in
1688        // words alone. A line still on screen keeps its shaped entry
1689        // whatever happens here; only a line edited afterwards shapes its
1690        // words again, once.
1691        fs.shape_run_cache.trim(0);
1692    }
1693
1694    /// Starts a frame. `keep_prev` retains the list just finished so the
1695    /// next frame can still read its texts — `Core` sets it exactly when it
1696    /// keeps the previous tree, and the two are read together.
1697    pub(crate) fn begin_frame(
1698        &mut self,
1699        fs: &mut FontSystem,
1700        scale: f32,
1701        keep_prev: bool,
1702        frame_no: u64,
1703    ) {
1704        self.joins.clear();
1705        // Scale change invalidates every physical-px measurement.
1706        if (scale - self.scale).abs() > f32::EPSILON {
1707            self.entries.clear();
1708            self.bytes = 0;
1709        }
1710        self.scale = scale;
1711        self.frame.begin(keep_prev);
1712        // Always kept, unlike `frame`: a query while this frame builds
1713        // answers from the last one, and this is what it answers from.
1714        self.places.begin(true);
1715        // The core's counter, not one of this store's own (backlog AR45).
1716        self.frame_no = frame_no;
1717        if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
1718            let mut freed = 0usize;
1719            self.entries.retain(|_, e| {
1720                let keep = e.last_used() >= cutoff;
1721                if !keep {
1722                    freed += e.bytes();
1723                }
1724                keep
1725            });
1726            self.bytes -= freed;
1727            // The shape-run cache makes single-line reshapes ~free while
1728            // editing; trim it so long sessions don't grow unboundedly.
1729            fs.shape_run_cache.trim(2);
1730        }
1731        // The clock above frees what nobody has shown for five seconds;
1732        // the budget frees what a stream of new text piles up faster than
1733        // that (backlog C16).
1734        self.evict_to_budget(fs);
1735    }
1736
1737    /// Drops every shaped entry, to shape again on its next draw: the
1738    /// weights a family is asked at changed under them.
1739    pub(crate) fn forget_shaped(&mut self) {
1740        self.entries.clear();
1741        self.bytes = 0;
1742    }
1743
1744    /// Inserts a fresh entry, charging it to the budget.
1745    fn insert(&mut self, key: u64, entry: Entry) {
1746        self.bytes += entry.bytes();
1747        if let Some(old) = self.entries.insert(key, entry) {
1748            self.bytes -= old.bytes();
1749        }
1750    }
1751
1752    /// The shaped run under `key`, if the entry there is one.
1753    fn run(&self, key: u64) -> Option<&CachedText> {
1754        self.entries.get(&key)?.run()
1755    }
1756
1757    fn run_mut(&mut self, key: u64) -> Option<&mut CachedText> {
1758        self.entries.get_mut(&key)?.run_mut()
1759    }
1760
1761    /// The long line under `key`, if the entry there is one.
1762    fn long(&self, key: u64) -> Option<&LongLine> {
1763        self.entries.get(&key)?.long()
1764    }
1765
1766    fn long_mut(&mut self, key: u64) -> Option<&mut LongLine> {
1767        self.entries.get_mut(&key)?.long_mut()
1768    }
1769
1770    pub(crate) fn style_key(content: &str, style: &TextStyle, scale: f32) -> u64 {
1771        // The content (word-wide past a few bytes, backlog C43) and the
1772        // shaping-relevant style bits (color excluded).
1773        let mut h = crate::key::mix_content(crate::key::FNV_OFFSET, content.as_bytes());
1774        let mut mix = |bytes: &[u8]| h = crate::key::fnv(h, bytes);
1775        mix(&style.size.to_bits().to_le_bytes());
1776        mix(&style.line_height.to_bits().to_le_bytes());
1777        mix(&scale.to_bits().to_le_bytes());
1778        mix(&[style.wrap as u8, style.ellipsis as u8]);
1779        mix(&style.max_lines.to_le_bytes());
1780        let (tag, font) = match style.family {
1781            FontFamily::Sans => (0u8, 0u64),
1782            FontFamily::Serif => (1, 0),
1783            FontFamily::Mono => (2, 0),
1784            FontFamily::Custom(id) => (3, id.to_ffi()),
1785        };
1786        mix(&[tag]);
1787        mix(&font.to_le_bytes());
1788        for (t, v) in style.features.iter() {
1789            mix(t);
1790            mix(&v.to_le_bytes());
1791        }
1792        // Paint only, but a decorated text is a different entry: the
1793        // decoration rects are built beside the glyph templates — and
1794        // their shape and colour with them (backlog K4).
1795        // Bold is a different face, so a different shape.
1796        mix(&[style.bold as u8]);
1797        mix(&[
1798            style.underline as u8,
1799            style.strikethrough as u8,
1800            style.underline_style as u8,
1801            style.underline_color.is_some() as u8,
1802        ]);
1803        if let Some(c) = style.underline_color {
1804            mix(&c.to_hex().to_le_bytes());
1805        }
1806        h
1807    }
1808
1809    /// Shapes (or reuses) the buffer for `content` in `style`; returns its
1810    /// cache key. Shared by text nodes and measurement, so measuring a
1811    /// string and then drawing it shapes once.
1812    fn intern(
1813        &mut self,
1814        content: &str,
1815        style: &TextStyle,
1816        res: &Resources,
1817        fs: &mut FontSystem,
1818    ) -> u64 {
1819        let key = Self::style_key(content, style, self.scale);
1820        self.intern_keyed(key, content, style, res, fs)
1821    }
1822
1823    /// [`Self::intern`] under a key already computed.
1824    fn intern_keyed(
1825        &mut self,
1826        key: u64,
1827        content: &str,
1828        style: &TextStyle,
1829        res: &Resources,
1830        fs: &mut FontSystem,
1831    ) -> u64 {
1832        let frame_no = self.frame_no;
1833        let scale = self.scale;
1834        if !self.entries.contains_key(&key) {
1835            let mut buffer = new_buffer(fs, style, scale);
1836            // At the family's regular (or its bold, F150), as a span or a
1837            // cell is: a family with no 400 face asked at 400 falls back
1838            // to another family (F100).
1839            buffer.set_text(
1840                &one_paragraph(content),
1841                &res.weights_of(style.family).apply(
1842                    Attrs::new()
1843                        .family(res.family_of(style.family))
1844                        .font_features(cosmic_features(&style.features)),
1845                    style.bold,
1846                ),
1847                Shaping::Advanced,
1848                None,
1849            );
1850            let entry = CachedText::new(
1851                buffer,
1852                content.to_string(),
1853                style,
1854                vec![SpanDeco::of_style(style)],
1855                fs,
1856                frame_no,
1857            );
1858            self.insert(key, Entry::Run(entry));
1859        }
1860        self.entries
1861            .get_mut(&key)
1862            .expect("just inserted")
1863            .touch(frame_no);
1864        key
1865    }
1866
1867    /// The content of one of this frame's texts (spans concatenated).
1868    pub(crate) fn content(&self, id: TextId) -> &str {
1869        let key = self.frame[id.0 as usize].cache_key;
1870        self.entries.get(&key).map_or("", |e| e.content())
1871    }
1872
1873    /// Appends the access runs of one of this frame's texts, as `src`
1874    /// places them (a custom editor's `line`s are read this way): a run's
1875    /// laid-out lines, or a long line's rows of the chunks it has shaped —
1876    /// one that never showed is not walked, the way a tall document's
1877    /// off-screen lines are not. Every `break-spaces` text is a long line.
1878    pub(crate) fn access_runs(
1879        &self,
1880        id: TextId,
1881        src: crate::access::RunSource,
1882        run_no: &mut usize,
1883        out: &mut Vec<crate::access::AccessRun>,
1884    ) {
1885        use crate::access::{RowGlyphs, push_row_runs};
1886        let Some(ft) = self.frame.get(id.0 as usize) else {
1887            return;
1888        };
1889        let line = match self.entries.get(&ft.cache_key) {
1890            Some(Entry::Run(e)) => {
1891                crate::access::runs_of_buffer(&e.buffer, src, run_no, out);
1892                return;
1893            }
1894            Some(Entry::Long(line)) => line,
1895            None => return,
1896        };
1897        let whole = [RowStart { byte: 0, x: 0.0 }];
1898        for (i, c) in line.chunks.iter().enumerate() {
1899            let Some(run) = c
1900                .width
1901                .and_then(|_| self.run(c.key))
1902                .and_then(|e| e.buffer.layout_runs().next())
1903            else {
1904                continue;
1905            };
1906            // Wrapped, the chunk's rows from where its first begins;
1907            // else one row at its place along the line.
1908            let (rows, row0, head_x) = match (line.wrap_w, line.starts.get(i)) {
1909                (Some(_), Some(&(row0, head_x))) if !c.rows.is_empty() => {
1910                    (&c.rows[..], row0, head_x)
1911                }
1912                (Some(_), _) => continue,
1913                (None, _) => (&whole[..], 0, line.prefix[i]),
1914            };
1915            for (r, rs) in rows.iter().enumerate() {
1916                let hi = rows.get(r + 1).map_or(usize::MAX, |n| n.byte as usize);
1917                let a = run.glyphs.partition_point(|g| g.start < rs.byte as usize);
1918                let b = run.glyphs.partition_point(|g| g.start < hi).max(a);
1919                if a == b {
1920                    continue;
1921                }
1922                let last = i + 1 == line.chunks.len() && r + 1 == rows.len();
1923                push_row_runs(
1924                    &RowGlyphs {
1925                        glyphs: &run.glyphs[a..b],
1926                        text: run.text,
1927                        line: 0,
1928                        top: (row0 as usize + r) as f32 * line.line_h,
1929                        height: line.line_h,
1930                        rtl: run.rtl,
1931                        dx: if r == 0 { head_x } else { 0.0 } - rs.x,
1932                        base: c.start,
1933                        newline: last && src.newline_after_last,
1934                    },
1935                    &src,
1936                    run_no,
1937                    out,
1938                );
1939            }
1940        }
1941    }
1942
1943    /// The cache key and colour behind one of the *previous* frame's texts
1944    /// — what a departing subtree keeps instead of its `TextId`, which
1945    /// indexes a list rebuilt every frame. The subtree is copied out of the
1946    /// previous frame's tree, so this is the list its ids belong to (see
1947    /// [`crate::depart`]).
1948    pub(crate) fn prev_frame_text(&self, id: TextId) -> (u64, Color) {
1949        match self.frame.prev().get(id.0 as usize) {
1950            Some(t) => (t.cache_key, t.color),
1951            None => (0, Color::TRANSPARENT),
1952        }
1953    }
1954
1955    /// Registers an already-shaped buffer as one of this frame's texts, by
1956    /// the cache key [`Self::frame_text`] handed out. None once the entry
1957    /// has been evicted — a ghost older than the cache draws no text
1958    /// rather than a wrong one. Touching it here keeps it alive for as
1959    /// long as something still draws it.
1960    pub(crate) fn readd(&mut self, cache_key: u64, color: Color) -> Option<TextId> {
1961        let frame_no = self.frame_no;
1962        let e = self.entries.get_mut(&cache_key)?;
1963        e.touch(frame_no);
1964        let cache_key = if e.long().is_some() {
1965            self.claim_long(cache_key)
1966        } else {
1967            cache_key
1968        };
1969        self.frame.push(FrameText { cache_key, color });
1970        Some(TextId((self.frame.len() - 1) as u32))
1971    }
1972
1973    /// Registers a text for this frame, shaping (or reusing) its buffer.
1974    pub fn add(
1975        &mut self,
1976        content: &str,
1977        style: &TextStyle,
1978        res: &Resources,
1979        fs: &mut FontSystem,
1980    ) -> TextId {
1981        // The one place the long/short decision is made: the entry's
1982        // variant carries it from here.
1983        let key = self.intern_any(content, style, res, fs);
1984        let key = if could_be_long(content.len(), style) {
1985            self.claim_long(key)
1986        } else {
1987            key
1988        };
1989        self.frame.push(FrameText {
1990            cache_key: key,
1991            color: style.color_or_default(),
1992        });
1993        TextId((self.frame.len() - 1) as u32)
1994    }
1995
1996    /// The key a node drawing the entry `key` holds it by. A run is
1997    /// shared, re-wrapped to each node as it is drawn; a long line's rows
1998    /// are the line's own and every query reads them, so the first node
1999    /// of a frame takes the line and each further one a copy of its own —
2000    /// the n-th node the n-th copy, so a frame like the last finds the
2001    /// copies it made laid out already. The copies share the chunks'
2002    /// shaped runs. Without it one `break-spaces` file open in two panes
2003    /// of different widths would draw and answer at the width laid out last.
2004    #[inline(never)]
2005    fn claim_long(&mut self, key: u64) -> u64 {
2006        let frame_no = self.frame_no;
2007        let mut at = key;
2008        let mut n = 0u64;
2009        loop {
2010            match self.entries.get_mut(&at) {
2011                Some(Entry::Long(l)) if l.claimed != frame_no => {
2012                    l.claimed = frame_no;
2013                    l.last_used = frame_no;
2014                    return at;
2015                }
2016                // A run is never a long line's copy: its key is.
2017                Some(Entry::Run(_)) if n == 0 => return key,
2018                Some(_) => {}
2019                None => {
2020                    let Some(line) = self.long(key) else {
2021                        return key;
2022                    };
2023                    let mut copy = line.clone();
2024                    copy.claimed = frame_no;
2025                    copy.last_used = frame_no;
2026                    self.insert(at, Entry::Long(copy));
2027                    return at;
2028                }
2029            }
2030            n += 1;
2031            at = crate::key::fnv(key ^ COPY_SALT, &n.to_le_bytes());
2032        }
2033    }
2034
2035    /// Interns `content` as the long line it is or the run it is, one
2036    /// hash either way: a text that could be long by its length and style
2037    /// is looked up under both keys before its bytes are scanned for a
2038    /// line break, so the steady state of a megabyte line is its hash and
2039    /// two lookups.
2040    fn intern_any(
2041        &mut self,
2042        content: &str,
2043        style: &TextStyle,
2044        res: &Resources,
2045        fs: &mut FontSystem,
2046    ) -> u64 {
2047        if !could_be_long(content.len(), style) {
2048            return self.intern(content, style, res, fs);
2049        }
2050        let key = Self::style_key(content, style, self.scale);
2051        let long_key = key ^ LONG_SALT;
2052        let frame_no = self.frame_no;
2053        if let Some(e) = self.entries.get_mut(&long_key) {
2054            e.touch(frame_no);
2055            return long_key;
2056        }
2057        if let Some(e) = self.entries.get_mut(&key) {
2058            e.touch(frame_no);
2059            return key;
2060        }
2061        if has_line_break(content) || content.len() < LONG_LINE_BYTES && has_rtl(content) {
2062            self.intern_keyed(key, content, style, res, fs)
2063        } else {
2064            self.build_long(long_key, content.to_string(), Vec::new(), style, res, fs)
2065        }
2066    }
2067
2068    /// The rich counterpart of [`Self::intern_any`]: a paragraph past the
2069    /// threshold with no line break is a long line whose chunks are rich
2070    /// runs; its content is concatenated only when the line
2071    /// is built.
2072    fn intern_rich_any(
2073        &mut self,
2074        spans: &[Span<'_>],
2075        base: &TextStyle,
2076        res: &Resources,
2077        fs: &mut FontSystem,
2078    ) -> u64 {
2079        let len: usize = spans.iter().map(|s| s.text.len()).sum();
2080        if !could_be_long(len, base) {
2081            return self.intern_rich(spans, base, res, fs);
2082        }
2083        let key = Self::rich_key(spans, base, self.scale);
2084        let long_key = key ^ LONG_SALT;
2085        let frame_no = self.frame_no;
2086        if let Some(e) = self.entries.get_mut(&long_key) {
2087            e.touch(frame_no);
2088            return long_key;
2089        }
2090        if let Some(e) = self.entries.get_mut(&key) {
2091            e.touch(frame_no);
2092            return key;
2093        }
2094        // A sized span makes a line as tall as it is, which the long
2095        // line's one estimated row height cannot follow: shaped whole.
2096        if spans
2097            .iter()
2098            .any(|s| has_line_break(s.text) || s.size.is_some())
2099            || len < LONG_LINE_BYTES && spans.iter().any(|s| has_rtl(s.text))
2100        {
2101            return self.intern_rich_keyed(key, spans, base, res, fs);
2102        }
2103        let mut content = String::with_capacity(len);
2104        let mut owned = Vec::with_capacity(spans.len());
2105        for s in spans {
2106            let start = content.len();
2107            content.push_str(s.text);
2108            owned.push(OwnedSpan {
2109                start,
2110                end: content.len(),
2111                attrs: s.attrs_only(),
2112            });
2113        }
2114        self.build_long(long_key, content, owned, base, res, fs)
2115    }
2116
2117    /// Builds the long line `content` is under `key` — plain, or rich
2118    /// with `spans` — shaping its first chunk for the line
2119    /// height and the advance the rest are estimated from; see
2120    /// [`LongLine`]. The callers have looked `key` up already.
2121    fn build_long(
2122        &mut self,
2123        key: u64,
2124        content: String,
2125        spans: Vec<OwnedSpan>,
2126        style: &TextStyle,
2127        res: &Resources,
2128        fs: &mut FontSystem,
2129    ) -> u64 {
2130        let frame_no = self.frame_no;
2131        let wrap = style.wrap;
2132        let style = &TextStyle {
2133            wrap: TextWrap::None,
2134            ..*style
2135        };
2136        let scale = self.scale;
2137        let chunks: Vec<Chunk> = chunk_ranges(&content)
2138            .into_iter()
2139            .map(|(start, end)| Chunk {
2140                start,
2141                end,
2142                key: if spans.is_empty() {
2143                    Self::style_key(&content[start..end], style, scale)
2144                } else {
2145                    Self::rich_key(&chunk_spans(&content, &spans, start, end), style, scale)
2146                },
2147                width: None,
2148                tab: None,
2149                rows: Vec::new(),
2150            })
2151            .collect();
2152        // The first chunk is shaped now: the line's height and the mean
2153        // advance the estimates need come from it.
2154        let (w0, line_h) = match chunks.first() {
2155            Some(c) => {
2156                let k = self.shape_chunk(&content, &spans, c.start, c.end, style, res, fs);
2157                let e = self.run(k).expect("just interned");
2158                (e.intrinsic.w, e.buffer.metrics().line_height)
2159            }
2160            None => (0.0, style.line_height * self.scale),
2161        };
2162        let avg = chunks
2163            .first()
2164            .map_or(0.0, |c| w0 / (c.end - c.start).max(1) as f32);
2165        let mut line = LongLine {
2166            content,
2167            spans,
2168            style: *style,
2169            wrap,
2170            wrap_w: None,
2171            starts: Vec::new(),
2172            chunks,
2173            prefix: Vec::new(),
2174            line_h,
2175            avg,
2176            last_used: frame_no,
2177            claimed: u64::MAX,
2178            laid_rows: 1,
2179            asked_rows: u32::MAX,
2180            bytes: 0,
2181        };
2182        if let Some(c) = line.chunks.first_mut() {
2183            c.width = Some(w0);
2184        }
2185        line.reprefix();
2186        line.bytes = ENTRY_BASE_BYTES
2187            + line.content.len()
2188            + line.chunks.len() * 48
2189            + line.spans.len() * std::mem::size_of::<OwnedSpan>();
2190        // A line long only by its `break-spaces` is one chunk
2191        // (`chunk_ranges`), shaped now: its first frame is laid out on the
2192        // rows it has, not on an estimate (RG68).
2193        self.insert(key, Entry::Long(line));
2194        key
2195    }
2196
2197    /// Shapes (or touches) the run for `start..end` of a long line's
2198    /// content: a plain run, or a rich one of the spans that intersect
2199    /// the range. Returns its key.
2200    #[allow(clippy::too_many_arguments)]
2201    fn shape_chunk(
2202        &mut self,
2203        content: &str,
2204        spans: &[OwnedSpan],
2205        start: usize,
2206        end: usize,
2207        style: &TextStyle,
2208        res: &Resources,
2209        fs: &mut FontSystem,
2210    ) -> u64 {
2211        if spans.is_empty() {
2212            self.intern(&content[start..end], style, res, fs)
2213        } else {
2214            let spans = chunk_spans(content, spans, start, end);
2215            self.intern_rich(&spans, style, res, fs)
2216        }
2217    }
2218
2219    /// Makes sure chunk `i` of the long line `key` is shaped, and moves
2220    /// the prefix sums if its width was an estimate. Returns whether it
2221    /// shaped now — what tells a wrapped line its rows need breaking.
2222    fn ensure_chunk(&mut self, key: u64, i: usize, res: &Resources, fs: &mut FontSystem) -> bool {
2223        let (chunk_key, known) = {
2224            let c = &self.long(key).expect("a long line").chunks[i];
2225            (c.key, c.width)
2226        };
2227        let frame_no = self.frame_no;
2228        if known.is_some()
2229            && let Some(e) = self.run_mut(chunk_key)
2230        {
2231            e.last_used = frame_no;
2232            return false;
2233        }
2234        // Shaping now: the chunk's text and its spans rebased to the
2235        // slice, copied out so the line stays in the map meanwhile.
2236        let (text, spans, style, off_stops) = {
2237            let line = self.long(key).expect("a long line");
2238            let c = &line.chunks[i];
2239            let first = line.spans.partition_point(|s| s.end <= c.start);
2240            let spans: Vec<OwnedSpan> = line.spans[first..]
2241                .iter()
2242                .take_while(|s| s.start < c.end)
2243                .map(|s| OwnedSpan {
2244                    start: s.start.max(c.start) - c.start,
2245                    end: s.end.min(c.end) - c.start,
2246                    attrs: s.attrs,
2247                })
2248                .collect();
2249            let bytes = line.content.as_bytes();
2250            let off_stops = c.start > 0 && bytes[c.start - 1] != b'\t';
2251            (
2252                line.content[c.start..c.end].to_string(),
2253                spans,
2254                line.style,
2255                off_stops,
2256            )
2257        };
2258        let k = self.shape_chunk(&text, &spans, 0, text.len(), &style, res, fs);
2259        let w = self.run(k).expect("just interned").intrinsic.w;
2260        let tab = match off_stops && text.ends_with('\t') {
2261            true => self.chunk_tab(k, &text, &spans, &style, res, fs),
2262            false => None,
2263        };
2264        let line = self.long_mut(key).expect("just read");
2265        line.chunks[i].key = k;
2266        line.chunks[i].tab = tab;
2267        if line.chunks[i].width != Some(w) {
2268            line.chunks[i].width = Some(w);
2269            line.reprefix();
2270        }
2271        true
2272    }
2273
2274    /// For a chunk that starts off the line's tab stops and ends in its one
2275    /// tab, shaped as `chunk`: where the tab starts in that run, and the
2276    /// stops' interval — the width of the tab shaped alone in its own
2277    /// span's style, which starts on a stop (backlog RG76). `None` for a
2278    /// right-to-left run, whose tab is not at its end.
2279    #[inline(never)]
2280    #[allow(clippy::too_many_arguments)]
2281    fn chunk_tab(
2282        &mut self,
2283        chunk: u64,
2284        text: &str,
2285        spans: &[OwnedSpan],
2286        style: &TextStyle,
2287        res: &Resources,
2288        fs: &mut FontSystem,
2289    ) -> Option<(f32, f32)> {
2290        let at = text.len() - 1;
2291        let tab_x = {
2292            let run = self.run(chunk)?.buffer.layout_runs().next()?;
2293            if run.rtl {
2294                return None;
2295            }
2296            run.glyphs.iter().rev().find(|g| g.start == at)?.x
2297        };
2298        let alone = self.shape_chunk(text, spans, at, text.len(), style, res, fs);
2299        let every = self.run(alone)?.intrinsic.w;
2300        Some((tab_x, every))
2301    }
2302
2303    /// Breaks the long line `key` into rows at `w` (physical px), or lays
2304    /// it as one row for `None`. Positions, not glyphs: each chunk's rows
2305    /// start where the previous chunk's last row ended; a shaped chunk
2306    /// breaks exactly ([`break_rows`]), one that never showed contributes
2307    /// the estimate its width is, corrected when it shapes — the way
2308    /// `prefix` is, so the height the scrollbar sees can move a little as
2309    /// chunks fill in.
2310    fn relayout_long(&mut self, key: u64, w: Option<f32>) {
2311        let Some(w) = w else {
2312            let line = self.long_mut(key).expect("a long line");
2313            line.wrap_w = None;
2314            line.starts.clear();
2315            for c in &mut line.chunks {
2316                c.rows.clear();
2317            }
2318            return;
2319        };
2320        let w = w.max(1.0);
2321        // Read every chunk's rows off its shaped run first — the runs and
2322        // the line are entries of one map — then write them back.
2323        let line = self.long(key).expect("a long line");
2324        let mut starts = Vec::with_capacity(line.chunks.len() + 1);
2325        starts.push((0, 0.0));
2326        let (mut row, mut x) = (0u32, 0.0f32);
2327        let rows: Vec<Vec<RowStart>> = line
2328            .chunks
2329            .iter()
2330            .enumerate()
2331            .map(|(i, c)| {
2332                let shaped = c.width.and_then(|_| self.run(c.key));
2333                let rows = match shaped {
2334                    Some(e) => {
2335                        let text = &line.content[c.start..c.end];
2336                        let (rows, end) = break_rows(&e.buffer, text, line.wrap, w, x);
2337                        row += rows.len() as u32 - 1;
2338                        // A tab the line places ends the chunk where the
2339                        // unwrapped line puts it, as a whole run's tab is
2340                        // measured before its rows are broken (RG76).
2341                        x = end + line.tab_shift(i);
2342                        rows
2343                    }
2344                    None => {
2345                        let est = x + c
2346                            .width
2347                            .unwrap_or_else(|| (c.end - c.start) as f32 * line.avg);
2348                        let added = (est / w).floor();
2349                        row += added as u32;
2350                        x = est - added * w;
2351                        Vec::new()
2352                    }
2353                };
2354                starts.push((row, x));
2355                rows
2356            })
2357            .collect();
2358        let line = self.long_mut(key).expect("a long line");
2359        line.wrap_w = Some(w);
2360        line.starts = starts;
2361        for (c, rows) in line.chunks.iter_mut().zip(rows) {
2362            c.rows = rows;
2363        }
2364    }
2365
2366    /// A long line's size at `max_w` (physical px): one row clamped to it,
2367    /// or, when the style wraps and the line does not fit, its rows broken
2368    /// to it. Returns the physical size and the row count.
2369    fn long_size(&mut self, key: u64, max_w: Option<f32>) -> (Size, u32) {
2370        let (wrap, width, line_h, cur) = {
2371            let l = self.long(key).expect("a long line");
2372            (l.wrap, l.width(), l.line_h, l.wrap_w)
2373        };
2374        let target = match max_w {
2375            Some(w) if wrap != TextWrap::None && width > w + 0.5 => Some(w.max(1.0)),
2376            _ => None,
2377        };
2378        let differs = match (cur, target) {
2379            (None, None) => false,
2380            (Some(a), Some(b)) => (a - b).abs() > 0.5,
2381            _ => true,
2382        };
2383        if differs {
2384            self.relayout_long(key, target);
2385        }
2386        match target {
2387            Some(w) => {
2388                let rows = self.long(key).expect("a long line").rows();
2389                (Size::new(w, rows as f32 * line_h), rows)
2390            }
2391            None => (Size::new(max_w.map_or(width, |m| width.min(m)), line_h), 1),
2392        }
2393    }
2394
2395    /// Measures `content` in `style` without adding a node: its unwrapped
2396    /// size, or with `max_w` (logical px) its size once wrapped to that
2397    /// width. The answer is what layout would give a text node with the
2398    /// same content and style at the current scale, `wrap` / `max_lines` /
2399    /// `ellipsis` included.
2400    pub fn measure(
2401        &mut self,
2402        content: &str,
2403        style: &TextStyle,
2404        res: &Resources,
2405        fs: &mut FontSystem,
2406        max_w: Option<f32>,
2407    ) -> TextMetrics {
2408        let key = self.intern_any(content, style, res, fs);
2409        self.measure_any(key, fs, max_w)
2410    }
2411
2412    /// `measure` for the entry under `key`, a long line or a run.
2413    fn measure_any(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2414        if self.long(key).is_some() {
2415            let scale = self.scale;
2416            // Without a width nothing is broken, and the layout the frame
2417            // holds is left as it is.
2418            let (size, lines) = match max_w {
2419                Some(m) => {
2420                    // Nor with one: the rows are put back for the node
2421                    // that drew the line, which queries read.
2422                    let held = self.long(key).expect("checked").wrap_w;
2423                    let measured = self.long_size(key, Some(m * scale));
2424                    if wrap_differs(held, self.long(key).expect("checked").wrap_w) {
2425                        self.relayout_long(key, held);
2426                    }
2427                    measured
2428                }
2429                None => {
2430                    let line = self.long(key).expect("checked");
2431                    (Size::new(line.width(), line.line_h), 1)
2432                }
2433            };
2434            return TextMetrics {
2435                width: size.w / scale,
2436                height: size.h / scale,
2437                lines,
2438            };
2439        }
2440        self.measure_key(key, fs, max_w)
2441    }
2442
2443    /// `measure` for a rich-text paragraph.
2444    pub fn measure_rich(
2445        &mut self,
2446        spans: &[Span<'_>],
2447        base: &TextStyle,
2448        res: &Resources,
2449        fs: &mut FontSystem,
2450        max_w: Option<f32>,
2451    ) -> TextMetrics {
2452        let key = self.intern_rich_any(spans, base, res, fs);
2453        self.measure_any(key, fs, max_w)
2454    }
2455
2456    fn measure_key(&mut self, key: u64, fs: &mut FontSystem, max_w: Option<f32>) -> TextMetrics {
2457        let scale = self.scale;
2458        let entry = self.run_mut(key).expect("just interned");
2459        let target = wrap_target(entry, max_w, scale);
2460        // A run a node has drawn keeps the rows it was drawn at: its
2461        // `text_hit` and `caret_rect` read them until the next frame, which
2462        // would only wrap them back. A measure at another width lays out a
2463        // copy at that width instead, kept for the next measure there —
2464        // between frames as well as within one, where RG72's claim is
2465        // (backlog RG76).
2466        let key = if entry.claimed != u64::MAX && wrap_differs(entry.wrap, target) {
2467            self.wrap_copy(key, target, fs)
2468        } else {
2469            key
2470        };
2471        let entry = self.run_mut(key).expect("just interned");
2472        wrap_entry(entry, fs, target);
2473        let (mut m, lines) = measure_buffer(&entry.buffer, entry.max_lines);
2474        if entry.clamp_w
2475            && let Some(t) = target
2476        {
2477            m.w = m.w.min(t);
2478        }
2479        TextMetrics {
2480            width: m.w / scale,
2481            height: m.h / scale,
2482            lines,
2483        }
2484    }
2485
2486    /// Registers a rich-text paragraph for this frame. Spans shape as one
2487    /// flow, so wrapping crosses style boundaries correctly; past the
2488    /// long-line threshold the flow is chunked like a plain line's.
2489    pub fn add_rich(
2490        &mut self,
2491        spans: &[Span<'_>],
2492        base: &TextStyle,
2493        res: &Resources,
2494        fs: &mut FontSystem,
2495    ) -> TextId {
2496        let key = self.intern_rich_any(spans, base, res, fs);
2497        let len = spans.iter().map(|s| s.text.len()).sum();
2498        let key = if could_be_long(len, base) {
2499            self.claim_long(key)
2500        } else {
2501            key
2502        };
2503        self.frame.push(FrameText {
2504            cache_key: key,
2505            color: base.color_or_default(),
2506        });
2507        TextId((self.frame.len() - 1) as u32)
2508    }
2509
2510    fn intern_rich(
2511        &mut self,
2512        spans: &[Span<'_>],
2513        base: &TextStyle,
2514        res: &Resources,
2515        fs: &mut FontSystem,
2516    ) -> u64 {
2517        let key = Self::rich_key(spans, base, self.scale);
2518        self.intern_rich_keyed(key, spans, base, res, fs)
2519    }
2520
2521    /// The cache key of a rich paragraph: the base style's, then every
2522    /// span's text and attributes in order. Never a plain text's key for
2523    /// the same content.
2524    fn rich_key(spans: &[Span<'_>], base: &TextStyle, scale: f32) -> u64 {
2525        let mut key = Self::style_key("", base, scale) ^ 0x9e37_79b9_7f4a_7c15;
2526        for s in spans {
2527            key = crate::key::mix_content(key, s.text.as_bytes());
2528            let mut mix = |bytes: &[u8]| key = crate::key::fnv(key, bytes);
2529            mix(&[
2530                s.bold as u8,
2531                s.italic as u8,
2532                s.color.is_some() as u8,
2533                s.underline as u8,
2534                s.strikethrough as u8,
2535                s.bg.is_some() as u8,
2536                s.underline_style as u8,
2537                s.underline_color.is_some() as u8,
2538            ]);
2539            mix(&s.bg_radius.to_bits().to_le_bytes());
2540            // The span's own face and size: a different shaping, so a
2541            // different entry.
2542            let (tag, font) = match s.family {
2543                None => (0u8, 0u64),
2544                Some(FontFamily::Sans) => (1, 0),
2545                Some(FontFamily::Serif) => (2, 0),
2546                Some(FontFamily::Mono) => (3, 0),
2547                Some(FontFamily::Custom(id)) => (4, id.to_ffi()),
2548            };
2549            mix(&[tag]);
2550            mix(&font.to_le_bytes());
2551            mix(&s.size.map_or(0, f32::to_bits).to_le_bytes());
2552            for c in [s.color, s.bg, s.underline_color].into_iter().flatten() {
2553                mix(&c.r.to_bits().to_le_bytes());
2554                mix(&c.g.to_bits().to_le_bytes());
2555                mix(&c.b.to_bits().to_le_bytes());
2556                mix(&c.a.to_bits().to_le_bytes());
2557            }
2558        }
2559        key
2560    }
2561
2562    /// [`Self::intern_rich`] under a key already computed.
2563    fn intern_rich_keyed(
2564        &mut self,
2565        key: u64,
2566        spans: &[Span<'_>],
2567        base: &TextStyle,
2568        res: &Resources,
2569        fs: &mut FontSystem,
2570    ) -> u64 {
2571        let frame_no = self.frame_no;
2572        let scale = self.scale;
2573        if !self.entries.contains_key(&key) {
2574            let mut buffer = new_buffer(fs, base, scale);
2575            let family = res.family_of(base.family);
2576            let weights = res.weights_of(base.family);
2577            let features = cosmic_features(&base.features);
2578            // Once one span has a size of its own every span carries its
2579            // metrics: cosmic-text makes a line as tall as the tallest
2580            // glyph that names its own, so a line holding only a smaller
2581            // span would otherwise come out shorter than the paragraph's.
2582            let sized = spans.iter().any(|s| s.size.is_some());
2583            let texts: Vec<_> = spans.iter().map(|s| one_paragraph(s.text)).collect();
2584            let metrics_of = |size: Option<f32>| {
2585                let size = size.unwrap_or(base.size);
2586                let ratio = if base.size > 0.0 {
2587                    base.line_height / base.size
2588                } else {
2589                    1.0
2590                };
2591                shaper_metrics(size * scale, size * ratio * scale)
2592            };
2593            // Each span's index rides its glyphs as metadata, which is how
2594            // the decoration rects find their span after layout. A span
2595            // with a face of its own is shaped in it, at its weights.
2596            buffer.set_rich_text(
2597                spans.iter().enumerate().map(|(i, s)| {
2598                    let (family, weights) = match s.family {
2599                        Some(f) => (res.family_of(f), res.weights_of(f)),
2600                        None => (family, weights),
2601                    };
2602                    let mut attrs = s
2603                        .attrs(family, weights)
2604                        .font_features(features.clone())
2605                        .metadata(i);
2606                    // A bold paragraph's spans are bold: a span cannot be
2607                    // lighter than its text (backlog F150).
2608                    if base.bold && !s.bold {
2609                        attrs = weights.apply(attrs, true);
2610                    }
2611                    if sized {
2612                        attrs = attrs.metrics(metrics_of(s.size));
2613                    }
2614                    (&*texts[i], attrs)
2615                }),
2616                &{
2617                    let attrs = weights.apply(
2618                        Attrs::new().family(family).font_features(features.clone()),
2619                        base.bold,
2620                    );
2621                    if sized {
2622                        attrs.metrics(metrics_of(None))
2623                    } else {
2624                        attrs
2625                    }
2626                },
2627                Shaping::Advanced,
2628                None,
2629            );
2630            let content = spans.iter().map(|s| s.text).collect::<String>();
2631            let mut start = 0u32;
2632            let decos = spans
2633                .iter()
2634                .map(|s| SpanDeco {
2635                    start: {
2636                        let at = start;
2637                        start += s.text.len() as u32;
2638                        at
2639                    },
2640                    color: s.color.map(shaped_color),
2641                    underline: s.underline || base.underline,
2642                    // The span's own where it says, else the paragraph's.
2643                    underline_color: s.underline_color.or(base.underline_color),
2644                    underline_style: if s.underline {
2645                        s.underline_style
2646                    } else {
2647                        base.underline_style
2648                    },
2649                    strikethrough: s.strikethrough || base.strikethrough,
2650                    bg: s.bg,
2651                    bg_radius: s.bg_radius,
2652                })
2653                .collect();
2654            let entry = CachedText::new(buffer, content, base, decos, fs, frame_no);
2655            self.insert(key, Entry::Run(entry));
2656        }
2657        self.entries
2658            .get_mut(&key)
2659            .expect("just inserted")
2660            .touch(frame_no);
2661        key
2662    }
2663
2664    /// The shaped run behind one of this frame's texts; callers have
2665    /// already taken the long line's path when it is one.
2666    fn entry_mut(&mut self, id: TextId) -> &mut CachedText {
2667        let key = self.frame[id.0 as usize].cache_key;
2668        self.run_mut(key).expect("frame text missing from cache")
2669    }
2670
2671    /// Wraps text `id`'s run to `max_w_logical`: layout at the node's
2672    /// width, and emission again, since the run is shared by every node
2673    /// drawing the same text. The first node of a frame claims it; one
2674    /// asking another width the same frame moves to a copy of its own.
2675    fn ensure_wrap(&mut self, id: TextId, max_w_logical: f32, fs: &mut FontSystem) {
2676        let (scale, frame_no) = (self.scale, self.frame_no);
2677        let entry = self.entry_mut(id);
2678        let target = wrap_target(entry, Some(max_w_logical), scale);
2679        if entry.claimed == frame_no && wrap_differs(entry.claimed_wrap, target) {
2680            self.own_wrap(id, target, fs);
2681            return;
2682        }
2683        entry.claimed = frame_no;
2684        entry.claimed_wrap = target;
2685        wrap_entry(entry, fs, target);
2686    }
2687
2688    /// `ensure_wrap` for a node drawing a run another node of this frame
2689    /// wrapped to another width — the same label in two panes: the node
2690    /// takes the copy of the run at its width, made the first time and
2691    /// found by width after, so each answers `text_hit` and `caret_rect`
2692    /// at its own rows and neither re-wraps the other's (a
2693    /// long line's twin is [`Self::claim_long`]). Off the path every other
2694    /// text takes: a copy costs a buffer clone once and a lookup a frame.
2695    #[inline(never)]
2696    fn own_wrap(&mut self, id: TextId, target: Option<f32>, fs: &mut FontSystem) {
2697        let frame_no = self.frame_no;
2698        let key = self.frame[id.0 as usize].cache_key;
2699        let copy = self.wrap_copy(key, target, fs);
2700        self.frame[id.0 as usize].cache_key = copy;
2701        let entry = self.run_mut(copy).expect("just made");
2702        entry.claimed = frame_no;
2703        entry.claimed_wrap = target;
2704    }
2705
2706    /// The copy of run `key` laid out at `target`, made the first time a
2707    /// width asks for it and found by width after: what a second node
2708    /// drawing the run at another width ([`Self::own_wrap`]) and a
2709    /// `measure_text` at a width no node drew it at ([`Self::measure_key`])
2710    /// lay out instead of the run a node drew.
2711    #[inline(never)]
2712    fn wrap_copy(&mut self, key: u64, target: Option<f32>, fs: &mut FontSystem) -> u64 {
2713        let frame_no = self.frame_no;
2714        // By the half pixel `wrap_differs` tells widths apart by.
2715        let slot = target.map_or(u64::MAX, |t| (t * 2.0).round() as u64);
2716        let copy = crate::key::fnv(key ^ COPY_SALT, &slot.to_le_bytes());
2717        if self.run(copy).is_none() {
2718            let fork = self.run(key).expect("a run").fork(frame_no);
2719            self.insert(copy, Entry::Run(fork));
2720        }
2721        let entry = self.run_mut(copy).expect("just made");
2722        entry.last_used = frame_no;
2723        wrap_entry(entry, fs, target);
2724        copy
2725    }
2726
2727    /// Emits positioned glyph quads for a laid-out text node.
2728    /// `origin` and `node` are logical; output quads are physical px. The
2729    /// rounded backgrounds it notes are clipped as its parent is, whatever
2730    /// its own box does (`JoinBg::outer`).
2731    #[allow(clippy::too_many_arguments)]
2732    pub(crate) fn emit(
2733        &mut self,
2734        id: TextId,
2735        origin: Vec2,
2736        node: Size,
2737        clip: Clip,
2738        clip_id: ClipId,
2739        clips: &mut Vec<Clip>,
2740        res: &Resources,
2741        fs: &mut FontSystem,
2742        atlas: &mut GlyphAtlas,
2743        out: &mut Vec<Quad>,
2744        sel: Option<((usize, usize), Color)>,
2745    ) {
2746        let from = self.joins.len();
2747        self.emit_text(
2748            id, origin, node, clip, clip_id, clips, res, fs, atlas, out, sel,
2749        );
2750        if self.joins.len() == from {
2751            return;
2752        }
2753        // The box a long line or an overflowing text clips to, as
2754        // `emit_text` works it out.
2755        let key = self.frame[id.0 as usize].cache_key;
2756        let clamps = match self.entries.get(&key) {
2757            Some(Entry::Long(l)) => l.wrap == TextWrap::None,
2758            Some(Entry::Run(e)) => e.clamp_w,
2759            None => false,
2760        };
2761        let own = clamps.then(|| {
2762            let ox = crate::geom::snap_px(origin.x * self.scale);
2763            (ox, ox + (node.w * self.scale).ceil())
2764        });
2765        for j in &mut self.joins[from..] {
2766            j.outer = clip_id;
2767            j.own = own;
2768        }
2769    }
2770
2771    /// `emit_text` for a long line, kept off the run's path: it draws the
2772    /// chunks inside the clip plus one either side, shaping them now if
2773    /// this is the first time they show. Layout broke its
2774    /// rows at the node's final width (`wrapped`), and the line is this
2775    /// node's alone (`claim_long`).
2776    #[allow(clippy::too_many_arguments)]
2777    #[inline(never)]
2778    fn emit_long(
2779        &mut self,
2780        key: u64,
2781        node: Size,
2782        ox: f32,
2783        oy: f32,
2784        nudge: Vec2,
2785        color: Color,
2786        clip: Clip,
2787        clip_id: ClipId,
2788        clips: &mut Vec<Clip>,
2789        res: &Resources,
2790        fs: &mut FontSystem,
2791        atlas: &mut GlyphAtlas,
2792        out: &mut Vec<Quad>,
2793        sel: Option<((usize, usize), Color)>,
2794    ) {
2795        let scale = self.scale;
2796        let line = self.long(key).expect("a long line");
2797        // A line that does not wrap owns its box, as a run that does not
2798        // does; one that wraps draws under its parent's clip, as a
2799        // wrapped run does, so a glyph overhanging a tight row is not cut
2800        // (the alpha.22 regression pass: every `break-spaces` text is a
2801        // long line).
2802        let (clip, clip_id) = if line.wrap == TextWrap::None {
2803            let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2804            let clip = clip.intersect(own, crate::display::SQUARE);
2805            let vis = clip.visible();
2806            if vis.w <= 0.0 || vis.h <= 0.0 {
2807                return;
2808            }
2809            (clip, crate::display::intern_clip(clips, clip))
2810        } else {
2811            (clip, clip_id)
2812        };
2813        // What can show, in the glyphs' own space (ADR 0043).
2814        let vis = clip.visible();
2815        if let Some(((from, to), tint)) = sel {
2816            let rects = self.long_highlight(line, from, to);
2817            push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2818        }
2819        if let Some(w) = line.wrap_w {
2820            self.emit_long_rows(
2821                key, w, ox, oy, nudge, color, clip, clip_id, res, fs, atlas, out,
2822            );
2823            return;
2824        }
2825        let (first, last) = {
2826            if line.chunks.is_empty() {
2827                return;
2828            }
2829            let a = line.chunk_at(vis.x - ox).saturating_sub(1);
2830            let b = (line.chunk_at(vis.x + vis.w - ox) + 1).min(line.chunks.len() - 1);
2831            (a, b)
2832        };
2833        for i in first..=last {
2834            self.ensure_chunk(key, i, res, fs);
2835        }
2836        let frame_no = self.frame_no;
2837        self.long_mut(key).expect("checked").last_used = frame_no;
2838        for i in first..=last {
2839            let (chunk_key, x) = {
2840                let line = self.long(key).expect("checked");
2841                (line.chunks[i].key, line.prefix[i])
2842            };
2843            let raster = &mut self.raster;
2844            let entry = self
2845                .entries
2846                .get_mut(&chunk_key)
2847                .and_then(Entry::run_mut)
2848                .expect("just ensured");
2849            emit_entry(
2850                entry,
2851                ox + x,
2852                oy,
2853                nudge,
2854                color,
2855                clip,
2856                clip_id,
2857                raster,
2858                fs,
2859                atlas,
2860                out,
2861                &mut self.joins,
2862            );
2863        }
2864    }
2865
2866    /// [`Self::emit`]'s body.
2867    #[allow(clippy::too_many_arguments)]
2868    fn emit_text(
2869        &mut self,
2870        id: TextId,
2871        origin: Vec2,
2872        node: Size,
2873        clip: Clip,
2874        clip_id: ClipId,
2875        clips: &mut Vec<Clip>,
2876        res: &Resources,
2877        fs: &mut FontSystem,
2878        atlas: &mut GlyphAtlas,
2879        out: &mut Vec<Quad>,
2880        // `sel`: the bytes of this run the window's selection covers and
2881        // the colour to paint them under (ADR 0017). Resolved by the
2882        // caller, the only place that knows this run's ordinal in its
2883        // scope.
2884        sel: Option<((usize, usize), Color)>,
2885    ) {
2886        let FrameText {
2887            cache_key: key,
2888            color,
2889        } = self.frame[id.0 as usize];
2890        let scale = self.scale;
2891        let ox = crate::geom::snap_px(origin.x * scale);
2892        let oy = crate::geom::snap_px(origin.y * scale);
2893        // Where layout put the text, less where it is drawn: what a
2894        // background's edges are snapped from (`on_pixels`).
2895        let nudge = Vec2::new(origin.x * scale - ox, origin.y * scale - oy);
2896        // A long line is drawn by chunks, off the run's path (backlog
2897        // C19).
2898        if self.long(key).is_some() {
2899            self.emit_long(
2900                key, node, ox, oy, nudge, color, clip, clip_id, clips, res, fs, atlas, out, sel,
2901            );
2902            return;
2903        }
2904        self.ensure_wrap(id, node.w, fs);
2905        let raster = &mut self.raster;
2906        let entry = self
2907            .entries
2908            .get_mut(&key)
2909            .and_then(Entry::run_mut)
2910            .expect("frame text missing from cache");
2911
2912        // Overflowing modes own their box: a line that runs past the node's
2913        // width is clipped there rather than painted over siblings.
2914        let (clip, clip_id) = if entry.clamp_w {
2915            let own = Rect::new(ox, oy, (node.w * scale).ceil(), (node.h * scale).ceil());
2916            let clip = clip.intersect(own, crate::display::SQUARE);
2917            (clip, crate::display::intern_clip(clips, clip))
2918        } else {
2919            (clip, clip_id)
2920        };
2921        if let Some(((from, to), tint)) = sel {
2922            let rects = Self::run_highlight(entry, from, to);
2923            push_highlight(&rects, ox, oy, nudge, tint, clip_id, out);
2924        }
2925        emit_entry(
2926            entry,
2927            ox,
2928            oy,
2929            nudge,
2930            color,
2931            clip,
2932            clip_id,
2933            raster,
2934            fs,
2935            atlas,
2936            out,
2937            &mut self.joins,
2938        );
2939    }
2940}
2941
2942/// A text's background rect at physical (`x`, `y`) with each edge
2943/// snapped to a whole pixel, so it meets another text's — the next run's,
2944/// the next row's — on a pixel line and not inside one, where each drew
2945/// part of the pixel and the two left a seam. A box is drawn where layout
2946/// put it, so a gap between two boxes is there at any scale, unless it
2947/// asks for this too (`pixelSnap`), from its layout rect.
2948///
2949/// The edges are snapped from where layout put them, `nudge` (layout's
2950/// origin less the drawn, snapped one) past `x` and `y`, so the next
2951/// text's edge, snapped from its own layout origin, lands on the same
2952/// pixel. From the drawn origin a row's rect ended a pixel into the next
2953/// row wherever their pitch was not whole pixels, and the join drew
2954/// twice.
2955fn on_pixels(x: f32, y: f32, w: f32, h: f32, nudge: Vec2) -> Rect {
2956    Rect::new(x + nudge.x, y + nudge.y, w, h).on_pixels()
2957}
2958
2959/// Pushes selection rects as quads at a run's origin — before its glyphs,
2960/// so the text stays on top of its own highlight.
2961fn push_highlight(
2962    rects: &[Rect],
2963    ox: f32,
2964    oy: f32,
2965    nudge: Vec2,
2966    tint: Color,
2967    clip: ClipId,
2968    out: &mut Vec<Quad>,
2969) {
2970    for r in rects {
2971        out.push(Quad {
2972            rect: on_pixels(ox + r.x, oy + r.y, r.w, r.h, nudge),
2973            color: tint,
2974            border_color: Color::TRANSPARENT,
2975            radius: [0.0; 4],
2976            border_w: 0.0,
2977            blur: 0.0,
2978            kind: QuadKind::Solid,
2979            uv: [0; 4],
2980            clip,
2981        });
2982    }
2983}
2984
2985impl TextSystem {
2986    /// `emit` for a wrapped long line: the chunks whose rows touch the
2987    /// clip, plus one either side, each drawn row by row from its
2988    /// unwrapped templates.
2989    #[allow(clippy::too_many_arguments)]
2990    fn emit_long_rows(
2991        &mut self,
2992        key: u64,
2993        w: f32,
2994        ox: f32,
2995        oy: f32,
2996        nudge: Vec2,
2997        color: Color,
2998        clip: Clip,
2999        clip_id: ClipId,
3000        res: &Resources,
3001        fs: &mut FontSystem,
3002        atlas: &mut GlyphAtlas,
3003        out: &mut Vec<Quad>,
3004    ) {
3005        let vis = clip.visible();
3006        let (first, last) = {
3007            let line = self.long(key).expect("a long line");
3008            let ra = ((vis.y - oy) / line.line_h).floor().max(0.0) as u32;
3009            let rb = ((vis.y + vis.h - oy) / line.line_h).floor().max(0.0) as u32;
3010            let Some((a, b)) = line.chunks_on_rows(ra, rb) else {
3011                return;
3012            };
3013            (a.saturating_sub(1), (b + 1).min(line.chunks.len() - 1))
3014        };
3015        let mut fresh = false;
3016        for i in first..=last {
3017            fresh |= self.ensure_chunk(key, i, res, fs);
3018        }
3019        if fresh {
3020            // A chunk shaped now moves every row after it — and the box
3021            // layout gave the line, which was the estimate's.
3022            self.relayout_long(key, Some(w));
3023            let line = self.long_mut(key).expect("checked");
3024            let rows = line.rows();
3025            if rows != line.laid_rows && rows != line.asked_rows {
3026                line.asked_rows = rows;
3027                self.owed = true;
3028            }
3029        }
3030        let frame_no = self.frame_no;
3031        self.long_mut(key).expect("checked").last_used = frame_no;
3032        for i in first..=last {
3033            let chunk_key = self.long(key).expect("checked").chunks[i].key;
3034            // The line and the chunk's run are two entries of one map, so
3035            // the pair is borrowed together; a chunk's key is never the
3036            // line's own (`LONG_SALT`).
3037            let [Some(Entry::Long(line)), Some(Entry::Run(entry))] =
3038                self.entries.get_disjoint_mut([&key, &chunk_key])
3039            else {
3040                continue;
3041            };
3042            let (row0, head_x) = line.starts[i];
3043            let chunk = &line.chunks[i];
3044            if chunk.rows.is_empty() {
3045                continue;
3046            }
3047            let raster = &mut self.raster;
3048            emit_entry_rows(
3049                entry,
3050                ox,
3051                oy + row0 as f32 * line.line_h,
3052                nudge,
3053                head_x,
3054                &chunk.rows,
3055                line.line_h,
3056                color,
3057                clip,
3058                clip_id,
3059                raster,
3060                fs,
3061                atlas,
3062                out,
3063                &mut self.joins,
3064            );
3065        }
3066    }
3067}
3068
3069/// `emit_entry` for one chunk of a wrapped long line: the templates are
3070/// the chunk's unwrapped run, and each row's glyphs are shifted back by
3071/// where the row starts in it and down by the row. `oy` is the chunk's
3072/// first row; `head_x` where that row begins.
3073#[allow(clippy::too_many_arguments)]
3074fn emit_entry_rows(
3075    entry: &mut CachedText,
3076    ox: f32,
3077    oy: f32,
3078    nudge: Vec2,
3079    head_x: f32,
3080    rows: &[RowStart],
3081    line_h: f32,
3082    color: Color,
3083    clip: Clip,
3084    clip_id: ClipId,
3085    raster: &mut Raster,
3086    fs: &mut FontSystem,
3087    atlas: &mut GlyphAtlas,
3088    out: &mut Vec<Quad>,
3089    joins: &mut Vec<JoinBg>,
3090) {
3091    build_templates(entry, raster, fs, atlas);
3092    // What can show, in the glyphs' own space (ADR 0043).
3093    let vis = clip.visible();
3094    // A decoration rect spans glyphs of the unwrapped run; the row a
3095    // glyph is on is decided by its byte, and a rect by its x — row `r`
3096    // covers `rows[r].x..rows[r + 1].x` of the run — so a rect a row
3097    // break falls inside is cut there, one piece a row, each shifted as
3098    // the row's glyphs are (backlog C42: a rich chunk's spans).
3099    let mut row_pieces = |d: &DecoTemplate, out: &mut Vec<Quad>| {
3100        let (x0, x1) = (d.x, d.x + d.w);
3101        for r in 0..rows.len() {
3102            let ra = rows[r].x;
3103            let rb = rows.get(r + 1).map_or(f32::INFINITY, |n| n.x);
3104            let (a, b) = (x0.max(ra), x1.min(rb));
3105            if b <= a {
3106                continue;
3107            }
3108            let dx = if r == 0 { head_x } else { 0.0 } - ra;
3109            let x = ox + a + dx;
3110            let y = oy + d.y + r as f32 * line_h;
3111            if y >= vis.y + vis.h {
3112                break;
3113            }
3114            if y + d.h <= vis.y || x >= vis.x + vis.w || x + (b - a) <= vis.x {
3115                continue;
3116            }
3117            let quad = Quad {
3118                rect: if d.under {
3119                    on_pixels(x, y, b - a, d.h, nudge)
3120                } else {
3121                    Rect::new(x, y, b - a, d.h)
3122                },
3123                color: d.color.unwrap_or(color),
3124                border_color: Color::TRANSPARENT,
3125                radius: [0.0; 4],
3126                border_w: 0.0,
3127                blur: 0.0,
3128                kind: QuadKind::Solid,
3129                clip: clip_id,
3130                uv: [0; 4],
3131            };
3132            // A background or a solid line is its rect; a wave or dots
3133            // are pieces built around it, as in `emit_entry`. A rounded
3134            // background is noted for the join pass (backlog F101).
3135            if d.under && d.radius > 0.0 {
3136                joins.push(JoinBg {
3137                    quad: out.len() as u32,
3138                    radius: d.radius,
3139                    outer: clip_id,
3140                    own: None,
3141                });
3142            }
3143            if d.under || d.style == UnderlineStyle::Solid {
3144                out.push(quad);
3145            } else {
3146                crate::deco::push_line(
3147                    out,
3148                    d.style,
3149                    x,
3150                    y,
3151                    b - a,
3152                    d.h,
3153                    d.color.unwrap_or(color),
3154                    clip_id,
3155                );
3156            }
3157        }
3158    };
3159    for d in entry.deco.iter().filter(|d| d.under) {
3160        row_pieces(d, out);
3161    }
3162    let mut r = 0usize;
3163    for g in &entry.glyphs {
3164        while r + 1 < rows.len() && g.byte >= rows[r + 1].byte {
3165            r += 1;
3166        }
3167        let dx = if r == 0 { head_x } else { 0.0 } - rows[r].x;
3168        let x = ox + g.x + dx;
3169        let y = oy + g.y + r as f32 * line_h;
3170        // Rows only go down: past the clip's bottom nothing comes back.
3171        // Both edges are inclusive, as the x test's are: a glyph whose ink
3172        // ends exactly at the clip's top or starts exactly at its bottom
3173        // has no pixel inside it, and which glyph that is depends on the
3174        // face the machine resolved `Mono` to (the alpha.12 Windows round
3175        // saw two, after C32 moved the face).
3176        if y >= vis.y + vis.h {
3177            break;
3178        }
3179        if y + g.h <= vis.y || x >= vis.x + vis.w || x + g.w <= vis.x {
3180            continue;
3181        }
3182        out.push(Quad {
3183            rect: Rect::new(x, y, g.w, g.h),
3184            color: g.color.unwrap_or(color),
3185            border_color: Color::TRANSPARENT,
3186            radius: [0.0; 4],
3187            border_w: 0.0,
3188            blur: 0.0,
3189            kind: g.kind,
3190            clip: clip_id,
3191            uv: g.uv,
3192        });
3193    }
3194    for d in entry.deco.iter().filter(|d| !d.under) {
3195        row_pieces(d, out);
3196    }
3197}
3198
3199/// The rows a chunk's unwrapped run breaks into at width `w` when its
3200/// first row starts `head_x` in: greedy, at the break opportunities UAX
3201/// #14 gives (what cosmic-text's `WordOrGlyph` takes) or at every glyph
3202/// for `Glyph`, a piece wider than a row breaking by glyph, trailing
3203/// whitespace hanging past the edge as cosmic-text lets it — except under
3204/// `BreakSpaces`, where each whitespace character is a piece of its own
3205/// and takes its room like ink, so one that does not fit starts the next
3206/// row and none hangs (CSS's `break-spaces`, a break before the run too).
3207/// Returns the
3208/// row starts — the first is the chunk's own — and the x its last row
3209/// ends at. Positions are read left to right: a bidi run breaks by its
3210/// glyph order.
3211fn break_rows(
3212    buffer: &Buffer,
3213    text: &str,
3214    wrap: TextWrap,
3215    w: f32,
3216    head_x: f32,
3217) -> (Vec<RowStart>, f32) {
3218    let mut rows = vec![RowStart { byte: 0, x: 0.0 }];
3219    let Some(run) = buffer.layout_runs().next() else {
3220        return (rows, head_x);
3221    };
3222    let glyphs = run.glyphs;
3223    let blank = |g: &cosmic_text::LayoutGlyph| {
3224        text.get(g.start..g.end)
3225            .is_some_and(|s| s.chars().all(char::is_whitespace))
3226    };
3227    let spaces = wrap == TextWrap::BreakSpaces;
3228    let mut pieces: Vec<(usize, usize)> = Vec::new();
3229    match wrap {
3230        TextWrap::Word | TextWrap::BreakSpaces => {
3231            let mut at = 0usize;
3232            let mut piece = |from: usize, to: usize| {
3233                if !spaces {
3234                    pieces.push((from, to));
3235                    return;
3236                }
3237                // The trailing whitespace a break follows, a piece a
3238                // character: the break is before each of them as well.
3239                let ink = text[from..to].trim_end_matches(char::is_whitespace).len();
3240                if ink > 0 {
3241                    pieces.push((from, from + ink));
3242                }
3243                for (i, c) in text[from + ink..to].char_indices() {
3244                    let at = from + ink + i;
3245                    pieces.push((at, at + c.len_utf8()));
3246                }
3247            };
3248            for (i, _) in unicode_linebreak::linebreaks(text) {
3249                if i > at {
3250                    piece(at, i);
3251                    at = i;
3252                }
3253            }
3254            if at < text.len() {
3255                piece(at, text.len());
3256            }
3257        }
3258        TextWrap::Glyph | TextWrap::None => {
3259            pieces.extend(glyphs.iter().map(|g| (g.start, g.end)));
3260        }
3261    }
3262    let mut row_x0 = 0.0f32;
3263    let mut avail = w - head_x;
3264    let mut gi = 0usize;
3265    for (s, e) in pieces {
3266        let g0 = gi;
3267        while gi < glyphs.len() && glyphs[gi].start < e {
3268            gi += 1;
3269        }
3270        if g0 == gi {
3271            continue;
3272        }
3273        let piece = &glyphs[g0..gi];
3274        let x0 = piece[0].x;
3275        let ink_end = piece
3276            .iter()
3277            .rev()
3278            .find(|g| spaces || !blank(g))
3279            .map_or(x0, |g| g.x + g.w);
3280        // A piece at the row's start has nowhere to go — except the
3281        // chunk's first, whose row is the one the previous chunk left off
3282        // on `head_x` in: that row holds something, so the piece can open
3283        // the next (the alpha.22 regression pass; the chunk's first row
3284        // is then empty, which the rest reads by byte).
3285        let opens = x0 > row_x0 || (rows.len() == 1 && head_x > 0.0);
3286        if ink_end - row_x0 > avail && opens {
3287            rows.push(RowStart {
3288                byte: s as u32,
3289                x: x0,
3290            });
3291            row_x0 = x0;
3292            avail = w;
3293        }
3294        if ink_end - row_x0 > avail {
3295            // Wider than a row on its own: by glyph.
3296            for g in piece {
3297                if g.x + g.w - row_x0 > avail && g.x > row_x0 && (spaces || !blank(g)) {
3298                    rows.push(RowStart {
3299                        byte: g.start as u32,
3300                        x: g.x,
3301                    });
3302                    row_x0 = g.x;
3303                    avail = w;
3304                }
3305            }
3306        }
3307    }
3308    let end = if rows.len() == 1 {
3309        head_x + run.line_w
3310    } else {
3311        run.line_w - row_x0
3312    };
3313    (rows, end)
3314}
3315
3316/// Builds the entry's positioned templates if the wrap or the atlas moved
3317/// since they were: the steady state reuses them.
3318fn build_templates(
3319    entry: &mut CachedText,
3320    raster: &mut Raster,
3321    fs: &mut FontSystem,
3322    atlas: &mut GlyphAtlas,
3323) {
3324    {
3325        // Steady state: same wrap, same atlas — reuse positioned templates.
3326        let built_for = (entry.wrap.map(f32::to_bits), atlas.stamp);
3327        if entry.glyphs_built_for != Some(built_for) {
3328            entry.glyphs.clear();
3329            entry.deco.clear();
3330            let lines = line_cap(entry.max_lines);
3331            let decorated = entry.span_deco.iter().any(SpanDeco::any);
3332            // Spans painted apart: a glyph's ink may lie over another's
3333            // place (backlog F162). One colour throughout costs nothing.
3334            let painted = entry.span_deco.windows(2).any(|w| w[0].color != w[1].color);
3335            let starts = if (decorated || painted) && entry.span_deco.len() > 1 {
3336                line_starts(&entry.buffer, &entry.content)
3337            } else {
3338                Vec::new()
3339            };
3340            let mut places = Places::default();
3341            for run in entry.buffer.layout_runs().take(lines) {
3342                if decorated || painted {
3343                    let base = starts.get(run.line_i).copied().unwrap_or(0);
3344                    places.of_line(&run, base, &entry.span_deco, painted);
3345                }
3346                if decorated {
3347                    build_decorations(&run, &places.parts, &entry.span_deco, fs, &mut entry.deco);
3348                }
3349                for (i, glyph) in run.glyphs.iter().enumerate() {
3350                    let physical = glyph.physical((0.0, 0.0), 1.0);
3351                    let Some(slot) = raster_glyph(physical.cache_key, fs, raster, atlas) else {
3352                        continue;
3353                    };
3354                    let template = GlyphTemplate {
3355                        x: physical.x as f32 + slot.left as f32,
3356                        y: run.line_y.round() + physical.y as f32 - slot.top as f32,
3357                        w: slot.w as f32,
3358                        h: slot.h as f32,
3359                        uv: [slot.x, slot.y, slot.w, slot.h],
3360                        kind: glyph_kind(&slot),
3361                        color: glyph
3362                            .color_opt
3363                            .map(|c| Color::rgba8(c.r(), c.g(), c.b(), c.a())),
3364                        byte: glyph.start as u32,
3365                    };
3366                    // A colour bitmap (an emoji) paints its own colours.
3367                    if painted && template.kind != QuadKind::GlyphColor {
3368                        places.push_painted(
3369                            template,
3370                            i,
3371                            run.glyphs,
3372                            &entry.span_deco,
3373                            &mut entry.glyphs,
3374                        );
3375                    } else {
3376                        entry.glyphs.push(template);
3377                    }
3378                }
3379            }
3380            // Rasterizing may have extended the page mid-build, which
3381            // keeps every slot where it was but moves the stamp: keep the
3382            // one we ended on.
3383            entry.glyphs_built_for = Some((entry.wrap.map(f32::to_bits), atlas.stamp));
3384        }
3385    }
3386}
3387
3388/// Emits one cache entry's glyphs and decorations at the physical origin
3389/// (`ox`, `oy`): the steady-state template walk, shared by a text node and
3390/// by each chunk of a long line.
3391#[allow(clippy::too_many_arguments)]
3392fn emit_entry(
3393    entry: &mut CachedText,
3394    ox: f32,
3395    oy: f32,
3396    nudge: Vec2,
3397    color: Color,
3398    clip: Clip,
3399    clip_id: ClipId,
3400    raster: &mut Raster,
3401    fs: &mut FontSystem,
3402    atlas: &mut GlyphAtlas,
3403    out: &mut Vec<Quad>,
3404    joins: &mut Vec<JoinBg>,
3405) {
3406    build_templates(entry, raster, fs, atlas);
3407    // What can show, in the glyphs' own space (ADR 0043).
3408    let vis = clip.visible();
3409    {
3410        let inside = |x: f32, y: f32, w: f32, h: f32| {
3411            oy + y + h >= vis.y
3412                && oy + y <= vis.y + vis.h
3413                && ox + x < vis.x + vis.w
3414                && ox + x + w > vis.x
3415        };
3416        let deco_quad = |d: &DecoTemplate| Quad {
3417            rect: Rect::new(ox + d.x, oy + d.y, d.w, d.h),
3418            color: d.color.unwrap_or(color),
3419            border_color: Color::TRANSPARENT,
3420            radius: [0.0; 4],
3421            border_w: 0.0,
3422            blur: 0.0,
3423            kind: QuadKind::Solid,
3424            clip: clip_id,
3425            uv: [0; 4],
3426        };
3427        // A span's background goes under its glyphs, on whole pixels;
3428        // its lines go over. A rounded one is noted for the join pass,
3429        // which gives it its corners once every text is painted (backlog
3430        // F101).
3431        for d in entry
3432            .deco
3433            .iter()
3434            .filter(|d| d.under && inside(d.x, d.y, d.w, d.h))
3435        {
3436            if d.radius > 0.0 {
3437                joins.push(JoinBg {
3438                    quad: out.len() as u32,
3439                    radius: d.radius,
3440                    outer: clip_id,
3441                    own: None,
3442                });
3443            }
3444            out.push(Quad {
3445                rect: on_pixels(ox + d.x, oy + d.y, d.w, d.h, nudge),
3446                ..deco_quad(d)
3447            });
3448        }
3449
3450        // Glyph templates are in layout order; skip everything above the clip
3451        // and stop at the first glyph past it (rows below never come back).
3452        // Horizontally clipped glyphs (an unwrapped line) are dropped too.
3453        out.extend(
3454            entry
3455                .glyphs
3456                .iter()
3457                .filter(|g| {
3458                    oy + g.y + g.h >= vis.y && ox + g.x < vis.x + vis.w && ox + g.x + g.w > vis.x
3459                })
3460                .take_while(|g| oy + g.y <= vis.y + vis.h)
3461                .map(|g| Quad {
3462                    rect: Rect::new(ox + g.x, oy + g.y, g.w, g.h),
3463                    color: g.color.unwrap_or(color),
3464                    border_color: Color::TRANSPARENT,
3465                    radius: [0.0; 4],
3466                    border_w: 0.0,
3467                    blur: 0.0,
3468                    kind: g.kind,
3469                    clip: clip_id,
3470                    uv: g.uv,
3471                }),
3472        );
3473        for d in entry
3474            .deco
3475            .iter()
3476            .filter(|d| !d.under && inside(d.x, d.y, d.w, d.h))
3477        {
3478            // A solid line is its rect; a wave or dots are pieces built
3479            // around it (`crate::deco`, backlog K4).
3480            match d.style {
3481                UnderlineStyle::Solid => out.push(deco_quad(d)),
3482                style => crate::deco::push_line(
3483                    out,
3484                    style,
3485                    ox + d.x,
3486                    oy + d.y,
3487                    d.w,
3488                    d.h,
3489                    d.color.unwrap_or(color),
3490                    clip_id,
3491                ),
3492            }
3493        }
3494    }
3495}
3496
3497/// Whether a text of `len` bytes in `style` is shaped in chunks, by what
3498/// costs nothing to ask: past `LONG_LINE_BYTES`, with no `max_lines` or
3499/// `ellipsis`, since a line budget is a property of the whole. The
3500/// other half is [`has_line_break`] — and [`has_rtl`] for a text long
3501/// only by its `break-spaces` — asked only when the cache has not
3502/// seen the text — a wrapped one breaks its chunks into rows
3503/// ([`LongLine::starts`]), whatever its `wrap`. Plain or rich alike.
3504fn could_be_long(len: usize, style: &TextStyle) -> bool {
3505    (len >= LONG_LINE_BYTES || len > 0 && style.wrap == TextWrap::BreakSpaces)
3506        && style.max_lines == 0
3507        && !style.ellipsis
3508}
3509
3510/// Whether the content breaks lines of its own — a byte scan, since the
3511/// `str` pattern search is per character.
3512fn has_line_break(content: &str) -> bool {
3513    content.bytes().any(|b| b == b'\n' || b == b'\r')
3514}
3515
3516/// Whether the content holds a right-to-left character — a strong one of
3517/// the right-to-left scripts' blocks, or a mark or embedding asking for
3518/// that direction. A text long only by its `break-spaces` and holding one
3519/// is shaped as the run it would otherwise be, wrapping as `word` does
3520/// (as one with line breaks of its own does): the long line breaks rows
3521/// over glyph positions left to right, which a right-to-left run's are
3522/// not, and a Hebrew paragraph that had to wrap would draw one glyph.
3523fn has_rtl(content: &str) -> bool {
3524    !content.is_ascii()
3525        && content.chars().any(|c| {
3526            matches!(c as u32,
3527                0x0590..=0x08FF
3528                | 0x200F | 0x202B | 0x202E | 0x2067
3529                | 0xFB1D..=0xFDFF
3530                | 0xFE70..=0xFEFF
3531                | 0x10800..=0x10FFF
3532                | 0x1E800..=0x1EFFF)
3533        })
3534}
3535
3536/// One span's place on a laid-out line, physical px: a glyph's advance,
3537/// or the share of it one span's graphemes take when the glyph is a
3538/// ligature of characters from more than one span.
3539#[derive(Clone, Copy, Debug)]
3540struct Part {
3541    x0: f32,
3542    x1: f32,
3543    /// The span's index into the entry's `span_deco`.
3544    span: usize,
3545    /// The glyph's index into the run.
3546    glyph: usize,
3547}
3548
3549/// The places of one laid-out line and what painting by them needs,
3550/// kept across the lines of a build so their buffers are allocated once.
3551#[derive(Default)]
3552struct Places {
3553    /// In glyph order, a glyph's own contiguous.
3554    parts: Vec<Part>,
3555    /// Indices into `parts` by `x0`, those with a width alone: what a
3556    /// point of the line is looked up in. Filled for a painted entry.
3557    by_x: Vec<u32>,
3558    /// A glyph's cut columns, reused.
3559    cuts: Vec<u32>,
3560}
3561
3562impl Places {
3563    /// The places `run` gives its spans, in glyph order: a glyph's advance
3564    /// is its span's, and a glyph whose bytes run over more than one span
3565    /// — a ligature of two characters painted apart — shares its advance
3566    /// among its graphemes evenly, as a caret inside it is placed
3567    /// (cosmic-text's `cursor_glyph`), each share its own span's, right
3568    /// to left in a right-to-left glyph. `base` is the byte the run's
3569    /// paragraph starts at in the content, where the spans' starts are.
3570    fn of_line(
3571        &mut self,
3572        run: &cosmic_text::LayoutRun<'_>,
3573        base: usize,
3574        spans: &[SpanDeco],
3575        painted: bool,
3576    ) {
3577        use unicode_segmentation::UnicodeSegmentation;
3578        self.parts.clear();
3579        let last = spans.len().saturating_sub(1);
3580        for (gi, g) in run.glyphs.iter().enumerate() {
3581            // A glyph's metadata is the span its first byte is in.
3582            let span = g.metadata.min(last);
3583            let crosses = spans
3584                .get(span + 1)
3585                .is_some_and(|next| (next.start as usize) < base + g.end);
3586            let cluster = run.text.get(g.start..g.end).filter(|_| crosses);
3587            let Some(cluster) = cluster else {
3588                self.parts.push(Part {
3589                    x0: g.x,
3590                    x1: g.x + g.w,
3591                    span,
3592                    glyph: gi,
3593                });
3594                continue;
3595            };
3596            let share = g.w / cluster.graphemes(true).count().max(1) as f32;
3597            for (k, (i, _)) in cluster.grapheme_indices(true).enumerate() {
3598                let byte = base + g.start + i;
3599                let span = spans
3600                    .partition_point(|s| s.start as usize <= byte)
3601                    .saturating_sub(1);
3602                let (x0, x1) = if g.level.is_rtl() {
3603                    let x1 = g.x + g.w - k as f32 * share;
3604                    (x1 - share, x1)
3605                } else {
3606                    let x0 = g.x + k as f32 * share;
3607                    (x0, x0 + share)
3608                };
3609                match self.parts.last_mut() {
3610                    Some(p) if p.glyph == gi && p.span == span => {
3611                        p.x0 = p.x0.min(x0);
3612                        p.x1 = p.x1.max(x1);
3613                    }
3614                    _ => self.parts.push(Part {
3615                        x0,
3616                        x1,
3617                        span,
3618                        glyph: gi,
3619                    }),
3620                }
3621            }
3622        }
3623        self.by_x.clear();
3624        if painted {
3625            let parts = &self.parts;
3626            self.by_x.extend(
3627                (0..parts.len() as u32).filter(|&k| parts[k as usize].x1 > parts[k as usize].x0),
3628            );
3629            // A left-to-right line is in order already.
3630            if !self
3631                .by_x
3632                .is_sorted_by(|&a, &b| parts[a as usize].x0 <= parts[b as usize].x0)
3633            {
3634                self.by_x
3635                    .sort_by(|&a, &b| parts[a as usize].x0.total_cmp(&parts[b as usize].x0));
3636            }
3637        }
3638    }
3639
3640    /// Pushes glyph `gi`'s template painted by the places its ink lies
3641    /// over (backlog F162). A colour belongs to the places of the
3642    /// characters that carry it — a span's background is drawn over
3643    /// exactly those — and a glyph's ink is not always over its own: a
3644    /// coding font's ligature is often one glyph drawing two characters
3645    /// beside an empty one (`->` in Fira Code, `##` in Berkeley Mono), or
3646    /// one glyph for both whose advance is shared. So the colour of each
3647    /// column of the glyph's ink is the colour of the place under it —
3648    /// its own, or a neighbour's shaped with it in one face, weight, size
3649    /// and style (a run cosmic-text could have joined into a ligature;
3650    /// an italic's lean into the upright text after it keeps its own) —
3651    /// and where those differ the template is cut into slices at whole
3652    /// pixel columns, each the same quad with its rect and its uv
3653    /// narrowed alike. A glyph over one colour stays one template.
3654    fn push_painted(
3655        &mut self,
3656        t: GlyphTemplate,
3657        gi: usize,
3658        glyphs: &[cosmic_text::LayoutGlyph],
3659        spans: &[SpanDeco],
3660        out: &mut Vec<GlyphTemplate>,
3661    ) {
3662        let parts = &self.parts;
3663        let own = {
3664            let lo = parts.partition_point(|p| p.glyph < gi);
3665            let hi = parts.partition_point(|p| p.glyph <= gi);
3666            &parts[lo..hi]
3667        };
3668        if own.is_empty() || t.w < 2.0 {
3669            out.push(t);
3670            return;
3671        }
3672        let g = &glyphs[gi];
3673        let kin = |p: &Part| {
3674            let n = &glyphs[p.glyph];
3675            p.glyph == gi
3676                || n.font_id == g.font_id
3677                    && n.font_weight == g.font_weight
3678                    && n.font_size == g.font_size
3679                    && n.cache_key_flags == g.cache_key_flags
3680        };
3681        let color_of = |p: &Part| spans.get(p.span).and_then(|s| s.color);
3682        // The colour at `x`: the place under it when that is the glyph's
3683        // own or a kin's, else the glyph's own place nearest it.
3684        let at = |x: f32| {
3685            let k = self
3686                .by_x
3687                .partition_point(|&k| parts[k as usize].x0 <= x)
3688                .checked_sub(1)
3689                .map(|k| &parts[self.by_x[k] as usize]);
3690            match k {
3691                Some(p) if x < p.x1 && kin(p) => color_of(p),
3692                _ => {
3693                    let near = own
3694                        .iter()
3695                        .min_by(|a, b| {
3696                            let d = |p: &Part| (p.x0 - x).max(x - p.x1).max(0.0);
3697                            d(a).total_cmp(&d(b))
3698                        })
3699                        .expect("not empty");
3700                    color_of(near)
3701                }
3702            }
3703        };
3704        let (x0, x1) = (t.x, t.x + t.w);
3705        let w = t.w as u32;
3706        self.cuts.clear();
3707        let from = self
3708            .by_x
3709            .partition_point(|&k| parts[k as usize].x0 < x0)
3710            .saturating_sub(1);
3711        for &k in &self.by_x[from..] {
3712            let p = &parts[k as usize];
3713            if p.x0 >= x1 {
3714                break;
3715            }
3716            for edge in [p.x0, p.x1] {
3717                let c = (edge - x0).round();
3718                if c >= 1.0 && c < t.w {
3719                    self.cuts.push(c as u32);
3720                }
3721            }
3722        }
3723        if self.cuts.is_empty() {
3724            out.push(GlyphTemplate {
3725                color: at(x0 + t.w / 2.0),
3726                ..t
3727            });
3728            return;
3729        }
3730        self.cuts.sort_unstable();
3731        self.cuts.dedup();
3732        self.cuts.push(w);
3733        let first = out.len();
3734        let mut a = 0u32;
3735        for &b in &self.cuts {
3736            let color = at(x0 + (a + b) as f32 / 2.0);
3737            let sliced = out.len() > first;
3738            match out.last_mut() {
3739                // The slice before is the same colour: it grows.
3740                Some(prev) if sliced && prev.color == color => {
3741                    prev.w += (b - a) as f32;
3742                    prev.uv[2] += b - a;
3743                }
3744                _ => out.push(GlyphTemplate {
3745                    x: x0 + a as f32,
3746                    w: (b - a) as f32,
3747                    uv: [t.uv[0] + a, t.uv[1], b - a, t.uv[3]],
3748                    color,
3749                    ..t
3750                }),
3751            }
3752            a = b;
3753        }
3754    }
3755}
3756
3757/// The decoration rects for one laid-out line: consecutive places of one
3758/// span ([`Places::of_line`]: a glyph's advance, or its graphemes' share
3759/// of a ligature's) become one background rect, one
3760/// underline and one strikethrough, as the span asked. Where the lines go
3761/// is the face's own recommendation — swash's `underline_offset`,
3762/// `strikeout_offset` and `stroke_size`, scaled to the glyph's size — read
3763/// from the run's first glyph, so a fallback glyph in the middle of a
3764/// span does not move the line. Neighbouring spans of one background are
3765/// one rect: an editor's selection is its syntax runs, a span each, and
3766/// a rect each drew a seam between every two of them wherever the join
3767/// fell inside a pixel.
3768fn build_decorations(
3769    run: &cosmic_text::LayoutRun<'_>,
3770    parts: &[Part],
3771    spans: &[SpanDeco],
3772    fs: &mut FontSystem,
3773    out: &mut Vec<DecoTemplate>,
3774) {
3775    // The background rect the last group drew, which the next one of the
3776    // same colour starting where it ends extends.
3777    let mut last_bg: Option<usize> = None;
3778    let mut i = 0;
3779    while i < parts.len() {
3780        let span_no = parts[i].span;
3781        let mut j = i + 1;
3782        while j < parts.len() && parts[j].span == span_no {
3783            j += 1;
3784        }
3785        let deco = spans.get(span_no).copied().unwrap_or_default();
3786        if deco.any() {
3787            let group = &parts[i..j];
3788            let x0 = group.iter().map(|p| p.x0).fold(f32::INFINITY, f32::min);
3789            let x1 = group.iter().map(|p| p.x1).fold(f32::NEG_INFINITY, f32::max);
3790            let head = &run.glyphs[group[0].glyph];
3791            let w = (x1 - x0).max(0.0);
3792            match (deco.bg, last_bg.map(|k| &mut out[k])) {
3793                (Some(bg), Some(prev))
3794                    if prev.color == Some(bg)
3795                        && prev.radius == deco.bg_radius
3796                        && (prev.x + prev.w - x0).abs() < 0.01 =>
3797                {
3798                    prev.w = (x1 - prev.x).max(prev.w);
3799                }
3800                (Some(bg), _) => {
3801                    // A span shorter than its line (`Span::size`, a line
3802                    // a larger span made taller) has a background its own
3803                    // height, around its glyphs, not the line's.
3804                    let (y, h) = match head.line_height_opt {
3805                        Some(lh) if lh < run.line_height - 0.01 => {
3806                            let g = head;
3807                            let (asc, desc) = fs
3808                                .get_font(g.font_id, g.font_weight)
3809                                .map(|font| {
3810                                    let m = font.as_swash().metrics(&[]).scale(g.font_size);
3811                                    (m.ascent, m.descent)
3812                                })
3813                                .unwrap_or((0.8 * g.font_size, 0.2 * g.font_size));
3814                            let mid = run.line_y - (asc - desc) / 2.0;
3815                            (mid - lh / 2.0, lh)
3816                        }
3817                        _ => (run.line_top, run.line_height),
3818                    };
3819                    last_bg = Some(out.len());
3820                    out.push(DecoTemplate {
3821                        x: x0,
3822                        y,
3823                        w,
3824                        h,
3825                        color: Some(bg),
3826                        under: true,
3827                        style: UnderlineStyle::Solid,
3828                        radius: deco.bg_radius,
3829                    });
3830                }
3831                (None, _) => last_bg = None,
3832            }
3833            if deco.underline || deco.strikethrough {
3834                let first = head;
3835                let metrics = fs
3836                    .get_font(first.font_id, first.font_weight)
3837                    .map(|font| font.as_swash().metrics(&[]).scale(first.font_size));
3838                // Without the face (it was unloaded between frames), a
3839                // line under the descent and one through the x-height.
3840                let (under_off, strike_off, stroke) = match metrics {
3841                    Some(m) => (m.underline_offset, m.strikeout_offset, m.stroke_size),
3842                    None => (
3843                        -0.1 * first.font_size,
3844                        0.3 * first.font_size,
3845                        0.06 * first.font_size,
3846                    ),
3847                };
3848                let stroke = stroke.max(1.0).round();
3849                let baseline = run.line_y.round();
3850                if deco.underline {
3851                    out.push(DecoTemplate {
3852                        x: x0,
3853                        y: (baseline - under_off).round(),
3854                        w,
3855                        h: stroke,
3856                        color: deco.underline_color.or(deco.color),
3857                        under: false,
3858                        style: deco.underline_style,
3859                        radius: 0.0,
3860                    });
3861                }
3862                if deco.strikethrough {
3863                    out.push(DecoTemplate {
3864                        x: x0,
3865                        y: (baseline - strike_off).round(),
3866                        w,
3867                        h: stroke,
3868                        color: deco.color,
3869                        under: false,
3870                        style: UnderlineStyle::Solid,
3871                        radius: 0.0,
3872                    });
3873                }
3874            }
3875        } else {
3876            last_bg = None;
3877        }
3878        i = j;
3879    }
3880}
3881
3882impl TextSystem {
3883    /// Records where a live text node was drawn, for the queries below,
3884    /// with the keys above it so a query by a `line` row or a wrapper
3885    /// finds the runs inside. Called beside [`Self::emit`] for the frame's
3886    /// own nodes and not for ghosts, which take no input.
3887    pub(crate) fn place(
3888        &mut self,
3889        key: Key,
3890        ancestry: &Ancestry,
3891        id: TextId,
3892        origin: Vec2,
3893        scope: Option<Key>,
3894        drawn: bool,
3895    ) {
3896        let cache_key = self.frame[id.0 as usize].cache_key;
3897        self.places.push(TextPlace {
3898            key,
3899            ancestors: ancestry.keys,
3900            depth: ancestry.depth.min(PLACE_ANCESTORS) as u8,
3901            none_at: ancestry.none_at.map_or(u8::MAX, |n| n as u8),
3902            cache_key,
3903            origin,
3904            scope,
3905            drawn,
3906        });
3907    }
3908
3909    /// The runs `key` names, in tree order, each with its entry and the
3910    /// byte offset its content starts at in the concatenation — from the
3911    /// frame that finished (`prev`) or the one being emitted. A long line
3912    /// is a run among them: every `break-spaces` text is one, and a `line`
3913    /// row of several would otherwise answer with the last one's bytes alone.
3914    fn runs_of(&self, key: Key, prev: bool) -> Vec<(&TextPlace, &Entry, usize)> {
3915        let list = if prev {
3916            self.places.prev()
3917        } else {
3918            &self.places
3919        };
3920        let mut base = 0usize;
3921        let mut out = Vec::new();
3922        for place in list.iter().filter(|p| p.drawn && p.answers_to(key)) {
3923            let Some(entry) = self.entries.get(&place.cache_key) else {
3924                continue;
3925            };
3926            out.push((place, entry, base));
3927            base += entry.content().len();
3928        }
3929        out
3930    }
3931
3932    /// How many bytes of content this frame's text `id` holds — what a
3933    /// selection range over it is measured against.
3934    pub(crate) fn content_len(&self, id: TextId) -> usize {
3935        let key = self.frame[id.0 as usize].cache_key;
3936        self.entries.get(&key).map_or(0, |e| e.content().len())
3937    }
3938
3939    /// Every run inside the selection scope `scope`, in emission order —
3940    /// which is tree order, which is reading order — each with the byte
3941    /// offset its content starts at in the scope's concatenation.
3942    ///
3943    /// Undrawn runs are included: a scoped run the frame culled recorded
3944    /// a place precisely so a selection can reach past the viewport, and
3945    /// leaving it out here would renumber everything after it the moment
3946    /// it scrolled away.
3947    pub(crate) fn scope_runs(&self, scope: Key, prev: bool) -> Vec<ScopeRun<'_>> {
3948        let list = if prev {
3949            self.places.prev()
3950        } else {
3951            &self.places
3952        };
3953        let mut base = 0usize;
3954        let mut out = Vec::new();
3955        for place in list.iter().filter(|p| p.scope == Some(scope)) {
3956            let Some(text) = self.entries.get(&place.cache_key) else {
3957                continue;
3958            };
3959            let len = text.content().len();
3960            out.push(ScopeRun { place, text, base });
3961            base += len;
3962        }
3963        out
3964    }
3965
3966    /// Where `point` (logical viewport px) lands in a selection scope, as
3967    /// the address a selection endpoint is made of: the node whose run it
3968    /// landed in and the byte offset inside *that node's* text. Nearest
3969    /// drawn run wins, vertically first, exactly as [`Self::hit_at`]
3970    /// resolves a point inside one node — an undrawn run is not under any
3971    /// pointer, so it is not a candidate.
3972    pub(crate) fn scope_hit(&self, scope: Key, point: Vec2, prev: bool) -> Option<(Key, usize)> {
3973        let runs = self.scope_runs(scope, prev);
3974        let px = point.x * self.scale;
3975        let py = point.y * self.scale;
3976        let gap = |r: &Rect| {
3977            let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
3978            let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
3979            (dy, dx)
3980        };
3981        let run = runs.iter().filter(|r| r.place.drawn).min_by(|a, b| {
3982            let ga = gap(&self.scope_box(a));
3983            let gb = gap(&self.scope_box(b));
3984            ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
3985        })?;
3986        let byte = self.hit_in_run(run, point)?;
3987        Some((run.place.key, byte))
3988    }
3989
3990    /// A scoped run's laid-out box, physical px in viewport space.
3991    fn scope_box(&self, run: &ScopeRun<'_>) -> Rect {
3992        self.entry_box(run.place, run.text)
3993    }
3994
3995    /// A run's laid-out box, physical px in viewport space, whichever way
3996    /// it was shaped. A wrapped long line is as wide as its rows were
3997    /// broken to — its unwrapped width would claim the text beside it in a
3998    /// selection scope.
3999    fn entry_box(&self, place: &TextPlace, entry: &Entry) -> Rect {
4000        match entry {
4001            Entry::Run(e) => self.physical_box(place, e),
4002            Entry::Long(l) => {
4003                let (ox, oy) = self.physical_origin(place);
4004                let w = l.wrap_w.unwrap_or_else(|| l.width());
4005                Rect::new(ox, oy, w, l.line_h * l.rows() as f32)
4006            }
4007        }
4008    }
4009
4010    /// The tops of a run's visual rows, physical px in viewport space.
4011    fn row_tops(&self, place: &TextPlace, entry: &Entry) -> Vec<f32> {
4012        let (_, oy) = self.physical_origin(place);
4013        match entry {
4014            Entry::Run(e) => e.buffer.layout_runs().map(|r| oy + r.line_top).collect(),
4015            Entry::Long(l) => (0..l.rows()).map(|r| oy + r as f32 * l.line_h).collect(),
4016        }
4017    }
4018
4019    /// Where `point` lands inside one scoped run, as a byte offset into
4020    /// that run's own content.
4021    fn hit_in_run(&self, run: &ScopeRun<'_>, point: Vec2) -> Option<usize> {
4022        match run.text {
4023            Entry::Long(l) => Some(self.long_hit(run.place, l, point)?.byte),
4024            Entry::Run(e) => {
4025                let (ox, oy) = self.physical_origin(run.place);
4026                let cursor = e
4027                    .buffer
4028                    .hit(point.x * self.scale - ox, point.y * self.scale - oy)?;
4029                Some(e.byte_of(cursor))
4030            }
4031        }
4032    }
4033
4034    /// The word around `byte` in one node's own text, as a byte range in
4035    /// that node — what a double click (and a force click) selects.
4036    /// Words are runs of alphanumerics and underscores; anything else is
4037    /// selected as the run of like characters around it, so a double
4038    /// click in whitespace takes the whitespace and one in `->` takes
4039    /// both arrows. `None` when the node holds no text at all.
4040    pub(crate) fn word_at(
4041        &self,
4042        scope: Key,
4043        node: Key,
4044        byte: usize,
4045        prev: bool,
4046    ) -> Option<(usize, usize)> {
4047        let run = self
4048            .scope_runs(scope, prev)
4049            .into_iter()
4050            .find(|r| r.place.key == node)?;
4051        let content = run.text.content();
4052        Some(word_range(content, byte))
4053    }
4054
4055    /// A selection endpoint's address — the node and a byte inside its
4056    /// text — as an offset in the scope's concatenation, which is what
4057    /// orders two endpoints against each other. `None` when that node is
4058    /// not (or no longer) in the scope: an address the frame cannot
4059    /// resolve resolves to nothing rather than to whatever took its place.
4060    pub(crate) fn scope_offset(
4061        &self,
4062        scope: Key,
4063        node: Key,
4064        byte: usize,
4065        prev: bool,
4066    ) -> Option<usize> {
4067        let run = self
4068            .scope_runs(scope, prev)
4069            .into_iter()
4070            .find(|r| r.place.key == node)?;
4071        Some(run.base + byte.min(run.text.content().len()))
4072    }
4073
4074    /// The selection `from..to` as HTML, carrying the styling that is
4075    /// *the text's* rather than the theme's: bold, italic, and a span's
4076    /// own colour where one was declared.
4077    ///
4078    /// What it deliberately leaves behind is the node's colour. A
4079    /// paragraph drawn light grey on a dark card is grey because of the
4080    /// app's theme, not because the words are grey; pasting it into a
4081    /// white document as grey-on-white is how "copy with formatting"
4082    /// earns its reputation. A `rich_text` span that declared a colour is
4083    /// the other case — that colour is authored, the way a highlighted
4084    /// keyword is — and it travels.
4085    ///
4086    /// Runs are joined the way [`Self::scope_slice`] joins them: a `<br>`
4087    /// where the plain text gets a newline.
4088    pub(crate) fn scope_html(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
4089        let (from, to) = (from.min(to), from.max(to));
4090        let mut out = String::new();
4091        let mut prev_run: Option<&ScopeRun<'_>> = None;
4092        for run in &self.scope_runs(scope, prev) {
4093            let (start, end) = run.span();
4094            if end <= from || start >= to {
4095                continue;
4096            }
4097            if let Some(p) = prev_run
4098                && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
4099            {
4100                out.push_str("<br>");
4101            }
4102            let content = run.text.content();
4103            let lo = floor_boundary(content, from.saturating_sub(start));
4104            let hi = floor_boundary(content, (to - start).min(content.len()));
4105            match run.text {
4106                Entry::Long(line) => self.long_html(line, lo, hi, &mut out),
4107                Entry::Run(entry) => html_of_run(entry, lo, hi, &mut out),
4108            }
4109            prev_run = Some(run);
4110        }
4111        out
4112    }
4113
4114    /// `html_of_run` for `lo..hi` of a long line: each chunk the range
4115    /// touches through its own shaped run, so a rich line's spans copy as
4116    /// a short rich text's do — every `break-spaces` text is a long line. A chunk that never
4117    /// showed, or whose run the cache dropped, is plain escaped text: its
4118    /// spans are the line's, but shaping it is not a copy's to do.
4119    fn long_html(&self, line: &LongLine, lo: usize, hi: usize, out: &mut String) {
4120        for c in &line.chunks {
4121            let (a, b) = (lo.max(c.start), hi.min(c.end));
4122            if a >= b {
4123                continue;
4124            }
4125            match c.width.and_then(|_| self.run(c.key)) {
4126                Some(e) => html_of_run(e, a - c.start, b - c.start, out),
4127                None => escape_into(&line.content[a..b], out),
4128            }
4129        }
4130    }
4131
4132    /// Where a platform panel about the selection `from..to` should point:
4133    /// the **baseline origin of its first line**, logical viewport px.
4134    ///
4135    /// Not the box's corner, and not the union's bottom. macOS's
4136    /// `showDefinitionForAttributedString:atPoint:` takes the point the
4137    /// string's own baseline starts at and draws the term back over the
4138    /// text there — so a box's bottom puts the term a line low, and a
4139    /// multi-run selection's union puts it at the bottom of the last line
4140    /// while the panel shows the first.
4141    pub(crate) fn scope_selection_anchor(
4142        &self,
4143        scope: Key,
4144        from: usize,
4145        to: usize,
4146        prev: bool,
4147    ) -> Option<Vec2> {
4148        let (from, to) = (from.min(to), from.max(to));
4149        for run in self.scope_runs(scope, prev) {
4150            let (start, end) = run.span();
4151            if !run.place.drawn || end <= from || start >= to {
4152                continue;
4153            }
4154            let content = run.text.content();
4155            let lo = floor_boundary(content, from.saturating_sub(start));
4156            let hi = floor_boundary(content, (to - start).min(content.len()));
4157            let (ox, oy) = self.physical_origin(run.place);
4158            match run.text {
4159                Entry::Run(e) => {
4160                    if let Some((x, base)) = Self::run_anchor(e, lo, hi) {
4161                        return Some(Vec2::new((ox + x) / self.scale, (oy + base) / self.scale));
4162                    }
4163                }
4164                Entry::Long(l) => {
4165                    if let Some((x, y)) = self.long_caret_local(l, lo) {
4166                        return Some(Vec2::new(
4167                            (ox + x) / self.scale,
4168                            (oy + y + self.long_baseline(l)) / self.scale,
4169                        ));
4170                    }
4171                }
4172            }
4173        }
4174        None
4175    }
4176
4177    /// The x and baseline of the first row of `lo..hi` inside one run,
4178    /// physical px from the run's origin.
4179    fn run_anchor(entry: &CachedText, lo: usize, hi: usize) -> Option<(f32, f32)> {
4180        let (a, b) = (entry.cursor_of(lo), entry.cursor_of(hi));
4181        for run in entry.buffer.layout_runs() {
4182            if run.line_i < a.line || run.line_i > b.line {
4183                continue;
4184            }
4185            if let Some((x, _)) = run.highlight(a, b).next() {
4186                return Some((x, run.line_y));
4187            }
4188        }
4189        None
4190    }
4191
4192    /// The box the selection `from..to` occupies in `scope`, logical
4193    /// viewport px: the union of the drawn runs it touches, clipped to
4194    /// the part of each run that is actually selected in x only where the
4195    /// run holds both ends. Rough on purpose — it anchors a platform
4196    /// panel, and a panel wants the block of text, not its outline.
4197    pub(crate) fn scope_selection_rect(
4198        &self,
4199        scope: Key,
4200        from: usize,
4201        to: usize,
4202        prev: bool,
4203    ) -> Option<Rect> {
4204        let (from, to) = (from.min(to), from.max(to));
4205        let mut out: Option<Rect> = None;
4206        for run in self.scope_runs(scope, prev) {
4207            let (start, end) = run.span();
4208            if !run.place.drawn || end <= from || start >= to {
4209                continue;
4210            }
4211            let content = run.text.content();
4212            let lo = floor_boundary(content, from.saturating_sub(start));
4213            let hi = floor_boundary(content, (to - start).min(content.len()));
4214            // The rects the *painter* would draw, not the run's box: a
4215            // paragraph that wraps is one run four rows tall, and its box
4216            // is four rows tall with it. Anchoring a panel to that puts it
4217            // under the whole paragraph instead of under the word, which
4218            // is what a reader sees as the popover pointing at nothing.
4219            let (ox, oy) = self.physical_origin(run.place);
4220            let rects = match run.text {
4221                Entry::Long(l) => self.long_highlight(l, lo, hi),
4222                Entry::Run(e) => Self::run_highlight(e, lo, hi),
4223            };
4224            for r in rects {
4225                let r = Rect::new(
4226                    (ox + r.x) / self.scale,
4227                    (oy + r.y) / self.scale,
4228                    r.w / self.scale,
4229                    r.h / self.scale,
4230                );
4231                out = Some(match out {
4232                    None => r,
4233                    Some(o) => o.union(&r),
4234                });
4235            }
4236        }
4237        out
4238    }
4239
4240    /// The scope's text between two offsets in its concatenation, with a
4241    /// newline between two runs that were laid out on different lines and
4242    /// nothing between two that share one. The exact length is known before a byte
4243    /// is copied, so this reserves once and grows never.
4244    pub(crate) fn scope_slice(&self, scope: Key, from: usize, to: usize, prev: bool) -> String {
4245        let (from, to) = (from.min(to), from.max(to));
4246        let runs = self.scope_runs(scope, prev);
4247        let mut out = String::new();
4248        let mut reserved = false;
4249        let mut prev_run: Option<&ScopeRun<'_>> = None;
4250        for run in &runs {
4251            let (start, end) = run.span();
4252            if end <= from || start >= to {
4253                continue;
4254            }
4255            if !reserved {
4256                out.reserve(to - from + runs.len());
4257                reserved = true;
4258            }
4259            if let Some(p) = prev_run
4260                && (p.place.origin.y - run.place.origin.y).abs() > f32::EPSILON
4261            {
4262                out.push('\n');
4263            }
4264            let content = run.text.content();
4265            let lo = from.saturating_sub(start).min(content.len());
4266            let hi = (to - start).min(content.len());
4267            out.push_str(&content[floor_boundary(content, lo)..floor_boundary(content, hi)]);
4268            prev_run = Some(run);
4269        }
4270        out
4271    }
4272
4273    /// `hit_at` for a long line: the chunk under the point answers through
4274    /// its shaped entry, an unshaped one (never on screen) by the mean
4275    /// advance.
4276    fn long_hit(&self, place: &TextPlace, line: &LongLine, point: Vec2) -> Option<TextHit> {
4277        let (ox, oy) = self.physical_origin(place);
4278        let px = point.x * self.scale - ox;
4279        let py = point.y * self.scale - oy;
4280        if line.chunks.is_empty() {
4281            return Some(TextHit { byte: 0, line: 0 });
4282        }
4283        if let Some(w) = line.wrap_w {
4284            return self.long_hit_wrapped(line, w, px, py);
4285        }
4286        if px >= line.width() {
4287            return Some(TextHit {
4288                byte: line.content.len(),
4289                line: 0,
4290            });
4291        }
4292        let i = line.chunk_at(px.max(0.0));
4293        let c = &line.chunks[i];
4294        let local = px - line.prefix[i];
4295        let byte = match self.run(c.key) {
4296            Some(_) if let Some(b) = line.placed_tab_hit(i, local) => b,
4297            Some(e) if c.width.is_some() => {
4298                let cursor = e.buffer.hit(local, py.clamp(0.0, line.line_h - 0.01))?;
4299                c.start + cursor.index.min(c.end - c.start)
4300            }
4301            _ => {
4302                let est = (local / line.avg.max(f32::EPSILON)).round() as usize;
4303                let mut b = (c.start + est).min(c.end);
4304                while !line.content.is_char_boundary(b) {
4305                    b -= 1;
4306                }
4307                b
4308            }
4309        };
4310        Some(TextHit { byte, line: 0 })
4311    }
4312
4313    /// `long_hit` while the line is wrapped: the row under the point, the
4314    /// chunk on that row the point is over, and the chunk's unwrapped run
4315    /// asked at the x the row was shifted from.
4316    fn long_hit_wrapped(&self, line: &LongLine, w: f32, px: f32, py: f32) -> Option<TextHit> {
4317        let row = ((py / line.line_h).floor().max(0.0) as u32).min(line.rows() - 1);
4318        let Some((a, b)) = line.chunks_on_rows(row, row) else {
4319            return Some(TextHit {
4320                byte: line.content.len(),
4321                line: row,
4322            });
4323        };
4324        // The first chunk whose span on this row reaches past the point,
4325        // else the last one on it.
4326        let i = (a..=b)
4327            .find(|&j| {
4328                let (r1, end_x) = line.starts[j + 1];
4329                px < if r1 == row { end_x } else { w }
4330            })
4331            .unwrap_or(b);
4332        let c = &line.chunks[i];
4333        let (row0, head_x) = line.starts[i];
4334        let r = (row - row0) as usize;
4335        let byte = match self.run(c.key) {
4336            Some(e) if r < c.rows.len() => {
4337                let rs = c.rows[r];
4338                let local_x = px - if r == 0 { head_x } else { 0.0 } + rs.x;
4339                let lo = rs.byte as usize;
4340                let hi = c
4341                    .rows
4342                    .get(r + 1)
4343                    .map_or(c.end - c.start, |n| n.byte as usize);
4344                // A tab the line places, when it is on this row.
4345                let tab_here = (lo..hi).contains(&(c.end - c.start - 1));
4346                match line.placed_tab_hit(i, local_x) {
4347                    Some(b) if tab_here => b,
4348                    _ => {
4349                        let cursor = e.buffer.hit(local_x, line.line_h / 2.0)?;
4350                        c.start + cursor.index.clamp(lo, hi)
4351                    }
4352                }
4353            }
4354            _ => {
4355                // The inverse of `long_caret`'s estimate: rows back into
4356                // one linear run from the chunk's start.
4357                let linear = r as f32 * w + px - head_x;
4358                let est = (linear / line.avg.max(f32::EPSILON)).round() as usize;
4359                let mut b = (c.start + est).min(c.end);
4360                while !line.content.is_char_boundary(b) {
4361                    b -= 1;
4362                }
4363                b
4364            }
4365        };
4366        // The byte a row breaks at is the next row's first, and its caret
4367        // is drawn there: a point past this row's end answers the byte
4368        // before the row's last character, which is on this row, as a
4369        // `word` run's hanging space does (the alpha.22 regression pass).
4370        let next_row = self
4371            .long_caret_local(line, byte)
4372            .is_some_and(|(_, y)| y > (row as f32 + 0.5) * line.line_h);
4373        let byte = if next_row && byte > 0 {
4374            floor_boundary(&line.content, byte - 1)
4375        } else {
4376            byte
4377        };
4378        Some(TextHit { byte, line: row })
4379    }
4380
4381    /// `caret_at` for a long line, exact in a shaped chunk and by the mean
4382    /// advance in one that never showed.
4383    fn long_caret(&self, place: &TextPlace, line: &LongLine, byte: usize) -> Option<Rect> {
4384        let (ox, oy) = self.physical_origin(place);
4385        let scale = self.scale;
4386        let (x, y) = self.long_caret_local(line, byte)?;
4387        Some(Rect::new(
4388            (ox + x) / scale,
4389            (oy + y) / scale,
4390            0.0,
4391            line.line_h / scale,
4392        ))
4393    }
4394
4395    /// A long line's first baseline, physical px below its top: its first
4396    /// chunk's, shaped when the line was built, rounded as its glyphs are
4397    /// drawn at it — a fifth of the row above the row's foot only while
4398    /// that chunk is not shaped. On that estimate alone a `break-spaces`
4399    /// text in a baseline row would sit a pixel off a `word` one.
4400    fn long_baseline(&self, line: &LongLine) -> f32 {
4401        line.chunks
4402            .first()
4403            .and_then(|c| self.run(c.key))
4404            .and_then(|e| e.buffer.layout_runs().next())
4405            .map_or(line.line_h * 0.8, |r| r.line_y.round())
4406    }
4407
4408    /// The caret for `byte`, in physical px from the long line's own
4409    /// origin: its x on its row, and that row's top. What `long_caret`
4410    /// places in the viewport and what a selection highlight measures
4411    /// between.
4412    fn long_caret_local(&self, line: &LongLine, byte: usize) -> Option<(f32, f32)> {
4413        let byte = byte.min(line.content.len());
4414        if let Some(w) = line.wrap_w
4415            && !line.chunks.is_empty()
4416        {
4417            let i = line.chunk_of_byte(byte);
4418            let c = &line.chunks[i];
4419            let local = byte - c.start;
4420            let (row0, head_x) = line.starts[i];
4421            let (r, x) = match self.run(c.key) {
4422                Some(e) if !c.rows.is_empty() => {
4423                    let r = c.rows.partition_point(|rs| rs.byte as usize <= local) - 1;
4424                    let run = e.buffer.layout_runs().next()?;
4425                    let cx = caret_x(&run, local.min(c.end - c.start)) + line.end_shift(i, local);
4426                    (r, cx - c.rows[r].x + if r == 0 { head_x } else { 0.0 })
4427                }
4428                _ => {
4429                    let linear = head_x + local as f32 * line.avg;
4430                    let r = (linear / w).floor();
4431                    (r as usize, linear - r * w)
4432                }
4433            };
4434            let y = (row0 as usize + r) as f32 * line.line_h;
4435            return Some((x, y));
4436        }
4437        let x = if line.chunks.is_empty() {
4438            0.0
4439        } else {
4440            let i = line.chunk_of_byte(byte);
4441            let c = &line.chunks[i];
4442            let local = byte - c.start;
4443            match self.run(c.key) {
4444                Some(e) if c.width.is_some() => {
4445                    let run = e.buffer.layout_runs().next()?;
4446                    let x = caret_x(&run, local.min(c.end - c.start));
4447                    line.prefix[i] + x + line.end_shift(i, local)
4448                }
4449                _ => line.prefix[i] + local as f32 * line.avg,
4450            }
4451        };
4452        Some((x, 0.0))
4453    }
4454
4455    /// Selection rects for the byte range `from..to` of a long line, in
4456    /// physical px from the line's origin. One rect per row it covers:
4457    /// the first from the start caret to the row's end, whole rows
4458    /// between, and the last from the row's start to the end caret — the
4459    /// shape any selection over wrapped text has.
4460    fn long_highlight(&self, line: &LongLine, from: usize, to: usize) -> Vec<Rect> {
4461        let Some((x0, y0)) = self.long_caret_local(line, from) else {
4462            return Vec::new();
4463        };
4464        let Some((x1, y1)) = self.long_caret_local(line, to) else {
4465            return Vec::new();
4466        };
4467        let h = line.line_h;
4468        if (y1 - y0).abs() < f32::EPSILON {
4469            return vec![Rect::new(x0, y0, (x1 - x0).max(0.0), h)];
4470        }
4471        // Several rows: only a wrapped line has any, so it has a width.
4472        let w = line.wrap_w.unwrap_or_else(|| line.width());
4473        let mut out = vec![Rect::new(x0, y0, (w - x0).max(0.0), h)];
4474        let mut y = y0 + h;
4475        while y < y1 - h / 2.0 {
4476            out.push(Rect::new(0.0, y, w, h));
4477            y += h;
4478        }
4479        out.push(Rect::new(0.0, y1, x1.max(0.0), h));
4480        out
4481    }
4482
4483    /// Selection rects for the byte range `from..to` of an ordinary
4484    /// shaped run, in physical px from the run's origin — cosmic-text's
4485    /// own `highlight`, which is what the editor paints with
4486    /// (`crates/kui-core/src/edit.rs`), so a selection over a label and
4487    /// one over a field are the same shape.
4488    fn run_highlight(entry: &CachedText, from: usize, to: usize) -> Vec<Rect> {
4489        let (a, b) = (entry.cursor_of(from), entry.cursor_of(to));
4490        let mut out = Vec::new();
4491        for run in entry.buffer.layout_runs() {
4492            if run.line_i < a.line || run.line_i > b.line {
4493                continue;
4494            }
4495            // The run's own height: a line holding a larger span is taller
4496            // than the paragraph's metric.
4497            let h = run.line_height;
4498            let mut any = false;
4499            for (x, w) in run.highlight(a, b) {
4500                any = true;
4501                out.push(Rect::new(x, run.line_top, w.max(2.0), h));
4502            }
4503            // A selected newline on an empty line keeps the highlight
4504            // continuous, the way the editor's does.
4505            if !any && run.glyphs.is_empty() && b.line > run.line_i {
4506                out.push(Rect::new(0.0, run.line_top, 2.0, h));
4507            }
4508        }
4509        out
4510    }
4511
4512    /// The physical origin `emit` drew a place at: what a point is
4513    /// measured from and a rect is measured to.
4514    fn physical_origin(&self, place: &TextPlace) -> (f32, f32) {
4515        (
4516            crate::geom::snap_px(place.origin.x * self.scale),
4517            crate::geom::snap_px(place.origin.y * self.scale),
4518        )
4519    }
4520
4521    /// A run's laid-out box, physical px in viewport space.
4522    fn physical_box(&self, place: &TextPlace, entry: &CachedText) -> Rect {
4523        let (ox, oy) = self.physical_origin(place);
4524        let (size, _) = measure_buffer(&entry.buffer, entry.max_lines);
4525        Rect::new(ox, oy, size.w, size.h)
4526    }
4527
4528    /// Where `point` (logical viewport px) lands in the text `key` drew;
4529    /// see `Core::text_hit`. With several runs, the run under the point,
4530    /// else the nearest one on the point's line, else the nearest line.
4531    pub(crate) fn hit_at(&self, key: Key, point: Vec2, prev: bool) -> Option<TextHit> {
4532        let runs = self.runs_of(key, prev);
4533        if runs.is_empty() {
4534            return None;
4535        }
4536        let px = point.x * self.scale;
4537        let py = point.y * self.scale;
4538        // Distance from a run's box: vertical first, so a point on a line
4539        // of runs picks among that line, then horizontal.
4540        let gap = |r: &Rect| {
4541            let dy = (r.y - py).max(py - (r.y + r.h)).max(0.0);
4542            let dx = (r.x - px).max(px - (r.x + r.w)).max(0.0);
4543            (dy, dx)
4544        };
4545        let (place, entry, base) = runs
4546            .iter()
4547            .min_by(|a, b| {
4548                let ga = gap(&self.entry_box(a.0, a.1));
4549                let gb = gap(&self.entry_box(b.0, b.1));
4550                ga.partial_cmp(&gb).unwrap_or(std::cmp::Ordering::Equal)
4551            })
4552            .copied()?;
4553        let (ox, oy) = self.physical_origin(place);
4554        let (byte, hit_top) = match entry {
4555            Entry::Long(line) => {
4556                let hit = self.long_hit(place, line, point)?;
4557                (hit.byte, oy + hit.line as f32 * line.line_h)
4558            }
4559            Entry::Run(entry) => {
4560                // Into the run's box vertically: the nearest run was
4561                // chosen for a point outside every run, and cosmic-text
4562                // answers a point above its first row with byte 0 whatever
4563                // the x — so a press in the padding above a line's text
4564                // placed the caret at its start (backlog C34). Clamped, it
4565                // hits the nearest row at that x.
4566                let bx = self.physical_box(place, entry);
4567                let py = py.clamp(bx.y, (bx.y + bx.h - 0.01).max(bx.y));
4568                let cursor = entry.buffer.hit(px - ox, py - oy)?;
4569                let row =
4570                    visual_line(&entry.buffer, cursor.line, cursor.index).map_or(0, |(l, _)| l);
4571                let top = entry
4572                    .buffer
4573                    .layout_runs()
4574                    .nth(row)
4575                    .map_or(0.0, |r| r.line_top);
4576                (entry.byte_of(cursor), oy + top)
4577            }
4578        };
4579        // The visual row within the node (AR30): the row the hit landed
4580        // on, placed among every row of every run the key covers by its
4581        // top edge, so runs side by side share a row and runs stacked
4582        // count in turn.
4583        let mut tops: Vec<f32> = runs
4584            .iter()
4585            .flat_map(|(p, e, _)| self.row_tops(p, e))
4586            .collect();
4587        tops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4588        tops.dedup_by(|a, b| (*a - *b).abs() < 0.5);
4589        let line = tops.iter().filter(|t| **t < hit_top - 0.5).count();
4590        Some(TextHit {
4591            byte: base + byte,
4592            line: line as u32,
4593        })
4594    }
4595
4596    /// The caret rect for `byte` in the text `key` drew; see
4597    /// `Core::caret_rect`. A byte on the seam between two runs is the
4598    /// start of the later one, except at the very end.
4599    pub(crate) fn caret_at(&self, key: Key, byte: usize, prev: bool) -> Option<Rect> {
4600        let runs = self.runs_of(key, prev);
4601        let total = runs.last().map(|(_, e, base)| base + e.content().len())?;
4602        let byte = byte.min(total);
4603        let (place, entry, base) = runs
4604            .iter()
4605            .find(|(_, e, base)| byte < base + e.content().len())
4606            .or_else(|| runs.last())
4607            .copied()?;
4608        let byte = (byte - base).min(entry.content().len());
4609        let entry = match entry {
4610            Entry::Long(line) => return self.long_caret(place, line, byte),
4611            Entry::Run(entry) => entry,
4612        };
4613        let (ox, oy) = self.physical_origin(place);
4614        let cursor = entry.cursor_of(byte);
4615        let (_, run_no) = visual_line(&entry.buffer, cursor.line, cursor.index)?;
4616        let run = entry.buffer.layout_runs().nth(run_no)?;
4617        let x = caret_x(&run, cursor.index);
4618        let scale = self.scale;
4619        Some(Rect::new(
4620            (ox + x) / scale,
4621            (oy + run.line_top) / scale,
4622            0.0,
4623            run.line_height / scale,
4624        ))
4625    }
4626}
4627
4628/// The byte offset in `content` at which each of the buffer's paragraphs
4629/// starts. cosmic-text splits on every line ending it knows and keeps the
4630/// ending out of the paragraph's text, so each start is found by matching
4631/// the paragraph back onto the content and skipping the ending after it.
4632/// The word around `byte`: the run of like characters it sits in, where
4633/// "like" is one of three classes — word characters (alphanumeric or
4634/// `_`), whitespace, and everything else. At the very end of the text the
4635/// word before it, so a double click past the last character selects the
4636/// last word rather than nothing.
4637fn word_range(content: &str, byte: usize) -> (usize, usize) {
4638    if content.is_empty() {
4639        return (0, 0);
4640    }
4641    #[derive(PartialEq)]
4642    enum Class {
4643        Word,
4644        Space,
4645        Other,
4646    }
4647    let class = |c: char| {
4648        if c.is_alphanumeric() || c == '_' {
4649            Class::Word
4650        } else if c.is_whitespace() {
4651            Class::Space
4652        } else {
4653            Class::Other
4654        }
4655    };
4656    // Past the end (or on the boundary after the last character), the
4657    // word being pointed at is the one just passed, so step back into it.
4658    let at = floor_boundary(content, byte.min(content.len()));
4659    let at = if at >= content.len() {
4660        content.char_indices().next_back().map_or(0, |(i, _)| i)
4661    } else {
4662        at
4663    };
4664    let Some(here) = content[at..].chars().next().map(class) else {
4665        return (at, at);
4666    };
4667    let mut start = at;
4668    for (i, c) in content[..at].char_indices().rev() {
4669        if class(c) != here {
4670            break;
4671        }
4672        start = i;
4673    }
4674    let mut end = at;
4675    for (i, c) in content[at..].char_indices() {
4676        if class(c) != here {
4677            break;
4678        }
4679        end = at + i + c.len_utf8();
4680    }
4681    (start, end)
4682}
4683
4684/// One shaped run's `lo..hi` as HTML, span by span: cosmic-text keeps the
4685/// attributes the text was shaped with, so the bold in a rich paragraph is
4686/// still readable off the buffer long after the spans that declared it are
4687/// gone.
4688fn html_of_run(entry: &CachedText, lo: usize, hi: usize, out: &mut String) {
4689    let starts = line_starts(&entry.buffer, &entry.content);
4690    for (li, line) in entry.buffer.lines.iter().enumerate() {
4691        let base = starts.get(li).copied().unwrap_or(0);
4692        let text = line.text();
4693        if li > 0 {
4694            // A newline inside one node's text is a line break in the
4695            // copy, the same way it is on screen.
4696            if base > lo && base <= hi {
4697                out.push_str("<br>");
4698            }
4699        }
4700        // `spans_iter` lists only the ranges something *changed* — a
4701        // plain paragraph has none at all and reads its line's defaults —
4702        // so this walks the line and asks per character, coalescing runs
4703        // that answer the same. `get_span` falls back to the defaults
4704        // itself, which is exactly the case a rich paragraph's gaps are.
4705        let attrs_list = line.attrs_list();
4706        let mut spans: Vec<(usize, usize, cosmic_text::AttrsOwned)> = Vec::new();
4707        for (i, _) in text.char_indices() {
4708            let a = cosmic_text::AttrsOwned::new(&attrs_list.get_span(i));
4709            match spans.last_mut() {
4710                Some((_, end, prev)) if *prev == a && *end == i => {
4711                    *end = i + text[i..].chars().next().map_or(1, char::len_utf8);
4712                }
4713                _ => spans.push((i, i + text[i..].chars().next().map_or(1, char::len_utf8), a)),
4714            }
4715        }
4716        for (start, end, attrs) in &spans {
4717            let (s, e) = (base + start, base + end);
4718            let (s, e) = (s.max(lo), e.min(hi));
4719            if s >= e {
4720                continue;
4721            }
4722            let piece = &entry.content[s..e];
4723            // Heavier than the line's default, which is the family's
4724            // regular (F100): a family whose bold face is its SemiBold
4725            // bolds at 600, and one whose only face is 700 is regular
4726            // there.
4727            let bold = attrs.weight > attrs_list.defaults().weight
4728                || attrs
4729                    .cache_key_flags
4730                    .contains(crate::weights::SYNTHETIC_BOLD);
4731            let italic = attrs.style != cosmic_text::Style::Normal;
4732            if let Some(c) = attrs.color_opt {
4733                let _ = std::fmt::Write::write_fmt(
4734                    out,
4735                    format_args!(
4736                        "<span style=\"color:#{:02x}{:02x}{:02x}\">",
4737                        c.r(),
4738                        c.g(),
4739                        c.b()
4740                    ),
4741                );
4742            }
4743            if bold {
4744                out.push_str("<b>");
4745            }
4746            if italic {
4747                out.push_str("<i>");
4748            }
4749            escape_into(piece, out);
4750            if italic {
4751                out.push_str("</i>");
4752            }
4753            if bold {
4754                out.push_str("</b>");
4755            }
4756            if attrs.color_opt.is_some() {
4757                out.push_str("</span>");
4758            }
4759        }
4760    }
4761}
4762
4763/// HTML-escapes into `out`. The four that matter in element content and
4764/// nothing else: a clipboard flavour is not a document, and over-escaping
4765/// is what turns a copied apostrophe into `&#39;` in someone's email.
4766fn escape_into(s: &str, out: &mut String) {
4767    for ch in s.chars() {
4768        match ch {
4769            '&' => out.push_str("&amp;"),
4770            '<' => out.push_str("&lt;"),
4771            '>' => out.push_str("&gt;"),
4772            _ => out.push(ch),
4773        }
4774    }
4775}
4776
4777/// The nearest char boundary at or below `i`, so a slice taken from a/// The
4778/// nearest char boundary at or below `i`, so a slice taken from a
4779/// selection never splits a character. A selection's ends come from
4780/// shaping and are boundaries already; this is for the arithmetic around
4781/// them (a clamp, a saturating subtraction) that has no such guarantee.
4782fn floor_boundary(s: &str, i: usize) -> usize {
4783    let mut i = i.min(s.len());
4784    while i > 0 && !s.is_char_boundary(i) {
4785        i -= 1;
4786    }
4787    i
4788}
4789
4790fn line_starts(buffer: &Buffer, content: &str) -> Vec<usize> {
4791    paragraph_starts(buffer, content).collect()
4792}
4793
4794/// Where each of `buffer`'s paragraphs starts in `content`, in order.
4795/// cosmic-text splits the text at line endings and keeps the pieces, so
4796/// the ending bytes between two paragraphs are found again in `content`
4797/// — any of the four spellings — and skipped. No allocation: the two
4798/// conversions below walk this once and stop.
4799fn paragraph_starts<'a>(buffer: &'a Buffer, content: &'a str) -> impl Iterator<Item = usize> + 'a {
4800    let mut at = 0usize;
4801    buffer.lines.iter().map(move |line| {
4802        let start = at.min(content.len());
4803        at += line.text().len();
4804        let rest = &content[at.min(content.len())..];
4805        for ending in ["\r\n", "\n\r", "\n", "\r"] {
4806            if rest.starts_with(ending) {
4807                at += ending.len();
4808                break;
4809            }
4810        }
4811        start
4812    })
4813}
4814
4815/// The visual line (0-based over every wrapped line of the buffer) that
4816/// byte `index` of paragraph `line_i` lays out on, and that run's ordinal
4817/// in `layout_runs()` (the same number, kept apart for reading): the run
4818/// whose glyphs cover the byte, else the last run that starts before it —
4819/// a byte with no glyph inside the paragraph is whitespace a `Word` break
4820/// swallowed, which ends the row it broke, and the end of the paragraph
4821/// ends its last — else the paragraph's last run, for a paragraph with no
4822/// glyphs. The paragraph's last run whatever the byte, as it was, put a
4823/// caret on a swallowed space at the end of the whole paragraph.
4824fn visual_line(buffer: &Buffer, line_i: usize, index: usize) -> Option<(usize, usize)> {
4825    let mut last_of_line = None;
4826    let mut before = None;
4827    for (n, run) in buffer.layout_runs().enumerate() {
4828        if run.line_i != line_i {
4829            if last_of_line.is_some() {
4830                break;
4831            }
4832            continue;
4833        }
4834        last_of_line = Some(n);
4835        if run.glyphs.iter().any(|g| g.start <= index && index < g.end) {
4836            return Some((n, n));
4837        }
4838        if run
4839            .glyphs
4840            .iter()
4841            .map(|g| g.start)
4842            .min()
4843            .is_some_and(|s| s <= index)
4844        {
4845            before = Some(n);
4846        }
4847    }
4848    before.or(last_of_line).map(|n| (n, n))
4849}
4850
4851/// Where the caret sits for byte `index` inside `run`, physical px from the
4852/// text origin: the leading edge of the glyph covering it — its right edge
4853/// in a right-to-left run — and past the last glyph at the run's end.
4854fn caret_x(run: &cosmic_text::LayoutRun<'_>, index: usize) -> f32 {
4855    if let Some(g) = run
4856        .glyphs
4857        .iter()
4858        .find(|g| g.start <= index && index < g.end)
4859    {
4860        return if g.level.is_rtl() { g.x + g.w } else { g.x };
4861    }
4862    match run.glyphs.last() {
4863        Some(g) if !g.level.is_rtl() => g.x + g.w,
4864        Some(g) => g.x,
4865        None => 0.0,
4866    }
4867}
4868
4869impl Default for TextSystem {
4870    fn default() -> Self {
4871        Self::new()
4872    }
4873}
4874
4875/// The physical wrap width an entry needs for a logical `max_w`: none when
4876/// the unwrapped text already fits (or no width was given).
4877fn wrap_target(entry: &CachedText, max_w_logical: Option<f32>, scale: f32) -> Option<f32> {
4878    let max_w = max_w_logical?;
4879    let intrinsic_fits = entry.intrinsic.w <= max_w * scale + 0.5;
4880    (!intrinsic_fits).then_some(max_w * scale)
4881}
4882
4883/// Re-lays the entry's buffer out at `target` (physical px) if it is not
4884/// already there.
4885/// Whether a buffer laid out at `current` needs laying out again for
4886/// `target`: a wrap width that moved by half a physical pixel or less is
4887/// the same wrap, which is what keeps a box that jitters by float error
4888/// from reshaping its text every frame. The one rule for the text cache
4889/// and the editors alike.
4890pub(crate) fn wrap_differs(current: Option<f32>, target: Option<f32>) -> bool {
4891    match (current, target) {
4892        (None, None) => false,
4893        (Some(a), Some(b)) => (a - b).abs() > 0.5,
4894        _ => true,
4895    }
4896}
4897
4898fn wrap_entry(entry: &mut CachedText, fs: &mut FontSystem, target: Option<f32>) {
4899    if wrap_differs(entry.wrap, target) {
4900        entry.buffer.set_size(target, None);
4901        entry.buffer.shape_until_scroll(fs, false);
4902        entry.wrap = target;
4903    }
4904}
4905
4906impl CachedText {
4907    /// The byte offset in `content` that `cursor` (a paragraph and an
4908    /// index into it) names, clamped to the content.
4909    fn byte_of(&self, cursor: cosmic_text::Cursor) -> usize {
4910        let start = paragraph_starts(&self.buffer, &self.content)
4911            .nth(cursor.line)
4912            .unwrap_or(0);
4913        (start + cursor.index).min(self.content.len())
4914    }
4915
4916    /// The cursor at `byte`: the paragraph starting at or before it — the
4917    /// last one that does — and the offset inside that paragraph's own
4918    /// text, clamped to it. The inverse of [`Self::byte_of`]; the one
4919    /// place the byte ↔ paragraph arithmetic lives, where three copies
4920    /// used to (`run_anchor`, `run_highlight`, `caret_at`).
4921    fn cursor_of(&self, byte: usize) -> cosmic_text::Cursor {
4922        let mut li = 0;
4923        let mut start = 0;
4924        for (i, s) in paragraph_starts(&self.buffer, &self.content).enumerate() {
4925            if s <= byte {
4926                (li, start) = (i, s);
4927            } else {
4928                break;
4929            }
4930        }
4931        let len = self.buffer.lines.get(li).map_or(0, |l| l.text().len());
4932        cosmic_text::Cursor::new(li, (byte - start).min(len))
4933    }
4934
4935    fn new(
4936        mut buffer: Buffer,
4937        content: String,
4938        style: &TextStyle,
4939        span_deco: Vec<SpanDeco>,
4940        fs: &mut FontSystem,
4941        frame_no: u64,
4942    ) -> Self {
4943        buffer.shape_until_scroll(fs, false);
4944        let max_lines = line_budget(style);
4945        let (intrinsic, _) = measure_buffer(&buffer, max_lines);
4946        let glyphs: usize = buffer.layout_runs().map(|r| r.glyphs.len()).sum();
4947        Self {
4948            buffer,
4949            bytes: ENTRY_BASE_BYTES + content.len() + ENTRY_GLYPH_BYTES * glyphs,
4950            content,
4951            wrap: None,
4952            intrinsic,
4953            max_lines,
4954            clamp_w: style.wrap == TextWrap::None || style.ellipsis,
4955            last_used: frame_no,
4956            glyphs: Vec::new(),
4957            deco: Vec::new(),
4958            span_deco,
4959            glyphs_built_for: None,
4960            claimed: u64::MAX,
4961            claimed_wrap: None,
4962            breaks_anywhere: style.wrap == TextWrap::Glyph,
4963            min_content: None,
4964        }
4965    }
4966
4967    /// A copy laid out as this one is, for a second node that draws the
4968    /// same text at another width ([`TextSystem::own_wrap`]): the shaped
4969    /// buffer, not the templates, which the copy builds when drawn.
4970    fn fork(&self, frame_no: u64) -> Self {
4971        Self {
4972            buffer: self.buffer.clone(),
4973            content: self.content.clone(),
4974            wrap: self.wrap,
4975            intrinsic: self.intrinsic,
4976            max_lines: self.max_lines,
4977            clamp_w: self.clamp_w,
4978            last_used: frame_no,
4979            bytes: self.bytes,
4980            glyphs: Vec::new(),
4981            deco: Vec::new(),
4982            span_deco: self.span_deco.clone(),
4983            glyphs_built_for: None,
4984            claimed: u64::MAX,
4985            claimed_wrap: None,
4986            breaks_anywhere: self.breaks_anywhere,
4987            min_content: self.min_content,
4988        }
4989    }
4990
4991    /// The widest stretch of this text that no break opportunity falls
4992    /// inside, physical px — CSS's min-content: the longest word under
4993    /// `word` and `break-spaces` (a word longer than the box still breaks
4994    /// between glyphs when drawn, as `overflow-wrap: break-word` does in
4995    /// CSS, which leaves the min-content alone), the widest glyph under
4996    /// `glyph`, and the whole line under `none` or an ellipsis, which
4997    /// never wrap. Trailing whitespace hangs, as in CSS. Read off the
4998    /// shaped glyphs of whatever width the buffer was last laid out at —
4999    /// a glyph's advance is the same at every width — with the breaks
5000    /// `unicode-linebreak` finds, so asking lays nothing out.
5001    fn min_content(&mut self) -> f32 {
5002        if let Some(w) = self.min_content {
5003            return w;
5004        }
5005        let w = if self.clamp_w {
5006            self.intrinsic.w
5007        } else {
5008            widest_unbreakable(&self.buffer, self.breaks_anywhere)
5009        };
5010        self.min_content = Some(w);
5011        w
5012    }
5013}
5014
5015/// [`CachedText::min_content`]'s walk: every run of a line in order, a
5016/// stretch closing at each glyph a break falls before, and a stretch's
5017/// width up to its last glyph that is not whitespace.
5018fn widest_unbreakable(buffer: &Buffer, anywhere: bool) -> f32 {
5019    let mut widest = 0.0f32;
5020    let mut line = usize::MAX;
5021    let mut breaks: Vec<usize> = Vec::new();
5022    let (mut run_w, mut ink) = (0.0f32, 0.0f32);
5023    for run in buffer.layout_runs() {
5024        if run.line_i != line {
5025            widest = widest.max(ink);
5026            (run_w, ink) = (0.0, 0.0);
5027            line = run.line_i;
5028            breaks.clear();
5029            if !anywhere {
5030                breaks.extend(unicode_linebreak::linebreaks(run.text).map(|(i, _)| i));
5031            }
5032        }
5033        for g in run.glyphs {
5034            if anywhere || breaks.binary_search(&g.start).is_ok() {
5035                widest = widest.max(ink);
5036                (run_w, ink) = (0.0, 0.0);
5037            }
5038            run_w += g.w;
5039            let blank = run
5040                .text
5041                .get(g.start..g.end)
5042                .is_some_and(|t| t.chars().all(char::is_whitespace));
5043            if !blank {
5044                ink = run_w;
5045            }
5046        }
5047    }
5048    widest.max(ink)
5049}
5050
5051/// The shaper's metrics for a font size and line height in physical px,
5052/// each at least one pixel: cosmic-text asserts a line height is not 0,
5053/// which aborted a Node or Lua process over `lineHeight = 0` (or a size
5054/// under 0.4, whose derived height rounds to 0), and a negative one spun
5055/// its layout without end. A NaN or an infinity is a pixel too. Every
5056/// buffer kui shapes into is made through here.
5057pub(crate) fn shaper_metrics(size: f32, line_height: f32) -> Metrics {
5058    let px = |v: f32| if v.is_finite() { v.max(1.0) } else { 1.0 };
5059    Metrics::new(px(size), px(line_height))
5060}
5061
5062/// `s` as the shaper can take it: one bidi paragraph a line (backlog
5063/// FZ6). cosmic-text breaks lines at `\n` and `\r` alone, but Unicode's
5064/// bidi algorithm ends a paragraph at every character of class B — the
5065/// information separators U+001C–U+001E, NEL (U+0085) and the paragraph
5066/// separator (U+2029) too — and cosmic-text asserts that each paragraph
5067/// it is handed in one line runs the way the first does: `a\u{1e}א`
5068/// panicked the shaper. Each is swapped for a character of the same
5069/// length in UTF-8 that ends nothing, so every byte offset into the text
5070/// — a caret's, a selection's, a span's — still lands where it did: the
5071/// unit separator (U+001F) for the other three, a C1 control (U+0080) for
5072/// NEL, the line separator (U+2028) for the paragraph one. They draw as
5073/// what they are, nothing. Borrowed when there is none, which is always
5074/// but for text that carries them.
5075pub(crate) fn one_paragraph(s: &str) -> std::borrow::Cow<'_, str> {
5076    let ends = |c: char| matches!(c, '\u{1c}'..='\u{1e}' | '\u{85}' | '\u{2029}');
5077    if !s.contains(ends) {
5078        return std::borrow::Cow::Borrowed(s);
5079    }
5080    std::borrow::Cow::Owned(
5081        s.chars()
5082            .map(|c| match c {
5083                '\u{1c}'..='\u{1e}' => '\u{1f}',
5084                '\u{85}' => '\u{80}',
5085                '\u{2029}' => '\u{2028}',
5086                c => c,
5087            })
5088            .collect(),
5089    )
5090}
5091
5092/// A buffer set up for the style's line breaking: cosmic-text's wrap mode,
5093/// plus tail ellipsizing at the line budget when asked for.
5094fn new_buffer(fs: &mut FontSystem, style: &TextStyle, scale: f32) -> Buffer {
5095    let metrics = shaper_metrics(style.size * scale, style.line_height * scale);
5096    let mut buffer = Buffer::new(fs, metrics);
5097    buffer.set_wrap(match style.wrap {
5098        TextWrap::Word | TextWrap::BreakSpaces => Wrap::WordOrGlyph,
5099        TextWrap::Glyph => Wrap::Glyph,
5100        TextWrap::None => Wrap::None,
5101    });
5102    if style.ellipsis {
5103        let lines = line_budget(style).max(1);
5104        buffer.set_ellipsize(Ellipsize::End(EllipsizeHeightLimit::Lines(lines)));
5105    }
5106    buffer.set_size(None, None);
5107    buffer
5108}
5109
5110/// The line budget a style implies: `max_lines`, or one line when only
5111/// `ellipsis` is set (0 = unlimited).
5112fn line_budget(style: &TextStyle) -> usize {
5113    if style.max_lines > 0 {
5114        style.max_lines as usize
5115    } else if style.ellipsis {
5116        1
5117    } else {
5118        0
5119    }
5120}
5121
5122fn line_cap(max_lines: usize) -> usize {
5123    if max_lines == 0 {
5124        usize::MAX
5125    } else {
5126        max_lines
5127    }
5128}
5129
5130/// Physical size of the laid-out buffer plus its line count (both capped
5131/// by the line budget).
5132/// The widest laid-out line and the line count of `buffer`, up to
5133/// `max_lines` of them (0 = all), as physical px: what a text node and an
5134/// editor both measure themselves by.
5135pub(crate) fn measure_buffer(buffer: &Buffer, max_lines: usize) -> (Size, u32) {
5136    let mut w = 0.0f32;
5137    let mut lines = 0u32;
5138    // Lines as tall as their tallest span (`Span::size`): the sum of the
5139    // runs' own heights, and the metric times the count when every run is
5140    // the metric's, exactly as before.
5141    let lh = buffer.metrics().line_height;
5142    let (mut sum, mut uniform) = (0.0f32, true);
5143    for run in buffer.layout_runs().take(line_cap(max_lines)) {
5144        w = w.max(run.line_w);
5145        lines += 1;
5146        sum += run.line_height;
5147        uniform &= run.line_height == lh;
5148    }
5149    (
5150        Size::new(w, if uniform { lines as f32 * lh } else { sum }),
5151        lines,
5152    )
5153}
5154
5155impl TextSystem {
5156    pub(crate) fn intrinsic(&mut self, id: TextId) -> Size {
5157        let scale = self.scale;
5158        if let Some(line) = self.long_of(id) {
5159            return Size::new(line.width() / scale, line.line_h / scale);
5160        }
5161        let e = self.entry_mut(id);
5162        Size::new(e.intrinsic.w / scale, e.intrinsic.h / scale)
5163    }
5164
5165    /// Text `id`'s min-content width, logical px: see
5166    /// [`CachedText::min_content`]. A long line is 0 — its rows are broken
5167    /// to any width, and measuring its words would shape what C19 exists
5168    /// not to.
5169    pub(crate) fn min_content(&mut self, id: TextId) -> f32 {
5170        let scale = self.scale;
5171        if self.long_of(id).is_some() {
5172            return 0.0;
5173        }
5174        self.entry_mut(id).min_content() / scale
5175    }
5176
5177    /// The long line behind one of this frame's texts, if it is one.
5178    fn long_of(&self, id: TextId) -> Option<&LongLine> {
5179        self.long(self.frame[id.0 as usize].cache_key)
5180    }
5181
5182    pub(crate) fn wrapped(&mut self, id: TextId, max_w: f32, fs: &mut FontSystem) -> Size {
5183        let scale = self.scale;
5184        if self.long_of(id).is_some() {
5185            // The box, not the line, is the node's width: emission clips a
5186            // single row to it, or the rows are broken to it.
5187            let key = self.frame[id.0 as usize].cache_key;
5188            let (size, rows) = self.long_size(key, Some(max_w * scale));
5189            self.long_mut(key).expect("a long line").laid_rows = rows;
5190            return Size::new(size.w / scale, size.h / scale);
5191        }
5192        self.ensure_wrap(id, max_w, fs);
5193        let e = self.entry_mut(id);
5194        let (mut m, _) = measure_buffer(&e.buffer, e.max_lines);
5195        if e.clamp_w {
5196            // The line may run past the box; the box, not the line, is
5197            // the node's width (emission clips to it).
5198            m.w = m.w.min(max_w * scale);
5199        }
5200        Size::new(m.w / scale, m.h / scale)
5201    }
5202}
5203
5204impl TextSystem {
5205    /// The first line's baseline of text `id` as `wrapped` last laid it
5206    /// out, logical px below its top. A run's `line_y`, rounded as the glyphs are drawn at
5207    /// it; a long line's is [`Self::long_baseline`]. An empty text is one
5208    /// line of its own metrics.
5209    pub(crate) fn baseline(&mut self, id: TextId) -> f32 {
5210        let scale = self.scale;
5211        if let Some(line) = self.long_of(id) {
5212            return self.long_baseline(line) / scale;
5213        }
5214        let e = self.entry_mut(id);
5215        let b = e
5216            .buffer
5217            .layout_runs()
5218            .next()
5219            .map_or(e.buffer.metrics().line_height * 0.8, |r| r.line_y.round());
5220        b / scale
5221    }
5222}
5223
5224/// The generic families resolve to a face that is what its name says:
5225/// upright, and monospaced for `Mono`.
5226#[cfg(test)]
5227mod default_families {
5228    use super::*;
5229    use cosmic_text::Family;
5230    use cosmic_text::fontdb::FaceInfo;
5231
5232    /// The face `M` shapes with under `family`, on the database as
5233    /// `new_font_system` set it up.
5234    fn resolved(fs: &mut FontSystem, family: Family<'_>) -> Option<FaceInfo> {
5235        let mut buffer = Buffer::new(fs, Metrics::new(14.0, 18.0));
5236        buffer.set_text("M", &Attrs::new().family(family), Shaping::Advanced, None);
5237        buffer.shape_until_scroll(fs, false);
5238        let id = buffer.layout_runs().next()?.glyphs.first()?.font_id;
5239        fs.db().face(id).cloned()
5240    }
5241
5242    #[test]
5243    fn mono_is_an_upright_monospaced_face() {
5244        let mut fs = new_font_system().0;
5245        if !fs.db().faces().any(|f| f.monospaced) {
5246            eprintln!("skipped: no monospaced face installed");
5247            return;
5248        }
5249        let face = resolved(&mut fs, Family::Monospace).expect("M shapes in Mono");
5250        let mono = default_families(&fs)[2].to_string();
5251        assert_eq!(
5252            face.style,
5253            cosmic_text::Style::Normal,
5254            "Mono ({mono}) resolved to {:?}",
5255            face.post_script_name
5256        );
5257        assert!(
5258            face.monospaced,
5259            "Mono ({mono}) resolved to {:?}, which is not monospaced",
5260            face.post_script_name
5261        );
5262    }
5263
5264    #[test]
5265    fn sans_and_serif_are_upright_and_not_monospaced() {
5266        let mut fs = new_font_system().0;
5267        if fs.db().faces().next().is_none() {
5268            eprintln!("skipped: no face installed");
5269            return;
5270        }
5271        let [sans, serif, _] = default_families(&fs).map(str::to_string);
5272        for (family, name) in [(Family::SansSerif, sans), (Family::Serif, serif)] {
5273            let face = resolved(&mut fs, family).expect("M shapes");
5274            assert_eq!(
5275                face.style,
5276                cosmic_text::Style::Normal,
5277                "{name} resolved to {:?}",
5278                face.post_script_name
5279            );
5280            assert!(
5281                !face.monospaced,
5282                "{name} resolved to {:?}, which is monospaced",
5283                face.post_script_name
5284            );
5285        }
5286    }
5287
5288    /// Every name pinned is one an installed face answers to, so
5289    /// `Family::Name(pinned)` and the generic family agree.
5290    #[test]
5291    fn pinned_names_are_installed_or_cosmic_texts_own() {
5292        let fs = new_font_system().0;
5293        let db = fs.db();
5294        for (name, list) in default_families(&fs).into_iter().zip(DEFAULT_FAMILIES) {
5295            let installed = db
5296                .faces()
5297                .any(|f| f.families.iter().any(|(fam, _)| fam == name));
5298            let on_list = list.contains(&name);
5299            assert!(
5300                installed == on_list,
5301                "{name}: installed {installed}, on the platform list {on_list}"
5302            );
5303        }
5304    }
5305}
5306
5307/// A face whose glyph advances cannot be measured never reaches the shaper.
5308/// macOS's GB18030 Bitmap has no `head`, `hhea` or `hmtx`;
5309/// it says it is fixed-pitch and maps the ideographs, so Han text in
5310/// `Mono` fell back to it, and its advances came out infinite: every glyph
5311/// after one was placed at infinity, and a debug build overflowed in
5312/// `LayoutGlyph::physical`. The fixture faces stand in for it here.
5313#[cfg(test)]
5314mod unmeasurable_faces {
5315    use super::*;
5316    use crate::testing::{font_face, han_face, unmeasurable_face};
5317    use cosmic_text::Family;
5318    use cosmic_text::fontdb::{Database, Source};
5319
5320    /// A session font system over the fixture faces alone: a monospaced
5321    /// family without Han, pinned as `Mono`, the face without metrics that
5322    /// maps 字 and says it is fixed-pitch, and a proportional face that
5323    /// maps it too.
5324    fn font_system() -> FontSystem {
5325        let mut db = Database::new();
5326        for bytes in [
5327            font_face("Kui Mono", 400, false, true),
5328            unmeasurable_face("Kui Bitmap"),
5329            han_face("Kui Han"),
5330        ] {
5331            db.load_font_source(Source::Binary(std::sync::Arc::new(bytes)));
5332        }
5333        let mut fs = font_system_with("en-US".into(), db);
5334        fs.db_mut().set_monospace_family("Kui Mono");
5335        fs
5336    }
5337
5338    fn family(fs: &FontSystem, id: cosmic_text::fontdb::ID) -> String {
5339        fs.db()
5340            .face(id)
5341            .map_or_else(String::new, |f| f.families[0].0.clone())
5342    }
5343
5344    #[test]
5345    fn a_face_without_metrics_is_not_in_the_database() {
5346        let fs = font_system();
5347        let families: Vec<String> = fs.db().faces().map(|f| family(&fs, f.id)).collect();
5348        assert_eq!(families, ["Kui Mono", "Kui Han"]);
5349    }
5350
5351    /// "字 a" in `Mono`: 字 from the face that can say how wide it is, the
5352    /// space and the `a` from the monospaced family after it, every glyph
5353    /// at a finite place.
5354    #[test]
5355    fn han_in_mono_falls_back_to_a_face_that_measures() {
5356        let mut fs = font_system();
5357        let mut buffer = Buffer::new(&mut fs, Metrics::new(14.0, 21.0));
5358        let attrs = Attrs::new().family(Family::Monospace);
5359        buffer.set_text("字 a", &attrs, Shaping::Advanced, None);
5360        buffer.shape_until_scroll(&mut fs, false);
5361        let run = buffer.layout_runs().next().expect("one line");
5362        let glyphs: Vec<(&str, f32, f32, String)> = run
5363            .glyphs
5364            .iter()
5365            .map(|g| (&run.text[g.start..g.end], g.x, g.w, family(&fs, g.font_id)))
5366            .collect();
5367        for &(text, x, w, _) in &glyphs {
5368            assert!(x.is_finite() && w.is_finite(), "{text:?} at {x}, {w} wide");
5369        }
5370        let faces: Vec<(&str, &str)> = glyphs.iter().map(|g| (g.0, g.3.as_str())).collect();
5371        assert_eq!(
5372            faces,
5373            [("字", "Kui Han"), (" ", "Kui Mono"), ("a", "Kui Mono")]
5374        );
5375        // The fixture's advance is half an em, 7 px at 14.
5376        let xs: Vec<f32> = glyphs.iter().map(|g| g.1).collect();
5377        assert_eq!(xs, [0.0, 7.0, 14.0]);
5378        for glyph in run.glyphs {
5379            glyph.physical((0.0, 0.0), 1.0);
5380        }
5381    }
5382}