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