Skip to main content

kui_core/
text.rs

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