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        // Clear stale ellipsis span so it gets recomputed with the current attrs.
1266        // Without this, reusing a ShapeLine from a previous text (via Cached::Unused)
1267        // would keep an ellipsis shaped with the old attrs.
1268        self.ellipsis_span = None;
1269
1270        let mut spans = mem::take(&mut self.spans);
1271
1272        // Cache the shape spans in reverse order so they can be popped for reuse in the same order.
1273        let mut cached_spans = mem::take(&mut font_system.shape_buffer.spans);
1274        cached_spans.clear();
1275        cached_spans.extend(spans.drain(..).rev());
1276
1277        let bidi = unicode_bidi::BidiInfo::new(line, None);
1278        let rtl = if bidi.paragraphs.is_empty() {
1279            false
1280        } else {
1281            bidi.paragraphs[0].level.is_rtl()
1282        };
1283
1284        log::trace!("Line {}: '{}'", if rtl { "RTL" } else { "LTR" }, line);
1285
1286        for para_info in &bidi.paragraphs {
1287            let line_rtl = para_info.level.is_rtl();
1288            assert_eq!(line_rtl, rtl);
1289
1290            let line_range = para_info.range.clone();
1291            let levels = Self::adjust_levels(&unicode_bidi::Paragraph::new(&bidi, para_info));
1292
1293            // Find consecutive level runs. We use this to create Spans.
1294            // Each span is a set of characters with equal levels.
1295            let mut start = line_range.start;
1296            let mut run_level = levels[start];
1297            spans.reserve(line_range.end - start + 1);
1298
1299            for (i, &new_level) in levels
1300                .iter()
1301                .enumerate()
1302                .take(line_range.end)
1303                .skip(start + 1)
1304            {
1305                if new_level != run_level {
1306                    // End of the previous run, start of a new one.
1307                    let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
1308                    span.build(
1309                        font_system,
1310                        line,
1311                        attrs_list,
1312                        start..i,
1313                        line_rtl,
1314                        run_level,
1315                        shaping,
1316                    );
1317                    spans.push(span);
1318                    start = i;
1319                    run_level = new_level;
1320                }
1321            }
1322            let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
1323            span.build(
1324                font_system,
1325                line,
1326                attrs_list,
1327                start..line_range.end,
1328                line_rtl,
1329                run_level,
1330                shaping,
1331            );
1332            spans.push(span);
1333        }
1334
1335        // Adjust for tabs
1336        let mut x = 0.0;
1337        for span in &mut spans {
1338            for word in &mut span.words {
1339                for glyph in &mut word.glyphs {
1340                    if line.get(glyph.start..glyph.end) == Some("\t") {
1341                        // Tabs are shaped as spaces, so they will always have the x_advance of a space.
1342                        let tab_x_advance = f32::from(tab_width) * glyph.x_advance;
1343                        let tab_stop = (math::floorf(x / tab_x_advance) + 1.0) * tab_x_advance;
1344                        glyph.x_advance = tab_stop - x;
1345                    }
1346                    x += glyph.x_advance;
1347                }
1348            }
1349        }
1350
1351        self.rtl = rtl;
1352        self.spans = spans;
1353        self.metrics_opt = attrs_list.defaults().metrics_opt.map(Into::into);
1354
1355        self.ellipsis_span.get_or_insert_with(|| {
1356            let attrs = if attrs_list.spans.is_empty() {
1357                attrs_list.defaults()
1358            } else {
1359                attrs_list.get_span(0) // TODO: using the attrs from the first span for
1360                                       // ellipsis even if it's at the end. Which for rich text may look weird if the first
1361                                       // span has a different color or size than where ellipsizing is happening
1362            };
1363            let mut glyphs = shape_ellipsis(font_system, &attrs, shaping, rtl);
1364            if rtl {
1365                glyphs.reverse();
1366            }
1367            let word = ShapeWord {
1368                blank: false,
1369                glyphs,
1370            };
1371            // The level here is a placeholder; the actual level used for BiDi reordering
1372            // is set on the VlRange when the ellipsis is inserted during layout.
1373            let level = if rtl {
1374                unicode_bidi::Level::rtl()
1375            } else {
1376                unicode_bidi::Level::ltr()
1377            };
1378            ShapeSpan {
1379                level,
1380                words: vec![word],
1381                decoration_spans: Vec::new(),
1382            }
1383        });
1384
1385        // Return the buffer for later reuse.
1386        font_system.shape_buffer.spans = cached_spans;
1387    }
1388
1389    // A modified version of first part of unicode_bidi::bidi_info::visual_run
1390    fn adjust_levels(para: &unicode_bidi::Paragraph) -> Vec<unicode_bidi::Level> {
1391        use unicode_bidi::BidiClass::{B, BN, FSI, LRE, LRI, LRO, PDF, PDI, RLE, RLI, RLO, S, WS};
1392        let text = para.info.text;
1393        let levels = &para.info.levels;
1394        let original_classes = &para.info.original_classes;
1395
1396        let mut levels = levels.clone();
1397        let line_classes = &original_classes[..];
1398        let line_levels = &mut levels[..];
1399
1400        // Reset some whitespace chars to paragraph level.
1401        // <http://www.unicode.org/reports/tr9/#L1>
1402        let mut reset_from: Option<usize> = Some(0);
1403        let mut reset_to: Option<usize> = None;
1404        for (i, c) in text.char_indices() {
1405            match line_classes[i] {
1406                // Ignored by X9
1407                RLE | LRE | RLO | LRO | PDF | BN => {}
1408                // Segment separator, Paragraph separator
1409                B | S => {
1410                    assert_eq!(reset_to, None);
1411                    reset_to = Some(i + c.len_utf8());
1412                    if reset_from.is_none() {
1413                        reset_from = Some(i);
1414                    }
1415                }
1416                // Whitespace, isolate formatting
1417                WS | FSI | LRI | RLI | PDI => {
1418                    if reset_from.is_none() {
1419                        reset_from = Some(i);
1420                    }
1421                }
1422                _ => {
1423                    reset_from = None;
1424                }
1425            }
1426            if let (Some(from), Some(to)) = (reset_from, reset_to) {
1427                for level in &mut line_levels[from..to] {
1428                    *level = para.para.level;
1429                }
1430                reset_from = None;
1431                reset_to = None;
1432            }
1433        }
1434        if let Some(from) = reset_from {
1435            for level in &mut line_levels[from..] {
1436                *level = para.para.level;
1437            }
1438        }
1439        levels
1440    }
1441
1442    // A modified version of second part of unicode_bidi::bidi_info::visual run
1443    fn reorder(&self, line_range: &[VlRange]) -> Vec<Range<usize>> {
1444        let line: Vec<unicode_bidi::Level> = line_range.iter().map(|range| range.level).collect();
1445        let count = line.len();
1446        if count == 0 {
1447            return Vec::new();
1448        }
1449
1450        // Each VlRange is its own element for L2 reordering.
1451        // Using individual elements (not grouped runs) ensures that reversal
1452        // correctly reorders elements even when consecutive ranges share a level.
1453        let mut elements: Vec<Range<usize>> = (0..count).map(|i| i..i + 1).collect();
1454
1455        let mut min_level = line[0];
1456        let mut max_level = line[0];
1457        for &level in &line[1..] {
1458            min_level = min(min_level, level);
1459            max_level = max(max_level, level);
1460        }
1461
1462        // Re-order the odd runs.
1463        // <http://www.unicode.org/reports/tr9/#L2>
1464
1465        // Stop at the lowest *odd* level.
1466        min_level = min_level.new_lowest_ge_rtl().expect("Level error");
1467
1468        while max_level >= min_level {
1469            // Look for the start of a sequence of consecutive elements at max_level or higher.
1470            let mut seq_start = 0;
1471            while seq_start < count {
1472                if line[elements[seq_start].start] < max_level {
1473                    seq_start += 1;
1474                    continue;
1475                }
1476
1477                // Found the start of a sequence. Now find the end.
1478                let mut seq_end = seq_start + 1;
1479                while seq_end < count {
1480                    if line[elements[seq_end].start] < max_level {
1481                        break;
1482                    }
1483                    seq_end += 1;
1484                }
1485
1486                // Reverse the individual elements within this sequence.
1487                elements[seq_start..seq_end].reverse();
1488
1489                seq_start = seq_end;
1490            }
1491            max_level
1492                .lower(1)
1493                .expect("Lowering embedding level below zero");
1494        }
1495
1496        elements
1497    }
1498
1499    pub fn layout(
1500        &self,
1501        font_size: f32,
1502        width_opt: Option<f32>,
1503        wrap: Wrap,
1504        align: Option<Align>,
1505        match_mono_width: Option<f32>,
1506        hinting: Hinting,
1507    ) -> Vec<LayoutLine> {
1508        let mut lines = Vec::with_capacity(1);
1509        let mut scratch = ShapeBuffer::default();
1510        self.layout_to_buffer(
1511            &mut scratch,
1512            font_size,
1513            width_opt,
1514            wrap,
1515            Ellipsize::None,
1516            align,
1517            &mut lines,
1518            match_mono_width,
1519            hinting,
1520        );
1521        lines
1522    }
1523
1524    fn get_glyph_start_end(
1525        word: &ShapeWord,
1526        start: SpanWordGlyphPos,
1527        span_index: usize,
1528        word_idx: usize,
1529        _direction: LayoutDirection,
1530        congruent: bool,
1531    ) -> (usize, usize) {
1532        if span_index != start.span || word_idx != start.word {
1533            return (0, word.glyphs.len());
1534        }
1535        let (start_glyph_pos, end_glyph_pos) = if congruent {
1536            (start.glyph, word.glyphs.len())
1537        } else {
1538            (0, start.glyph)
1539        };
1540        (start_glyph_pos, end_glyph_pos)
1541    }
1542
1543    fn fit_glyphs(
1544        word: &ShapeWord,
1545        font_size: f32,
1546        start: SpanWordGlyphPos,
1547        span_index: usize,
1548        word_idx: usize,
1549        direction: LayoutDirection,
1550        congruent: bool,
1551        currently_used_width: f32,
1552        total_available_width: f32,
1553        forward: bool,
1554    ) -> (usize, f32) {
1555        let mut glyphs_w = 0.0;
1556        let (start_glyph_pos, end_glyph_pos) =
1557            Self::get_glyph_start_end(word, start, span_index, word_idx, direction, congruent);
1558
1559        if forward {
1560            let mut glyph_end = start_glyph_pos;
1561            for glyph_idx in start_glyph_pos..end_glyph_pos {
1562                let g_w = word.glyphs[glyph_idx].width(font_size);
1563                if currently_used_width + glyphs_w + g_w > total_available_width {
1564                    break;
1565                }
1566                glyphs_w += g_w;
1567                glyph_end = glyph_idx + 1;
1568            }
1569            (glyph_end, glyphs_w)
1570        } else {
1571            let mut glyph_end = word.glyphs.len();
1572            for glyph_idx in (start_glyph_pos..end_glyph_pos).rev() {
1573                let g_w = word.glyphs[glyph_idx].width(font_size);
1574                if currently_used_width + glyphs_w + g_w > total_available_width {
1575                    break;
1576                }
1577                glyphs_w += g_w;
1578                glyph_end = glyph_idx;
1579            }
1580            (glyph_end, glyphs_w)
1581        }
1582    }
1583
1584    #[inline]
1585    fn add_to_visual_line(
1586        &self,
1587        vl: &mut VisualLine,
1588        span_index: usize,
1589        start: WordGlyphPos,
1590        end: WordGlyphPos,
1591        width: f32,
1592        number_of_blanks: u32,
1593    ) {
1594        if end == start {
1595            return;
1596        }
1597
1598        vl.ranges.push(VlRange {
1599            span: span_index,
1600            start,
1601            end,
1602            level: self.spans[span_index].level,
1603        });
1604        vl.w += width;
1605        vl.spaces += number_of_blanks;
1606    }
1607
1608    fn remaining_content_exceeds(
1609        spans: &[ShapeSpan],
1610        font_size: f32,
1611        span_index: usize,
1612        word_idx: usize,
1613        word_count: usize,
1614        starting_word_index: usize,
1615        direction: LayoutDirection,
1616        congruent: bool,
1617        start_span: usize,
1618        span_count: usize,
1619        threshold: f32,
1620    ) -> bool {
1621        let mut acc: f32 = 0.0;
1622
1623        // Remaining words in the current span
1624        let word_range: Range<usize> = match (direction, congruent) {
1625            (LayoutDirection::Forward, true) => word_idx + 1..word_count,
1626            (LayoutDirection::Forward, false) => 0..word_idx,
1627            (LayoutDirection::Backward, true) => starting_word_index..word_idx,
1628            (LayoutDirection::Backward, false) => word_idx + 1..word_count,
1629        };
1630        for wi in word_range {
1631            acc += spans[span_index].words[wi].width(font_size);
1632            if acc > threshold {
1633                return true;
1634            }
1635        }
1636
1637        // Remaining spans
1638        let span_range: Range<usize> = match direction {
1639            LayoutDirection::Forward => span_index + 1..span_count,
1640            LayoutDirection::Backward => start_span..span_index,
1641        };
1642        for si in span_range {
1643            for w in &spans[si].words {
1644                acc += w.width(font_size);
1645                if acc > threshold {
1646                    return true;
1647                }
1648            }
1649        }
1650
1651        false
1652    }
1653
1654    /// This will fit as much as possible in one line
1655    /// If forward is false, it will fit as much as possible from the end of the spans
1656    /// it will stop when it gets to "start".
1657    /// If forward is true, it will start from start and keep going to the end of the spans
1658    #[inline]
1659    fn layout_spans(
1660        &self,
1661        current_visual_line: &mut VisualLine,
1662        font_size: f32,
1663        spans: &[ShapeSpan],
1664        start_opt: Option<SpanWordGlyphPos>,
1665        rtl: bool,
1666        width_opt: Option<f32>,
1667        ellipsize: Ellipsize,
1668        ellipsis_w: f32,
1669        direction: LayoutDirection,
1670    ) {
1671        let check_ellipsizing = matches!(ellipsize, Ellipsize::Start(_) | Ellipsize::End(_))
1672            && width_opt.is_some_and(|w| w.is_finite());
1673
1674        let max_width = width_opt.unwrap_or(f32::INFINITY);
1675        let span_count = spans.len();
1676
1677        let mut total_w: f32 = 0.0;
1678
1679        let start = if let Some(s) = start_opt {
1680            s
1681        } else {
1682            SpanWordGlyphPos::ZERO
1683        };
1684
1685        let span_indices: Vec<usize> = if matches!(direction, LayoutDirection::Forward) {
1686            (start.span..spans.len()).collect()
1687        } else {
1688            (start.span..spans.len()).rev().collect()
1689        };
1690
1691        'outer: for span_index in span_indices {
1692            let mut word_range_width = 0.;
1693            let mut number_of_blanks: u32 = 0;
1694
1695            let span = &spans[span_index];
1696            let word_count = span.words.len();
1697
1698            let starting_word_index = if span_index == start.span {
1699                start.word
1700            } else {
1701                0
1702            };
1703
1704            let congruent = rtl == span.level.is_rtl();
1705            let word_forward: bool = congruent == (direction == LayoutDirection::Forward);
1706
1707            let word_indices: Vec<usize> = match (direction, congruent, start_opt) {
1708                (LayoutDirection::Forward, true, _) => (starting_word_index..word_count).collect(),
1709                (LayoutDirection::Forward, false, Some(start)) => {
1710                    if span_index == start.span {
1711                        (0..start.word).rev().collect()
1712                    } else {
1713                        (0..word_count).rev().collect()
1714                    }
1715                }
1716                (LayoutDirection::Forward, false, None) => (0..word_count).rev().collect(),
1717                (LayoutDirection::Backward, true, _) => {
1718                    ((starting_word_index)..word_count).rev().collect()
1719                }
1720                (LayoutDirection::Backward, false, Some(start)) => {
1721                    if span_index == start.span {
1722                        if start.glyph > 0 {
1723                            (0..(start.word + 1)).collect()
1724                        } else {
1725                            (0..(start.word)).collect()
1726                        }
1727                    } else {
1728                        (0..word_count).collect()
1729                    }
1730                }
1731                (LayoutDirection::Backward, false, None) => (0..span.words.len()).collect(),
1732            };
1733            for word_idx in word_indices {
1734                let word = &span.words[word_idx];
1735                let word_width = if span_index == start.span && word_idx == start.word {
1736                    let (start_glyph_pos, end_glyph_pos) = Self::get_glyph_start_end(
1737                        word, start, span_index, word_idx, direction, congruent,
1738                    );
1739                    let mut w = 0.;
1740                    for glyph_idx in start_glyph_pos..end_glyph_pos {
1741                        w += word.glyphs[glyph_idx].width(font_size);
1742                    }
1743                    w
1744                } else {
1745                    word.width(font_size)
1746                };
1747
1748                let overflowing = {
1749                    // only check this if we're ellipsizing
1750                    check_ellipsizing
1751                        && (
1752                            // if this  word doesn't fit, then we have an overflow
1753                            (total_w + word_range_width + word_width > max_width)
1754                                || (Self::remaining_content_exceeds(
1755                                    spans,
1756                                    font_size,
1757                                    span_index,
1758                                    word_idx,
1759                                    word_count,
1760                                    starting_word_index,
1761                                    direction,
1762                                    congruent,
1763                                    start.span,
1764                                    span_count,
1765                                    ellipsis_w,
1766                                ) && total_w + word_range_width + word_width + ellipsis_w
1767                                    > max_width)
1768                        )
1769                };
1770
1771                if overflowing {
1772                    // overflow detected
1773                    let available = (max_width - ellipsis_w).max(0.0);
1774
1775                    let (glyph_end, glyphs_w) = Self::fit_glyphs(
1776                        word,
1777                        font_size,
1778                        start,
1779                        span_index,
1780                        word_idx,
1781                        direction,
1782                        congruent,
1783                        total_w + word_range_width,
1784                        available,
1785                        word_forward,
1786                    );
1787
1788                    let (start_pos, end_pos) = if word_forward {
1789                        if span_index == start.span {
1790                            if !congruent {
1791                                (WordGlyphPos::ZERO, WordGlyphPos::new(word_idx, glyph_end))
1792                            } else {
1793                                (
1794                                    start.word_glyph_pos(),
1795                                    WordGlyphPos::new(word_idx, glyph_end),
1796                                )
1797                            }
1798                        } else {
1799                            (WordGlyphPos::ZERO, WordGlyphPos::new(word_idx, glyph_end))
1800                        }
1801                    } else {
1802                        // For an incongruent span in the forward direction, the
1803                        // word indices are (0..start.word).rev(). Cap the VlRange
1804                        // end at start.word_glyph_pos() so it doesn't include
1805                        // words beyond start.word that belong to a previous line.
1806                        // For the backward direction (congruent span), the word
1807                        // indices are (start.word..word_count).rev() and
1808                        // span.words.len() is the correct end.
1809                        let range_end = if span_index == start.span && !congruent {
1810                            start.word_glyph_pos()
1811                        } else {
1812                            WordGlyphPos::new(span.words.len(), 0)
1813                        };
1814                        (WordGlyphPos::new(word_idx, glyph_end), range_end)
1815                    };
1816                    self.add_to_visual_line(
1817                        current_visual_line,
1818                        span_index,
1819                        start_pos,
1820                        end_pos,
1821                        word_range_width + glyphs_w,
1822                        number_of_blanks,
1823                    );
1824
1825                    // don't iterate anymore since we overflowed
1826                    current_visual_line.ellipsized = true;
1827                    break 'outer;
1828                }
1829
1830                word_range_width += word_width;
1831                if word.blank {
1832                    number_of_blanks += 1;
1833                }
1834
1835                // Backward-only: if we've reached the starting point, commit and stop.
1836                if matches!(direction, LayoutDirection::Backward)
1837                    && word_idx == start.word
1838                    && span_index == start.span
1839                {
1840                    let (start_pos, end_pos) = if word_forward {
1841                        (WordGlyphPos::ZERO, start.word_glyph_pos())
1842                    } else {
1843                        (
1844                            start.word_glyph_pos(),
1845                            WordGlyphPos::new(span.words.len(), 0),
1846                        )
1847                    };
1848
1849                    self.add_to_visual_line(
1850                        current_visual_line,
1851                        span_index,
1852                        start_pos,
1853                        end_pos,
1854                        word_range_width,
1855                        number_of_blanks,
1856                    );
1857
1858                    break 'outer;
1859                }
1860            }
1861
1862            // if we get to here that means we didn't ellipsize, so either the whole span fits,
1863            // or we don't really care
1864            total_w += word_range_width;
1865            let (start_pos, end_pos) = if congruent {
1866                if span_index == start.span {
1867                    (
1868                        start.word_glyph_pos(),
1869                        WordGlyphPos::new(span.words.len(), 0),
1870                    )
1871                } else {
1872                    (WordGlyphPos::ZERO, WordGlyphPos::new(span.words.len(), 0))
1873                }
1874            } else if span_index == start.span {
1875                (WordGlyphPos::ZERO, start.word_glyph_pos())
1876            } else {
1877                (WordGlyphPos::ZERO, WordGlyphPos::new(span.words.len(), 0))
1878            };
1879
1880            self.add_to_visual_line(
1881                current_visual_line,
1882                span_index,
1883                start_pos,
1884                end_pos,
1885                word_range_width,
1886                number_of_blanks,
1887            );
1888        }
1889
1890        if matches!(direction, LayoutDirection::Backward) {
1891            current_visual_line.ranges.reverse();
1892        }
1893    }
1894
1895    fn layout_middle(
1896        &self,
1897        current_visual_line: &mut VisualLine,
1898        font_size: f32,
1899        spans: &[ShapeSpan],
1900        start_opt: Option<SpanWordGlyphPos>,
1901        rtl: bool,
1902        width: f32,
1903        ellipsize: Ellipsize,
1904        ellipsis_w: f32,
1905    ) {
1906        assert!(matches!(ellipsize, Ellipsize::Middle(_)));
1907
1908        // First check if all content fits without any ellipsis.
1909        {
1910            let mut test_line = VisualLine::default();
1911            self.layout_spans(
1912                &mut test_line,
1913                font_size,
1914                spans,
1915                start_opt,
1916                rtl,
1917                Some(width),
1918                Ellipsize::End(EllipsizeHeightLimit::Lines(1)),
1919                ellipsis_w,
1920                LayoutDirection::Forward,
1921            );
1922            if !test_line.ellipsized && test_line.w <= width {
1923                *current_visual_line = test_line;
1924                return;
1925            }
1926        }
1927
1928        let mut starting_line = VisualLine::default();
1929        self.layout_spans(
1930            &mut starting_line,
1931            font_size,
1932            spans,
1933            start_opt,
1934            rtl,
1935            Some(width / 2.0),
1936            Ellipsize::End(EllipsizeHeightLimit::Lines(1)),
1937            0., //pass 0 for ellipsis_w
1938            LayoutDirection::Forward,
1939        );
1940        let forward_pass_overflowed = starting_line.ellipsized;
1941        let end_range_opt = starting_line.ranges.last();
1942        match end_range_opt {
1943            Some(range) if forward_pass_overflowed => {
1944                let congruent = rtl == self.spans[range.span].level.is_rtl();
1945                // create a new range and do the other half
1946                let mut ending_line = VisualLine::default();
1947                let start = if congruent {
1948                    SpanWordGlyphPos {
1949                        span: range.span,
1950                        word: range.end.word,
1951                        glyph: range.end.glyph,
1952                    }
1953                } else {
1954                    SpanWordGlyphPos {
1955                        span: range.span,
1956                        word: range.start.word,
1957                        glyph: range.start.glyph,
1958                    }
1959                };
1960                self.layout_spans(
1961                    &mut ending_line,
1962                    font_size,
1963                    spans,
1964                    Some(start),
1965                    rtl,
1966                    Some((width - starting_line.w - ellipsis_w).max(0.0)),
1967                    Ellipsize::Start(EllipsizeHeightLimit::Lines(1)),
1968                    0., //pass 0 for ellipsis_w
1969                    LayoutDirection::Backward,
1970                );
1971                // Insert the ellipsis VlRange between the two halves.
1972                // Its BiDi level is determined by the adjacent ranges.
1973                let ellipsis_level = self.ellipsis_level_between(
1974                    starting_line.ranges.last(),
1975                    ending_line.ranges.first(),
1976                );
1977                starting_line
1978                    .ranges
1979                    .push(self.ellipsis_vlrange(ellipsis_level));
1980                starting_line.ranges.extend(ending_line.ranges);
1981                current_visual_line.ranges = starting_line.ranges;
1982                current_visual_line.ellipsized = true;
1983                current_visual_line.w = starting_line.w + ending_line.w + ellipsis_w;
1984                current_visual_line.spaces = starting_line.spaces + ending_line.spaces;
1985            }
1986            None if forward_pass_overflowed && width > ellipsis_w => {
1987                // buffer is small enough that the forward pass didn't fit
1988                // only show the ellipsis
1989                current_visual_line
1990                    .ranges
1991                    .push(self.ellipsis_vlrange(if self.rtl {
1992                        unicode_bidi::Level::rtl()
1993                    } else {
1994                        unicode_bidi::Level::ltr()
1995                    }));
1996                current_visual_line.ellipsized = true;
1997                current_visual_line.w = ellipsis_w;
1998                current_visual_line.spaces = 0;
1999            }
2000            _ => {
2001                // everything fit in the forward pass
2002                current_visual_line.ranges = starting_line.ranges;
2003                current_visual_line.w = starting_line.w;
2004                current_visual_line.spaces = starting_line.spaces;
2005                current_visual_line.ellipsized = false;
2006            }
2007        }
2008    }
2009
2010    /// Returns the words for a given span index, handling the ellipsis sentinel.
2011    fn get_span_words(&self, span_index: usize) -> &[ShapeWord] {
2012        if span_index == ELLIPSIS_SPAN {
2013            &self
2014                .ellipsis_span
2015                .as_ref()
2016                .expect("ellipsis_span not set")
2017                .words
2018        } else {
2019            &self.spans[span_index].words
2020        }
2021    }
2022
2023    fn byte_range_of_vlrange(&self, r: &VlRange) -> Option<(usize, usize)> {
2024        debug_assert_ne!(r.span, ELLIPSIS_SPAN);
2025        let words = self.get_span_words(r.span);
2026        let mut min_byte = usize::MAX;
2027        let mut max_byte = 0usize;
2028        let end_word = r.end.word + usize::from(r.end.glyph != 0);
2029        for (i, word) in words.iter().enumerate().take(end_word).skip(r.start.word) {
2030            let included_glyphs = match (i == r.start.word, i == r.end.word) {
2031                (false, false) => &word.glyphs[..],
2032                (true, false) => &word.glyphs[r.start.glyph..],
2033                (false, true) => &word.glyphs[..r.end.glyph],
2034                (true, true) => &word.glyphs[r.start.glyph..r.end.glyph],
2035            };
2036            for glyph in included_glyphs {
2037                min_byte = min_byte.min(glyph.start);
2038                max_byte = max_byte.max(glyph.end);
2039            }
2040        }
2041        if min_byte <= max_byte {
2042            Some((min_byte, max_byte))
2043        } else {
2044            None
2045        }
2046    }
2047
2048    fn compute_elided_byte_range(
2049        &self,
2050        visual_line: &VisualLine,
2051        line_len: usize,
2052    ) -> Option<(usize, usize)> {
2053        if !visual_line.ellipsized {
2054            return None;
2055        }
2056        // Find the position of the ellipsis VlRange
2057        let ellipsis_idx = visual_line
2058            .ranges
2059            .iter()
2060            .position(|r| r.span == ELLIPSIS_SPAN)?;
2061
2062        // Find the byte range of the visible content before the ellipsis
2063        let before_end = (0..ellipsis_idx)
2064            .rev()
2065            .find_map(|i| self.byte_range_of_vlrange(&visual_line.ranges[i]))
2066            .map(|(_, end)| end)
2067            .unwrap_or(0);
2068
2069        // Find the byte range of the visible content after the ellipsis
2070        let after_start = (ellipsis_idx + 1..visual_line.ranges.len())
2071            .find_map(|i| self.byte_range_of_vlrange(&visual_line.ranges[i]))
2072            .map(|(start, _)| start)
2073            .unwrap_or(line_len);
2074
2075        Some((before_end, after_start))
2076    }
2077
2078    /// Returns the maximum byte offset across all glyphs in all non-ellipsis spans.
2079    /// This effectively gives the byte length of the original shaped text.
2080    fn max_byte_offset(&self) -> usize {
2081        self.spans
2082            .iter()
2083            .flat_map(|span| span.words.iter())
2084            .flat_map(|word| word.glyphs.iter())
2085            .map(|g| g.end)
2086            .max()
2087            .unwrap_or(0)
2088    }
2089
2090    /// Returns the width of the ellipsis in the given font size.
2091    fn ellipsis_w(&self, font_size: f32) -> f32 {
2092        self.ellipsis_span
2093            .as_ref()
2094            .map_or(0.0, |s| s.words.iter().map(|w| w.width(font_size)).sum())
2095    }
2096
2097    /// Creates a `VlRange` for the ellipsis with the give`BiDi`Di level.
2098    fn ellipsis_vlrange(&self, level: unicode_bidi::Level) -> VlRange {
2099        VlRange {
2100            span: ELLIPSIS_SPAN,
2101            start: WordGlyphPos::ZERO,
2102            end: WordGlyphPos::new(1, 0),
2103            level,
2104        }
2105    }
2106
2107    /// Determines the appropriate `BiDi` level for the ellipsis based on the
2108    /// adjacent ranges, following UAX#9 N1/N2 rules for neutral characters.
2109    fn ellipsis_level_between(
2110        &self,
2111        before: Option<&VlRange>,
2112        after: Option<&VlRange>,
2113    ) -> unicode_bidi::Level {
2114        match (before, after) {
2115            (Some(a), Some(b)) if a.level == b.level => a.level,
2116            (Some(a), None) => a.level,
2117            (None, Some(b)) => b.level,
2118            _ => {
2119                if self.rtl {
2120                    unicode_bidi::Level::rtl()
2121                } else {
2122                    unicode_bidi::Level::ltr()
2123                }
2124            }
2125        }
2126    }
2127
2128    fn layout_line(
2129        &self,
2130        current_visual_line: &mut VisualLine,
2131        font_size: f32,
2132        spans: &[ShapeSpan],
2133        start_opt: Option<SpanWordGlyphPos>,
2134        rtl: bool,
2135        width_opt: Option<f32>,
2136        ellipsize: Ellipsize,
2137    ) {
2138        let ellipsis_w = self.ellipsis_w(font_size);
2139
2140        match (ellipsize, width_opt) {
2141            (Ellipsize::Start(_), Some(_)) => {
2142                self.layout_spans(
2143                    current_visual_line,
2144                    font_size,
2145                    spans,
2146                    start_opt,
2147                    rtl,
2148                    width_opt,
2149                    ellipsize,
2150                    ellipsis_w,
2151                    LayoutDirection::Backward,
2152                );
2153                // Insert ellipsis at the visual start (index 0, after backward reversal)
2154                if current_visual_line.ellipsized {
2155                    let level =
2156                        self.ellipsis_level_between(None, current_visual_line.ranges.first());
2157                    current_visual_line
2158                        .ranges
2159                        .insert(0, self.ellipsis_vlrange(level));
2160                    current_visual_line.w += ellipsis_w;
2161                }
2162            }
2163            (Ellipsize::Middle(_), Some(width)) => {
2164                self.layout_middle(
2165                    current_visual_line,
2166                    font_size,
2167                    spans,
2168                    start_opt,
2169                    rtl,
2170                    width,
2171                    ellipsize,
2172                    ellipsis_w,
2173                );
2174            }
2175            _ => {
2176                self.layout_spans(
2177                    current_visual_line,
2178                    font_size,
2179                    spans,
2180                    start_opt,
2181                    rtl,
2182                    width_opt,
2183                    ellipsize,
2184                    ellipsis_w,
2185                    LayoutDirection::Forward,
2186                );
2187                // Insert ellipsis at the visual end
2188                if current_visual_line.ellipsized {
2189                    let level =
2190                        self.ellipsis_level_between(current_visual_line.ranges.last(), None);
2191                    current_visual_line
2192                        .ranges
2193                        .push(self.ellipsis_vlrange(level));
2194                    current_visual_line.w += ellipsis_w;
2195                }
2196            }
2197        }
2198
2199        // Compute the byte range of ellipsized text so the ellipsis LayoutGlyph
2200        // can have valid start/end indices into the original line text.
2201        if current_visual_line.ellipsized {
2202            let line_len = self.max_byte_offset();
2203            current_visual_line.elided_byte_range =
2204                self.compute_elided_byte_range(current_visual_line, line_len);
2205        }
2206    }
2207
2208    pub fn layout_to_buffer(
2209        &self,
2210        scratch: &mut ShapeBuffer,
2211        font_size: f32,
2212        width_opt: Option<f32>,
2213        wrap: Wrap,
2214        ellipsize: Ellipsize,
2215        align: Option<Align>,
2216        layout_lines: &mut Vec<LayoutLine>,
2217        match_mono_width: Option<f32>,
2218        hinting: Hinting,
2219    ) {
2220        // For each visual line a list of  (span index,  and range of words in that span)
2221        // Note that a BiDi visual line could have multiple spans or parts of them
2222        // let mut vl_range_of_spans = Vec::with_capacity(1);
2223        let mut visual_lines = mem::take(&mut scratch.visual_lines);
2224        let mut cached_visual_lines = mem::take(&mut scratch.cached_visual_lines);
2225        cached_visual_lines.clear();
2226        cached_visual_lines.extend(visual_lines.drain(..).map(|mut l| {
2227            l.clear();
2228            l
2229        }));
2230
2231        // Cache glyph sets in reverse order so they will ideally be reused in exactly the same lines.
2232        let mut cached_glyph_sets = mem::take(&mut scratch.glyph_sets);
2233        cached_glyph_sets.clear();
2234        cached_glyph_sets.extend(layout_lines.drain(..).rev().map(|mut v| {
2235            v.glyphs.clear();
2236            v.glyphs
2237        }));
2238
2239        // This would keep the maximum number of spans that would fit on a visual line
2240        // If one span is too large, this variable will hold the range of words inside that span
2241        // that fits on a line.
2242        // let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
2243        let mut current_visual_line = cached_visual_lines.pop().unwrap_or_default();
2244
2245        if wrap == Wrap::None {
2246            self.layout_line(
2247                &mut current_visual_line,
2248                font_size,
2249                &self.spans,
2250                None,
2251                self.rtl,
2252                width_opt,
2253                ellipsize,
2254            );
2255        } else {
2256            let mut total_line_height = 0.0;
2257            let mut total_line_count = 0;
2258            let max_line_count_opt = match ellipsize {
2259                Ellipsize::Start(EllipsizeHeightLimit::Lines(lines))
2260                | Ellipsize::Middle(EllipsizeHeightLimit::Lines(lines))
2261                | Ellipsize::End(EllipsizeHeightLimit::Lines(lines)) => Some(lines.max(1)),
2262                _ => None,
2263            };
2264            let max_height_opt = match ellipsize {
2265                Ellipsize::Start(EllipsizeHeightLimit::Height(height))
2266                | Ellipsize::Middle(EllipsizeHeightLimit::Height(height))
2267                | Ellipsize::End(EllipsizeHeightLimit::Height(height)) => Some(height),
2268                _ => None,
2269            };
2270            let line_height = self
2271                .metrics_opt
2272                .map_or_else(|| font_size, |m| m.line_height);
2273
2274            let try_ellipsize_last_line = |total_line_count: usize,
2275                                           total_line_height: f32,
2276                                           current_visual_line: &mut VisualLine,
2277                                           font_size: f32,
2278                                           start_opt: Option<SpanWordGlyphPos>,
2279                                           width_opt: Option<f32>,
2280                                           ellipsize: Ellipsize|
2281             -> bool {
2282                // If Ellipsize::End, then how many lines can we fit or how much is the available height
2283                if max_line_count_opt == Some(total_line_count + 1)
2284                    || max_height_opt.is_some_and(|max_height| {
2285                        total_line_height + line_height * 2.0 > max_height
2286                    })
2287                {
2288                    self.layout_line(
2289                        current_visual_line,
2290                        font_size,
2291                        &self.spans,
2292                        start_opt,
2293                        self.rtl,
2294                        width_opt,
2295                        ellipsize,
2296                    );
2297                    return true;
2298                }
2299                false
2300            };
2301
2302            if !try_ellipsize_last_line(
2303                total_line_count,
2304                total_line_height,
2305                &mut current_visual_line,
2306                font_size,
2307                None,
2308                width_opt,
2309                ellipsize,
2310            ) {
2311                'outer: for (span_index, span) in self.spans.iter().enumerate() {
2312                    let mut word_range_width = 0.;
2313                    let mut width_before_last_blank = 0.;
2314                    let mut number_of_blanks: u32 = 0;
2315
2316                    // Create the word ranges that fits in a visual line
2317                    if self.rtl != span.level.is_rtl() {
2318                        // incongruent directions
2319                        let mut fitting_start = WordGlyphPos::new(span.words.len(), 0);
2320                        for (i, word) in span.words.iter().enumerate().rev() {
2321                            let word_width = word.width(font_size);
2322                            // Addition in the same order used to compute the final width, so that
2323                            // relayouts with that width as the `line_width` will produce the same
2324                            // wrapping results.
2325                            if current_visual_line.w + (word_range_width + word_width)
2326                            <= width_opt.unwrap_or(f32::INFINITY)
2327                            // Include one blank word over the width limit since it won't be
2328                            // counted in the final width
2329                            || (word.blank
2330                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
2331                            {
2332                                // fits
2333                                if word.blank {
2334                                    number_of_blanks += 1;
2335                                    width_before_last_blank = word_range_width;
2336                                }
2337                                word_range_width += word_width;
2338                            } else if wrap == Wrap::Glyph
2339                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
2340                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
2341                            {
2342                                // Commit the current line so that the word starts on the next line.
2343                                if word_range_width > 0.
2344                                    && wrap == Wrap::WordOrGlyph
2345                                    && word_width > width_opt.unwrap_or(f32::INFINITY)
2346                                {
2347                                    self.add_to_visual_line(
2348                                        &mut current_visual_line,
2349                                        span_index,
2350                                        WordGlyphPos::new(i + 1, 0),
2351                                        fitting_start,
2352                                        word_range_width,
2353                                        number_of_blanks,
2354                                    );
2355
2356                                    visual_lines.push(current_visual_line);
2357                                    current_visual_line =
2358                                        cached_visual_lines.pop().unwrap_or_default();
2359
2360                                    number_of_blanks = 0;
2361                                    word_range_width = 0.;
2362
2363                                    fitting_start = WordGlyphPos::new(i, 0);
2364                                    total_line_count += 1;
2365                                    total_line_height += line_height;
2366                                    if try_ellipsize_last_line(
2367                                        total_line_count,
2368                                        total_line_height,
2369                                        &mut current_visual_line,
2370                                        font_size,
2371                                        Some(SpanWordGlyphPos::with_wordglyph(
2372                                            span_index,
2373                                            fitting_start,
2374                                        )),
2375                                        width_opt,
2376                                        ellipsize,
2377                                    ) {
2378                                        break 'outer;
2379                                    }
2380                                }
2381
2382                                for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
2383                                    let glyph_width = glyph.width(font_size);
2384                                    if current_visual_line.w + (word_range_width + glyph_width)
2385                                        <= width_opt.unwrap_or(f32::INFINITY)
2386                                    {
2387                                        word_range_width += glyph_width;
2388                                    } else {
2389                                        self.add_to_visual_line(
2390                                            &mut current_visual_line,
2391                                            span_index,
2392                                            WordGlyphPos::new(i, glyph_i + 1),
2393                                            fitting_start,
2394                                            word_range_width,
2395                                            number_of_blanks,
2396                                        );
2397                                        visual_lines.push(current_visual_line);
2398                                        current_visual_line =
2399                                            cached_visual_lines.pop().unwrap_or_default();
2400
2401                                        number_of_blanks = 0;
2402                                        word_range_width = glyph_width;
2403                                        fitting_start = WordGlyphPos::new(i, glyph_i + 1);
2404                                        total_line_count += 1;
2405                                        total_line_height += line_height;
2406                                        if try_ellipsize_last_line(
2407                                            total_line_count,
2408                                            total_line_height,
2409                                            &mut current_visual_line,
2410                                            font_size,
2411                                            Some(SpanWordGlyphPos::with_wordglyph(
2412                                                span_index,
2413                                                fitting_start,
2414                                            )),
2415                                            width_opt,
2416                                            ellipsize,
2417                                        ) {
2418                                            break 'outer;
2419                                        }
2420                                    }
2421                                }
2422                            } else {
2423                                // Wrap::Word, Wrap::WordOrGlyph
2424
2425                                // If we had a previous range, commit that line before the next word.
2426                                if word_range_width > 0. {
2427                                    // Current word causing a wrap is not whitespace, so we ignore the
2428                                    // previous word if it's a whitespace
2429                                    let trailing_blank = span
2430                                        .words
2431                                        .get(i + 1)
2432                                        .is_some_and(|previous_word| previous_word.blank);
2433
2434                                    if trailing_blank {
2435                                        number_of_blanks = number_of_blanks.saturating_sub(1);
2436                                        self.add_to_visual_line(
2437                                            &mut current_visual_line,
2438                                            span_index,
2439                                            WordGlyphPos::new(i + 2, 0),
2440                                            fitting_start,
2441                                            width_before_last_blank,
2442                                            number_of_blanks,
2443                                        );
2444                                    } else {
2445                                        self.add_to_visual_line(
2446                                            &mut current_visual_line,
2447                                            span_index,
2448                                            WordGlyphPos::new(i + 1, 0),
2449                                            fitting_start,
2450                                            word_range_width,
2451                                            number_of_blanks,
2452                                        );
2453                                    }
2454                                }
2455
2456                                // This fixes a bug that a long first word at the boundary of
2457                                // was overflowing
2458                                if !current_visual_line.ranges.is_empty() {
2459                                    visual_lines.push(current_visual_line);
2460                                    current_visual_line =
2461                                        cached_visual_lines.pop().unwrap_or_default();
2462                                    number_of_blanks = 0;
2463                                    total_line_count += 1;
2464                                    total_line_height += line_height;
2465
2466                                    if try_ellipsize_last_line(
2467                                        total_line_count,
2468                                        total_line_height,
2469                                        &mut current_visual_line,
2470                                        font_size,
2471                                        Some(SpanWordGlyphPos::with_wordglyph(
2472                                            span_index,
2473                                            if word.blank {
2474                                                WordGlyphPos::new(i, 0)
2475                                            } else {
2476                                                WordGlyphPos::new(i + 1, 0)
2477                                            },
2478                                        )),
2479                                        width_opt,
2480                                        ellipsize,
2481                                    ) {
2482                                        break 'outer;
2483                                    }
2484                                }
2485
2486                                if word.blank {
2487                                    word_range_width = 0.;
2488                                    fitting_start = WordGlyphPos::new(i, 0);
2489                                } else {
2490                                    word_range_width = word_width;
2491                                    fitting_start = WordGlyphPos::new(i + 1, 0);
2492                                }
2493                            }
2494                        }
2495                        self.add_to_visual_line(
2496                            &mut current_visual_line,
2497                            span_index,
2498                            WordGlyphPos::new(0, 0),
2499                            fitting_start,
2500                            word_range_width,
2501                            number_of_blanks,
2502                        );
2503                    } else {
2504                        // congruent direction
2505                        let mut fitting_start = WordGlyphPos::ZERO;
2506                        for (i, word) in span.words.iter().enumerate() {
2507                            let word_width = word.width(font_size);
2508                            if current_visual_line.w + (word_range_width + word_width)
2509                            <= width_opt.unwrap_or(f32::INFINITY)
2510                            // Include one blank word over the width limit since it won't be
2511                            // counted in the final width.
2512                            || (word.blank
2513                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
2514                            {
2515                                // fits
2516                                if word.blank {
2517                                    number_of_blanks += 1;
2518                                    width_before_last_blank = word_range_width;
2519                                }
2520                                word_range_width += word_width;
2521                            } else if wrap == Wrap::Glyph
2522                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
2523                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
2524                            {
2525                                // Commit the current line so that the word starts on the next line.
2526                                if word_range_width > 0.
2527                                    && wrap == Wrap::WordOrGlyph
2528                                    && word_width > width_opt.unwrap_or(f32::INFINITY)
2529                                {
2530                                    self.add_to_visual_line(
2531                                        &mut current_visual_line,
2532                                        span_index,
2533                                        fitting_start,
2534                                        WordGlyphPos::new(i, 0),
2535                                        word_range_width,
2536                                        number_of_blanks,
2537                                    );
2538
2539                                    visual_lines.push(current_visual_line);
2540                                    current_visual_line =
2541                                        cached_visual_lines.pop().unwrap_or_default();
2542
2543                                    number_of_blanks = 0;
2544                                    word_range_width = 0.;
2545
2546                                    fitting_start = WordGlyphPos::new(i, 0);
2547                                    total_line_count += 1;
2548                                    total_line_height += line_height;
2549                                    if try_ellipsize_last_line(
2550                                        total_line_count,
2551                                        total_line_height,
2552                                        &mut current_visual_line,
2553                                        font_size,
2554                                        Some(SpanWordGlyphPos::with_wordglyph(
2555                                            span_index,
2556                                            fitting_start,
2557                                        )),
2558                                        width_opt,
2559                                        ellipsize,
2560                                    ) {
2561                                        break 'outer;
2562                                    }
2563                                }
2564
2565                                for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
2566                                    let glyph_width = glyph.width(font_size);
2567                                    if current_visual_line.w + (word_range_width + glyph_width)
2568                                        <= width_opt.unwrap_or(f32::INFINITY)
2569                                    {
2570                                        word_range_width += glyph_width;
2571                                    } else {
2572                                        self.add_to_visual_line(
2573                                            &mut current_visual_line,
2574                                            span_index,
2575                                            fitting_start,
2576                                            WordGlyphPos::new(i, glyph_i),
2577                                            word_range_width,
2578                                            number_of_blanks,
2579                                        );
2580                                        visual_lines.push(current_visual_line);
2581                                        current_visual_line =
2582                                            cached_visual_lines.pop().unwrap_or_default();
2583
2584                                        number_of_blanks = 0;
2585                                        word_range_width = glyph_width;
2586                                        fitting_start = WordGlyphPos::new(i, glyph_i);
2587                                        total_line_count += 1;
2588                                        total_line_height += line_height;
2589                                        if try_ellipsize_last_line(
2590                                            total_line_count,
2591                                            total_line_height,
2592                                            &mut current_visual_line,
2593                                            font_size,
2594                                            Some(SpanWordGlyphPos::with_wordglyph(
2595                                                span_index,
2596                                                fitting_start,
2597                                            )),
2598                                            width_opt,
2599                                            ellipsize,
2600                                        ) {
2601                                            break 'outer;
2602                                        }
2603                                    }
2604                                }
2605                            } else {
2606                                // Wrap::Word, Wrap::WordOrGlyph
2607
2608                                // If we had a previous range, commit that line before the next word.
2609                                if word_range_width > 0. {
2610                                    // Current word causing a wrap is not whitespace, so we ignore the
2611                                    // previous word if it's a whitespace.
2612                                    let trailing_blank = i > 0 && span.words[i - 1].blank;
2613
2614                                    if trailing_blank {
2615                                        number_of_blanks = number_of_blanks.saturating_sub(1);
2616                                        self.add_to_visual_line(
2617                                            &mut current_visual_line,
2618                                            span_index,
2619                                            fitting_start,
2620                                            WordGlyphPos::new(i - 1, 0),
2621                                            width_before_last_blank,
2622                                            number_of_blanks,
2623                                        );
2624                                    } else {
2625                                        self.add_to_visual_line(
2626                                            &mut current_visual_line,
2627                                            span_index,
2628                                            fitting_start,
2629                                            WordGlyphPos::new(i, 0),
2630                                            word_range_width,
2631                                            number_of_blanks,
2632                                        );
2633                                    }
2634                                }
2635
2636                                if !current_visual_line.ranges.is_empty() {
2637                                    visual_lines.push(current_visual_line);
2638                                    current_visual_line =
2639                                        cached_visual_lines.pop().unwrap_or_default();
2640                                    number_of_blanks = 0;
2641                                    total_line_count += 1;
2642                                    total_line_height += line_height;
2643                                    if try_ellipsize_last_line(
2644                                        total_line_count,
2645                                        total_line_height,
2646                                        &mut current_visual_line,
2647                                        font_size,
2648                                        Some(SpanWordGlyphPos::with_wordglyph(
2649                                            span_index,
2650                                            if i > 0 && span.words[i - 1].blank {
2651                                                WordGlyphPos::new(i - 1, 0)
2652                                            } else {
2653                                                WordGlyphPos::new(i, 0)
2654                                            },
2655                                        )),
2656                                        width_opt,
2657                                        ellipsize,
2658                                    ) {
2659                                        break 'outer;
2660                                    }
2661                                }
2662
2663                                if word.blank {
2664                                    word_range_width = 0.;
2665                                    fitting_start = WordGlyphPos::new(i + 1, 0);
2666                                } else {
2667                                    word_range_width = word_width;
2668                                    fitting_start = WordGlyphPos::new(i, 0);
2669                                }
2670                            }
2671                        }
2672                        self.add_to_visual_line(
2673                            &mut current_visual_line,
2674                            span_index,
2675                            fitting_start,
2676                            WordGlyphPos::new(span.words.len(), 0),
2677                            word_range_width,
2678                            number_of_blanks,
2679                        );
2680                    }
2681                }
2682            }
2683        }
2684
2685        if current_visual_line.ranges.is_empty() {
2686            current_visual_line.clear();
2687            cached_visual_lines.push(current_visual_line);
2688        } else {
2689            visual_lines.push(current_visual_line);
2690        }
2691
2692        // Create the LayoutLines using the ranges inside visual lines
2693        let align = align.unwrap_or(if self.rtl { Align::Right } else { Align::Left });
2694
2695        let line_width = width_opt.unwrap_or_else(|| {
2696            let mut width: f32 = 0.0;
2697            for visual_line in &visual_lines {
2698                width = width.max(visual_line.w);
2699            }
2700            width
2701        });
2702
2703        let start_x = if self.rtl { line_width } else { 0.0 };
2704
2705        let number_of_visual_lines = visual_lines.len();
2706        for (index, visual_line) in visual_lines.iter().enumerate() {
2707            if visual_line.ranges.is_empty() {
2708                continue;
2709            }
2710
2711            let new_order = self.reorder(&visual_line.ranges);
2712
2713            let mut glyphs = cached_glyph_sets
2714                .pop()
2715                .unwrap_or_else(|| Vec::with_capacity(1));
2716            let mut x = start_x;
2717            let mut y = 0.;
2718            let mut max_ascent: f32 = 0.;
2719            let mut max_descent: f32 = 0.;
2720            let alignment_correction = match (align, self.rtl) {
2721                (Align::Left, true) => (line_width - visual_line.w).max(0.),
2722                (Align::Left, false) => 0.,
2723                (Align::Right, true) => 0.,
2724                (Align::Right, false) => (line_width - visual_line.w).max(0.),
2725                (Align::Center, _) => (line_width - visual_line.w).max(0.) / 2.0,
2726                (Align::End, _) => (line_width - visual_line.w).max(0.),
2727                (Align::Justified, _) => 0.,
2728            };
2729
2730            if self.rtl {
2731                x -= alignment_correction;
2732            } else {
2733                x += alignment_correction;
2734            }
2735
2736            if hinting == Hinting::Enabled {
2737                x = x.round();
2738            }
2739
2740            // TODO: Only certain `is_whitespace` chars are typically expanded but this is what is
2741            // currently used to compute `visual_line.spaces`.
2742            //
2743            // https://www.unicode.org/reports/tr14/#Introduction
2744            // > When expanding or compressing interword space according to common
2745            // > typographical practice, only the spaces marked by U+0020 SPACE and U+00A0
2746            // > NO-BREAK SPACE are subject to compression, and only spaces marked by U+0020
2747            // > SPACE, U+00A0 NO-BREAK SPACE, and occasionally spaces marked by U+2009 THIN
2748            // > SPACE are subject to expansion. All other space characters normally have
2749            // > fixed width.
2750            //
2751            // (also some spaces aren't followed by potential linebreaks but they could
2752            //  still be expanded)
2753
2754            // Amount of extra width added to each blank space within a line.
2755            let justification_expansion = if matches!(align, Align::Justified)
2756                && visual_line.spaces > 0
2757                // Don't justify the last line in a paragraph.
2758                && index != number_of_visual_lines - 1
2759            {
2760                (line_width - visual_line.w) / visual_line.spaces as f32
2761            } else {
2762                0.
2763            };
2764
2765            let elided_byte_range = if visual_line.ellipsized {
2766                visual_line.elided_byte_range
2767            } else {
2768                None
2769            };
2770
2771            let mut decorations: Vec<DecorationSpan> = Vec::new();
2772
2773            let process_range = |range: Range<usize>,
2774                                 x: &mut f32,
2775                                 y: &mut f32,
2776                                 glyphs: &mut Vec<LayoutGlyph>,
2777                                 decorations: &mut Vec<DecorationSpan>,
2778                                 max_ascent: &mut f32,
2779                                 max_descent: &mut f32| {
2780                for r in visual_line.ranges[range.clone()].iter() {
2781                    let is_ellipsis = r.span == ELLIPSIS_SPAN;
2782                    let span_words = self.get_span_words(r.span);
2783                    let deco_spans: &[(Range<usize>, GlyphDecorationData)] = if is_ellipsis {
2784                        &[]
2785                    } else {
2786                        &self.spans[r.span].decoration_spans
2787                    };
2788                    // Cursor into deco_spans — advances forward as glyphs are
2789                    // emitted in byte order, giving amortized O(1) lookup.
2790                    let mut deco_cursor: usize = 0;
2791                    // If ending_glyph is not 0 we need to include glyphs from the ending_word
2792                    for i in r.start.word..r.end.word + usize::from(r.end.glyph != 0) {
2793                        let word = &span_words[i];
2794                        let included_glyphs = match (i == r.start.word, i == r.end.word) {
2795                            (false, false) => &word.glyphs[..],
2796                            (true, false) => &word.glyphs[r.start.glyph..],
2797                            (false, true) => &word.glyphs[..r.end.glyph],
2798                            (true, true) => &word.glyphs[r.start.glyph..r.end.glyph],
2799                        };
2800
2801                        for glyph in included_glyphs {
2802                            // Use overridden font size
2803                            let font_size = glyph.metrics_opt.map_or(font_size, |x| x.font_size);
2804
2805                            let match_mono_em_width = match_mono_width.map(|w| w / font_size);
2806
2807                            let glyph_font_size = match (
2808                                match_mono_em_width,
2809                                glyph.font_monospace_em_width,
2810                            ) {
2811                                (Some(match_em_width), Some(glyph_em_width))
2812                                    if glyph_em_width != match_em_width =>
2813                                {
2814                                    let glyph_to_match_factor = glyph_em_width / match_em_width;
2815                                    let glyph_font_size = math::roundf(glyph_to_match_factor)
2816                                        .max(1.0)
2817                                        / glyph_to_match_factor
2818                                        * font_size;
2819                                    log::trace!(
2820                                        "Adjusted glyph font size ({font_size} => {glyph_font_size})"
2821                                    );
2822                                    glyph_font_size
2823                                }
2824                                _ => font_size,
2825                            };
2826
2827                            let mut x_advance = glyph_font_size.mul_add(
2828                                glyph.x_advance,
2829                                if word.blank {
2830                                    justification_expansion
2831                                } else {
2832                                    0.0
2833                                },
2834                            );
2835                            if let Some(match_em_width) = match_mono_em_width {
2836                                // Round to nearest monospace width
2837                                x_advance = ((x_advance / match_em_width).round()) * match_em_width;
2838                            }
2839                            if hinting == Hinting::Enabled {
2840                                x_advance = x_advance.round();
2841                            }
2842                            if self.rtl {
2843                                *x -= x_advance;
2844                            }
2845                            let y_advance = glyph_font_size * glyph.y_advance;
2846                            let mut layout_glyph = glyph.layout(
2847                                glyph_font_size,
2848                                glyph.metrics_opt.map(|x| x.line_height),
2849                                *x,
2850                                *y,
2851                                x_advance,
2852                                r.level,
2853                            );
2854                            // Fix ellipsis glyph indices: point both start and
2855                            // end to the elision boundary so that hit-detection
2856                            // places the cursor at the seam between visible and
2857                            // elided text instead of selecting invisible content.
2858                            if is_ellipsis {
2859                                if let Some((elided_start, elided_end)) = elided_byte_range {
2860                                    // Use the boundary closest to the visible
2861                                    // content that is adjacent to this ellipsis:
2862                                    //   Start:  …|visible  → boundary = elided_end
2863                                    //   End:    visible|…  → boundary = elided_start
2864                                    //   Middle: vis|…|vis  → boundary = elided_start
2865                                    let boundary = if elided_start == 0 {
2866                                        elided_end
2867                                    } else {
2868                                        elided_start
2869                                    };
2870                                    layout_glyph.start = boundary;
2871                                    layout_glyph.end = boundary;
2872                                }
2873                            }
2874                            glyphs.push(layout_glyph);
2875
2876                            if deco_cursor >= deco_spans.len()
2877                                || glyph.start < deco_spans[deco_cursor].0.start
2878                            {
2879                                deco_cursor = 0;
2880                            }
2881                            while deco_cursor < deco_spans.len()
2882                                && deco_spans[deco_cursor].0.end <= glyph.start
2883                            {
2884                                deco_cursor += 1;
2885                            }
2886                            let glyph_deco = deco_spans
2887                                .get(deco_cursor)
2888                                .filter(|(range, _)| glyph.start >= range.start);
2889                            let glyph_idx = glyphs.len() - 1;
2890                            let extends = matches!(
2891                                (decorations.last(), &glyph_deco),
2892                                (Some(span), Some((_, d))) if span.data == *d
2893                            );
2894                            if extends {
2895                                if let Some(last) = decorations.last_mut() {
2896                                    last.glyph_range.end = glyph_idx + 1;
2897                                }
2898                            } else if let Some((_, d)) = glyph_deco {
2899                                decorations.push(DecorationSpan {
2900                                    glyph_range: glyph_idx..glyph_idx + 1,
2901                                    data: d.clone(),
2902                                    color_opt: glyphs[glyph_idx].color_opt,
2903                                    font_size: glyphs[glyph_idx].font_size,
2904                                });
2905                            }
2906                            if !self.rtl {
2907                                *x += x_advance;
2908                            }
2909                            *y += y_advance;
2910                            *max_ascent = max_ascent.max(glyph_font_size * glyph.ascent);
2911                            *max_descent = max_descent.max(glyph_font_size * glyph.descent);
2912                        }
2913                    }
2914                }
2915            };
2916
2917            if self.rtl {
2918                for range in new_order.into_iter().rev() {
2919                    process_range(
2920                        range,
2921                        &mut x,
2922                        &mut y,
2923                        &mut glyphs,
2924                        &mut decorations,
2925                        &mut max_ascent,
2926                        &mut max_descent,
2927                    );
2928                }
2929            } else {
2930                /* LTR */
2931                for range in new_order {
2932                    process_range(
2933                        range,
2934                        &mut x,
2935                        &mut y,
2936                        &mut glyphs,
2937                        &mut decorations,
2938                        &mut max_ascent,
2939                        &mut max_descent,
2940                    );
2941                }
2942            }
2943
2944            let mut line_height_opt: Option<f32> = None;
2945            for glyph in &glyphs {
2946                if let Some(glyph_line_height) = glyph.line_height_opt {
2947                    line_height_opt = line_height_opt
2948                        .map_or(Some(glyph_line_height), |line_height| {
2949                            Some(line_height.max(glyph_line_height))
2950                        });
2951                }
2952            }
2953
2954            layout_lines.push(LayoutLine {
2955                w: if align != Align::Justified {
2956                    visual_line.w
2957                } else if self.rtl {
2958                    start_x - x
2959                } else {
2960                    x
2961                },
2962                max_ascent,
2963                max_descent,
2964                line_height_opt,
2965                glyphs,
2966                decorations,
2967            });
2968        }
2969
2970        // This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
2971        if layout_lines.is_empty() {
2972            layout_lines.push(LayoutLine {
2973                w: 0.0,
2974                max_ascent: 0.0,
2975                max_descent: 0.0,
2976                line_height_opt: self.metrics_opt.map(|x| x.line_height),
2977                glyphs: Vec::default(),
2978                decorations: Vec::new(),
2979            });
2980        }
2981
2982        // Restore the buffer to the scratch set to prevent reallocations.
2983        scratch.visual_lines = visual_lines;
2984        scratch.visual_lines.append(&mut cached_visual_lines);
2985        scratch.cached_visual_lines = cached_visual_lines;
2986        scratch.glyph_sets = cached_glyph_sets;
2987    }
2988}