cosmic_text/
shape.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3#![allow(clippy::too_many_arguments)]
4
5use crate::fallback::FontFallbackIter;
6use crate::{
7    math, Align, Attrs, AttrsList, CacheKeyFlags, Color, DecorationMetrics, DecorationSpan,
8    Ellipsize, EllipsizeHeightLimit, Font, FontSystem, GlyphDecorationData, Hinting, LayoutGlyph,
9    LayoutLine, Metrics, Wrap,
10};
11#[cfg(not(feature = "std"))]
12use alloc::{format, vec, vec::Vec};
13
14use alloc::collections::VecDeque;
15use core::cmp::{max, min};
16use core::fmt;
17use core::mem;
18use core::ops::Range;
19
20#[cfg(not(feature = "std"))]
21use core_maths::CoreFloat;
22use fontdb::Style;
23use unicode_script::{Script, UnicodeScript};
24use unicode_segmentation::UnicodeSegmentation;
25
26/// The shaping strategy of some text.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum Shaping {
29    /// Basic shaping with no font fallback.
30    ///
31    /// This shaping strategy is very cheap, but it will not display complex
32    /// scripts properly nor try to find missing glyphs in your system fonts.
33    ///
34    /// You should use this strategy when you have complete control of the text
35    /// and the font you are displaying in your application.
36    #[cfg(feature = "swash")]
37    Basic,
38    /// Advanced text shaping and font fallback.
39    ///
40    /// You will need to enable this strategy if the text contains a complex
41    /// script, the font used needs it, and/or multiple fonts in your system
42    /// may be needed to display all of the glyphs.
43    Advanced,
44}
45
46impl Shaping {
47    fn run(
48        self,
49        glyphs: &mut Vec<ShapeGlyph>,
50        font_system: &mut FontSystem,
51        line: &str,
52        attrs_list: &AttrsList,
53        start_run: usize,
54        end_run: usize,
55        span_rtl: bool,
56    ) {
57        match self {
58            #[cfg(feature = "swash")]
59            Self::Basic => shape_skip(font_system, glyphs, line, attrs_list, start_run, end_run),
60            #[cfg(not(feature = "shape-run-cache"))]
61            Self::Advanced => shape_run(
62                glyphs,
63                font_system,
64                line,
65                attrs_list,
66                start_run,
67                end_run,
68                span_rtl,
69            ),
70            #[cfg(feature = "shape-run-cache")]
71            Self::Advanced => shape_run_cached(
72                glyphs,
73                font_system,
74                line,
75                attrs_list,
76                start_run,
77                end_run,
78                span_rtl,
79            ),
80        }
81    }
82}
83
84const NUM_SHAPE_PLANS: usize = 6;
85
86/// A set of buffers containing allocations for shaped text.
87#[derive(Default)]
88pub struct ShapeBuffer {
89    /// Cache for harfrust shape plans. Stores up to [`NUM_SHAPE_PLANS`] plans at once. Inserting a new one past that
90    /// will remove the one that was least recently added (not least recently used).
91    shape_plan_cache: VecDeque<(fontdb::ID, harfrust::ShapePlan)>,
92
93    /// Buffer for holding unicode text.
94    harfrust_buffer: Option<harfrust::UnicodeBuffer>,
95
96    /// Temporary buffers for scripts.
97    scripts: Vec<Script>,
98
99    /// Buffer for shape spans.
100    spans: Vec<ShapeSpan>,
101
102    /// Buffer for shape words.
103    words: Vec<ShapeWord>,
104
105    /// Buffers for visual lines.
106    visual_lines: Vec<VisualLine>,
107    cached_visual_lines: Vec<VisualLine>,
108
109    /// Buffer for sets of layout glyphs.
110    glyph_sets: Vec<Vec<LayoutGlyph>>,
111}
112
113impl fmt::Debug for ShapeBuffer {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        f.pad("ShapeBuffer { .. }")
116    }
117}
118
119fn shape_fallback(
120    scratch: &mut ShapeBuffer,
121    glyphs: &mut Vec<ShapeGlyph>,
122    font: &Font,
123    line: &str,
124    attrs_list: &AttrsList,
125    start_run: usize,
126    end_run: usize,
127    span_rtl: bool,
128) -> Vec<usize> {
129    let run = &line[start_run..end_run];
130
131    let font_scale = font.metrics().units_per_em as f32;
132    let ascent = font.metrics().ascent / font_scale;
133    let descent = -font.metrics().descent / font_scale;
134
135    let mut buffer = scratch.harfrust_buffer.take().unwrap_or_default();
136    buffer.set_direction(if span_rtl {
137        harfrust::Direction::RightToLeft
138    } else {
139        harfrust::Direction::LeftToRight
140    });
141    if run.contains('\t') {
142        // Push string to buffer, replacing tabs with spaces
143        //TODO: Find a way to do this with minimal allocating, calling
144        // UnicodeBuffer::push_str multiple times causes issues and
145        // UnicodeBuffer::add resizes the buffer with every character
146        buffer.push_str(&run.replace('\t', " "));
147    } else {
148        buffer.push_str(run);
149    }
150    buffer.guess_segment_properties();
151
152    let rtl = matches!(buffer.direction(), harfrust::Direction::RightToLeft);
153    assert_eq!(rtl, span_rtl);
154
155    let attrs = attrs_list.get_span(start_run);
156    let mut rb_font_features = Vec::new();
157
158    // Convert attrs::Feature to harfrust::Feature
159    for feature in &attrs.font_features.features {
160        rb_font_features.push(harfrust::Feature::new(
161            harfrust::Tag::new(feature.tag.as_bytes()),
162            feature.value,
163            0..usize::MAX,
164        ));
165    }
166
167    let language = buffer.language();
168    let key = harfrust::ShapePlanKey::new(Some(buffer.script()), buffer.direction())
169        .features(&rb_font_features)
170        .instance(Some(font.shaper_instance()))
171        .language(language.as_ref());
172
173    let shape_plan = match scratch
174        .shape_plan_cache
175        .iter()
176        .find(|(id, plan)| *id == font.id() && key.matches(plan))
177    {
178        Some((_font_id, plan)) => plan,
179        None => {
180            let plan = harfrust::ShapePlan::new(
181                font.shaper(),
182                buffer.direction(),
183                Some(buffer.script()),
184                buffer.language().as_ref(),
185                &rb_font_features,
186            );
187            if scratch.shape_plan_cache.len() >= NUM_SHAPE_PLANS {
188                scratch.shape_plan_cache.pop_front();
189            }
190            scratch.shape_plan_cache.push_back((font.id(), plan));
191            &scratch
192                .shape_plan_cache
193                .back()
194                .expect("we just pushed the shape plan")
195                .1
196        }
197    };
198
199    let glyph_buffer = font
200        .shaper()
201        .shape_with_plan(shape_plan, buffer, &rb_font_features);
202    let glyph_infos = glyph_buffer.glyph_infos();
203    let glyph_positions = glyph_buffer.glyph_positions();
204
205    let mut missing = Vec::new();
206    glyphs.reserve(glyph_infos.len());
207    let glyph_start = glyphs.len();
208    for (info, pos) in glyph_infos.iter().zip(glyph_positions.iter()) {
209        let start_glyph = start_run + info.cluster as usize;
210
211        if info.glyph_id == 0 {
212            missing.push(start_glyph);
213        }
214
215        let attrs = attrs_list.get_span(start_glyph);
216        let x_advance = pos.x_advance as f32 / font_scale
217            + attrs.letter_spacing_opt.map_or(0.0, |spacing| spacing.0);
218        let y_advance = pos.y_advance as f32 / font_scale;
219        let x_offset = pos.x_offset as f32 / font_scale;
220        let y_offset = pos.y_offset as f32 / font_scale;
221
222        glyphs.push(ShapeGlyph {
223            start: start_glyph,
224            end: end_run, // Set later
225            x_advance,
226            y_advance,
227            x_offset,
228            y_offset,
229            ascent,
230            descent,
231            font_monospace_em_width: font.monospace_em_width(),
232            font_id: font.id(),
233            font_weight: attrs.weight,
234            glyph_id: info.glyph_id.try_into().expect("failed to cast glyph ID"),
235            //TODO: color should not be related to shaping
236            color_opt: attrs.color_opt,
237            metadata: attrs.metadata,
238            cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs),
239            metrics_opt: attrs.metrics_opt.map(Into::into),
240        });
241    }
242
243    // Adjust end of glyphs
244    if rtl {
245        for i in glyph_start + 1..glyphs.len() {
246            let next_start = glyphs[i - 1].start;
247            let next_end = glyphs[i - 1].end;
248            let prev = &mut glyphs[i];
249            if prev.start == next_start {
250                prev.end = next_end;
251            } else {
252                prev.end = next_start;
253            }
254        }
255    } else {
256        for i in (glyph_start + 1..glyphs.len()).rev() {
257            let next_start = glyphs[i].start;
258            let next_end = glyphs[i].end;
259            let prev = &mut glyphs[i - 1];
260            if prev.start == next_start {
261                prev.end = next_end;
262            } else {
263                prev.end = next_start;
264            }
265        }
266    }
267
268    // Restore the buffer to save an allocation.
269    scratch.harfrust_buffer = Some(glyph_buffer.clear());
270
271    missing
272}
273
274fn shape_run(
275    glyphs: &mut Vec<ShapeGlyph>,
276    font_system: &mut FontSystem,
277    line: &str,
278    attrs_list: &AttrsList,
279    start_run: usize,
280    end_run: usize,
281    span_rtl: bool,
282) {
283    // Re-use the previous script buffer if possible.
284    let mut scripts = {
285        let mut scripts = mem::take(&mut font_system.shape_buffer.scripts);
286        scripts.clear();
287        scripts
288    };
289    for c in line[start_run..end_run].chars() {
290        match c.script() {
291            Script::Common | Script::Inherited | Script::Latin | Script::Unknown => (),
292            script => {
293                if !scripts.contains(&script) {
294                    scripts.push(script);
295                }
296            }
297        }
298    }
299
300    log::trace!("      Run {:?}: '{}'", &scripts, &line[start_run..end_run],);
301
302    let attrs = attrs_list.get_span(start_run);
303
304    let fonts = font_system.get_font_matches(&attrs);
305
306    let default_families = [&attrs.family];
307    let mut font_iter = FontFallbackIter::new(
308        font_system,
309        &fonts,
310        &default_families,
311        &scripts,
312        &line[start_run..end_run],
313        attrs.weight,
314    );
315
316    let font = font_iter.next().expect("no default font found");
317
318    let glyph_start = glyphs.len();
319    let mut missing = {
320        let scratch = font_iter.shape_caches();
321        shape_fallback(
322            scratch, glyphs, &font, line, attrs_list, start_run, end_run, span_rtl,
323        )
324    };
325
326    //TODO: improve performance!
327    while !missing.is_empty() {
328        let Some(font) = font_iter.next() else {
329            break;
330        };
331
332        log::trace!(
333            "Evaluating fallback with font '{}'",
334            font_iter.face_name(font.id())
335        );
336        let mut fb_glyphs = Vec::new();
337        let scratch = font_iter.shape_caches();
338        let fb_missing = shape_fallback(
339            scratch,
340            &mut fb_glyphs,
341            &font,
342            line,
343            attrs_list,
344            start_run,
345            end_run,
346            span_rtl,
347        );
348
349        // Insert all matching glyphs
350        let mut fb_i = 0;
351        while fb_i < fb_glyphs.len() {
352            let start = fb_glyphs[fb_i].start;
353            let end = fb_glyphs[fb_i].end;
354
355            // Skip clusters that are not missing, or where the fallback font is missing
356            if !missing.contains(&start) || fb_missing.contains(&start) {
357                fb_i += 1;
358                continue;
359            }
360
361            let mut missing_i = 0;
362            while missing_i < missing.len() {
363                if missing[missing_i] >= start && missing[missing_i] < end {
364                    // println!("No longer missing {}", missing[missing_i]);
365                    missing.remove(missing_i);
366                } else {
367                    missing_i += 1;
368                }
369            }
370
371            // Find prior glyphs
372            let mut i = glyph_start;
373            while i < glyphs.len() {
374                if glyphs[i].start >= start && glyphs[i].end <= end {
375                    break;
376                }
377                i += 1;
378            }
379
380            // Remove prior glyphs
381            while i < glyphs.len() {
382                if glyphs[i].start >= start && glyphs[i].end <= end {
383                    let _glyph = glyphs.remove(i);
384                    // log::trace!("Removed {},{} from {}", _glyph.start, _glyph.end, i);
385                } else {
386                    break;
387                }
388            }
389
390            while fb_i < fb_glyphs.len() {
391                if fb_glyphs[fb_i].start >= start && fb_glyphs[fb_i].end <= end {
392                    let fb_glyph = fb_glyphs.remove(fb_i);
393                    // log::trace!("Insert {},{} from font {} at {}", fb_glyph.start, fb_glyph.end, font_i, i);
394                    glyphs.insert(i, fb_glyph);
395                    i += 1;
396                } else {
397                    break;
398                }
399            }
400        }
401    }
402
403    // Debug missing font fallbacks
404    font_iter.check_missing(&line[start_run..end_run]);
405
406    /*
407    for glyph in glyphs.iter() {
408        log::trace!("'{}': {}, {}, {}, {}", &line[glyph.start..glyph.end], glyph.x_advance, glyph.y_advance, glyph.x_offset, glyph.y_offset);
409    }
410    */
411
412    // Restore the scripts buffer.
413    font_system.shape_buffer.scripts = scripts;
414}
415
416#[cfg(feature = "shape-run-cache")]
417fn shape_run_cached(
418    glyphs: &mut Vec<ShapeGlyph>,
419    font_system: &mut FontSystem,
420    line: &str,
421    attrs_list: &AttrsList,
422    start_run: usize,
423    end_run: usize,
424    span_rtl: bool,
425) {
426    use crate::{AttrsOwned, ShapeRunKey};
427
428    let run_range = start_run..end_run;
429    let mut key = ShapeRunKey {
430        text: line[run_range.clone()].to_string(),
431        default_attrs: AttrsOwned::new(&attrs_list.defaults()),
432        attrs_spans: Vec::new(),
433    };
434    for (attrs_range, attrs) in attrs_list.spans.overlapping(&run_range) {
435        if attrs == &key.default_attrs {
436            // Skip if attrs matches default attrs
437            continue;
438        }
439        let start = max(attrs_range.start, start_run).saturating_sub(start_run);
440        let end = min(attrs_range.end, end_run).saturating_sub(start_run);
441        if end > start {
442            let range = start..end;
443            key.attrs_spans.push((range, attrs.clone()));
444        }
445    }
446    if let Some(cache_glyphs) = font_system.shape_run_cache.get(&key) {
447        for mut glyph in cache_glyphs.iter().cloned() {
448            // Adjust glyph start and end to match run position
449            glyph.start += start_run;
450            glyph.end += start_run;
451            glyphs.push(glyph);
452        }
453        return;
454    }
455
456    // Fill in cache if not already set
457    let mut cache_glyphs = Vec::new();
458    shape_run(
459        &mut cache_glyphs,
460        font_system,
461        line,
462        attrs_list,
463        start_run,
464        end_run,
465        span_rtl,
466    );
467    glyphs.extend_from_slice(&cache_glyphs);
468    for glyph in cache_glyphs.iter_mut() {
469        // Adjust glyph start and end to remove run position
470        glyph.start -= start_run;
471        glyph.end -= start_run;
472    }
473    font_system.shape_run_cache.insert(key, cache_glyphs);
474}
475
476#[cfg(feature = "swash")]
477fn shape_skip(
478    font_system: &mut FontSystem,
479    glyphs: &mut Vec<ShapeGlyph>,
480    line: &str,
481    attrs_list: &AttrsList,
482    start_run: usize,
483    end_run: usize,
484) {
485    let attrs = attrs_list.get_span(start_run);
486    let fonts = font_system.get_font_matches(&attrs);
487
488    let default_families = [&attrs.family];
489    let mut font_iter = FontFallbackIter::new(
490        font_system,
491        &fonts,
492        &default_families,
493        &[],
494        "",
495        attrs.weight,
496    );
497
498    let font = font_iter.next().expect("no default font found");
499    let font_id = font.id();
500    let font_monospace_em_width = font.monospace_em_width();
501    let swash_font = font.as_swash();
502
503    let charmap = swash_font.charmap();
504    let metrics = swash_font.metrics(&[]);
505    let glyph_metrics = swash_font.glyph_metrics(&[]).scale(1.0);
506
507    let ascent = metrics.ascent / f32::from(metrics.units_per_em);
508    let descent = metrics.descent / f32::from(metrics.units_per_em);
509
510    glyphs.extend(
511        line[start_run..end_run]
512            .char_indices()
513            .map(|(chr_idx, codepoint)| {
514                let glyph_id = charmap.map(codepoint);
515                let x_advance = glyph_metrics.advance_width(glyph_id)
516                    + attrs.letter_spacing_opt.map_or(0.0, |spacing| spacing.0);
517                let attrs = attrs_list.get_span(start_run + chr_idx);
518
519                ShapeGlyph {
520                    start: chr_idx + start_run,
521                    end: chr_idx + start_run + codepoint.len_utf8(),
522                    x_advance,
523                    y_advance: 0.0,
524                    x_offset: 0.0,
525                    y_offset: 0.0,
526                    ascent,
527                    descent,
528                    font_monospace_em_width,
529                    font_id,
530                    font_weight: attrs.weight,
531                    glyph_id,
532                    color_opt: attrs.color_opt,
533                    metadata: attrs.metadata,
534                    cache_key_flags: override_fake_italic(
535                        attrs.cache_key_flags,
536                        font.as_ref(),
537                        &attrs,
538                    ),
539                    metrics_opt: attrs.metrics_opt.map(Into::into),
540                }
541            }),
542    );
543}
544
545fn override_fake_italic(
546    cache_key_flags: CacheKeyFlags,
547    font: &Font,
548    attrs: &Attrs,
549) -> CacheKeyFlags {
550    if !font.italic_or_oblique && (attrs.style == Style::Italic || attrs.style == Style::Oblique) {
551        cache_key_flags | CacheKeyFlags::FAKE_ITALIC
552    } else {
553        cache_key_flags
554    }
555}
556
557/// A shaped glyph
558#[derive(Clone, Debug)]
559pub struct ShapeGlyph {
560    pub start: usize,
561    pub end: usize,
562    pub x_advance: f32,
563    pub y_advance: f32,
564    pub x_offset: f32,
565    pub y_offset: f32,
566    pub ascent: f32,
567    pub descent: f32,
568    pub font_monospace_em_width: Option<f32>,
569    pub font_id: fontdb::ID,
570    pub font_weight: fontdb::Weight,
571    pub glyph_id: u16,
572    pub color_opt: Option<Color>,
573    pub metadata: usize,
574    pub cache_key_flags: CacheKeyFlags,
575    pub metrics_opt: Option<Metrics>,
576}
577
578impl ShapeGlyph {
579    const fn layout(
580        &self,
581        font_size: f32,
582        line_height_opt: Option<f32>,
583        x: f32,
584        y: f32,
585        w: f32,
586        level: unicode_bidi::Level,
587    ) -> LayoutGlyph {
588        LayoutGlyph {
589            start: self.start,
590            end: self.end,
591            font_size,
592            line_height_opt,
593            font_id: self.font_id,
594            font_weight: self.font_weight,
595            glyph_id: self.glyph_id,
596            x,
597            y,
598            w,
599            level,
600            x_offset: self.x_offset,
601            y_offset: self.y_offset,
602            color_opt: self.color_opt,
603            metadata: self.metadata,
604            cache_key_flags: self.cache_key_flags,
605        }
606    }
607
608    /// Get the width of the [`ShapeGlyph`] in pixels, either using the provided font size
609    /// or the [`ShapeGlyph::metrics_opt`] override.
610    pub fn width(&self, font_size: f32) -> f32 {
611        self.metrics_opt.map_or(font_size, |x| x.font_size) * self.x_advance
612    }
613}
614
615fn decoration_metrics(font: &Font) -> (DecorationMetrics, DecorationMetrics, f32) {
616    let metrics = font.metrics();
617    let upem = metrics.units_per_em as f32;
618    if upem == 0.0 {
619        return (
620            DecorationMetrics::default(),
621            DecorationMetrics::default(),
622            0.0,
623        );
624    }
625    (
626        DecorationMetrics {
627            offset: metrics.underline.map_or(-0.125, |d| d.offset / upem),
628            thickness: metrics.underline.map_or(1.0 / 14.0, |d| d.thickness / upem),
629        },
630        DecorationMetrics {
631            offset: metrics.strikeout.map_or(0.3, |d| d.offset / upem),
632            thickness: metrics.strikeout.map_or(1.0 / 14.0, |d| d.thickness / upem),
633        },
634        metrics.ascent / upem,
635    )
636}
637
638/// span index used in `VlRange` to indicate this range is the ellipsis.
639const ELLIPSIS_SPAN: usize = usize::MAX;
640
641fn shape_ellipsis(
642    font_system: &mut FontSystem,
643    attrs: &Attrs,
644    shaping: Shaping,
645    span_rtl: bool,
646) -> Vec<ShapeGlyph> {
647    let attrs_list = AttrsList::new(attrs);
648    let level = if span_rtl {
649        unicode_bidi::Level::rtl()
650    } else {
651        unicode_bidi::Level::ltr()
652    };
653    let word = ShapeWord::new(
654        font_system,
655        "\u{2026}", // TODO: maybe do CJK ellipsis
656        &attrs_list,
657        0.."\u{2026}".len(),
658        level,
659        false,
660        shaping,
661    );
662    let mut glyphs = word.glyphs;
663
664    // did we fail to shape it?
665    if glyphs.is_empty() || glyphs.iter().all(|g| g.glyph_id == 0) {
666        let fallback = ShapeWord::new(
667            font_system,
668            "...",
669            &attrs_list,
670            0.."...".len(),
671            level,
672            false,
673            shaping,
674        );
675        glyphs = fallback.glyphs;
676    }
677    glyphs
678}
679
680/// A shaped word (for word wrapping)
681#[derive(Clone, Debug)]
682pub struct ShapeWord {
683    pub blank: bool,
684    pub glyphs: Vec<ShapeGlyph>,
685}
686
687impl ShapeWord {
688    /// Creates an empty word.
689    ///
690    /// The returned word is in an invalid state until [`Self::build_in_buffer`] is called.
691    pub(crate) fn empty() -> Self {
692        Self {
693            blank: true,
694            glyphs: Vec::default(),
695        }
696    }
697
698    /// Shape a word into a set of glyphs.
699    #[allow(clippy::too_many_arguments)]
700    pub fn new(
701        font_system: &mut FontSystem,
702        line: &str,
703        attrs_list: &AttrsList,
704        word_range: Range<usize>,
705        level: unicode_bidi::Level,
706        blank: bool,
707        shaping: Shaping,
708    ) -> Self {
709        let mut empty = Self::empty();
710        empty.build(
711            font_system,
712            line,
713            attrs_list,
714            word_range,
715            level,
716            blank,
717            shaping,
718        );
719        empty
720    }
721
722    /// See [`Self::new`].
723    ///
724    /// Reuses as much of the pre-existing internal allocations as possible.
725    #[allow(clippy::too_many_arguments)]
726    pub fn build(
727        &mut self,
728        font_system: &mut FontSystem,
729        line: &str,
730        attrs_list: &AttrsList,
731        word_range: Range<usize>,
732        level: unicode_bidi::Level,
733        blank: bool,
734        shaping: Shaping,
735    ) {
736        let word = &line[word_range.clone()];
737
738        log::trace!(
739            "      Word{}: '{}'",
740            if blank { " BLANK" } else { "" },
741            word
742        );
743
744        let mut glyphs = mem::take(&mut self.glyphs);
745        glyphs.clear();
746
747        let span_rtl = level.is_rtl();
748
749        // Fast path optimization: For simple ASCII words, skip expensive grapheme iteration
750        let is_simple_ascii =
751            word.is_ascii() && !word.chars().any(|c| c.is_ascii_control() && c != '\t');
752
753        if is_simple_ascii && !word.is_empty() && {
754            let attrs_start = attrs_list.get_span(word_range.start);
755            attrs_list.spans_iter().all(|(other_range, other_attrs)| {
756                word_range.end <= other_range.start
757                    || other_range.end <= word_range.start
758                    || attrs_start.compatible(&other_attrs.as_attrs())
759            })
760        } {
761            shaping.run(
762                &mut glyphs,
763                font_system,
764                line,
765                attrs_list,
766                word_range.start,
767                word_range.end,
768                span_rtl,
769            );
770        } else {
771            // Complex text path: Full grapheme iteration and attribute processing
772            let mut start_run = word_range.start;
773            let mut attrs = attrs_list.defaults();
774            for (egc_i, _egc) in word.grapheme_indices(true) {
775                let start_egc = word_range.start + egc_i;
776                let attrs_egc = attrs_list.get_span(start_egc);
777                if !attrs.compatible(&attrs_egc) {
778                    shaping.run(
779                        &mut glyphs,
780                        font_system,
781                        line,
782                        attrs_list,
783                        start_run,
784                        start_egc,
785                        span_rtl,
786                    );
787
788                    start_run = start_egc;
789                    attrs = attrs_egc;
790                }
791            }
792            if start_run < word_range.end {
793                shaping.run(
794                    &mut glyphs,
795                    font_system,
796                    line,
797                    attrs_list,
798                    start_run,
799                    word_range.end,
800                    span_rtl,
801                );
802            }
803        }
804
805        self.blank = blank;
806        self.glyphs = glyphs;
807    }
808
809    /// Get the width of the [`ShapeWord`] in pixels, using the [`ShapeGlyph::width`] function.
810    pub fn width(&self, font_size: f32) -> f32 {
811        let mut width = 0.0;
812        for glyph in &self.glyphs {
813            width += glyph.width(font_size);
814        }
815        width
816    }
817}
818
819/// A shaped span (for bidirectional processing)
820#[derive(Clone, Debug)]
821pub struct ShapeSpan {
822    pub level: unicode_bidi::Level,
823    pub words: Vec<ShapeWord>,
824    /// Decoration data per user-level attr span within this shape span.
825    /// Each entry maps a byte range to its decoration config and font metrics.
826    /// Empty when no decorations are active.
827    pub decoration_spans: Vec<(Range<usize>, GlyphDecorationData)>,
828}
829
830impl ShapeSpan {
831    /// Creates an empty span.
832    ///
833    /// The returned span is in an invalid state until [`Self::build_in_buffer`] is called.
834    pub(crate) fn empty() -> Self {
835        Self {
836            level: unicode_bidi::Level::ltr(),
837            words: Vec::default(),
838            decoration_spans: Vec::new(),
839        }
840    }
841
842    /// Shape a span into a set of words.
843    pub fn new(
844        font_system: &mut FontSystem,
845        line: &str,
846        attrs_list: &AttrsList,
847        span_range: Range<usize>,
848        line_rtl: bool,
849        level: unicode_bidi::Level,
850        shaping: Shaping,
851    ) -> Self {
852        let mut empty = Self::empty();
853        empty.build(
854            font_system,
855            line,
856            attrs_list,
857            span_range,
858            line_rtl,
859            level,
860            shaping,
861        );
862        empty
863    }
864
865    /// See [`Self::new`].
866    ///
867    /// Reuses as much of the pre-existing internal allocations as possible.
868    pub fn build(
869        &mut self,
870        font_system: &mut FontSystem,
871        line: &str,
872        attrs_list: &AttrsList,
873        span_range: Range<usize>,
874        line_rtl: bool,
875        level: unicode_bidi::Level,
876        shaping: Shaping,
877    ) {
878        let span = &line[span_range.start..span_range.end];
879
880        log::trace!(
881            "  Span {}: '{}'",
882            if level.is_rtl() { "RTL" } else { "LTR" },
883            span
884        );
885
886        let mut words = mem::take(&mut self.words);
887
888        // Cache the shape words in reverse order so they can be popped for reuse in the same order.
889        let mut cached_words = mem::take(&mut font_system.shape_buffer.words);
890        cached_words.clear();
891        if line_rtl != level.is_rtl() {
892            // Un-reverse previous words so the internal glyph counts match accurately when rewriting memory.
893            cached_words.append(&mut words);
894        } else {
895            cached_words.extend(words.drain(..).rev());
896        }
897
898        let mut start_word = 0;
899        for (end_lb, _) in unicode_linebreak::linebreaks(span) {
900            // Check if this break opportunity splits a likely ligature (e.g. "|>" or "!=")
901            if end_lb > 0 && end_lb < span.len() {
902                let start_idx = span_range.start;
903                let pre_char = span[..end_lb].chars().last();
904                let post_char = span[end_lb..].chars().next();
905
906                if let (Some(c1), Some(c2)) = (pre_char, post_char) {
907                    // Only probe if both are punctuation (optimization for coding ligatures)
908                    if c1.is_ascii_punctuation() && c2.is_ascii_punctuation() {
909                        let probe_text = format!("{}{}", c1, c2);
910                        let attrs = attrs_list.get_span(start_idx + end_lb);
911                        let fonts = font_system.get_font_matches(&attrs);
912                        let default_families = [&attrs.family];
913
914                        let mut font_iter = FontFallbackIter::new(
915                            font_system,
916                            &fonts,
917                            &default_families,
918                            &[],
919                            &probe_text,
920                            attrs.weight,
921                        );
922
923                        if let Some(font) = font_iter.next() {
924                            let mut glyphs = Vec::new();
925                            let scratch = font_iter.shape_caches();
926                            shape_fallback(
927                                scratch,
928                                &mut glyphs,
929                                &font,
930                                &probe_text,
931                                attrs_list,
932                                0,
933                                probe_text.len(),
934                                false,
935                            );
936
937                            // 1. If we have fewer glyphs than chars, it's definitely a ligature (e.g. -> becoming 1 arrow).
938                            if glyphs.len() < probe_text.chars().count() {
939                                continue;
940                            }
941
942                            // 2. If we have the same number of glyphs, they might be contextual alternates (e.g. |> becoming 2 special glyphs).
943                            // Check if the glyphs match the standard "cmap" (character to glyph) mapping.
944                            // If they differ, the shaper substituted them, so we should keep them together.
945                            #[cfg(feature = "swash")]
946                            if glyphs.len() == probe_text.chars().count() {
947                                let charmap = font.as_swash().charmap();
948                                let mut is_modified = false;
949                                for (i, c) in probe_text.chars().enumerate() {
950                                    let std_id = charmap.map(c);
951                                    if glyphs[i].glyph_id != std_id {
952                                        is_modified = true;
953                                        break;
954                                    }
955                                }
956
957                                if is_modified {
958                                    // Ligature/Contextual Alternate detected!
959                                    continue;
960                                }
961                            }
962                        }
963                    }
964                }
965            }
966
967            let mut start_lb = end_lb;
968            for (i, c) in span[start_word..end_lb].char_indices().rev() {
969                // TODO: Not all whitespace characters are linebreakable, e.g. 00A0 (No-break
970                // space)
971                // https://www.unicode.org/reports/tr14/#GL
972                // https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt
973                if c.is_whitespace() {
974                    start_lb = start_word + i;
975                } else {
976                    break;
977                }
978            }
979            if start_word < start_lb {
980                let mut word = cached_words.pop().unwrap_or_else(ShapeWord::empty);
981                word.build(
982                    font_system,
983                    line,
984                    attrs_list,
985                    (span_range.start + start_word)..(span_range.start + start_lb),
986                    level,
987                    false,
988                    shaping,
989                );
990                words.push(word);
991            }
992            if start_lb < end_lb {
993                for (i, c) in span[start_lb..end_lb].char_indices() {
994                    // assert!(c.is_whitespace());
995                    let mut word = cached_words.pop().unwrap_or_else(ShapeWord::empty);
996                    word.build(
997                        font_system,
998                        line,
999                        attrs_list,
1000                        (span_range.start + start_lb + i)
1001                            ..(span_range.start + start_lb + i + c.len_utf8()),
1002                        level,
1003                        true,
1004                        shaping,
1005                    );
1006                    words.push(word);
1007                }
1008            }
1009            start_word = end_lb;
1010        }
1011
1012        // Reverse glyphs in RTL lines
1013        if line_rtl {
1014            for word in &mut words {
1015                word.glyphs.reverse();
1016            }
1017        }
1018
1019        // Reverse words in spans that do not match line direction
1020        if line_rtl != level.is_rtl() {
1021            words.reverse();
1022        }
1023
1024        self.level = level;
1025        self.words = words;
1026
1027        // Build decoration spans: one entry per user-level attr span that has
1028        // decorations within this shape span's byte range.  Font metrics come from
1029        // the primary font (first shaped glyph), following Pango convention.
1030        self.decoration_spans.clear();
1031
1032        // Early-out: skip font lookup and span iteration when no decorations exist.
1033        // For plain text (the common case) this is a single bool check.
1034        let any_decoration = attrs_list.defaults().text_decoration.has_decoration()
1035            || attrs_list.spans_iter().any(|(range, attr_owned)| {
1036                let start = range.start.max(span_range.start);
1037                let end = range.end.min(span_range.end);
1038                start < end && attr_owned.as_attrs().text_decoration.has_decoration()
1039            });
1040
1041        if any_decoration {
1042            // Get font metrics once from the primary glyph of this shape span
1043            let primary_metrics = self
1044                .words
1045                .iter()
1046                .flat_map(|w| w.glyphs.first())
1047                .next()
1048                .and_then(|glyph| {
1049                    font_system
1050                        .get_font(glyph.font_id, glyph.font_weight)
1051                        .map(|font| decoration_metrics(&font))
1052                });
1053
1054            if let Some((ul_metrics, st_metrics, ascent)) = primary_metrics {
1055                // Track which sub-ranges of span_range are covered by explicit spans
1056                let mut covered_end = span_range.start;
1057
1058                for (range, attr_owned) in attrs_list.spans_iter() {
1059                    // Compute intersection with our shape span's byte range
1060                    let start = range.start.max(span_range.start);
1061                    let end = range.end.min(span_range.end);
1062                    if start >= end {
1063                        continue;
1064                    }
1065
1066                    // Check the gap before this span (covered by defaults)
1067                    if covered_end < start {
1068                        let default_attrs = attrs_list.defaults();
1069                        if default_attrs.text_decoration.has_decoration() {
1070                            self.decoration_spans.push((
1071                                covered_end..start,
1072                                GlyphDecorationData {
1073                                    text_decoration: default_attrs.text_decoration,
1074                                    underline_metrics: ul_metrics,
1075                                    strikethrough_metrics: st_metrics,
1076                                    ascent,
1077                                },
1078                            ));
1079                        }
1080                    }
1081                    covered_end = end;
1082
1083                    let attrs = attr_owned.as_attrs();
1084                    if attrs.text_decoration.has_decoration() {
1085                        self.decoration_spans.push((
1086                            start..end,
1087                            GlyphDecorationData {
1088                                text_decoration: attrs.text_decoration,
1089                                underline_metrics: ul_metrics,
1090                                strikethrough_metrics: st_metrics,
1091                                ascent,
1092                            },
1093                        ));
1094                    }
1095                }
1096
1097                // Check trailing gap (covered by defaults)
1098                if covered_end < span_range.end {
1099                    let default_attrs = attrs_list.defaults();
1100                    if default_attrs.text_decoration.has_decoration() {
1101                        self.decoration_spans.push((
1102                            covered_end..span_range.end,
1103                            GlyphDecorationData {
1104                                text_decoration: default_attrs.text_decoration,
1105                                underline_metrics: ul_metrics,
1106                                strikethrough_metrics: st_metrics,
1107                                ascent,
1108                            },
1109                        ));
1110                    }
1111                }
1112            }
1113        }
1114
1115        // Cache buffer for future reuse.
1116        font_system.shape_buffer.words = cached_words;
1117    }
1118}
1119
1120/// A shaped line (or paragraph)
1121#[derive(Clone, Debug)]
1122pub struct ShapeLine {
1123    pub rtl: bool,
1124    pub spans: Vec<ShapeSpan>,
1125    pub metrics_opt: Option<Metrics>,
1126    ellipsis_span: Option<ShapeSpan>,
1127}
1128
1129#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1130struct WordGlyphPos {
1131    word: usize,
1132    glyph: usize,
1133}
1134
1135impl WordGlyphPos {
1136    const ZERO: Self = Self { word: 0, glyph: 0 };
1137    fn new(word: usize, glyph: usize) -> Self {
1138        Self { word, glyph }
1139    }
1140}
1141
1142#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1143struct SpanWordGlyphPos {
1144    span: usize,
1145    word: usize,
1146    glyph: usize,
1147}
1148
1149impl SpanWordGlyphPos {
1150    const ZERO: Self = Self {
1151        span: 0,
1152        word: 0,
1153        glyph: 0,
1154    };
1155    fn word_glyph_pos(&self) -> WordGlyphPos {
1156        WordGlyphPos {
1157            word: self.word,
1158            glyph: self.glyph,
1159        }
1160    }
1161    fn with_wordglyph(span: usize, wordglyph: WordGlyphPos) -> Self {
1162        Self {
1163            span,
1164            word: wordglyph.word,
1165            glyph: wordglyph.glyph,
1166        }
1167    }
1168}
1169
1170/// Controls whether we layout spans forward or backward.
1171/// Backward layout is used to improve efficiency
1172#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1173enum LayoutDirection {
1174    Forward,
1175    Backward,
1176}
1177
1178// Visual Line Ranges
1179#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1180struct VlRange {
1181    span: usize,
1182    start: WordGlyphPos,
1183    end: WordGlyphPos,
1184    level: unicode_bidi::Level,
1185}
1186
1187impl Default for VlRange {
1188    fn default() -> Self {
1189        Self {
1190            span: Default::default(),
1191            start: Default::default(),
1192            end: Default::default(),
1193            level: unicode_bidi::Level::ltr(),
1194        }
1195    }
1196}
1197
1198#[derive(Default, Debug)]
1199struct VisualLine {
1200    ranges: Vec<VlRange>,
1201    spaces: u32,
1202    w: f32,
1203    ellipsized: bool,
1204    /// Byte range (start, end) of the original line text that was replaced by the ellipsis.
1205    /// Only set when `ellipsized` is true.
1206    elided_byte_range: Option<(usize, usize)>,
1207}
1208
1209impl VisualLine {
1210    fn clear(&mut self) {
1211        self.ranges.clear();
1212        self.spaces = 0;
1213        self.w = 0.;
1214        self.ellipsized = false;
1215        self.elided_byte_range = None;
1216    }
1217}
1218
1219impl ShapeLine {
1220    /// Creates an empty line.
1221    ///
1222    /// The returned line is in an invalid state until [`Self::build_in_buffer`] is called.
1223    pub(crate) fn empty() -> Self {
1224        Self {
1225            rtl: false,
1226            spans: Vec::default(),
1227            metrics_opt: None,
1228            ellipsis_span: None,
1229        }
1230    }
1231
1232    /// Shape a line into a set of spans, using a scratch buffer. If [`unicode_bidi::BidiInfo`]
1233    /// detects multiple paragraphs, they will be joined.
1234    ///
1235    /// # Panics
1236    ///
1237    /// Will panic if `line` contains multiple paragraphs that do not have matching direction
1238    pub fn new(
1239        font_system: &mut FontSystem,
1240        line: &str,
1241        attrs_list: &AttrsList,
1242        shaping: Shaping,
1243        tab_width: u16,
1244    ) -> Self {
1245        let mut empty = Self::empty();
1246        empty.build(font_system, line, attrs_list, shaping, tab_width);
1247        empty
1248    }
1249
1250    /// See [`Self::new`].
1251    ///
1252    /// Reuses as much of the pre-existing internal allocations as possible.
1253    ///
1254    /// # Panics
1255    ///
1256    /// Will panic if `line` contains multiple paragraphs that do not have matching direction
1257    pub fn build(
1258        &mut self,
1259        font_system: &mut FontSystem,
1260        line: &str,
1261        attrs_list: &AttrsList,
1262        shaping: Shaping,
1263        tab_width: u16,
1264    ) {
1265        let mut spans = mem::take(&mut self.spans);
1266
1267        // Cache the shape spans in reverse order so they can be popped for reuse in the same order.
1268        let mut cached_spans = mem::take(&mut font_system.shape_buffer.spans);
1269        cached_spans.clear();
1270        cached_spans.extend(spans.drain(..).rev());
1271
1272        let bidi = unicode_bidi::BidiInfo::new(line, None);
1273        let rtl = if bidi.paragraphs.is_empty() {
1274            false
1275        } else {
1276            bidi.paragraphs[0].level.is_rtl()
1277        };
1278
1279        log::trace!("Line {}: '{}'", if rtl { "RTL" } else { "LTR" }, line);
1280
1281        for para_info in &bidi.paragraphs {
1282            let line_rtl = para_info.level.is_rtl();
1283            assert_eq!(line_rtl, rtl);
1284
1285            let line_range = para_info.range.clone();
1286            let levels = Self::adjust_levels(&unicode_bidi::Paragraph::new(&bidi, para_info));
1287
1288            // Find consecutive level runs. We use this to create Spans.
1289            // Each span is a set of characters with equal levels.
1290            let mut start = line_range.start;
1291            let mut run_level = levels[start];
1292            spans.reserve(line_range.end - start + 1);
1293
1294            for (i, &new_level) in levels
1295                .iter()
1296                .enumerate()
1297                .take(line_range.end)
1298                .skip(start + 1)
1299            {
1300                if new_level != run_level {
1301                    // End of the previous run, start of a new one.
1302                    let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
1303                    span.build(
1304                        font_system,
1305                        line,
1306                        attrs_list,
1307                        start..i,
1308                        line_rtl,
1309                        run_level,
1310                        shaping,
1311                    );
1312                    spans.push(span);
1313                    start = i;
1314                    run_level = new_level;
1315                }
1316            }
1317            let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
1318            span.build(
1319                font_system,
1320                line,
1321                attrs_list,
1322                start..line_range.end,
1323                line_rtl,
1324                run_level,
1325                shaping,
1326            );
1327            spans.push(span);
1328        }
1329
1330        // Adjust for tabs
1331        let mut x = 0.0;
1332        for span in &mut spans {
1333            for word in &mut span.words {
1334                for glyph in &mut word.glyphs {
1335                    if line.get(glyph.start..glyph.end) == Some("\t") {
1336                        // Tabs are shaped as spaces, so they will always have the x_advance of a space.
1337                        let tab_x_advance = f32::from(tab_width) * glyph.x_advance;
1338                        let tab_stop = (math::floorf(x / tab_x_advance) + 1.0) * tab_x_advance;
1339                        glyph.x_advance = tab_stop - x;
1340                    }
1341                    x += glyph.x_advance;
1342                }
1343            }
1344        }
1345
1346        self.rtl = rtl;
1347        self.spans = spans;
1348        self.metrics_opt = attrs_list.defaults().metrics_opt.map(Into::into);
1349
1350        self.ellipsis_span.get_or_insert_with(|| {
1351            let attrs = if attrs_list.spans.is_empty() {
1352                attrs_list.defaults()
1353            } else {
1354                attrs_list.get_span(0) // TODO: using the attrs from the first span for
1355                                       // ellipsis even if it's at the end. Which for rich text may look weird if the first
1356                                       // span has a different color or size than where ellipsizing is happening
1357            };
1358            let mut glyphs = shape_ellipsis(font_system, &attrs, shaping, rtl);
1359            if rtl {
1360                glyphs.reverse();
1361            }
1362            let word = ShapeWord {
1363                blank: false,
1364                glyphs,
1365            };
1366            // The level here is a placeholder; the actual level used for BiDi reordering
1367            // is set on the VlRange when the ellipsis is inserted during layout.
1368            let level = if rtl {
1369                unicode_bidi::Level::rtl()
1370            } else {
1371                unicode_bidi::Level::ltr()
1372            };
1373            ShapeSpan {
1374                level,
1375                words: vec![word],
1376                decoration_spans: Vec::new(),
1377            }
1378        });
1379
1380        // Return the buffer for later reuse.
1381        font_system.shape_buffer.spans = cached_spans;
1382    }
1383
1384    // A modified version of first part of unicode_bidi::bidi_info::visual_run
1385    fn adjust_levels(para: &unicode_bidi::Paragraph) -> Vec<unicode_bidi::Level> {
1386        use unicode_bidi::BidiClass::{B, BN, FSI, LRE, LRI, LRO, PDF, PDI, RLE, RLI, RLO, S, WS};
1387        let text = para.info.text;
1388        let levels = &para.info.levels;
1389        let original_classes = &para.info.original_classes;
1390
1391        let mut levels = levels.clone();
1392        let line_classes = &original_classes[..];
1393        let line_levels = &mut levels[..];
1394
1395        // Reset some whitespace chars to paragraph level.
1396        // <http://www.unicode.org/reports/tr9/#L1>
1397        let mut reset_from: Option<usize> = Some(0);
1398        let mut reset_to: Option<usize> = None;
1399        for (i, c) in text.char_indices() {
1400            match line_classes[i] {
1401                // Ignored by X9
1402                RLE | LRE | RLO | LRO | PDF | BN => {}
1403                // Segment separator, Paragraph separator
1404                B | S => {
1405                    assert_eq!(reset_to, None);
1406                    reset_to = Some(i + c.len_utf8());
1407                    if reset_from.is_none() {
1408                        reset_from = Some(i);
1409                    }
1410                }
1411                // Whitespace, isolate formatting
1412                WS | FSI | LRI | RLI | PDI => {
1413                    if reset_from.is_none() {
1414                        reset_from = Some(i);
1415                    }
1416                }
1417                _ => {
1418                    reset_from = None;
1419                }
1420            }
1421            if let (Some(from), Some(to)) = (reset_from, reset_to) {
1422                for level in &mut line_levels[from..to] {
1423                    *level = para.para.level;
1424                }
1425                reset_from = None;
1426                reset_to = None;
1427            }
1428        }
1429        if let Some(from) = reset_from {
1430            for level in &mut line_levels[from..] {
1431                *level = para.para.level;
1432            }
1433        }
1434        levels
1435    }
1436
1437    // A modified version of second part of unicode_bidi::bidi_info::visual run
1438    fn reorder(&self, line_range: &[VlRange]) -> Vec<Range<usize>> {
1439        let line: Vec<unicode_bidi::Level> = line_range.iter().map(|range| range.level).collect();
1440        let count = line.len();
1441        if count == 0 {
1442            return Vec::new();
1443        }
1444
1445        // Each VlRange is its own element for L2 reordering.
1446        // Using individual elements (not grouped runs) ensures that reversal
1447        // correctly reorders elements even when consecutive ranges share a level.
1448        let mut elements: Vec<Range<usize>> = (0..count).map(|i| i..i + 1).collect();
1449
1450        let mut min_level = line[0];
1451        let mut max_level = line[0];
1452        for &level in &line[1..] {
1453            min_level = min(min_level, level);
1454            max_level = max(max_level, level);
1455        }
1456
1457        // Re-order the odd runs.
1458        // <http://www.unicode.org/reports/tr9/#L2>
1459
1460        // Stop at the lowest *odd* level.
1461        min_level = min_level.new_lowest_ge_rtl().expect("Level error");
1462
1463        while max_level >= min_level {
1464            // Look for the start of a sequence of consecutive elements at max_level or higher.
1465            let mut seq_start = 0;
1466            while seq_start < count {
1467                if line[elements[seq_start].start] < max_level {
1468                    seq_start += 1;
1469                    continue;
1470                }
1471
1472                // Found the start of a sequence. Now find the end.
1473                let mut seq_end = seq_start + 1;
1474                while seq_end < count {
1475                    if line[elements[seq_end].start] < max_level {
1476                        break;
1477                    }
1478                    seq_end += 1;
1479                }
1480
1481                // Reverse the individual elements within this sequence.
1482                elements[seq_start..seq_end].reverse();
1483
1484                seq_start = seq_end;
1485            }
1486            max_level
1487                .lower(1)
1488                .expect("Lowering embedding level below zero");
1489        }
1490
1491        elements
1492    }
1493
1494    pub fn layout(
1495        &self,
1496        font_size: f32,
1497        width_opt: Option<f32>,
1498        wrap: Wrap,
1499        align: Option<Align>,
1500        match_mono_width: Option<f32>,
1501        hinting: Hinting,
1502    ) -> Vec<LayoutLine> {
1503        let mut lines = Vec::with_capacity(1);
1504        let mut scratch = ShapeBuffer::default();
1505        self.layout_to_buffer(
1506            &mut scratch,
1507            font_size,
1508            width_opt,
1509            wrap,
1510            Ellipsize::None,
1511            align,
1512            &mut lines,
1513            match_mono_width,
1514            hinting,
1515        );
1516        lines
1517    }
1518
1519    fn get_glyph_start_end(
1520        word: &ShapeWord,
1521        start: SpanWordGlyphPos,
1522        span_index: usize,
1523        word_idx: usize,
1524        _direction: LayoutDirection,
1525        congruent: bool,
1526    ) -> (usize, usize) {
1527        if span_index != start.span || word_idx != start.word {
1528            return (0, word.glyphs.len());
1529        }
1530        let (start_glyph_pos, end_glyph_pos) = if congruent {
1531            (start.glyph, word.glyphs.len())
1532        } else {
1533            (0, start.glyph)
1534        };
1535        (start_glyph_pos, end_glyph_pos)
1536    }
1537
1538    fn fit_glyphs(
1539        word: &ShapeWord,
1540        font_size: f32,
1541        start: SpanWordGlyphPos,
1542        span_index: usize,
1543        word_idx: usize,
1544        direction: LayoutDirection,
1545        congruent: bool,
1546        currently_used_width: f32,
1547        total_available_width: f32,
1548        forward: bool,
1549    ) -> (usize, f32) {
1550        let mut glyphs_w = 0.0;
1551        let (start_glyph_pos, end_glyph_pos) =
1552            Self::get_glyph_start_end(word, start, span_index, word_idx, direction, congruent);
1553
1554        if forward {
1555            let mut glyph_end = start_glyph_pos;
1556            for glyph_idx in start_glyph_pos..end_glyph_pos {
1557                let g_w = word.glyphs[glyph_idx].width(font_size);
1558                if currently_used_width + glyphs_w + g_w > total_available_width {
1559                    break;
1560                }
1561                glyphs_w += g_w;
1562                glyph_end = glyph_idx + 1;
1563            }
1564            (glyph_end, glyphs_w)
1565        } else {
1566            let mut glyph_end = word.glyphs.len();
1567            for glyph_idx in (start_glyph_pos..end_glyph_pos).rev() {
1568                let g_w = word.glyphs[glyph_idx].width(font_size);
1569                if currently_used_width + glyphs_w + g_w > total_available_width {
1570                    break;
1571                }
1572                glyphs_w += g_w;
1573                glyph_end = glyph_idx;
1574            }
1575            (glyph_end, glyphs_w)
1576        }
1577    }
1578
1579    #[inline]
1580    fn add_to_visual_line(
1581        &self,
1582        vl: &mut VisualLine,
1583        span_index: usize,
1584        start: WordGlyphPos,
1585        end: WordGlyphPos,
1586        width: f32,
1587        number_of_blanks: u32,
1588    ) {
1589        if end == start {
1590            return;
1591        }
1592
1593        vl.ranges.push(VlRange {
1594            span: span_index,
1595            start,
1596            end,
1597            level: self.spans[span_index].level,
1598        });
1599        vl.w += width;
1600        vl.spaces += number_of_blanks;
1601    }
1602
1603    fn remaining_content_exceeds(
1604        spans: &[ShapeSpan],
1605        font_size: f32,
1606        span_index: usize,
1607        word_idx: usize,
1608        word_count: usize,
1609        starting_word_index: usize,
1610        direction: LayoutDirection,
1611        congruent: bool,
1612        start_span: usize,
1613        span_count: usize,
1614        threshold: f32,
1615    ) -> bool {
1616        let mut acc: f32 = 0.0;
1617
1618        // Remaining words in the current span
1619        let word_range: Range<usize> = match (direction, congruent) {
1620            (LayoutDirection::Forward, true) => word_idx + 1..word_count,
1621            (LayoutDirection::Forward, false) => 0..word_idx,
1622            (LayoutDirection::Backward, true) => starting_word_index..word_idx,
1623            (LayoutDirection::Backward, false) => word_idx + 1..word_count,
1624        };
1625        for wi in word_range {
1626            acc += spans[span_index].words[wi].width(font_size);
1627            if acc > threshold {
1628                return true;
1629            }
1630        }
1631
1632        // Remaining spans
1633        let span_range: Range<usize> = match direction {
1634            LayoutDirection::Forward => span_index + 1..span_count,
1635            LayoutDirection::Backward => start_span..span_index,
1636        };
1637        for si in span_range {
1638            for w in &spans[si].words {
1639                acc += w.width(font_size);
1640                if acc > threshold {
1641                    return true;
1642                }
1643            }
1644        }
1645
1646        false
1647    }
1648
1649    /// This will fit as much as possible in one line
1650    /// If forward is false, it will fit as much as possible from the end of the spans
1651    /// it will stop when it gets to "start".
1652    /// If forward is true, it will start from start and keep going to the end of the spans
1653    #[inline]
1654    fn layout_spans(
1655        &self,
1656        current_visual_line: &mut VisualLine,
1657        font_size: f32,
1658        spans: &[ShapeSpan],
1659        start_opt: Option<SpanWordGlyphPos>,
1660        rtl: bool,
1661        width_opt: Option<f32>,
1662        ellipsize: Ellipsize,
1663        ellipsis_w: f32,
1664        direction: LayoutDirection,
1665    ) {
1666        let check_ellipsizing = matches!(ellipsize, Ellipsize::Start(_) | Ellipsize::End(_))
1667            && width_opt.is_some_and(|w| w.is_finite());
1668
1669        let max_width = width_opt.unwrap_or(f32::INFINITY);
1670        let span_count = spans.len();
1671
1672        let mut total_w: f32 = 0.0;
1673
1674        let start = if let Some(s) = start_opt {
1675            s
1676        } else {
1677            SpanWordGlyphPos::ZERO
1678        };
1679
1680        let span_indices: Vec<usize> = if matches!(direction, LayoutDirection::Forward) {
1681            (start.span..spans.len()).collect()
1682        } else {
1683            (start.span..spans.len()).rev().collect()
1684        };
1685
1686        'outer: for span_index in span_indices {
1687            let mut word_range_width = 0.;
1688            let mut number_of_blanks: u32 = 0;
1689
1690            let span = &spans[span_index];
1691            let word_count = span.words.len();
1692
1693            let starting_word_index = if span_index == start.span {
1694                start.word
1695            } else {
1696                0
1697            };
1698
1699            let congruent = rtl == span.level.is_rtl();
1700            let word_forward: bool = congruent == (direction == LayoutDirection::Forward);
1701
1702            let word_indices: Vec<usize> = match (direction, congruent, start_opt) {
1703                (LayoutDirection::Forward, true, _) => (starting_word_index..word_count).collect(),
1704                (LayoutDirection::Forward, false, Some(start)) => {
1705                    if span_index == start.span {
1706                        (0..start.word).rev().collect()
1707                    } else {
1708                        (0..word_count).rev().collect()
1709                    }
1710                }
1711                (LayoutDirection::Forward, false, None) => (0..word_count).rev().collect(),
1712                (LayoutDirection::Backward, true, _) => {
1713                    ((starting_word_index)..word_count).rev().collect()
1714                }
1715                (LayoutDirection::Backward, false, Some(start)) => {
1716                    if span_index == start.span {
1717                        if start.glyph > 0 {
1718                            (0..(start.word + 1)).collect()
1719                        } else {
1720                            (0..(start.word)).collect()
1721                        }
1722                    } else {
1723                        (0..word_count).collect()
1724                    }
1725                }
1726                (LayoutDirection::Backward, false, None) => (0..span.words.len()).collect(),
1727            };
1728            for word_idx in word_indices {
1729                let word = &span.words[word_idx];
1730                let word_width = if span_index == start.span && word_idx == start.word {
1731                    let (start_glyph_pos, end_glyph_pos) = Self::get_glyph_start_end(
1732                        word, start, span_index, word_idx, direction, congruent,
1733                    );
1734                    let mut w = 0.;
1735                    for glyph_idx in start_glyph_pos..end_glyph_pos {
1736                        w += word.glyphs[glyph_idx].width(font_size);
1737                    }
1738                    w
1739                } else {
1740                    word.width(font_size)
1741                };
1742
1743                let overflowing = {
1744                    // only check this if we're ellipsizing
1745                    check_ellipsizing
1746                        && (
1747                            // if this  word doesn't fit, then we have an overflow
1748                            (total_w + word_range_width + word_width > max_width)
1749                                || (Self::remaining_content_exceeds(
1750                                    spans,
1751                                    font_size,
1752                                    span_index,
1753                                    word_idx,
1754                                    word_count,
1755                                    starting_word_index,
1756                                    direction,
1757                                    congruent,
1758                                    start.span,
1759                                    span_count,
1760                                    ellipsis_w,
1761                                ) && total_w + word_range_width + word_width + ellipsis_w
1762                                    > max_width)
1763                        )
1764                };
1765
1766                if overflowing {
1767                    // overflow detected
1768                    let available = (max_width - ellipsis_w).max(0.0);
1769
1770                    let (glyph_end, glyphs_w) = Self::fit_glyphs(
1771                        word,
1772                        font_size,
1773                        start,
1774                        span_index,
1775                        word_idx,
1776                        direction,
1777                        congruent,
1778                        total_w + word_range_width,
1779                        available,
1780                        word_forward,
1781                    );
1782
1783                    let (start_pos, end_pos) = if word_forward {
1784                        if span_index == start.span {
1785                            if !congruent {
1786                                (WordGlyphPos::ZERO, WordGlyphPos::new(word_idx, glyph_end))
1787                            } else {
1788                                (
1789                                    start.word_glyph_pos(),
1790                                    WordGlyphPos::new(word_idx, glyph_end),
1791                                )
1792                            }
1793                        } else {
1794                            (WordGlyphPos::ZERO, WordGlyphPos::new(word_idx, glyph_end))
1795                        }
1796                    } else {
1797                        // For an incongruent span in the forward direction, the
1798                        // word indices are (0..start.word).rev(). Cap the VlRange
1799                        // end at start.word_glyph_pos() so it doesn't include
1800                        // words beyond start.word that belong to a previous line.
1801                        // For the backward direction (congruent span), the word
1802                        // indices are (start.word..word_count).rev() and
1803                        // span.words.len() is the correct end.
1804                        let range_end = if span_index == start.span && !congruent {
1805                            start.word_glyph_pos()
1806                        } else {
1807                            WordGlyphPos::new(span.words.len(), 0)
1808                        };
1809                        (WordGlyphPos::new(word_idx, glyph_end), range_end)
1810                    };
1811                    self.add_to_visual_line(
1812                        current_visual_line,
1813                        span_index,
1814                        start_pos,
1815                        end_pos,
1816                        word_range_width + glyphs_w,
1817                        number_of_blanks,
1818                    );
1819
1820                    // don't iterate anymore since we overflowed
1821                    current_visual_line.ellipsized = true;
1822                    break 'outer;
1823                }
1824
1825                word_range_width += word_width;
1826                if word.blank {
1827                    number_of_blanks += 1;
1828                }
1829
1830                // Backward-only: if we've reached the starting point, commit and stop.
1831                if matches!(direction, LayoutDirection::Backward)
1832                    && word_idx == start.word
1833                    && span_index == start.span
1834                {
1835                    let (start_pos, end_pos) = if word_forward {
1836                        (WordGlyphPos::ZERO, start.word_glyph_pos())
1837                    } else {
1838                        (
1839                            start.word_glyph_pos(),
1840                            WordGlyphPos::new(span.words.len(), 0),
1841                        )
1842                    };
1843
1844                    self.add_to_visual_line(
1845                        current_visual_line,
1846                        span_index,
1847                        start_pos,
1848                        end_pos,
1849                        word_range_width,
1850                        number_of_blanks,
1851                    );
1852
1853                    break 'outer;
1854                }
1855            }
1856
1857            // if we get to here that means we didn't ellipsize, so either the whole span fits,
1858            // or we don't really care
1859            total_w += word_range_width;
1860            let (start_pos, end_pos) = if congruent {
1861                if span_index == start.span {
1862                    (
1863                        start.word_glyph_pos(),
1864                        WordGlyphPos::new(span.words.len(), 0),
1865                    )
1866                } else {
1867                    (WordGlyphPos::ZERO, WordGlyphPos::new(span.words.len(), 0))
1868                }
1869            } else if span_index == start.span {
1870                (WordGlyphPos::ZERO, start.word_glyph_pos())
1871            } else {
1872                (WordGlyphPos::ZERO, WordGlyphPos::new(span.words.len(), 0))
1873            };
1874
1875            self.add_to_visual_line(
1876                current_visual_line,
1877                span_index,
1878                start_pos,
1879                end_pos,
1880                word_range_width,
1881                number_of_blanks,
1882            );
1883        }
1884
1885        if matches!(direction, LayoutDirection::Backward) {
1886            current_visual_line.ranges.reverse();
1887        }
1888    }
1889
1890    fn layout_middle(
1891        &self,
1892        current_visual_line: &mut VisualLine,
1893        font_size: f32,
1894        spans: &[ShapeSpan],
1895        start_opt: Option<SpanWordGlyphPos>,
1896        rtl: bool,
1897        width: f32,
1898        ellipsize: Ellipsize,
1899        ellipsis_w: f32,
1900    ) {
1901        assert!(matches!(ellipsize, Ellipsize::Middle(_)));
1902
1903        // First check if all content fits without any ellipsis.
1904        {
1905            let mut test_line = VisualLine::default();
1906            self.layout_spans(
1907                &mut test_line,
1908                font_size,
1909                spans,
1910                start_opt,
1911                rtl,
1912                Some(width),
1913                Ellipsize::End(EllipsizeHeightLimit::Lines(1)),
1914                ellipsis_w,
1915                LayoutDirection::Forward,
1916            );
1917            if !test_line.ellipsized && test_line.w <= width {
1918                *current_visual_line = test_line;
1919                return;
1920            }
1921        }
1922
1923        let mut starting_line = VisualLine::default();
1924        self.layout_spans(
1925            &mut starting_line,
1926            font_size,
1927            spans,
1928            start_opt,
1929            rtl,
1930            Some(width / 2.0),
1931            Ellipsize::End(EllipsizeHeightLimit::Lines(1)),
1932            0., //pass 0 for ellipsis_w
1933            LayoutDirection::Forward,
1934        );
1935        let forward_pass_overflowed = starting_line.ellipsized;
1936        let end_range_opt = starting_line.ranges.last();
1937        match end_range_opt {
1938            Some(range) if forward_pass_overflowed => {
1939                let congruent = rtl == self.spans[range.span].level.is_rtl();
1940                // create a new range and do the other half
1941                let mut ending_line = VisualLine::default();
1942                let start = if congruent {
1943                    SpanWordGlyphPos {
1944                        span: range.span,
1945                        word: range.end.word,
1946                        glyph: range.end.glyph,
1947                    }
1948                } else {
1949                    SpanWordGlyphPos {
1950                        span: range.span,
1951                        word: range.start.word,
1952                        glyph: range.start.glyph,
1953                    }
1954                };
1955                self.layout_spans(
1956                    &mut ending_line,
1957                    font_size,
1958                    spans,
1959                    Some(start),
1960                    rtl,
1961                    Some((width - starting_line.w - ellipsis_w).max(0.0)),
1962                    Ellipsize::Start(EllipsizeHeightLimit::Lines(1)),
1963                    0., //pass 0 for ellipsis_w
1964                    LayoutDirection::Backward,
1965                );
1966                // Insert the ellipsis VlRange between the two halves.
1967                // Its BiDi level is determined by the adjacent ranges.
1968                let ellipsis_level = self.ellipsis_level_between(
1969                    starting_line.ranges.last(),
1970                    ending_line.ranges.first(),
1971                );
1972                starting_line
1973                    .ranges
1974                    .push(self.ellipsis_vlrange(ellipsis_level));
1975                starting_line.ranges.extend(ending_line.ranges);
1976                current_visual_line.ranges = starting_line.ranges;
1977                current_visual_line.ellipsized = true;
1978                current_visual_line.w = starting_line.w + ending_line.w + ellipsis_w;
1979                current_visual_line.spaces = starting_line.spaces + ending_line.spaces;
1980            }
1981            None if forward_pass_overflowed && width > ellipsis_w => {
1982                // buffer is small enough that the forward pass didn't fit
1983                // only show the ellipsis
1984                current_visual_line
1985                    .ranges
1986                    .push(self.ellipsis_vlrange(if self.rtl {
1987                        unicode_bidi::Level::rtl()
1988                    } else {
1989                        unicode_bidi::Level::ltr()
1990                    }));
1991                current_visual_line.ellipsized = true;
1992                current_visual_line.w = ellipsis_w;
1993                current_visual_line.spaces = 0;
1994            }
1995            _ => {
1996                // everything fit in the forward pass
1997                current_visual_line.ranges = starting_line.ranges;
1998                current_visual_line.w = starting_line.w;
1999                current_visual_line.spaces = starting_line.spaces;
2000                current_visual_line.ellipsized = false;
2001            }
2002        }
2003    }
2004
2005    /// Returns the words for a given span index, handling the ellipsis sentinel.
2006    fn get_span_words(&self, span_index: usize) -> &[ShapeWord] {
2007        if span_index == ELLIPSIS_SPAN {
2008            &self
2009                .ellipsis_span
2010                .as_ref()
2011                .expect("ellipsis_span not set")
2012                .words
2013        } else {
2014            &self.spans[span_index].words
2015        }
2016    }
2017
2018    fn byte_range_of_vlrange(&self, r: &VlRange) -> Option<(usize, usize)> {
2019        debug_assert_ne!(r.span, ELLIPSIS_SPAN);
2020        let words = self.get_span_words(r.span);
2021        let mut min_byte = usize::MAX;
2022        let mut max_byte = 0usize;
2023        let end_word = r.end.word + usize::from(r.end.glyph != 0);
2024        for (i, word) in words.iter().enumerate().take(end_word).skip(r.start.word) {
2025            let included_glyphs = match (i == r.start.word, i == r.end.word) {
2026                (false, false) => &word.glyphs[..],
2027                (true, false) => &word.glyphs[r.start.glyph..],
2028                (false, true) => &word.glyphs[..r.end.glyph],
2029                (true, true) => &word.glyphs[r.start.glyph..r.end.glyph],
2030            };
2031            for glyph in included_glyphs {
2032                min_byte = min_byte.min(glyph.start);
2033                max_byte = max_byte.max(glyph.end);
2034            }
2035        }
2036        if min_byte <= max_byte {
2037            Some((min_byte, max_byte))
2038        } else {
2039            None
2040        }
2041    }
2042
2043    fn compute_elided_byte_range(
2044        &self,
2045        visual_line: &VisualLine,
2046        line_len: usize,
2047    ) -> Option<(usize, usize)> {
2048        if !visual_line.ellipsized {
2049            return None;
2050        }
2051        // Find the position of the ellipsis VlRange
2052        let ellipsis_idx = visual_line
2053            .ranges
2054            .iter()
2055            .position(|r| r.span == ELLIPSIS_SPAN)?;
2056
2057        // Find the byte range of the visible content before the ellipsis
2058        let before_end = (0..ellipsis_idx)
2059            .rev()
2060            .find_map(|i| self.byte_range_of_vlrange(&visual_line.ranges[i]))
2061            .map(|(_, end)| end)
2062            .unwrap_or(0);
2063
2064        // Find the byte range of the visible content after the ellipsis
2065        let after_start = (ellipsis_idx + 1..visual_line.ranges.len())
2066            .find_map(|i| self.byte_range_of_vlrange(&visual_line.ranges[i]))
2067            .map(|(start, _)| start)
2068            .unwrap_or(line_len);
2069
2070        Some((before_end, after_start))
2071    }
2072
2073    /// Returns the maximum byte offset across all glyphs in all non-ellipsis spans.
2074    /// This effectively gives the byte length of the original shaped text.
2075    fn max_byte_offset(&self) -> usize {
2076        self.spans
2077            .iter()
2078            .flat_map(|span| span.words.iter())
2079            .flat_map(|word| word.glyphs.iter())
2080            .map(|g| g.end)
2081            .max()
2082            .unwrap_or(0)
2083    }
2084
2085    /// Returns the width of the ellipsis in the given font size.
2086    fn ellipsis_w(&self, font_size: f32) -> f32 {
2087        self.ellipsis_span
2088            .as_ref()
2089            .map_or(0.0, |s| s.words.iter().map(|w| w.width(font_size)).sum())
2090    }
2091
2092    /// Creates a `VlRange` for the ellipsis with the give`BiDi`Di level.
2093    fn ellipsis_vlrange(&self, level: unicode_bidi::Level) -> VlRange {
2094        VlRange {
2095            span: ELLIPSIS_SPAN,
2096            start: WordGlyphPos::ZERO,
2097            end: WordGlyphPos::new(1, 0),
2098            level,
2099        }
2100    }
2101
2102    /// Determines the appropriate `BiDi` level for the ellipsis based on the
2103    /// adjacent ranges, following UAX#9 N1/N2 rules for neutral characters.
2104    fn ellipsis_level_between(
2105        &self,
2106        before: Option<&VlRange>,
2107        after: Option<&VlRange>,
2108    ) -> unicode_bidi::Level {
2109        match (before, after) {
2110            (Some(a), Some(b)) if a.level == b.level => a.level,
2111            (Some(a), None) => a.level,
2112            (None, Some(b)) => b.level,
2113            _ => {
2114                if self.rtl {
2115                    unicode_bidi::Level::rtl()
2116                } else {
2117                    unicode_bidi::Level::ltr()
2118                }
2119            }
2120        }
2121    }
2122
2123    fn layout_line(
2124        &self,
2125        current_visual_line: &mut VisualLine,
2126        font_size: f32,
2127        spans: &[ShapeSpan],
2128        start_opt: Option<SpanWordGlyphPos>,
2129        rtl: bool,
2130        width_opt: Option<f32>,
2131        ellipsize: Ellipsize,
2132    ) {
2133        let ellipsis_w = self.ellipsis_w(font_size);
2134
2135        match (ellipsize, width_opt) {
2136            (Ellipsize::Start(_), Some(_)) => {
2137                self.layout_spans(
2138                    current_visual_line,
2139                    font_size,
2140                    spans,
2141                    start_opt,
2142                    rtl,
2143                    width_opt,
2144                    ellipsize,
2145                    ellipsis_w,
2146                    LayoutDirection::Backward,
2147                );
2148                // Insert ellipsis at the visual start (index 0, after backward reversal)
2149                if current_visual_line.ellipsized {
2150                    let level =
2151                        self.ellipsis_level_between(None, current_visual_line.ranges.first());
2152                    current_visual_line
2153                        .ranges
2154                        .insert(0, self.ellipsis_vlrange(level));
2155                    current_visual_line.w += ellipsis_w;
2156                }
2157            }
2158            (Ellipsize::Middle(_), Some(width)) => {
2159                self.layout_middle(
2160                    current_visual_line,
2161                    font_size,
2162                    spans,
2163                    start_opt,
2164                    rtl,
2165                    width,
2166                    ellipsize,
2167                    ellipsis_w,
2168                );
2169            }
2170            _ => {
2171                self.layout_spans(
2172                    current_visual_line,
2173                    font_size,
2174                    spans,
2175                    start_opt,
2176                    rtl,
2177                    width_opt,
2178                    ellipsize,
2179                    ellipsis_w,
2180                    LayoutDirection::Forward,
2181                );
2182                // Insert ellipsis at the visual end
2183                if current_visual_line.ellipsized {
2184                    let level =
2185                        self.ellipsis_level_between(current_visual_line.ranges.last(), None);
2186                    current_visual_line
2187                        .ranges
2188                        .push(self.ellipsis_vlrange(level));
2189                    current_visual_line.w += ellipsis_w;
2190                }
2191            }
2192        }
2193
2194        // Compute the byte range of ellipsized text so the ellipsis LayoutGlyph
2195        // can have valid start/end indices into the original line text.
2196        if current_visual_line.ellipsized {
2197            let line_len = self.max_byte_offset();
2198            current_visual_line.elided_byte_range =
2199                self.compute_elided_byte_range(current_visual_line, line_len);
2200        }
2201    }
2202
2203    pub fn layout_to_buffer(
2204        &self,
2205        scratch: &mut ShapeBuffer,
2206        font_size: f32,
2207        width_opt: Option<f32>,
2208        wrap: Wrap,
2209        ellipsize: Ellipsize,
2210        align: Option<Align>,
2211        layout_lines: &mut Vec<LayoutLine>,
2212        match_mono_width: Option<f32>,
2213        hinting: Hinting,
2214    ) {
2215        // For each visual line a list of  (span index,  and range of words in that span)
2216        // Note that a BiDi visual line could have multiple spans or parts of them
2217        // let mut vl_range_of_spans = Vec::with_capacity(1);
2218        let mut visual_lines = mem::take(&mut scratch.visual_lines);
2219        let mut cached_visual_lines = mem::take(&mut scratch.cached_visual_lines);
2220        cached_visual_lines.clear();
2221        cached_visual_lines.extend(visual_lines.drain(..).map(|mut l| {
2222            l.clear();
2223            l
2224        }));
2225
2226        // Cache glyph sets in reverse order so they will ideally be reused in exactly the same lines.
2227        let mut cached_glyph_sets = mem::take(&mut scratch.glyph_sets);
2228        cached_glyph_sets.clear();
2229        cached_glyph_sets.extend(layout_lines.drain(..).rev().map(|mut v| {
2230            v.glyphs.clear();
2231            v.glyphs
2232        }));
2233
2234        // This would keep the maximum number of spans that would fit on a visual line
2235        // If one span is too large, this variable will hold the range of words inside that span
2236        // that fits on a line.
2237        // let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
2238        let mut current_visual_line = cached_visual_lines.pop().unwrap_or_default();
2239
2240        if wrap == Wrap::None {
2241            self.layout_line(
2242                &mut current_visual_line,
2243                font_size,
2244                &self.spans,
2245                None,
2246                self.rtl,
2247                width_opt,
2248                ellipsize,
2249            );
2250        } else {
2251            let mut total_line_height = 0.0;
2252            let mut total_line_count = 0;
2253            let max_line_count_opt = match ellipsize {
2254                Ellipsize::Start(EllipsizeHeightLimit::Lines(lines))
2255                | Ellipsize::Middle(EllipsizeHeightLimit::Lines(lines))
2256                | Ellipsize::End(EllipsizeHeightLimit::Lines(lines)) => Some(lines.max(1)),
2257                _ => None,
2258            };
2259            let max_height_opt = match ellipsize {
2260                Ellipsize::Start(EllipsizeHeightLimit::Height(height))
2261                | Ellipsize::Middle(EllipsizeHeightLimit::Height(height))
2262                | Ellipsize::End(EllipsizeHeightLimit::Height(height)) => Some(height),
2263                _ => None,
2264            };
2265            let line_height = self
2266                .metrics_opt
2267                .map_or_else(|| font_size, |m| m.line_height);
2268
2269            let try_ellipsize_last_line = |total_line_count: usize,
2270                                           total_line_height: f32,
2271                                           current_visual_line: &mut VisualLine,
2272                                           font_size: f32,
2273                                           start_opt: Option<SpanWordGlyphPos>,
2274                                           width_opt: Option<f32>,
2275                                           ellipsize: Ellipsize|
2276             -> bool {
2277                // If Ellipsize::End, then how many lines can we fit or how much is the available height
2278                if max_line_count_opt == Some(total_line_count + 1)
2279                    || max_height_opt.is_some_and(|max_height| {
2280                        total_line_height + line_height * 2.0 > max_height
2281                    })
2282                {
2283                    self.layout_line(
2284                        current_visual_line,
2285                        font_size,
2286                        &self.spans,
2287                        start_opt,
2288                        self.rtl,
2289                        width_opt,
2290                        ellipsize,
2291                    );
2292                    return true;
2293                }
2294                false
2295            };
2296
2297            if !try_ellipsize_last_line(
2298                total_line_count,
2299                total_line_height,
2300                &mut current_visual_line,
2301                font_size,
2302                None,
2303                width_opt,
2304                ellipsize,
2305            ) {
2306                'outer: for (span_index, span) in self.spans.iter().enumerate() {
2307                    let mut word_range_width = 0.;
2308                    let mut width_before_last_blank = 0.;
2309                    let mut number_of_blanks: u32 = 0;
2310
2311                    // Create the word ranges that fits in a visual line
2312                    if self.rtl != span.level.is_rtl() {
2313                        // incongruent directions
2314                        let mut fitting_start = WordGlyphPos::new(span.words.len(), 0);
2315                        for (i, word) in span.words.iter().enumerate().rev() {
2316                            let word_width = word.width(font_size);
2317                            // Addition in the same order used to compute the final width, so that
2318                            // relayouts with that width as the `line_width` will produce the same
2319                            // wrapping results.
2320                            if current_visual_line.w + (word_range_width + word_width)
2321                            <= width_opt.unwrap_or(f32::INFINITY)
2322                            // Include one blank word over the width limit since it won't be
2323                            // counted in the final width
2324                            || (word.blank
2325                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
2326                            {
2327                                // fits
2328                                if word.blank {
2329                                    number_of_blanks += 1;
2330                                    width_before_last_blank = word_range_width;
2331                                }
2332                                word_range_width += word_width;
2333                            } else if wrap == Wrap::Glyph
2334                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
2335                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
2336                            {
2337                                // Commit the current line so that the word starts on the next line.
2338                                if word_range_width > 0.
2339                                    && wrap == Wrap::WordOrGlyph
2340                                    && word_width > width_opt.unwrap_or(f32::INFINITY)
2341                                {
2342                                    self.add_to_visual_line(
2343                                        &mut current_visual_line,
2344                                        span_index,
2345                                        WordGlyphPos::new(i + 1, 0),
2346                                        fitting_start,
2347                                        word_range_width,
2348                                        number_of_blanks,
2349                                    );
2350
2351                                    visual_lines.push(current_visual_line);
2352                                    current_visual_line =
2353                                        cached_visual_lines.pop().unwrap_or_default();
2354
2355                                    number_of_blanks = 0;
2356                                    word_range_width = 0.;
2357
2358                                    fitting_start = WordGlyphPos::new(i, 0);
2359                                    total_line_count += 1;
2360                                    total_line_height += line_height;
2361                                    if try_ellipsize_last_line(
2362                                        total_line_count,
2363                                        total_line_height,
2364                                        &mut current_visual_line,
2365                                        font_size,
2366                                        Some(SpanWordGlyphPos::with_wordglyph(
2367                                            span_index,
2368                                            fitting_start,
2369                                        )),
2370                                        width_opt,
2371                                        ellipsize,
2372                                    ) {
2373                                        break 'outer;
2374                                    }
2375                                }
2376
2377                                for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
2378                                    let glyph_width = glyph.width(font_size);
2379                                    if current_visual_line.w + (word_range_width + glyph_width)
2380                                        <= width_opt.unwrap_or(f32::INFINITY)
2381                                    {
2382                                        word_range_width += glyph_width;
2383                                    } else {
2384                                        self.add_to_visual_line(
2385                                            &mut current_visual_line,
2386                                            span_index,
2387                                            WordGlyphPos::new(i, glyph_i + 1),
2388                                            fitting_start,
2389                                            word_range_width,
2390                                            number_of_blanks,
2391                                        );
2392                                        visual_lines.push(current_visual_line);
2393                                        current_visual_line =
2394                                            cached_visual_lines.pop().unwrap_or_default();
2395
2396                                        number_of_blanks = 0;
2397                                        word_range_width = glyph_width;
2398                                        fitting_start = WordGlyphPos::new(i, glyph_i + 1);
2399                                        total_line_count += 1;
2400                                        total_line_height += line_height;
2401                                        if try_ellipsize_last_line(
2402                                            total_line_count,
2403                                            total_line_height,
2404                                            &mut current_visual_line,
2405                                            font_size,
2406                                            Some(SpanWordGlyphPos::with_wordglyph(
2407                                                span_index,
2408                                                fitting_start,
2409                                            )),
2410                                            width_opt,
2411                                            ellipsize,
2412                                        ) {
2413                                            break 'outer;
2414                                        }
2415                                    }
2416                                }
2417                            } else {
2418                                // Wrap::Word, Wrap::WordOrGlyph
2419
2420                                // If we had a previous range, commit that line before the next word.
2421                                if word_range_width > 0. {
2422                                    // Current word causing a wrap is not whitespace, so we ignore the
2423                                    // previous word if it's a whitespace
2424                                    let trailing_blank = span
2425                                        .words
2426                                        .get(i + 1)
2427                                        .is_some_and(|previous_word| previous_word.blank);
2428
2429                                    if trailing_blank {
2430                                        number_of_blanks = number_of_blanks.saturating_sub(1);
2431                                        self.add_to_visual_line(
2432                                            &mut current_visual_line,
2433                                            span_index,
2434                                            WordGlyphPos::new(i + 2, 0),
2435                                            fitting_start,
2436                                            width_before_last_blank,
2437                                            number_of_blanks,
2438                                        );
2439                                    } else {
2440                                        self.add_to_visual_line(
2441                                            &mut current_visual_line,
2442                                            span_index,
2443                                            WordGlyphPos::new(i + 1, 0),
2444                                            fitting_start,
2445                                            word_range_width,
2446                                            number_of_blanks,
2447                                        );
2448                                    }
2449                                }
2450
2451                                // This fixes a bug that a long first word at the boundary of
2452                                // was overflowing
2453                                if !current_visual_line.ranges.is_empty() {
2454                                    visual_lines.push(current_visual_line);
2455                                    current_visual_line =
2456                                        cached_visual_lines.pop().unwrap_or_default();
2457                                    number_of_blanks = 0;
2458                                    total_line_count += 1;
2459                                    total_line_height += line_height;
2460
2461                                    if try_ellipsize_last_line(
2462                                        total_line_count,
2463                                        total_line_height,
2464                                        &mut current_visual_line,
2465                                        font_size,
2466                                        Some(SpanWordGlyphPos::with_wordglyph(
2467                                            span_index,
2468                                            if word.blank {
2469                                                WordGlyphPos::new(i, 0)
2470                                            } else {
2471                                                WordGlyphPos::new(i + 1, 0)
2472                                            },
2473                                        )),
2474                                        width_opt,
2475                                        ellipsize,
2476                                    ) {
2477                                        break 'outer;
2478                                    }
2479                                }
2480
2481                                if word.blank {
2482                                    word_range_width = 0.;
2483                                    fitting_start = WordGlyphPos::new(i, 0);
2484                                } else {
2485                                    word_range_width = word_width;
2486                                    fitting_start = WordGlyphPos::new(i + 1, 0);
2487                                }
2488                            }
2489                        }
2490                        self.add_to_visual_line(
2491                            &mut current_visual_line,
2492                            span_index,
2493                            WordGlyphPos::new(0, 0),
2494                            fitting_start,
2495                            word_range_width,
2496                            number_of_blanks,
2497                        );
2498                    } else {
2499                        // congruent direction
2500                        let mut fitting_start = WordGlyphPos::ZERO;
2501                        for (i, word) in span.words.iter().enumerate() {
2502                            let word_width = word.width(font_size);
2503                            if current_visual_line.w + (word_range_width + word_width)
2504                            <= width_opt.unwrap_or(f32::INFINITY)
2505                            // Include one blank word over the width limit since it won't be
2506                            // counted in the final width.
2507                            || (word.blank
2508                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
2509                            {
2510                                // fits
2511                                if word.blank {
2512                                    number_of_blanks += 1;
2513                                    width_before_last_blank = word_range_width;
2514                                }
2515                                word_range_width += word_width;
2516                            } else if wrap == Wrap::Glyph
2517                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
2518                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
2519                            {
2520                                // Commit the current line so that the word starts on the next line.
2521                                if word_range_width > 0.
2522                                    && wrap == Wrap::WordOrGlyph
2523                                    && word_width > width_opt.unwrap_or(f32::INFINITY)
2524                                {
2525                                    self.add_to_visual_line(
2526                                        &mut current_visual_line,
2527                                        span_index,
2528                                        fitting_start,
2529                                        WordGlyphPos::new(i, 0),
2530                                        word_range_width,
2531                                        number_of_blanks,
2532                                    );
2533
2534                                    visual_lines.push(current_visual_line);
2535                                    current_visual_line =
2536                                        cached_visual_lines.pop().unwrap_or_default();
2537
2538                                    number_of_blanks = 0;
2539                                    word_range_width = 0.;
2540
2541                                    fitting_start = WordGlyphPos::new(i, 0);
2542                                    total_line_count += 1;
2543                                    total_line_height += line_height;
2544                                    if try_ellipsize_last_line(
2545                                        total_line_count,
2546                                        total_line_height,
2547                                        &mut current_visual_line,
2548                                        font_size,
2549                                        Some(SpanWordGlyphPos::with_wordglyph(
2550                                            span_index,
2551                                            fitting_start,
2552                                        )),
2553                                        width_opt,
2554                                        ellipsize,
2555                                    ) {
2556                                        break 'outer;
2557                                    }
2558                                }
2559
2560                                for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
2561                                    let glyph_width = glyph.width(font_size);
2562                                    if current_visual_line.w + (word_range_width + glyph_width)
2563                                        <= width_opt.unwrap_or(f32::INFINITY)
2564                                    {
2565                                        word_range_width += glyph_width;
2566                                    } else {
2567                                        self.add_to_visual_line(
2568                                            &mut current_visual_line,
2569                                            span_index,
2570                                            fitting_start,
2571                                            WordGlyphPos::new(i, glyph_i),
2572                                            word_range_width,
2573                                            number_of_blanks,
2574                                        );
2575                                        visual_lines.push(current_visual_line);
2576                                        current_visual_line =
2577                                            cached_visual_lines.pop().unwrap_or_default();
2578
2579                                        number_of_blanks = 0;
2580                                        word_range_width = glyph_width;
2581                                        fitting_start = WordGlyphPos::new(i, glyph_i);
2582                                        total_line_count += 1;
2583                                        total_line_height += line_height;
2584                                        if try_ellipsize_last_line(
2585                                            total_line_count,
2586                                            total_line_height,
2587                                            &mut current_visual_line,
2588                                            font_size,
2589                                            Some(SpanWordGlyphPos::with_wordglyph(
2590                                                span_index,
2591                                                fitting_start,
2592                                            )),
2593                                            width_opt,
2594                                            ellipsize,
2595                                        ) {
2596                                            break 'outer;
2597                                        }
2598                                    }
2599                                }
2600                            } else {
2601                                // Wrap::Word, Wrap::WordOrGlyph
2602
2603                                // If we had a previous range, commit that line before the next word.
2604                                if word_range_width > 0. {
2605                                    // Current word causing a wrap is not whitespace, so we ignore the
2606                                    // previous word if it's a whitespace.
2607                                    let trailing_blank = i > 0 && span.words[i - 1].blank;
2608
2609                                    if trailing_blank {
2610                                        number_of_blanks = number_of_blanks.saturating_sub(1);
2611                                        self.add_to_visual_line(
2612                                            &mut current_visual_line,
2613                                            span_index,
2614                                            fitting_start,
2615                                            WordGlyphPos::new(i - 1, 0),
2616                                            width_before_last_blank,
2617                                            number_of_blanks,
2618                                        );
2619                                    } else {
2620                                        self.add_to_visual_line(
2621                                            &mut current_visual_line,
2622                                            span_index,
2623                                            fitting_start,
2624                                            WordGlyphPos::new(i, 0),
2625                                            word_range_width,
2626                                            number_of_blanks,
2627                                        );
2628                                    }
2629                                }
2630
2631                                if !current_visual_line.ranges.is_empty() {
2632                                    visual_lines.push(current_visual_line);
2633                                    current_visual_line =
2634                                        cached_visual_lines.pop().unwrap_or_default();
2635                                    number_of_blanks = 0;
2636                                    total_line_count += 1;
2637                                    total_line_height += line_height;
2638                                    if try_ellipsize_last_line(
2639                                        total_line_count,
2640                                        total_line_height,
2641                                        &mut current_visual_line,
2642                                        font_size,
2643                                        Some(SpanWordGlyphPos::with_wordglyph(
2644                                            span_index,
2645                                            if i > 0 && span.words[i - 1].blank {
2646                                                WordGlyphPos::new(i - 1, 0)
2647                                            } else {
2648                                                WordGlyphPos::new(i, 0)
2649                                            },
2650                                        )),
2651                                        width_opt,
2652                                        ellipsize,
2653                                    ) {
2654                                        break 'outer;
2655                                    }
2656                                }
2657
2658                                if word.blank {
2659                                    word_range_width = 0.;
2660                                    fitting_start = WordGlyphPos::new(i + 1, 0);
2661                                } else {
2662                                    word_range_width = word_width;
2663                                    fitting_start = WordGlyphPos::new(i, 0);
2664                                }
2665                            }
2666                        }
2667                        self.add_to_visual_line(
2668                            &mut current_visual_line,
2669                            span_index,
2670                            fitting_start,
2671                            WordGlyphPos::new(span.words.len(), 0),
2672                            word_range_width,
2673                            number_of_blanks,
2674                        );
2675                    }
2676                }
2677            }
2678        }
2679
2680        if current_visual_line.ranges.is_empty() {
2681            current_visual_line.clear();
2682            cached_visual_lines.push(current_visual_line);
2683        } else {
2684            visual_lines.push(current_visual_line);
2685        }
2686
2687        // Create the LayoutLines using the ranges inside visual lines
2688        let align = align.unwrap_or(if self.rtl { Align::Right } else { Align::Left });
2689
2690        let line_width = width_opt.unwrap_or_else(|| {
2691            let mut width: f32 = 0.0;
2692            for visual_line in &visual_lines {
2693                width = width.max(visual_line.w);
2694            }
2695            width
2696        });
2697
2698        let start_x = if self.rtl { line_width } else { 0.0 };
2699
2700        let number_of_visual_lines = visual_lines.len();
2701        for (index, visual_line) in visual_lines.iter().enumerate() {
2702            if visual_line.ranges.is_empty() {
2703                continue;
2704            }
2705
2706            let new_order = self.reorder(&visual_line.ranges);
2707
2708            let mut glyphs = cached_glyph_sets
2709                .pop()
2710                .unwrap_or_else(|| Vec::with_capacity(1));
2711            let mut x = start_x;
2712            let mut y = 0.;
2713            let mut max_ascent: f32 = 0.;
2714            let mut max_descent: f32 = 0.;
2715            let alignment_correction = match (align, self.rtl) {
2716                (Align::Left, true) => (line_width - visual_line.w).max(0.),
2717                (Align::Left, false) => 0.,
2718                (Align::Right, true) => 0.,
2719                (Align::Right, false) => (line_width - visual_line.w).max(0.),
2720                (Align::Center, _) => (line_width - visual_line.w).max(0.) / 2.0,
2721                (Align::End, _) => (line_width - visual_line.w).max(0.),
2722                (Align::Justified, _) => 0.,
2723            };
2724
2725            if self.rtl {
2726                x -= alignment_correction;
2727            } else {
2728                x += alignment_correction;
2729            }
2730
2731            if hinting == Hinting::Enabled {
2732                x = x.round();
2733            }
2734
2735            // TODO: Only certain `is_whitespace` chars are typically expanded but this is what is
2736            // currently used to compute `visual_line.spaces`.
2737            //
2738            // https://www.unicode.org/reports/tr14/#Introduction
2739            // > When expanding or compressing interword space according to common
2740            // > typographical practice, only the spaces marked by U+0020 SPACE and U+00A0
2741            // > NO-BREAK SPACE are subject to compression, and only spaces marked by U+0020
2742            // > SPACE, U+00A0 NO-BREAK SPACE, and occasionally spaces marked by U+2009 THIN
2743            // > SPACE are subject to expansion. All other space characters normally have
2744            // > fixed width.
2745            //
2746            // (also some spaces aren't followed by potential linebreaks but they could
2747            //  still be expanded)
2748
2749            // Amount of extra width added to each blank space within a line.
2750            let justification_expansion = if matches!(align, Align::Justified)
2751                && visual_line.spaces > 0
2752                // Don't justify the last line in a paragraph.
2753                && index != number_of_visual_lines - 1
2754            {
2755                (line_width - visual_line.w) / visual_line.spaces as f32
2756            } else {
2757                0.
2758            };
2759
2760            let elided_byte_range = if visual_line.ellipsized {
2761                visual_line.elided_byte_range
2762            } else {
2763                None
2764            };
2765
2766            let mut decorations: Vec<DecorationSpan> = Vec::new();
2767
2768            let process_range = |range: Range<usize>,
2769                                 x: &mut f32,
2770                                 y: &mut f32,
2771                                 glyphs: &mut Vec<LayoutGlyph>,
2772                                 decorations: &mut Vec<DecorationSpan>,
2773                                 max_ascent: &mut f32,
2774                                 max_descent: &mut f32| {
2775                for r in visual_line.ranges[range.clone()].iter() {
2776                    let is_ellipsis = r.span == ELLIPSIS_SPAN;
2777                    let span_words = self.get_span_words(r.span);
2778                    let deco_spans: &[(Range<usize>, GlyphDecorationData)] = if is_ellipsis {
2779                        &[]
2780                    } else {
2781                        &self.spans[r.span].decoration_spans
2782                    };
2783                    // Cursor into deco_spans — advances forward as glyphs are
2784                    // emitted in byte order, giving amortized O(1) lookup.
2785                    let mut deco_cursor: usize = 0;
2786                    // If ending_glyph is not 0 we need to include glyphs from the ending_word
2787                    for i in r.start.word..r.end.word + usize::from(r.end.glyph != 0) {
2788                        let word = &span_words[i];
2789                        let included_glyphs = match (i == r.start.word, i == r.end.word) {
2790                            (false, false) => &word.glyphs[..],
2791                            (true, false) => &word.glyphs[r.start.glyph..],
2792                            (false, true) => &word.glyphs[..r.end.glyph],
2793                            (true, true) => &word.glyphs[r.start.glyph..r.end.glyph],
2794                        };
2795
2796                        for glyph in included_glyphs {
2797                            // Use overridden font size
2798                            let font_size = glyph.metrics_opt.map_or(font_size, |x| x.font_size);
2799
2800                            let match_mono_em_width = match_mono_width.map(|w| w / font_size);
2801
2802                            let glyph_font_size = match (
2803                                match_mono_em_width,
2804                                glyph.font_monospace_em_width,
2805                            ) {
2806                                (Some(match_em_width), Some(glyph_em_width))
2807                                    if glyph_em_width != match_em_width =>
2808                                {
2809                                    let glyph_to_match_factor = glyph_em_width / match_em_width;
2810                                    let glyph_font_size = math::roundf(glyph_to_match_factor)
2811                                        .max(1.0)
2812                                        / glyph_to_match_factor
2813                                        * font_size;
2814                                    log::trace!(
2815                                        "Adjusted glyph font size ({font_size} => {glyph_font_size})"
2816                                    );
2817                                    glyph_font_size
2818                                }
2819                                _ => font_size,
2820                            };
2821
2822                            let mut x_advance = glyph_font_size.mul_add(
2823                                glyph.x_advance,
2824                                if word.blank {
2825                                    justification_expansion
2826                                } else {
2827                                    0.0
2828                                },
2829                            );
2830                            if let Some(match_em_width) = match_mono_em_width {
2831                                // Round to nearest monospace width
2832                                x_advance = ((x_advance / match_em_width).round()) * match_em_width;
2833                            }
2834                            if hinting == Hinting::Enabled {
2835                                x_advance = x_advance.round();
2836                            }
2837                            if self.rtl {
2838                                *x -= x_advance;
2839                            }
2840                            let y_advance = glyph_font_size * glyph.y_advance;
2841                            let mut layout_glyph = glyph.layout(
2842                                glyph_font_size,
2843                                glyph.metrics_opt.map(|x| x.line_height),
2844                                *x,
2845                                *y,
2846                                x_advance,
2847                                r.level,
2848                            );
2849                            // Fix ellipsis glyph indices: point both start and
2850                            // end to the elision boundary so that hit-detection
2851                            // places the cursor at the seam between visible and
2852                            // elided text instead of selecting invisible content.
2853                            if is_ellipsis {
2854                                if let Some((elided_start, elided_end)) = elided_byte_range {
2855                                    // Use the boundary closest to the visible
2856                                    // content that is adjacent to this ellipsis:
2857                                    //   Start:  …|visible  → boundary = elided_end
2858                                    //   End:    visible|…  → boundary = elided_start
2859                                    //   Middle: vis|…|vis  → boundary = elided_start
2860                                    let boundary = if elided_start == 0 {
2861                                        elided_end
2862                                    } else {
2863                                        elided_start
2864                                    };
2865                                    layout_glyph.start = boundary;
2866                                    layout_glyph.end = boundary;
2867                                }
2868                            }
2869                            glyphs.push(layout_glyph);
2870
2871                            if deco_cursor >= deco_spans.len()
2872                                || glyph.start < deco_spans[deco_cursor].0.start
2873                            {
2874                                deco_cursor = 0;
2875                            }
2876                            while deco_cursor < deco_spans.len()
2877                                && deco_spans[deco_cursor].0.end <= glyph.start
2878                            {
2879                                deco_cursor += 1;
2880                            }
2881                            let glyph_deco = deco_spans
2882                                .get(deco_cursor)
2883                                .filter(|(range, _)| glyph.start >= range.start);
2884                            let glyph_idx = glyphs.len() - 1;
2885                            let extends = matches!(
2886                                (decorations.last(), &glyph_deco),
2887                                (Some(span), Some((_, d))) if span.data == *d
2888                            );
2889                            if extends {
2890                                if let Some(last) = decorations.last_mut() {
2891                                    last.glyph_range.end = glyph_idx + 1;
2892                                }
2893                            } else if let Some((_, d)) = glyph_deco {
2894                                decorations.push(DecorationSpan {
2895                                    glyph_range: glyph_idx..glyph_idx + 1,
2896                                    data: d.clone(),
2897                                    color_opt: glyphs[glyph_idx].color_opt,
2898                                    font_size: glyphs[glyph_idx].font_size,
2899                                });
2900                            }
2901                            if !self.rtl {
2902                                *x += x_advance;
2903                            }
2904                            *y += y_advance;
2905                            *max_ascent = max_ascent.max(glyph_font_size * glyph.ascent);
2906                            *max_descent = max_descent.max(glyph_font_size * glyph.descent);
2907                        }
2908                    }
2909                }
2910            };
2911
2912            if self.rtl {
2913                for range in new_order.into_iter().rev() {
2914                    process_range(
2915                        range,
2916                        &mut x,
2917                        &mut y,
2918                        &mut glyphs,
2919                        &mut decorations,
2920                        &mut max_ascent,
2921                        &mut max_descent,
2922                    );
2923                }
2924            } else {
2925                /* LTR */
2926                for range in new_order {
2927                    process_range(
2928                        range,
2929                        &mut x,
2930                        &mut y,
2931                        &mut glyphs,
2932                        &mut decorations,
2933                        &mut max_ascent,
2934                        &mut max_descent,
2935                    );
2936                }
2937            }
2938
2939            let mut line_height_opt: Option<f32> = None;
2940            for glyph in &glyphs {
2941                if let Some(glyph_line_height) = glyph.line_height_opt {
2942                    line_height_opt = line_height_opt
2943                        .map_or(Some(glyph_line_height), |line_height| {
2944                            Some(line_height.max(glyph_line_height))
2945                        });
2946                }
2947            }
2948
2949            layout_lines.push(LayoutLine {
2950                w: if align != Align::Justified {
2951                    visual_line.w
2952                } else if self.rtl {
2953                    start_x - x
2954                } else {
2955                    x
2956                },
2957                max_ascent,
2958                max_descent,
2959                line_height_opt,
2960                glyphs,
2961                decorations,
2962            });
2963        }
2964
2965        // This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
2966        if layout_lines.is_empty() {
2967            layout_lines.push(LayoutLine {
2968                w: 0.0,
2969                max_ascent: 0.0,
2970                max_descent: 0.0,
2971                line_height_opt: self.metrics_opt.map(|x| x.line_height),
2972                glyphs: Vec::default(),
2973                decorations: Vec::new(),
2974            });
2975        }
2976
2977        // Restore the buffer to the scratch set to prevent reallocations.
2978        scratch.visual_lines = visual_lines;
2979        scratch.visual_lines.append(&mut cached_visual_lines);
2980        scratch.cached_visual_lines = cached_visual_lines;
2981        scratch.glyph_sets = cached_glyph_sets;
2982    }
2983}