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