Skip to main content

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