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::{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    /// This will fit as much as possible in one line
1486    /// If forward is false, it will fit as much as possible from the end of the spans
1487    /// it will stop when it gets to "start".
1488    /// If forward is true, it will start from start and keep going to the end of the spans
1489    #[inline]
1490    fn layout_spans(
1491        &self,
1492        current_visual_line: &mut VisualLine,
1493        font_size: f32,
1494        spans: &[ShapeSpan],
1495        start_opt: Option<SpanWordGlyphPos>,
1496        rtl: bool,
1497        width_opt: Option<f32>,
1498        ellipsize: Ellipsize,
1499        ellipsis_w: f32,
1500        direction: LayoutDirection,
1501    ) {
1502        let check_ellipsizing = matches!(ellipsize, Ellipsize::Start(_) | Ellipsize::End(_))
1503            && width_opt.is_some_and(|w| w > 0.0 && w.is_finite());
1504
1505        let max_width = width_opt.unwrap_or(f32::INFINITY);
1506        let span_count = spans.len();
1507
1508        let mut total_w: f32 = 0.0;
1509
1510        let start = if let Some(s) = start_opt {
1511            s
1512        } else {
1513            SpanWordGlyphPos::ZERO
1514        };
1515
1516        let span_indices: Vec<usize> = if matches!(direction, LayoutDirection::Forward) {
1517            (start.span..spans.len()).collect()
1518        } else {
1519            (start.span..spans.len()).rev().collect()
1520        };
1521
1522        'outer: for span_index in span_indices {
1523            let mut word_range_width = 0.;
1524            let mut number_of_blanks: u32 = 0;
1525
1526            let span = &spans[span_index];
1527            let word_count = span.words.len();
1528
1529            let starting_word_index = if span_index == start.span {
1530                start.word
1531            } else {
1532                0
1533            };
1534
1535            let congruent = rtl == span.level.is_rtl();
1536            let word_forward: bool = congruent == (direction == LayoutDirection::Forward);
1537
1538            let word_indices: Vec<usize> = match (direction, congruent, start_opt) {
1539                (LayoutDirection::Forward, true, _) => (starting_word_index..word_count).collect(),
1540                (LayoutDirection::Forward, false, Some(start)) => {
1541                    if span_index == start.span {
1542                        (0..start.word).rev().collect()
1543                    } else {
1544                        (0..word_count).rev().collect()
1545                    }
1546                }
1547                (LayoutDirection::Forward, false, None) => (0..word_count).rev().collect(),
1548                (LayoutDirection::Backward, true, _) => {
1549                    ((starting_word_index)..word_count).rev().collect()
1550                }
1551                (LayoutDirection::Backward, false, Some(start)) => {
1552                    if span_index == start.span {
1553                        if start.glyph > 0 {
1554                            (0..(start.word + 1)).collect()
1555                        } else {
1556                            (0..(start.word)).collect()
1557                        }
1558                    } else {
1559                        (0..word_count).collect()
1560                    }
1561                }
1562                (LayoutDirection::Backward, false, None) => (0..span.words.len()).collect(),
1563            };
1564            for word_idx in word_indices {
1565                let word = &span.words[word_idx];
1566                let word_width = if span_index == start.span && word_idx == start.word {
1567                    let (start_glyph_pos, end_glyph_pos) = Self::get_glyph_start_end(
1568                        word, start, span_index, word_idx, direction, congruent,
1569                    );
1570                    let mut w = 0.;
1571                    for glyph_idx in start_glyph_pos..end_glyph_pos {
1572                        w += word.glyphs[glyph_idx].width(font_size);
1573                    }
1574                    w
1575                } else {
1576                    word.width(font_size)
1577                };
1578
1579                let overflowing = {
1580                    // only check this if we're ellipsizing
1581                    check_ellipsizing
1582                        && (
1583                            // if this  word doesn't fit, then we have an overflow
1584                            (total_w + word_range_width + word_width > max_width)
1585                                // otherwise if this is not the last word of the last span
1586                                // and we can't fit the ellipsis
1587                                ||(
1588                                    !(match (direction, congruent) {
1589                                        (LayoutDirection::Forward, true) => {
1590                                            (span_index == span_count - 1) && (word_idx == word_count - 1)
1591                                        }
1592                                        (LayoutDirection::Forward, false) => (span_index == span_count - 1) && (word_idx == 0),
1593                                        (LayoutDirection::Backward, true) => {
1594                                            (span_index == start.span) && (word_idx == starting_word_index)
1595                                        }
1596                                        (LayoutDirection::Backward, false) => {
1597                                            (span_index == start.span) && (word_idx == word_count - 1)
1598                                        }
1599                                    })
1600
1601                                    && total_w + word_range_width + word_width + ellipsis_w > max_width
1602                                )
1603                        )
1604                };
1605
1606                if overflowing {
1607                    // overflow detected
1608                    let available = (max_width - ellipsis_w).max(0.0);
1609
1610                    let (glyph_end, glyphs_w) = Self::fit_glyphs(
1611                        word,
1612                        font_size,
1613                        start,
1614                        span_index,
1615                        word_idx,
1616                        direction,
1617                        congruent,
1618                        total_w + word_range_width,
1619                        available,
1620                        word_forward,
1621                    );
1622
1623                    let (start_pos, end_pos) = if word_forward {
1624                        if span_index == start.span {
1625                            if !congruent {
1626                                (WordGlyphPos::ZERO, WordGlyphPos::new(word_idx, glyph_end))
1627                            } else {
1628                                (
1629                                    start.word_glyph_pos(),
1630                                    WordGlyphPos::new(word_idx, glyph_end),
1631                                )
1632                            }
1633                        } else {
1634                            (WordGlyphPos::ZERO, WordGlyphPos::new(word_idx, glyph_end))
1635                        }
1636                    } else {
1637                        // For an incongruent span in the forward direction, the
1638                        // word indices are (0..start.word).rev(). Cap the VlRange
1639                        // end at start.word_glyph_pos() so it doesn't include
1640                        // words beyond start.word that belong to a previous line.
1641                        // For the backward direction (congruent span), the word
1642                        // indices are (start.word..word_count).rev() and
1643                        // span.words.len() is the correct end.
1644                        let range_end = if span_index == start.span && !congruent {
1645                            start.word_glyph_pos()
1646                        } else {
1647                            WordGlyphPos::new(span.words.len(), 0)
1648                        };
1649                        (WordGlyphPos::new(word_idx, glyph_end), range_end)
1650                    };
1651                    self.add_to_visual_line(
1652                        current_visual_line,
1653                        span_index,
1654                        start_pos,
1655                        end_pos,
1656                        word_range_width + glyphs_w,
1657                        number_of_blanks,
1658                    );
1659
1660                    // don't iterate anymore since we overflowed
1661                    current_visual_line.ellipsized = true;
1662                    break 'outer;
1663                }
1664
1665                word_range_width += word_width;
1666                if word.blank {
1667                    number_of_blanks += 1;
1668                }
1669
1670                // Backward-only: if we've reached the starting point, commit and stop.
1671                if matches!(direction, LayoutDirection::Backward)
1672                    && word_idx == start.word
1673                    && span_index == start.span
1674                {
1675                    let (start_pos, end_pos) = if word_forward {
1676                        (WordGlyphPos::ZERO, start.word_glyph_pos())
1677                    } else {
1678                        (
1679                            start.word_glyph_pos(),
1680                            WordGlyphPos::new(span.words.len(), 0),
1681                        )
1682                    };
1683
1684                    self.add_to_visual_line(
1685                        current_visual_line,
1686                        span_index,
1687                        start_pos,
1688                        end_pos,
1689                        word_range_width,
1690                        number_of_blanks,
1691                    );
1692
1693                    break 'outer;
1694                }
1695            }
1696
1697            // if we get to here that means we didn't ellipsize, so either the whole span fits,
1698            // or we don't really care
1699            total_w += word_range_width;
1700            let (start_pos, end_pos) = if congruent {
1701                if span_index == start.span {
1702                    (
1703                        start.word_glyph_pos(),
1704                        WordGlyphPos::new(span.words.len(), 0),
1705                    )
1706                } else {
1707                    (WordGlyphPos::ZERO, WordGlyphPos::new(span.words.len(), 0))
1708                }
1709            } else if span_index == start.span {
1710                (WordGlyphPos::ZERO, start.word_glyph_pos())
1711            } else {
1712                (WordGlyphPos::ZERO, WordGlyphPos::new(span.words.len(), 0))
1713            };
1714
1715            self.add_to_visual_line(
1716                current_visual_line,
1717                span_index,
1718                start_pos,
1719                end_pos,
1720                word_range_width,
1721                number_of_blanks,
1722            );
1723        }
1724
1725        if matches!(direction, LayoutDirection::Backward) {
1726            current_visual_line.ranges.reverse();
1727        }
1728    }
1729
1730    fn layout_middle(
1731        &self,
1732        current_visual_line: &mut VisualLine,
1733        font_size: f32,
1734        spans: &[ShapeSpan],
1735        start_opt: Option<SpanWordGlyphPos>,
1736        rtl: bool,
1737        width: f32,
1738        ellipsize: Ellipsize,
1739        ellipsis_w: f32,
1740    ) {
1741        assert!(matches!(ellipsize, Ellipsize::Middle(_)));
1742        let mut starting_line = VisualLine::default();
1743        self.layout_spans(
1744            &mut starting_line,
1745            font_size,
1746            spans,
1747            start_opt,
1748            rtl,
1749            Some(width / 2.0),
1750            Ellipsize::End(EllipsizeHeightLimit::Lines(1)),
1751            0., //pass 0 for ellipsis_w
1752            LayoutDirection::Forward,
1753        );
1754        let forward_pass_overflowed = starting_line.ellipsized;
1755        let end_range_opt = starting_line.ranges.last();
1756        match end_range_opt {
1757            Some(range) if forward_pass_overflowed => {
1758                let congruent = rtl == self.spans[range.span].level.is_rtl();
1759                // create a new range and do the other half
1760                let mut ending_line = VisualLine::default();
1761                let start = if congruent {
1762                    SpanWordGlyphPos {
1763                        span: range.span,
1764                        word: range.end.word,
1765                        glyph: range.end.glyph,
1766                    }
1767                } else {
1768                    SpanWordGlyphPos {
1769                        span: range.span,
1770                        word: range.start.word,
1771                        glyph: range.start.glyph,
1772                    }
1773                };
1774                self.layout_spans(
1775                    &mut ending_line,
1776                    font_size,
1777                    spans,
1778                    Some(start),
1779                    rtl,
1780                    Some((width - starting_line.w - ellipsis_w).max(0.0)),
1781                    Ellipsize::Start(EllipsizeHeightLimit::Lines(1)),
1782                    0., //pass 0 for ellipsis_w
1783                    LayoutDirection::Backward,
1784                );
1785                // Check if anything was actually skipped between the two halves
1786                let first_half_end = if spans[range.span].level.is_rtl() != rtl {
1787                    SpanWordGlyphPos {
1788                        span: range.span,
1789                        word: range.start.word,
1790                        glyph: range.start.glyph,
1791                    }
1792                } else {
1793                    SpanWordGlyphPos {
1794                        span: range.span,
1795                        word: range.end.word,
1796                        glyph: range.end.glyph,
1797                    }
1798                };
1799                let second_half_start = ending_line.ranges.first().map(|r| {
1800                    if spans[r.span].level.is_rtl() != rtl {
1801                        SpanWordGlyphPos {
1802                            span: r.span,
1803                            word: r.end.word,
1804                            glyph: r.end.glyph,
1805                        }
1806                    } else {
1807                        SpanWordGlyphPos {
1808                            span: r.span,
1809                            word: r.start.word,
1810                            glyph: r.start.glyph,
1811                        }
1812                    }
1813                });
1814                let actually_ellipsized = match second_half_start {
1815                    Some(shs) => shs != first_half_end,
1816                    None => false, // nothing in backward pass = nothing was skipped
1817                };
1818                // add both to the current_visual_line
1819                if actually_ellipsized {
1820                    // Insert the ellipsis VlRange between the two halves.
1821                    // Its BiDi level is determined by the adjacent ranges.
1822                    let ellipsis_level = self.ellipsis_level_between(
1823                        starting_line.ranges.last(),
1824                        ending_line.ranges.first(),
1825                    );
1826                    starting_line
1827                        .ranges
1828                        .push(self.ellipsis_vlrange(ellipsis_level));
1829                    starting_line.ranges.extend(ending_line.ranges);
1830                    current_visual_line.ranges = starting_line.ranges;
1831                    current_visual_line.ellipsized = true;
1832                    current_visual_line.w = starting_line.w + ending_line.w + ellipsis_w;
1833                } else {
1834                    self.layout_spans(
1835                        current_visual_line,
1836                        font_size,
1837                        spans,
1838                        start_opt,
1839                        rtl,
1840                        Some(width),
1841                        Ellipsize::None,
1842                        0., //pass 0 for ellipsis_w
1843                        LayoutDirection::Backward,
1844                    );
1845                    return;
1846                }
1847                current_visual_line.spaces = starting_line.spaces + ending_line.spaces;
1848            }
1849            _ => {
1850                // everything fit in the forward pass
1851                current_visual_line.ranges = starting_line.ranges;
1852                current_visual_line.w = starting_line.w;
1853                current_visual_line.spaces = starting_line.spaces;
1854            }
1855        }
1856    }
1857
1858    /// Returns the words for a given span index, handling the ellipsis sentinel.
1859    fn get_span_words(&self, span_index: usize) -> &[ShapeWord] {
1860        if span_index == ELLIPSIS_SPAN {
1861            &self
1862                .ellipsis_span
1863                .as_ref()
1864                .expect("ellipsis_span not set")
1865                .words
1866        } else {
1867            &self.spans[span_index].words
1868        }
1869    }
1870
1871    fn byte_range_of_vlrange(&self, r: &VlRange) -> Option<(usize, usize)> {
1872        debug_assert_ne!(r.span, ELLIPSIS_SPAN);
1873        let words = self.get_span_words(r.span);
1874        let mut min_byte = usize::MAX;
1875        let mut max_byte = 0usize;
1876        let end_word = r.end.word + usize::from(r.end.glyph != 0);
1877        for (i, word) in words.iter().enumerate().take(end_word).skip(r.start.word) {
1878            let included_glyphs = match (i == r.start.word, i == r.end.word) {
1879                (false, false) => &word.glyphs[..],
1880                (true, false) => &word.glyphs[r.start.glyph..],
1881                (false, true) => &word.glyphs[..r.end.glyph],
1882                (true, true) => &word.glyphs[r.start.glyph..r.end.glyph],
1883            };
1884            for glyph in included_glyphs {
1885                min_byte = min_byte.min(glyph.start);
1886                max_byte = max_byte.max(glyph.end);
1887            }
1888        }
1889        if min_byte <= max_byte {
1890            Some((min_byte, max_byte))
1891        } else {
1892            None
1893        }
1894    }
1895
1896    fn compute_elided_byte_range(
1897        &self,
1898        visual_line: &VisualLine,
1899        line_len: usize,
1900    ) -> Option<(usize, usize)> {
1901        if !visual_line.ellipsized {
1902            return None;
1903        }
1904        // Find the position of the ellipsis VlRange
1905        let ellipsis_idx = visual_line
1906            .ranges
1907            .iter()
1908            .position(|r| r.span == ELLIPSIS_SPAN)?;
1909
1910        // Find the byte range of the visible content before the ellipsis
1911        let before_end = (0..ellipsis_idx)
1912            .rev()
1913            .find_map(|i| self.byte_range_of_vlrange(&visual_line.ranges[i]))
1914            .map(|(_, end)| end)
1915            .unwrap_or(0);
1916
1917        // Find the byte range of the visible content after the ellipsis
1918        let after_start = (ellipsis_idx + 1..visual_line.ranges.len())
1919            .find_map(|i| self.byte_range_of_vlrange(&visual_line.ranges[i]))
1920            .map(|(start, _)| start)
1921            .unwrap_or(line_len);
1922
1923        Some((before_end, after_start))
1924    }
1925
1926    /// Returns the maximum byte offset across all glyphs in all non-ellipsis spans.
1927    /// This effectively gives the byte length of the original shaped text.
1928    fn max_byte_offset(&self) -> usize {
1929        self.spans
1930            .iter()
1931            .flat_map(|span| span.words.iter())
1932            .flat_map(|word| word.glyphs.iter())
1933            .map(|g| g.end)
1934            .max()
1935            .unwrap_or(0)
1936    }
1937
1938    /// Returns the width of the ellipsis in the given font size.
1939    fn ellipsis_w(&self, font_size: f32) -> f32 {
1940        self.ellipsis_span
1941            .as_ref()
1942            .map_or(0.0, |s| s.words.iter().map(|w| w.width(font_size)).sum())
1943    }
1944
1945    /// Creates a VlRange for the ellipsis with the given BiDi level.
1946    fn ellipsis_vlrange(&self, level: unicode_bidi::Level) -> VlRange {
1947        VlRange {
1948            span: ELLIPSIS_SPAN,
1949            start: WordGlyphPos::ZERO,
1950            end: WordGlyphPos::new(1, 0),
1951            level,
1952        }
1953    }
1954
1955    /// Determines the appropriate BiDi level for the ellipsis based on the
1956    /// adjacent ranges, following UAX#9 N1/N2 rules for neutral characters.
1957    fn ellipsis_level_between(
1958        &self,
1959        before: Option<&VlRange>,
1960        after: Option<&VlRange>,
1961    ) -> unicode_bidi::Level {
1962        match (before, after) {
1963            (Some(a), Some(b)) if a.level == b.level => a.level,
1964            (Some(a), None) => a.level,
1965            (None, Some(b)) => b.level,
1966            _ => {
1967                if self.rtl {
1968                    unicode_bidi::Level::rtl()
1969                } else {
1970                    unicode_bidi::Level::ltr()
1971                }
1972            }
1973        }
1974    }
1975
1976    fn layout_line(
1977        &self,
1978        current_visual_line: &mut VisualLine,
1979        font_size: f32,
1980        spans: &[ShapeSpan],
1981        start_opt: Option<SpanWordGlyphPos>,
1982        rtl: bool,
1983        width_opt: Option<f32>,
1984        ellipsize: Ellipsize,
1985    ) {
1986        let ellipsis_w = self.ellipsis_w(font_size);
1987
1988        match (ellipsize, width_opt) {
1989            (Ellipsize::Start(_), Some(_)) => {
1990                self.layout_spans(
1991                    current_visual_line,
1992                    font_size,
1993                    spans,
1994                    start_opt,
1995                    rtl,
1996                    width_opt,
1997                    ellipsize,
1998                    ellipsis_w,
1999                    LayoutDirection::Backward,
2000                );
2001                // Insert ellipsis at the visual start (index 0, after backward reversal)
2002                if current_visual_line.ellipsized {
2003                    let level =
2004                        self.ellipsis_level_between(None, current_visual_line.ranges.first());
2005                    current_visual_line
2006                        .ranges
2007                        .insert(0, self.ellipsis_vlrange(level));
2008                    current_visual_line.w += ellipsis_w;
2009                }
2010            }
2011            (Ellipsize::Middle(_), Some(width)) => {
2012                self.layout_middle(
2013                    current_visual_line,
2014                    font_size,
2015                    spans,
2016                    start_opt,
2017                    rtl,
2018                    width,
2019                    ellipsize,
2020                    ellipsis_w,
2021                );
2022            }
2023            _ => {
2024                self.layout_spans(
2025                    current_visual_line,
2026                    font_size,
2027                    spans,
2028                    start_opt,
2029                    rtl,
2030                    width_opt,
2031                    ellipsize,
2032                    ellipsis_w,
2033                    LayoutDirection::Forward,
2034                );
2035                // Insert ellipsis at the visual end
2036                if current_visual_line.ellipsized {
2037                    let level =
2038                        self.ellipsis_level_between(current_visual_line.ranges.last(), None);
2039                    current_visual_line
2040                        .ranges
2041                        .push(self.ellipsis_vlrange(level));
2042                    current_visual_line.w += ellipsis_w;
2043                }
2044            }
2045        }
2046
2047        // Compute the byte range of ellipsized text so the ellipsis LayoutGlyph
2048        // can have valid start/end indices into the original line text.
2049        if current_visual_line.ellipsized {
2050            let line_len = self.max_byte_offset();
2051            current_visual_line.elided_byte_range =
2052                self.compute_elided_byte_range(current_visual_line, line_len);
2053        }
2054    }
2055
2056    pub fn layout_to_buffer(
2057        &self,
2058        scratch: &mut ShapeBuffer,
2059        font_size: f32,
2060        width_opt: Option<f32>,
2061        wrap: Wrap,
2062        ellipsize: Ellipsize,
2063        align: Option<Align>,
2064        layout_lines: &mut Vec<LayoutLine>,
2065        match_mono_width: Option<f32>,
2066        hinting: Hinting,
2067    ) {
2068        // For each visual line a list of  (span index,  and range of words in that span)
2069        // Note that a BiDi visual line could have multiple spans or parts of them
2070        // let mut vl_range_of_spans = Vec::with_capacity(1);
2071        let mut visual_lines = mem::take(&mut scratch.visual_lines);
2072        let mut cached_visual_lines = mem::take(&mut scratch.cached_visual_lines);
2073        cached_visual_lines.clear();
2074        cached_visual_lines.extend(visual_lines.drain(..).map(|mut l| {
2075            l.clear();
2076            l
2077        }));
2078
2079        // Cache glyph sets in reverse order so they will ideally be reused in exactly the same lines.
2080        let mut cached_glyph_sets = mem::take(&mut scratch.glyph_sets);
2081        cached_glyph_sets.clear();
2082        cached_glyph_sets.extend(layout_lines.drain(..).rev().map(|mut v| {
2083            v.glyphs.clear();
2084            v.glyphs
2085        }));
2086
2087        // This would keep the maximum number of spans that would fit on a visual line
2088        // If one span is too large, this variable will hold the range of words inside that span
2089        // that fits on a line.
2090        // let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
2091        let mut current_visual_line = cached_visual_lines.pop().unwrap_or_default();
2092
2093        if wrap == Wrap::None {
2094            self.layout_line(
2095                &mut current_visual_line,
2096                font_size,
2097                &self.spans,
2098                None,
2099                self.rtl,
2100                width_opt,
2101                ellipsize,
2102            );
2103        } else {
2104            let mut total_line_height = 0.0;
2105            let mut total_line_count = 0;
2106            let max_line_count_opt = match ellipsize {
2107                Ellipsize::Start(EllipsizeHeightLimit::Lines(lines))
2108                | Ellipsize::Middle(EllipsizeHeightLimit::Lines(lines))
2109                | Ellipsize::End(EllipsizeHeightLimit::Lines(lines)) => Some(lines.max(1)),
2110                _ => None,
2111            };
2112            let max_height_opt = match ellipsize {
2113                Ellipsize::Start(EllipsizeHeightLimit::Height(height))
2114                | Ellipsize::Middle(EllipsizeHeightLimit::Height(height))
2115                | Ellipsize::End(EllipsizeHeightLimit::Height(height)) => Some(height),
2116                _ => None,
2117            };
2118            let line_height = self
2119                .metrics_opt
2120                .map_or_else(|| font_size, |m| m.line_height);
2121
2122            let try_ellipsize_last_line = |total_line_count: usize,
2123                                           total_line_height: f32,
2124                                           current_visual_line: &mut VisualLine,
2125                                           font_size: f32,
2126                                           start_opt: Option<SpanWordGlyphPos>,
2127                                           width_opt: Option<f32>,
2128                                           ellipsize: Ellipsize|
2129             -> bool {
2130                // If Ellipsize::End, then how many lines can we fit or how much is the available height
2131                if max_line_count_opt == Some(total_line_count + 1)
2132                    || max_height_opt.is_some_and(|max_height| {
2133                        total_line_height + line_height * 2.0 > max_height
2134                    })
2135                {
2136                    self.layout_line(
2137                        current_visual_line,
2138                        font_size,
2139                        &self.spans,
2140                        start_opt,
2141                        self.rtl,
2142                        width_opt,
2143                        ellipsize,
2144                    );
2145                    return true;
2146                }
2147                false
2148            };
2149
2150            if !try_ellipsize_last_line(
2151                total_line_count,
2152                total_line_height,
2153                &mut current_visual_line,
2154                font_size,
2155                None,
2156                width_opt,
2157                ellipsize,
2158            ) {
2159                'outer: for (span_index, span) in self.spans.iter().enumerate() {
2160                    let mut word_range_width = 0.;
2161                    let mut width_before_last_blank = 0.;
2162                    let mut number_of_blanks: u32 = 0;
2163
2164                    // Create the word ranges that fits in a visual line
2165                    if self.rtl != span.level.is_rtl() {
2166                        // incongruent directions
2167                        let mut fitting_start = WordGlyphPos::new(span.words.len(), 0);
2168                        for (i, word) in span.words.iter().enumerate().rev() {
2169                            let word_width = word.width(font_size);
2170                            // Addition in the same order used to compute the final width, so that
2171                            // relayouts with that width as the `line_width` will produce the same
2172                            // wrapping results.
2173                            if current_visual_line.w + (word_range_width + word_width)
2174                            <= width_opt.unwrap_or(f32::INFINITY)
2175                            // Include one blank word over the width limit since it won't be
2176                            // counted in the final width
2177                            || (word.blank
2178                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
2179                            {
2180                                // fits
2181                                if word.blank {
2182                                    number_of_blanks += 1;
2183                                    width_before_last_blank = word_range_width;
2184                                }
2185                                word_range_width += word_width;
2186                            } else if wrap == Wrap::Glyph
2187                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
2188                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
2189                            {
2190                                // Commit the current line so that the word starts on the next line.
2191                                if word_range_width > 0.
2192                                    && wrap == Wrap::WordOrGlyph
2193                                    && word_width > width_opt.unwrap_or(f32::INFINITY)
2194                                {
2195                                    self.add_to_visual_line(
2196                                        &mut current_visual_line,
2197                                        span_index,
2198                                        WordGlyphPos::new(i + 1, 0),
2199                                        fitting_start,
2200                                        word_range_width,
2201                                        number_of_blanks,
2202                                    );
2203
2204                                    visual_lines.push(current_visual_line);
2205                                    current_visual_line =
2206                                        cached_visual_lines.pop().unwrap_or_default();
2207
2208                                    number_of_blanks = 0;
2209                                    word_range_width = 0.;
2210
2211                                    fitting_start = WordGlyphPos::new(i, 0);
2212                                    total_line_count += 1;
2213                                    total_line_height += line_height;
2214                                    if try_ellipsize_last_line(
2215                                        total_line_count,
2216                                        total_line_height,
2217                                        &mut current_visual_line,
2218                                        font_size,
2219                                        Some(SpanWordGlyphPos::with_wordglyph(
2220                                            span_index,
2221                                            fitting_start,
2222                                        )),
2223                                        width_opt,
2224                                        ellipsize,
2225                                    ) {
2226                                        break 'outer;
2227                                    }
2228                                }
2229
2230                                for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
2231                                    let glyph_width = glyph.width(font_size);
2232                                    if current_visual_line.w + (word_range_width + glyph_width)
2233                                        <= width_opt.unwrap_or(f32::INFINITY)
2234                                    {
2235                                        word_range_width += glyph_width;
2236                                    } else {
2237                                        self.add_to_visual_line(
2238                                            &mut current_visual_line,
2239                                            span_index,
2240                                            WordGlyphPos::new(i, glyph_i + 1),
2241                                            fitting_start,
2242                                            word_range_width,
2243                                            number_of_blanks,
2244                                        );
2245                                        visual_lines.push(current_visual_line);
2246                                        current_visual_line =
2247                                            cached_visual_lines.pop().unwrap_or_default();
2248
2249                                        number_of_blanks = 0;
2250                                        word_range_width = glyph_width;
2251                                        fitting_start = WordGlyphPos::new(i, glyph_i + 1);
2252                                        total_line_count += 1;
2253                                        total_line_height += line_height;
2254                                        if try_ellipsize_last_line(
2255                                            total_line_count,
2256                                            total_line_height,
2257                                            &mut current_visual_line,
2258                                            font_size,
2259                                            Some(SpanWordGlyphPos::with_wordglyph(
2260                                                span_index,
2261                                                fitting_start,
2262                                            )),
2263                                            width_opt,
2264                                            ellipsize,
2265                                        ) {
2266                                            break 'outer;
2267                                        }
2268                                    }
2269                                }
2270                            } else {
2271                                // Wrap::Word, Wrap::WordOrGlyph
2272
2273                                // If we had a previous range, commit that line before the next word.
2274                                if word_range_width > 0. {
2275                                    // Current word causing a wrap is not whitespace, so we ignore the
2276                                    // previous word if it's a whitespace
2277                                    let trailing_blank = span
2278                                        .words
2279                                        .get(i + 1)
2280                                        .is_some_and(|previous_word| previous_word.blank);
2281
2282                                    if trailing_blank {
2283                                        number_of_blanks = number_of_blanks.saturating_sub(1);
2284                                        self.add_to_visual_line(
2285                                            &mut current_visual_line,
2286                                            span_index,
2287                                            WordGlyphPos::new(i + 2, 0),
2288                                            fitting_start,
2289                                            width_before_last_blank,
2290                                            number_of_blanks,
2291                                        );
2292                                    } else {
2293                                        self.add_to_visual_line(
2294                                            &mut current_visual_line,
2295                                            span_index,
2296                                            WordGlyphPos::new(i + 1, 0),
2297                                            fitting_start,
2298                                            word_range_width,
2299                                            number_of_blanks,
2300                                        );
2301                                    }
2302                                }
2303
2304                                // This fixes a bug that a long first word at the boundary of
2305                                // was overflowing
2306                                if !current_visual_line.ranges.is_empty() {
2307                                    visual_lines.push(current_visual_line);
2308                                    current_visual_line =
2309                                        cached_visual_lines.pop().unwrap_or_default();
2310                                    number_of_blanks = 0;
2311                                    total_line_count += 1;
2312                                    total_line_height += line_height;
2313
2314                                    if try_ellipsize_last_line(
2315                                        total_line_count,
2316                                        total_line_height,
2317                                        &mut current_visual_line,
2318                                        font_size,
2319                                        Some(SpanWordGlyphPos::with_wordglyph(
2320                                            span_index,
2321                                            if word.blank {
2322                                                WordGlyphPos::new(i, 0)
2323                                            } else {
2324                                                WordGlyphPos::new(i + 1, 0)
2325                                            },
2326                                        )),
2327                                        width_opt,
2328                                        ellipsize,
2329                                    ) {
2330                                        break 'outer;
2331                                    }
2332                                }
2333
2334                                if word.blank {
2335                                    word_range_width = 0.;
2336                                    fitting_start = WordGlyphPos::new(i, 0);
2337                                } else {
2338                                    word_range_width = word_width;
2339                                    fitting_start = WordGlyphPos::new(i + 1, 0);
2340                                }
2341                            }
2342                        }
2343                        self.add_to_visual_line(
2344                            &mut current_visual_line,
2345                            span_index,
2346                            WordGlyphPos::new(0, 0),
2347                            fitting_start,
2348                            word_range_width,
2349                            number_of_blanks,
2350                        );
2351                    } else {
2352                        // congruent direction
2353                        let mut fitting_start = WordGlyphPos::ZERO;
2354                        for (i, word) in span.words.iter().enumerate() {
2355                            let word_width = word.width(font_size);
2356                            if current_visual_line.w + (word_range_width + word_width)
2357                            <= width_opt.unwrap_or(f32::INFINITY)
2358                            // Include one blank word over the width limit since it won't be
2359                            // counted in the final width.
2360                            || (word.blank
2361                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
2362                            {
2363                                // fits
2364                                if word.blank {
2365                                    number_of_blanks += 1;
2366                                    width_before_last_blank = word_range_width;
2367                                }
2368                                word_range_width += word_width;
2369                            } else if wrap == Wrap::Glyph
2370                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
2371                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
2372                            {
2373                                // Commit the current line so that the word starts on the next line.
2374                                if word_range_width > 0.
2375                                    && wrap == Wrap::WordOrGlyph
2376                                    && word_width > width_opt.unwrap_or(f32::INFINITY)
2377                                {
2378                                    self.add_to_visual_line(
2379                                        &mut current_visual_line,
2380                                        span_index,
2381                                        fitting_start,
2382                                        WordGlyphPos::new(i, 0),
2383                                        word_range_width,
2384                                        number_of_blanks,
2385                                    );
2386
2387                                    visual_lines.push(current_visual_line);
2388                                    current_visual_line =
2389                                        cached_visual_lines.pop().unwrap_or_default();
2390
2391                                    number_of_blanks = 0;
2392                                    word_range_width = 0.;
2393
2394                                    fitting_start = WordGlyphPos::new(i, 0);
2395                                    total_line_count += 1;
2396                                    total_line_height += line_height;
2397                                    if try_ellipsize_last_line(
2398                                        total_line_count,
2399                                        total_line_height,
2400                                        &mut current_visual_line,
2401                                        font_size,
2402                                        Some(SpanWordGlyphPos::with_wordglyph(
2403                                            span_index,
2404                                            fitting_start,
2405                                        )),
2406                                        width_opt,
2407                                        ellipsize,
2408                                    ) {
2409                                        break 'outer;
2410                                    }
2411                                }
2412
2413                                for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
2414                                    let glyph_width = glyph.width(font_size);
2415                                    if current_visual_line.w + (word_range_width + glyph_width)
2416                                        <= width_opt.unwrap_or(f32::INFINITY)
2417                                    {
2418                                        word_range_width += glyph_width;
2419                                    } else {
2420                                        self.add_to_visual_line(
2421                                            &mut current_visual_line,
2422                                            span_index,
2423                                            fitting_start,
2424                                            WordGlyphPos::new(i, glyph_i),
2425                                            word_range_width,
2426                                            number_of_blanks,
2427                                        );
2428                                        visual_lines.push(current_visual_line);
2429                                        current_visual_line =
2430                                            cached_visual_lines.pop().unwrap_or_default();
2431
2432                                        number_of_blanks = 0;
2433                                        word_range_width = glyph_width;
2434                                        fitting_start = WordGlyphPos::new(i, glyph_i);
2435                                        total_line_count += 1;
2436                                        total_line_height += line_height;
2437                                        if try_ellipsize_last_line(
2438                                            total_line_count,
2439                                            total_line_height,
2440                                            &mut current_visual_line,
2441                                            font_size,
2442                                            Some(SpanWordGlyphPos::with_wordglyph(
2443                                                span_index,
2444                                                fitting_start,
2445                                            )),
2446                                            width_opt,
2447                                            ellipsize,
2448                                        ) {
2449                                            break 'outer;
2450                                        }
2451                                    }
2452                                }
2453                            } else {
2454                                // Wrap::Word, Wrap::WordOrGlyph
2455
2456                                // If we had a previous range, commit that line before the next word.
2457                                if word_range_width > 0. {
2458                                    // Current word causing a wrap is not whitespace, so we ignore the
2459                                    // previous word if it's a whitespace.
2460                                    let trailing_blank = i > 0 && span.words[i - 1].blank;
2461
2462                                    if trailing_blank {
2463                                        number_of_blanks = number_of_blanks.saturating_sub(1);
2464                                        self.add_to_visual_line(
2465                                            &mut current_visual_line,
2466                                            span_index,
2467                                            fitting_start,
2468                                            WordGlyphPos::new(i - 1, 0),
2469                                            width_before_last_blank,
2470                                            number_of_blanks,
2471                                        );
2472                                    } else {
2473                                        self.add_to_visual_line(
2474                                            &mut current_visual_line,
2475                                            span_index,
2476                                            fitting_start,
2477                                            WordGlyphPos::new(i, 0),
2478                                            word_range_width,
2479                                            number_of_blanks,
2480                                        );
2481                                    }
2482                                }
2483
2484                                if !current_visual_line.ranges.is_empty() {
2485                                    visual_lines.push(current_visual_line);
2486                                    current_visual_line =
2487                                        cached_visual_lines.pop().unwrap_or_default();
2488                                    number_of_blanks = 0;
2489                                    total_line_count += 1;
2490                                    total_line_height += line_height;
2491                                    if try_ellipsize_last_line(
2492                                        total_line_count,
2493                                        total_line_height,
2494                                        &mut current_visual_line,
2495                                        font_size,
2496                                        Some(SpanWordGlyphPos::with_wordglyph(
2497                                            span_index,
2498                                            if i > 0 && span.words[i - 1].blank {
2499                                                WordGlyphPos::new(i - 1, 0)
2500                                            } else {
2501                                                WordGlyphPos::new(i, 0)
2502                                            },
2503                                        )),
2504                                        width_opt,
2505                                        ellipsize,
2506                                    ) {
2507                                        break 'outer;
2508                                    }
2509                                }
2510
2511                                if word.blank {
2512                                    word_range_width = 0.;
2513                                    fitting_start = WordGlyphPos::new(i + 1, 0);
2514                                } else {
2515                                    word_range_width = word_width;
2516                                    fitting_start = WordGlyphPos::new(i, 0);
2517                                }
2518                            }
2519                        }
2520                        self.add_to_visual_line(
2521                            &mut current_visual_line,
2522                            span_index,
2523                            fitting_start,
2524                            WordGlyphPos::new(span.words.len(), 0),
2525                            word_range_width,
2526                            number_of_blanks,
2527                        );
2528                    }
2529                }
2530            }
2531        }
2532
2533        if current_visual_line.ranges.is_empty() {
2534            current_visual_line.clear();
2535            cached_visual_lines.push(current_visual_line);
2536        } else {
2537            visual_lines.push(current_visual_line);
2538        }
2539
2540        // Create the LayoutLines using the ranges inside visual lines
2541        let align = align.unwrap_or(if self.rtl { Align::Right } else { Align::Left });
2542
2543        let line_width = width_opt.unwrap_or_else(|| {
2544            let mut width: f32 = 0.0;
2545            for visual_line in &visual_lines {
2546                width = width.max(visual_line.w);
2547            }
2548            width
2549        });
2550
2551        let start_x = if self.rtl { line_width } else { 0.0 };
2552
2553        let number_of_visual_lines = visual_lines.len();
2554        for (index, visual_line) in visual_lines.iter().enumerate() {
2555            if visual_line.ranges.is_empty() {
2556                continue;
2557            }
2558
2559            let new_order = self.reorder(&visual_line.ranges);
2560
2561            let mut glyphs = cached_glyph_sets
2562                .pop()
2563                .unwrap_or_else(|| Vec::with_capacity(1));
2564            let mut x = start_x;
2565            let mut y = 0.;
2566            let mut max_ascent: f32 = 0.;
2567            let mut max_descent: f32 = 0.;
2568            let alignment_correction = match (align, self.rtl) {
2569                (Align::Left, true) => (line_width - visual_line.w).max(0.),
2570                (Align::Left, false) => 0.,
2571                (Align::Right, true) => 0.,
2572                (Align::Right, false) => (line_width - visual_line.w).max(0.),
2573                (Align::Center, _) => (line_width - visual_line.w).max(0.) / 2.0,
2574                (Align::End, _) => (line_width - visual_line.w).max(0.),
2575                (Align::Justified, _) => 0.,
2576            };
2577
2578            if self.rtl {
2579                x -= alignment_correction;
2580            } else {
2581                x += alignment_correction;
2582            }
2583
2584            if hinting == Hinting::Enabled {
2585                x = x.round();
2586            }
2587
2588            // TODO: Only certain `is_whitespace` chars are typically expanded but this is what is
2589            // currently used to compute `visual_line.spaces`.
2590            //
2591            // https://www.unicode.org/reports/tr14/#Introduction
2592            // > When expanding or compressing interword space according to common
2593            // > typographical practice, only the spaces marked by U+0020 SPACE and U+00A0
2594            // > NO-BREAK SPACE are subject to compression, and only spaces marked by U+0020
2595            // > SPACE, U+00A0 NO-BREAK SPACE, and occasionally spaces marked by U+2009 THIN
2596            // > SPACE are subject to expansion. All other space characters normally have
2597            // > fixed width.
2598            //
2599            // (also some spaces aren't followed by potential linebreaks but they could
2600            //  still be expanded)
2601
2602            // Amount of extra width added to each blank space within a line.
2603            let justification_expansion = if matches!(align, Align::Justified)
2604                && visual_line.spaces > 0
2605                // Don't justify the last line in a paragraph.
2606                && index != number_of_visual_lines - 1
2607            {
2608                (line_width - visual_line.w) / visual_line.spaces as f32
2609            } else {
2610                0.
2611            };
2612
2613            let elided_byte_range = if visual_line.ellipsized {
2614                visual_line.elided_byte_range
2615            } else {
2616                None
2617            };
2618
2619            let process_range = |range: Range<usize>,
2620                                 x: &mut f32,
2621                                 y: &mut f32,
2622                                 glyphs: &mut Vec<LayoutGlyph>,
2623                                 max_ascent: &mut f32,
2624                                 max_descent: &mut f32| {
2625                for r in visual_line.ranges[range.clone()].iter() {
2626                    let is_ellipsis = r.span == ELLIPSIS_SPAN;
2627                    let span_words = self.get_span_words(r.span);
2628                    // If ending_glyph is not 0 we need to include glyphs from the ending_word
2629                    for i in r.start.word..r.end.word + usize::from(r.end.glyph != 0) {
2630                        let word = &span_words[i];
2631                        let included_glyphs = match (i == r.start.word, i == r.end.word) {
2632                            (false, false) => &word.glyphs[..],
2633                            (true, false) => &word.glyphs[r.start.glyph..],
2634                            (false, true) => &word.glyphs[..r.end.glyph],
2635                            (true, true) => &word.glyphs[r.start.glyph..r.end.glyph],
2636                        };
2637
2638                        for glyph in included_glyphs {
2639                            // Use overridden font size
2640                            let font_size = glyph.metrics_opt.map_or(font_size, |x| x.font_size);
2641
2642                            let match_mono_em_width = match_mono_width.map(|w| w / font_size);
2643
2644                            let glyph_font_size = match (
2645                                match_mono_em_width,
2646                                glyph.font_monospace_em_width,
2647                            ) {
2648                                (Some(match_em_width), Some(glyph_em_width))
2649                                    if glyph_em_width != match_em_width =>
2650                                {
2651                                    let glyph_to_match_factor = glyph_em_width / match_em_width;
2652                                    let glyph_font_size = math::roundf(glyph_to_match_factor)
2653                                        .max(1.0)
2654                                        / glyph_to_match_factor
2655                                        * font_size;
2656                                    log::trace!(
2657                                        "Adjusted glyph font size ({font_size} => {glyph_font_size})"
2658                                    );
2659                                    glyph_font_size
2660                                }
2661                                _ => font_size,
2662                            };
2663
2664                            let mut x_advance = glyph_font_size.mul_add(
2665                                glyph.x_advance,
2666                                if word.blank {
2667                                    justification_expansion
2668                                } else {
2669                                    0.0
2670                                },
2671                            );
2672                            if let Some(match_em_width) = match_mono_em_width {
2673                                // Round to nearest monospace width
2674                                x_advance = ((x_advance / match_em_width).round()) * match_em_width;
2675                            }
2676                            if hinting == Hinting::Enabled {
2677                                x_advance = x_advance.round();
2678                            }
2679                            if self.rtl {
2680                                *x -= x_advance;
2681                            }
2682                            let y_advance = glyph_font_size * glyph.y_advance;
2683                            let mut layout_glyph = glyph.layout(
2684                                glyph_font_size,
2685                                glyph.metrics_opt.map(|x| x.line_height),
2686                                *x,
2687                                *y,
2688                                x_advance,
2689                                r.level,
2690                            );
2691                            // Fix ellipsis glyph indices: point both start and
2692                            // end to the elision boundary so that hit-detection
2693                            // places the cursor at the seam between visible and
2694                            // elided text instead of selecting invisible content.
2695                            if is_ellipsis {
2696                                if let Some((elided_start, elided_end)) = elided_byte_range {
2697                                    // Use the boundary closest to the visible
2698                                    // content that is adjacent to this ellipsis:
2699                                    //   Start:  …|visible  → boundary = elided_end
2700                                    //   End:    visible|…  → boundary = elided_start
2701                                    //   Middle: vis|…|vis  → boundary = elided_start
2702                                    let boundary = if elided_start == 0 {
2703                                        elided_end
2704                                    } else {
2705                                        elided_start
2706                                    };
2707                                    layout_glyph.start = boundary;
2708                                    layout_glyph.end = boundary;
2709                                }
2710                            }
2711                            glyphs.push(layout_glyph);
2712                            if !self.rtl {
2713                                *x += x_advance;
2714                            }
2715                            *y += y_advance;
2716                            *max_ascent = max_ascent.max(glyph_font_size * glyph.ascent);
2717                            *max_descent = max_descent.max(glyph_font_size * glyph.descent);
2718                        }
2719                    }
2720                }
2721            };
2722
2723            if self.rtl {
2724                for range in new_order.into_iter().rev() {
2725                    process_range(
2726                        range,
2727                        &mut x,
2728                        &mut y,
2729                        &mut glyphs,
2730                        &mut max_ascent,
2731                        &mut max_descent,
2732                    );
2733                }
2734            } else {
2735                /* LTR */
2736                for range in new_order {
2737                    process_range(
2738                        range,
2739                        &mut x,
2740                        &mut y,
2741                        &mut glyphs,
2742                        &mut max_ascent,
2743                        &mut max_descent,
2744                    );
2745                }
2746            }
2747
2748            let mut line_height_opt: Option<f32> = None;
2749            for glyph in &glyphs {
2750                if let Some(glyph_line_height) = glyph.line_height_opt {
2751                    line_height_opt = line_height_opt
2752                        .map_or(Some(glyph_line_height), |line_height| {
2753                            Some(line_height.max(glyph_line_height))
2754                        });
2755                }
2756            }
2757
2758            layout_lines.push(LayoutLine {
2759                w: if align != Align::Justified {
2760                    visual_line.w
2761                } else if self.rtl {
2762                    start_x - x
2763                } else {
2764                    x
2765                },
2766                max_ascent,
2767                max_descent,
2768                line_height_opt,
2769                glyphs,
2770            });
2771        }
2772
2773        // This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
2774        if layout_lines.is_empty() {
2775            layout_lines.push(LayoutLine {
2776                w: 0.0,
2777                max_ascent: 0.0,
2778                max_descent: 0.0,
2779                line_height_opt: self.metrics_opt.map(|x| x.line_height),
2780                glyphs: Vec::default(),
2781            });
2782        }
2783
2784        // Restore the buffer to the scratch set to prevent reallocations.
2785        scratch.visual_lines = visual_lines;
2786        scratch.visual_lines.append(&mut cached_visual_lines);
2787        scratch.cached_visual_lines = cached_visual_lines;
2788        scratch.glyph_sets = cached_glyph_sets;
2789    }
2790}