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