Skip to main content

kui_core/
text.rs

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