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, Font, FontSystem, Hinting, LayoutGlyph,
8    LayoutLine, Metrics, Wrap,
9};
10#[cfg(not(feature = "std"))]
11use alloc::format;
12#[cfg(not(feature = "std"))]
13use alloc::vec::Vec;
14
15use alloc::collections::VecDeque;
16use core::cmp::{max, min};
17use core::fmt;
18use core::mem;
19use core::ops::Range;
20
21#[cfg(not(feature = "std"))]
22use core_maths::CoreFloat;
23use fontdb::Style;
24use unicode_script::{Script, UnicodeScript};
25use unicode_segmentation::UnicodeSegmentation;
26
27/// The shaping strategy of some text.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum Shaping {
30    /// Basic shaping with no font fallback.
31    ///
32    /// This shaping strategy is very cheap, but it will not display complex
33    /// scripts properly nor try to find missing glyphs in your system fonts.
34    ///
35    /// You should use this strategy when you have complete control of the text
36    /// and the font you are displaying in your application.
37    #[cfg(feature = "swash")]
38    Basic,
39    /// Advanced text shaping and font fallback.
40    ///
41    /// You will need to enable this strategy if the text contains a complex
42    /// script, the font used needs it, and/or multiple fonts in your system
43    /// may be needed to display all of the glyphs.
44    Advanced,
45}
46
47impl Shaping {
48    fn run(
49        self,
50        glyphs: &mut Vec<ShapeGlyph>,
51        font_system: &mut FontSystem,
52        line: &str,
53        attrs_list: &AttrsList,
54        start_run: usize,
55        end_run: usize,
56        span_rtl: bool,
57    ) {
58        match self {
59            #[cfg(feature = "swash")]
60            Self::Basic => shape_skip(font_system, glyphs, line, attrs_list, start_run, end_run),
61            #[cfg(not(feature = "shape-run-cache"))]
62            Self::Advanced => shape_run(
63                glyphs,
64                font_system,
65                line,
66                attrs_list,
67                start_run,
68                end_run,
69                span_rtl,
70            ),
71            #[cfg(feature = "shape-run-cache")]
72            Self::Advanced => shape_run_cached(
73                glyphs,
74                font_system,
75                line,
76                attrs_list,
77                start_run,
78                end_run,
79                span_rtl,
80            ),
81        }
82    }
83}
84
85const NUM_SHAPE_PLANS: usize = 6;
86
87/// A set of buffers containing allocations for shaped text.
88#[derive(Default)]
89pub struct ShapeBuffer {
90    /// Cache for harfrust shape plans. Stores up to [`NUM_SHAPE_PLANS`] plans at once. Inserting a new one past that
91    /// will remove the one that was least recently added (not least recently used).
92    shape_plan_cache: VecDeque<(fontdb::ID, harfrust::ShapePlan)>,
93
94    /// Buffer for holding unicode text.
95    harfrust_buffer: Option<harfrust::UnicodeBuffer>,
96
97    /// Temporary buffers for scripts.
98    scripts: Vec<Script>,
99
100    /// Buffer for shape spans.
101    spans: Vec<ShapeSpan>,
102
103    /// Buffer for shape words.
104    words: Vec<ShapeWord>,
105
106    /// Buffers for visual lines.
107    visual_lines: Vec<VisualLine>,
108    cached_visual_lines: Vec<VisualLine>,
109
110    /// Buffer for sets of layout glyphs.
111    glyph_sets: Vec<Vec<LayoutGlyph>>,
112}
113
114impl fmt::Debug for ShapeBuffer {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        f.pad("ShapeBuffer { .. }")
117    }
118}
119
120fn shape_fallback(
121    scratch: &mut ShapeBuffer,
122    glyphs: &mut Vec<ShapeGlyph>,
123    font: &Font,
124    line: &str,
125    attrs_list: &AttrsList,
126    start_run: usize,
127    end_run: usize,
128    span_rtl: bool,
129) -> Vec<usize> {
130    let run = &line[start_run..end_run];
131
132    let font_scale = font.metrics().units_per_em as f32;
133    let ascent = font.metrics().ascent / font_scale;
134    let descent = -font.metrics().descent / font_scale;
135
136    let mut buffer = scratch.harfrust_buffer.take().unwrap_or_default();
137    buffer.set_direction(if span_rtl {
138        harfrust::Direction::RightToLeft
139    } else {
140        harfrust::Direction::LeftToRight
141    });
142    if run.contains('\t') {
143        // Push string to buffer, replacing tabs with spaces
144        //TODO: Find a way to do this with minimal allocating, calling
145        // UnicodeBuffer::push_str multiple times causes issues and
146        // UnicodeBuffer::add resizes the buffer with every character
147        buffer.push_str(&run.replace('\t', " "));
148    } else {
149        buffer.push_str(run);
150    }
151    buffer.guess_segment_properties();
152
153    let rtl = matches!(buffer.direction(), harfrust::Direction::RightToLeft);
154    assert_eq!(rtl, span_rtl);
155
156    let attrs = attrs_list.get_span(start_run);
157    let mut rb_font_features = Vec::new();
158
159    // Convert attrs::Feature to harfrust::Feature
160    for feature in &attrs.font_features.features {
161        rb_font_features.push(harfrust::Feature::new(
162            harfrust::Tag::new(feature.tag.as_bytes()),
163            feature.value,
164            0..usize::MAX,
165        ));
166    }
167
168    let language = buffer.language();
169    let key = harfrust::ShapePlanKey::new(Some(buffer.script()), buffer.direction())
170        .features(&rb_font_features)
171        .instance(Some(font.shaper_instance()))
172        .language(language.as_ref());
173
174    let shape_plan = match scratch
175        .shape_plan_cache
176        .iter()
177        .find(|(id, plan)| *id == font.id() && key.matches(plan))
178    {
179        Some((_font_id, plan)) => plan,
180        None => {
181            let plan = harfrust::ShapePlan::new(
182                font.shaper(),
183                buffer.direction(),
184                Some(buffer.script()),
185                buffer.language().as_ref(),
186                &rb_font_features,
187            );
188            if scratch.shape_plan_cache.len() >= NUM_SHAPE_PLANS {
189                scratch.shape_plan_cache.pop_front();
190            }
191            scratch.shape_plan_cache.push_back((font.id(), plan));
192            &scratch
193                .shape_plan_cache
194                .back()
195                .expect("we just pushed the shape plan")
196                .1
197        }
198    };
199
200    let glyph_buffer = font
201        .shaper()
202        .shape_with_plan(shape_plan, buffer, &rb_font_features);
203    let glyph_infos = glyph_buffer.glyph_infos();
204    let glyph_positions = glyph_buffer.glyph_positions();
205
206    let mut missing = Vec::new();
207    glyphs.reserve(glyph_infos.len());
208    let glyph_start = glyphs.len();
209    for (info, pos) in glyph_infos.iter().zip(glyph_positions.iter()) {
210        let start_glyph = start_run + info.cluster as usize;
211
212        if info.glyph_id == 0 {
213            missing.push(start_glyph);
214        }
215
216        let attrs = attrs_list.get_span(start_glyph);
217        let x_advance = pos.x_advance as f32 / font_scale
218            + attrs.letter_spacing_opt.map_or(0.0, |spacing| spacing.0);
219        let y_advance = pos.y_advance as f32 / font_scale;
220        let x_offset = pos.x_offset as f32 / font_scale;
221        let y_offset = pos.y_offset as f32 / font_scale;
222
223        glyphs.push(ShapeGlyph {
224            start: start_glyph,
225            end: end_run, // Set later
226            x_advance,
227            y_advance,
228            x_offset,
229            y_offset,
230            ascent,
231            descent,
232            font_monospace_em_width: font.monospace_em_width(),
233            font_id: font.id(),
234            font_weight: attrs.weight,
235            glyph_id: info.glyph_id.try_into().expect("failed to cast glyph ID"),
236            //TODO: color should not be related to shaping
237            color_opt: attrs.color_opt,
238            metadata: attrs.metadata,
239            cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs),
240            metrics_opt: attrs.metrics_opt.map(Into::into),
241        });
242    }
243
244    // Adjust end of glyphs
245    if rtl {
246        for i in glyph_start + 1..glyphs.len() {
247            let next_start = glyphs[i - 1].start;
248            let next_end = glyphs[i - 1].end;
249            let prev = &mut glyphs[i];
250            if prev.start == next_start {
251                prev.end = next_end;
252            } else {
253                prev.end = next_start;
254            }
255        }
256    } else {
257        for i in (glyph_start + 1..glyphs.len()).rev() {
258            let next_start = glyphs[i].start;
259            let next_end = glyphs[i].end;
260            let prev = &mut glyphs[i - 1];
261            if prev.start == next_start {
262                prev.end = next_end;
263            } else {
264                prev.end = next_start;
265            }
266        }
267    }
268
269    // Restore the buffer to save an allocation.
270    scratch.harfrust_buffer = Some(glyph_buffer.clear());
271
272    missing
273}
274
275fn shape_run(
276    glyphs: &mut Vec<ShapeGlyph>,
277    font_system: &mut FontSystem,
278    line: &str,
279    attrs_list: &AttrsList,
280    start_run: usize,
281    end_run: usize,
282    span_rtl: bool,
283) {
284    // Re-use the previous script buffer if possible.
285    let mut scripts = {
286        let mut scripts = mem::take(&mut font_system.shape_buffer.scripts);
287        scripts.clear();
288        scripts
289    };
290    for c in line[start_run..end_run].chars() {
291        match c.script() {
292            Script::Common | Script::Inherited | Script::Latin | Script::Unknown => (),
293            script => {
294                if !scripts.contains(&script) {
295                    scripts.push(script);
296                }
297            }
298        }
299    }
300
301    log::trace!("      Run {:?}: '{}'", &scripts, &line[start_run..end_run],);
302
303    let attrs = attrs_list.get_span(start_run);
304
305    let fonts = font_system.get_font_matches(&attrs);
306
307    let default_families = [&attrs.family];
308    let mut font_iter = FontFallbackIter::new(
309        font_system,
310        &fonts,
311        &default_families,
312        &scripts,
313        &line[start_run..end_run],
314        attrs.weight,
315    );
316
317    let font = font_iter.next().expect("no default font found");
318
319    let glyph_start = glyphs.len();
320    let mut missing = {
321        let scratch = font_iter.shape_caches();
322        shape_fallback(
323            scratch, glyphs, &font, line, attrs_list, start_run, end_run, span_rtl,
324        )
325    };
326
327    //TODO: improve performance!
328    while !missing.is_empty() {
329        let Some(font) = font_iter.next() else {
330            break;
331        };
332
333        log::trace!(
334            "Evaluating fallback with font '{}'",
335            font_iter.face_name(font.id())
336        );
337        let mut fb_glyphs = Vec::new();
338        let scratch = font_iter.shape_caches();
339        let fb_missing = shape_fallback(
340            scratch,
341            &mut fb_glyphs,
342            &font,
343            line,
344            attrs_list,
345            start_run,
346            end_run,
347            span_rtl,
348        );
349
350        // Insert all matching glyphs
351        let mut fb_i = 0;
352        while fb_i < fb_glyphs.len() {
353            let start = fb_glyphs[fb_i].start;
354            let end = fb_glyphs[fb_i].end;
355
356            // Skip clusters that are not missing, or where the fallback font is missing
357            if !missing.contains(&start) || fb_missing.contains(&start) {
358                fb_i += 1;
359                continue;
360            }
361
362            let mut missing_i = 0;
363            while missing_i < missing.len() {
364                if missing[missing_i] >= start && missing[missing_i] < end {
365                    // println!("No longer missing {}", missing[missing_i]);
366                    missing.remove(missing_i);
367                } else {
368                    missing_i += 1;
369                }
370            }
371
372            // Find prior glyphs
373            let mut i = glyph_start;
374            while i < glyphs.len() {
375                if glyphs[i].start >= start && glyphs[i].end <= end {
376                    break;
377                }
378                i += 1;
379            }
380
381            // Remove prior glyphs
382            while i < glyphs.len() {
383                if glyphs[i].start >= start && glyphs[i].end <= end {
384                    let _glyph = glyphs.remove(i);
385                    // log::trace!("Removed {},{} from {}", _glyph.start, _glyph.end, i);
386                } else {
387                    break;
388                }
389            }
390
391            while fb_i < fb_glyphs.len() {
392                if fb_glyphs[fb_i].start >= start && fb_glyphs[fb_i].end <= end {
393                    let fb_glyph = fb_glyphs.remove(fb_i);
394                    // log::trace!("Insert {},{} from font {} at {}", fb_glyph.start, fb_glyph.end, font_i, i);
395                    glyphs.insert(i, fb_glyph);
396                    i += 1;
397                } else {
398                    break;
399                }
400            }
401        }
402    }
403
404    // Debug missing font fallbacks
405    font_iter.check_missing(&line[start_run..end_run]);
406
407    /*
408    for glyph in glyphs.iter() {
409        log::trace!("'{}': {}, {}, {}, {}", &line[glyph.start..glyph.end], glyph.x_advance, glyph.y_advance, glyph.x_offset, glyph.y_offset);
410    }
411    */
412
413    // Restore the scripts buffer.
414    font_system.shape_buffer.scripts = scripts;
415}
416
417#[cfg(feature = "shape-run-cache")]
418fn shape_run_cached(
419    glyphs: &mut Vec<ShapeGlyph>,
420    font_system: &mut FontSystem,
421    line: &str,
422    attrs_list: &AttrsList,
423    start_run: usize,
424    end_run: usize,
425    span_rtl: bool,
426) {
427    use crate::{AttrsOwned, ShapeRunKey};
428
429    let run_range = start_run..end_run;
430    let mut key = ShapeRunKey {
431        text: line[run_range.clone()].to_string(),
432        default_attrs: AttrsOwned::new(&attrs_list.defaults()),
433        attrs_spans: Vec::new(),
434    };
435    for (attrs_range, attrs) in attrs_list.spans.overlapping(&run_range) {
436        if attrs == &key.default_attrs {
437            // Skip if attrs matches default attrs
438            continue;
439        }
440        let start = max(attrs_range.start, start_run).saturating_sub(start_run);
441        let end = min(attrs_range.end, end_run).saturating_sub(start_run);
442        if end > start {
443            let range = start..end;
444            key.attrs_spans.push((range, attrs.clone()));
445        }
446    }
447    if let Some(cache_glyphs) = font_system.shape_run_cache.get(&key) {
448        for mut glyph in cache_glyphs.iter().cloned() {
449            // Adjust glyph start and end to match run position
450            glyph.start += start_run;
451            glyph.end += start_run;
452            glyphs.push(glyph);
453        }
454        return;
455    }
456
457    // Fill in cache if not already set
458    let mut cache_glyphs = Vec::new();
459    shape_run(
460        &mut cache_glyphs,
461        font_system,
462        line,
463        attrs_list,
464        start_run,
465        end_run,
466        span_rtl,
467    );
468    glyphs.extend_from_slice(&cache_glyphs);
469    for glyph in cache_glyphs.iter_mut() {
470        // Adjust glyph start and end to remove run position
471        glyph.start -= start_run;
472        glyph.end -= start_run;
473    }
474    font_system.shape_run_cache.insert(key, cache_glyphs);
475}
476
477#[cfg(feature = "swash")]
478fn shape_skip(
479    font_system: &mut FontSystem,
480    glyphs: &mut Vec<ShapeGlyph>,
481    line: &str,
482    attrs_list: &AttrsList,
483    start_run: usize,
484    end_run: usize,
485) {
486    let attrs = attrs_list.get_span(start_run);
487    let fonts = font_system.get_font_matches(&attrs);
488
489    let default_families = [&attrs.family];
490    let mut font_iter = FontFallbackIter::new(
491        font_system,
492        &fonts,
493        &default_families,
494        &[],
495        "",
496        attrs.weight,
497    );
498
499    let font = font_iter.next().expect("no default font found");
500    let font_id = font.id();
501    let font_monospace_em_width = font.monospace_em_width();
502    let swash_font = font.as_swash();
503
504    let charmap = swash_font.charmap();
505    let metrics = swash_font.metrics(&[]);
506    let glyph_metrics = swash_font.glyph_metrics(&[]).scale(1.0);
507
508    let ascent = metrics.ascent / f32::from(metrics.units_per_em);
509    let descent = metrics.descent / f32::from(metrics.units_per_em);
510
511    glyphs.extend(
512        line[start_run..end_run]
513            .char_indices()
514            .map(|(chr_idx, codepoint)| {
515                let glyph_id = charmap.map(codepoint);
516                let x_advance = glyph_metrics.advance_width(glyph_id)
517                    + attrs.letter_spacing_opt.map_or(0.0, |spacing| spacing.0);
518                let attrs = attrs_list.get_span(start_run + chr_idx);
519
520                ShapeGlyph {
521                    start: chr_idx + start_run,
522                    end: chr_idx + start_run + codepoint.len_utf8(),
523                    x_advance,
524                    y_advance: 0.0,
525                    x_offset: 0.0,
526                    y_offset: 0.0,
527                    ascent,
528                    descent,
529                    font_monospace_em_width,
530                    font_id,
531                    font_weight: attrs.weight,
532                    glyph_id,
533                    color_opt: attrs.color_opt,
534                    metadata: attrs.metadata,
535                    cache_key_flags: override_fake_italic(
536                        attrs.cache_key_flags,
537                        font.as_ref(),
538                        &attrs,
539                    ),
540                    metrics_opt: attrs.metrics_opt.map(Into::into),
541                }
542            }),
543    );
544}
545
546fn override_fake_italic(
547    cache_key_flags: CacheKeyFlags,
548    font: &Font,
549    attrs: &Attrs,
550) -> CacheKeyFlags {
551    if !font.italic_or_oblique && (attrs.style == Style::Italic || attrs.style == Style::Oblique) {
552        cache_key_flags | CacheKeyFlags::FAKE_ITALIC
553    } else {
554        cache_key_flags
555    }
556}
557
558/// A shaped glyph
559#[derive(Clone, Debug)]
560pub struct ShapeGlyph {
561    pub start: usize,
562    pub end: usize,
563    pub x_advance: f32,
564    pub y_advance: f32,
565    pub x_offset: f32,
566    pub y_offset: f32,
567    pub ascent: f32,
568    pub descent: f32,
569    pub font_monospace_em_width: Option<f32>,
570    pub font_id: fontdb::ID,
571    pub font_weight: fontdb::Weight,
572    pub glyph_id: u16,
573    pub color_opt: Option<Color>,
574    pub metadata: usize,
575    pub cache_key_flags: CacheKeyFlags,
576    pub metrics_opt: Option<Metrics>,
577}
578
579impl ShapeGlyph {
580    const fn layout(
581        &self,
582        font_size: f32,
583        line_height_opt: Option<f32>,
584        x: f32,
585        y: f32,
586        w: f32,
587        level: unicode_bidi::Level,
588    ) -> LayoutGlyph {
589        LayoutGlyph {
590            start: self.start,
591            end: self.end,
592            font_size,
593            line_height_opt,
594            font_id: self.font_id,
595            font_weight: self.font_weight,
596            glyph_id: self.glyph_id,
597            x,
598            y,
599            w,
600            level,
601            x_offset: self.x_offset,
602            y_offset: self.y_offset,
603            color_opt: self.color_opt,
604            metadata: self.metadata,
605            cache_key_flags: self.cache_key_flags,
606        }
607    }
608
609    /// Get the width of the [`ShapeGlyph`] in pixels, either using the provided font size
610    /// or the [`ShapeGlyph::metrics_opt`] override.
611    pub fn width(&self, font_size: f32) -> f32 {
612        self.metrics_opt.map_or(font_size, |x| x.font_size) * self.x_advance
613    }
614}
615
616/// A shaped word (for word wrapping)
617#[derive(Clone, Debug)]
618pub struct ShapeWord {
619    pub blank: bool,
620    pub glyphs: Vec<ShapeGlyph>,
621}
622
623impl ShapeWord {
624    /// Creates an empty word.
625    ///
626    /// The returned word is in an invalid state until [`Self::build_in_buffer`] is called.
627    pub(crate) fn empty() -> Self {
628        Self {
629            blank: true,
630            glyphs: Vec::default(),
631        }
632    }
633
634    /// Shape a word into a set of glyphs.
635    #[allow(clippy::too_many_arguments)]
636    pub fn new(
637        font_system: &mut FontSystem,
638        line: &str,
639        attrs_list: &AttrsList,
640        word_range: Range<usize>,
641        level: unicode_bidi::Level,
642        blank: bool,
643        shaping: Shaping,
644    ) -> Self {
645        let mut empty = Self::empty();
646        empty.build(
647            font_system,
648            line,
649            attrs_list,
650            word_range,
651            level,
652            blank,
653            shaping,
654        );
655        empty
656    }
657
658    /// See [`Self::new`].
659    ///
660    /// Reuses as much of the pre-existing internal allocations as possible.
661    #[allow(clippy::too_many_arguments)]
662    pub fn build(
663        &mut self,
664        font_system: &mut FontSystem,
665        line: &str,
666        attrs_list: &AttrsList,
667        word_range: Range<usize>,
668        level: unicode_bidi::Level,
669        blank: bool,
670        shaping: Shaping,
671    ) {
672        let word = &line[word_range.clone()];
673
674        log::trace!(
675            "      Word{}: '{}'",
676            if blank { " BLANK" } else { "" },
677            word
678        );
679
680        let mut glyphs = mem::take(&mut self.glyphs);
681        glyphs.clear();
682
683        let span_rtl = level.is_rtl();
684
685        // Fast path optimization: For simple ASCII words, skip expensive grapheme iteration
686        let is_simple_ascii =
687            word.is_ascii() && !word.chars().any(|c| c.is_ascii_control() && c != '\t');
688
689        if is_simple_ascii && !word.is_empty() && {
690            let attrs_start = attrs_list.get_span(word_range.start);
691            attrs_list.spans_iter().all(|(other_range, other_attrs)| {
692                word_range.end <= other_range.start
693                    || other_range.end <= word_range.start
694                    || attrs_start.compatible(&other_attrs.as_attrs())
695            })
696        } {
697            shaping.run(
698                &mut glyphs,
699                font_system,
700                line,
701                attrs_list,
702                word_range.start,
703                word_range.end,
704                span_rtl,
705            );
706        } else {
707            // Complex text path: Full grapheme iteration and attribute processing
708            let mut start_run = word_range.start;
709            let mut attrs = attrs_list.defaults();
710            for (egc_i, _egc) in word.grapheme_indices(true) {
711                let start_egc = word_range.start + egc_i;
712                let attrs_egc = attrs_list.get_span(start_egc);
713                if !attrs.compatible(&attrs_egc) {
714                    shaping.run(
715                        &mut glyphs,
716                        font_system,
717                        line,
718                        attrs_list,
719                        start_run,
720                        start_egc,
721                        span_rtl,
722                    );
723
724                    start_run = start_egc;
725                    attrs = attrs_egc;
726                }
727            }
728            if start_run < word_range.end {
729                shaping.run(
730                    &mut glyphs,
731                    font_system,
732                    line,
733                    attrs_list,
734                    start_run,
735                    word_range.end,
736                    span_rtl,
737                );
738            }
739        }
740
741        self.blank = blank;
742        self.glyphs = glyphs;
743    }
744
745    /// Get the width of the [`ShapeWord`] in pixels, using the [`ShapeGlyph::width`] function.
746    pub fn width(&self, font_size: f32) -> f32 {
747        let mut width = 0.0;
748        for glyph in &self.glyphs {
749            width += glyph.width(font_size);
750        }
751        width
752    }
753}
754
755/// A shaped span (for bidirectional processing)
756#[derive(Clone, Debug)]
757pub struct ShapeSpan {
758    pub level: unicode_bidi::Level,
759    pub words: Vec<ShapeWord>,
760}
761
762impl ShapeSpan {
763    /// Creates an empty span.
764    ///
765    /// The returned span is in an invalid state until [`Self::build_in_buffer`] is called.
766    pub(crate) fn empty() -> Self {
767        Self {
768            level: unicode_bidi::Level::ltr(),
769            words: Vec::default(),
770        }
771    }
772
773    /// Shape a span into a set of words.
774    pub fn new(
775        font_system: &mut FontSystem,
776        line: &str,
777        attrs_list: &AttrsList,
778        span_range: Range<usize>,
779        line_rtl: bool,
780        level: unicode_bidi::Level,
781        shaping: Shaping,
782    ) -> Self {
783        let mut empty = Self::empty();
784        empty.build(
785            font_system,
786            line,
787            attrs_list,
788            span_range,
789            line_rtl,
790            level,
791            shaping,
792        );
793        empty
794    }
795
796    /// See [`Self::new`].
797    ///
798    /// Reuses as much of the pre-existing internal allocations as possible.
799    pub fn build(
800        &mut self,
801        font_system: &mut FontSystem,
802        line: &str,
803        attrs_list: &AttrsList,
804        span_range: Range<usize>,
805        line_rtl: bool,
806        level: unicode_bidi::Level,
807        shaping: Shaping,
808    ) {
809        let span = &line[span_range.start..span_range.end];
810
811        log::trace!(
812            "  Span {}: '{}'",
813            if level.is_rtl() { "RTL" } else { "LTR" },
814            span
815        );
816
817        let mut words = mem::take(&mut self.words);
818
819        // Cache the shape words in reverse order so they can be popped for reuse in the same order.
820        let mut cached_words = mem::take(&mut font_system.shape_buffer.words);
821        cached_words.clear();
822        if line_rtl != level.is_rtl() {
823            // Un-reverse previous words so the internal glyph counts match accurately when rewriting memory.
824            cached_words.append(&mut words);
825        } else {
826            cached_words.extend(words.drain(..).rev());
827        }
828
829        let mut start_word = 0;
830        for (end_lb, _) in unicode_linebreak::linebreaks(span) {
831            // Check if this break opportunity splits a likely ligature (e.g. "|>" or "!=")
832            if end_lb > 0 && end_lb < span.len() {
833                let start_idx = span_range.start;
834                let pre_char = span[..end_lb].chars().last();
835                let post_char = span[end_lb..].chars().next();
836
837                if let (Some(c1), Some(c2)) = (pre_char, post_char) {
838                    // Only probe if both are punctuation (optimization for coding ligatures)
839                    if c1.is_ascii_punctuation() && c2.is_ascii_punctuation() {
840                        let probe_text = format!("{}{}", c1, c2);
841                        let attrs = attrs_list.get_span(start_idx + end_lb);
842                        let fonts = font_system.get_font_matches(&attrs);
843                        let default_families = [&attrs.family];
844
845                        let mut font_iter = FontFallbackIter::new(
846                            font_system,
847                            &fonts,
848                            &default_families,
849                            &[],
850                            &probe_text,
851                            attrs.weight,
852                        );
853
854                        if let Some(font) = font_iter.next() {
855                            let mut glyphs = Vec::new();
856                            let scratch = font_iter.shape_caches();
857                            shape_fallback(
858                                scratch,
859                                &mut glyphs,
860                                &font,
861                                &probe_text,
862                                attrs_list,
863                                0,
864                                probe_text.len(),
865                                false,
866                            );
867
868                            // 1. If we have fewer glyphs than chars, it's definitely a ligature (e.g. -> becoming 1 arrow).
869                            if glyphs.len() < probe_text.chars().count() {
870                                continue;
871                            }
872
873                            // 2. If we have the same number of glyphs, they might be contextual alternates (e.g. |> becoming 2 special glyphs).
874                            // Check if the glyphs match the standard "cmap" (character to glyph) mapping.
875                            // If they differ, the shaper substituted them, so we should keep them together.
876                            #[cfg(feature = "swash")]
877                            if glyphs.len() == probe_text.chars().count() {
878                                let charmap = font.as_swash().charmap();
879                                let mut is_modified = false;
880                                for (i, c) in probe_text.chars().enumerate() {
881                                    let std_id = charmap.map(c);
882                                    if glyphs[i].glyph_id != std_id {
883                                        is_modified = true;
884                                        break;
885                                    }
886                                }
887
888                                if is_modified {
889                                    // Ligature/Contextual Alternate detected!
890                                    continue;
891                                }
892                            }
893                        }
894                    }
895                }
896            }
897
898            let mut start_lb = end_lb;
899            for (i, c) in span[start_word..end_lb].char_indices().rev() {
900                // TODO: Not all whitespace characters are linebreakable, e.g. 00A0 (No-break
901                // space)
902                // https://www.unicode.org/reports/tr14/#GL
903                // https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt
904                if c.is_whitespace() {
905                    start_lb = start_word + i;
906                } else {
907                    break;
908                }
909            }
910            if start_word < start_lb {
911                let mut word = cached_words.pop().unwrap_or_else(ShapeWord::empty);
912                word.build(
913                    font_system,
914                    line,
915                    attrs_list,
916                    (span_range.start + start_word)..(span_range.start + start_lb),
917                    level,
918                    false,
919                    shaping,
920                );
921                words.push(word);
922            }
923            if start_lb < end_lb {
924                for (i, c) in span[start_lb..end_lb].char_indices() {
925                    // assert!(c.is_whitespace());
926                    let mut word = cached_words.pop().unwrap_or_else(ShapeWord::empty);
927                    word.build(
928                        font_system,
929                        line,
930                        attrs_list,
931                        (span_range.start + start_lb + i)
932                            ..(span_range.start + start_lb + i + c.len_utf8()),
933                        level,
934                        true,
935                        shaping,
936                    );
937                    words.push(word);
938                }
939            }
940            start_word = end_lb;
941        }
942
943        // Reverse glyphs in RTL lines
944        if line_rtl {
945            for word in &mut words {
946                word.glyphs.reverse();
947            }
948        }
949
950        // Reverse words in spans that do not match line direction
951        if line_rtl != level.is_rtl() {
952            words.reverse();
953        }
954
955        self.level = level;
956        self.words = words;
957
958        // Cache buffer for future reuse.
959        font_system.shape_buffer.words = cached_words;
960    }
961}
962
963/// A shaped line (or paragraph)
964#[derive(Clone, Debug)]
965pub struct ShapeLine {
966    pub rtl: bool,
967    pub spans: Vec<ShapeSpan>,
968    pub metrics_opt: Option<Metrics>,
969}
970
971// Visual Line Ranges: (span_index, (first_word_index, first_glyph_index), (last_word_index, last_glyph_index))
972type VlRange = (usize, (usize, usize), (usize, usize));
973
974#[derive(Default)]
975struct VisualLine {
976    ranges: Vec<VlRange>,
977    spaces: u32,
978    w: f32,
979}
980
981impl VisualLine {
982    fn clear(&mut self) {
983        self.ranges.clear();
984        self.spaces = 0;
985        self.w = 0.;
986    }
987}
988
989impl ShapeLine {
990    /// Creates an empty line.
991    ///
992    /// The returned line is in an invalid state until [`Self::build_in_buffer`] is called.
993    pub(crate) fn empty() -> Self {
994        Self {
995            rtl: false,
996            spans: Vec::default(),
997            metrics_opt: None,
998        }
999    }
1000
1001    /// Shape a line into a set of spans, using a scratch buffer. If [`unicode_bidi::BidiInfo`]
1002    /// detects multiple paragraphs, they will be joined.
1003    ///
1004    /// # Panics
1005    ///
1006    /// Will panic if `line` contains multiple paragraphs that do not have matching direction
1007    pub fn new(
1008        font_system: &mut FontSystem,
1009        line: &str,
1010        attrs_list: &AttrsList,
1011        shaping: Shaping,
1012        tab_width: u16,
1013    ) -> Self {
1014        let mut empty = Self::empty();
1015        empty.build(font_system, line, attrs_list, shaping, tab_width);
1016        empty
1017    }
1018
1019    /// See [`Self::new`].
1020    ///
1021    /// Reuses as much of the pre-existing internal allocations as possible.
1022    ///
1023    /// # Panics
1024    ///
1025    /// Will panic if `line` contains multiple paragraphs that do not have matching direction
1026    pub fn build(
1027        &mut self,
1028        font_system: &mut FontSystem,
1029        line: &str,
1030        attrs_list: &AttrsList,
1031        shaping: Shaping,
1032        tab_width: u16,
1033    ) {
1034        let mut spans = mem::take(&mut self.spans);
1035
1036        // Cache the shape spans in reverse order so they can be popped for reuse in the same order.
1037        let mut cached_spans = mem::take(&mut font_system.shape_buffer.spans);
1038        cached_spans.clear();
1039        cached_spans.extend(spans.drain(..).rev());
1040
1041        let bidi = unicode_bidi::BidiInfo::new(line, None);
1042        let rtl = if bidi.paragraphs.is_empty() {
1043            false
1044        } else {
1045            bidi.paragraphs[0].level.is_rtl()
1046        };
1047
1048        log::trace!("Line {}: '{}'", if rtl { "RTL" } else { "LTR" }, line);
1049
1050        for para_info in &bidi.paragraphs {
1051            let line_rtl = para_info.level.is_rtl();
1052            assert_eq!(line_rtl, rtl);
1053
1054            let line_range = para_info.range.clone();
1055            let levels = Self::adjust_levels(&unicode_bidi::Paragraph::new(&bidi, para_info));
1056
1057            // Find consecutive level runs. We use this to create Spans.
1058            // Each span is a set of characters with equal levels.
1059            let mut start = line_range.start;
1060            let mut run_level = levels[start];
1061            spans.reserve(line_range.end - start + 1);
1062
1063            for (i, &new_level) in levels
1064                .iter()
1065                .enumerate()
1066                .take(line_range.end)
1067                .skip(start + 1)
1068            {
1069                if new_level != run_level {
1070                    // End of the previous run, start of a new one.
1071                    let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
1072                    span.build(
1073                        font_system,
1074                        line,
1075                        attrs_list,
1076                        start..i,
1077                        line_rtl,
1078                        run_level,
1079                        shaping,
1080                    );
1081                    spans.push(span);
1082                    start = i;
1083                    run_level = new_level;
1084                }
1085            }
1086            let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
1087            span.build(
1088                font_system,
1089                line,
1090                attrs_list,
1091                start..line_range.end,
1092                line_rtl,
1093                run_level,
1094                shaping,
1095            );
1096            spans.push(span);
1097        }
1098
1099        // Adjust for tabs
1100        let mut x = 0.0;
1101        for span in &mut spans {
1102            for word in &mut span.words {
1103                for glyph in &mut word.glyphs {
1104                    if line.get(glyph.start..glyph.end) == Some("\t") {
1105                        // Tabs are shaped as spaces, so they will always have the x_advance of a space.
1106                        let tab_x_advance = f32::from(tab_width) * glyph.x_advance;
1107                        let tab_stop = (math::floorf(x / tab_x_advance) + 1.0) * tab_x_advance;
1108                        glyph.x_advance = tab_stop - x;
1109                    }
1110                    x += glyph.x_advance;
1111                }
1112            }
1113        }
1114
1115        self.rtl = rtl;
1116        self.spans = spans;
1117        self.metrics_opt = attrs_list.defaults().metrics_opt.map(Into::into);
1118
1119        // Return the buffer for later reuse.
1120        font_system.shape_buffer.spans = cached_spans;
1121    }
1122
1123    // A modified version of first part of unicode_bidi::bidi_info::visual_run
1124    fn adjust_levels(para: &unicode_bidi::Paragraph) -> Vec<unicode_bidi::Level> {
1125        use unicode_bidi::BidiClass::{B, BN, FSI, LRE, LRI, LRO, PDF, PDI, RLE, RLI, RLO, S, WS};
1126        let text = para.info.text;
1127        let levels = &para.info.levels;
1128        let original_classes = &para.info.original_classes;
1129
1130        let mut levels = levels.clone();
1131        let line_classes = &original_classes[..];
1132        let line_levels = &mut levels[..];
1133
1134        // Reset some whitespace chars to paragraph level.
1135        // <http://www.unicode.org/reports/tr9/#L1>
1136        let mut reset_from: Option<usize> = Some(0);
1137        let mut reset_to: Option<usize> = None;
1138        for (i, c) in text.char_indices() {
1139            match line_classes[i] {
1140                // Ignored by X9
1141                RLE | LRE | RLO | LRO | PDF | BN => {}
1142                // Segment separator, Paragraph separator
1143                B | S => {
1144                    assert_eq!(reset_to, None);
1145                    reset_to = Some(i + c.len_utf8());
1146                    if reset_from.is_none() {
1147                        reset_from = Some(i);
1148                    }
1149                }
1150                // Whitespace, isolate formatting
1151                WS | FSI | LRI | RLI | PDI => {
1152                    if reset_from.is_none() {
1153                        reset_from = Some(i);
1154                    }
1155                }
1156                _ => {
1157                    reset_from = None;
1158                }
1159            }
1160            if let (Some(from), Some(to)) = (reset_from, reset_to) {
1161                for level in &mut line_levels[from..to] {
1162                    *level = para.para.level;
1163                }
1164                reset_from = None;
1165                reset_to = None;
1166            }
1167        }
1168        if let Some(from) = reset_from {
1169            for level in &mut line_levels[from..] {
1170                *level = para.para.level;
1171            }
1172        }
1173        levels
1174    }
1175
1176    // A modified version of second part of unicode_bidi::bidi_info::visual run
1177    fn reorder(&self, line_range: &[VlRange]) -> Vec<Range<usize>> {
1178        let line: Vec<unicode_bidi::Level> = line_range
1179            .iter()
1180            .map(|(span_index, _, _)| self.spans[*span_index].level)
1181            .collect();
1182        // Find consecutive level runs.
1183        let mut runs = Vec::new();
1184        let mut start = 0;
1185        let mut run_level = line[start];
1186        let mut min_level = run_level;
1187        let mut max_level = run_level;
1188
1189        for (i, &new_level) in line.iter().enumerate().skip(start + 1) {
1190            if new_level != run_level {
1191                // End of the previous run, start of a new one.
1192                runs.push(start..i);
1193                start = i;
1194                run_level = new_level;
1195                min_level = min(run_level, min_level);
1196                max_level = max(run_level, max_level);
1197            }
1198        }
1199        runs.push(start..line.len());
1200
1201        let run_count = runs.len();
1202
1203        // Re-order the odd runs.
1204        // <http://www.unicode.org/reports/tr9/#L2>
1205
1206        // Stop at the lowest *odd* level.
1207        min_level = min_level.new_lowest_ge_rtl().expect("Level error");
1208
1209        while max_level >= min_level {
1210            // Look for the start of a sequence of consecutive runs of max_level or higher.
1211            let mut seq_start = 0;
1212            while seq_start < run_count {
1213                if line[runs[seq_start].start] < max_level {
1214                    seq_start += 1;
1215                    continue;
1216                }
1217
1218                // Found the start of a sequence. Now find the end.
1219                let mut seq_end = seq_start + 1;
1220                while seq_end < run_count {
1221                    if line[runs[seq_end].start] < max_level {
1222                        break;
1223                    }
1224                    seq_end += 1;
1225                }
1226
1227                // Reverse the runs within this sequence.
1228                runs[seq_start..seq_end].reverse();
1229
1230                seq_start = seq_end;
1231            }
1232            max_level
1233                .lower(1)
1234                .expect("Lowering embedding level below zero");
1235        }
1236
1237        runs
1238    }
1239
1240    pub fn layout(
1241        &self,
1242        font_size: f32,
1243        width_opt: Option<f32>,
1244        wrap: Wrap,
1245        align: Option<Align>,
1246        match_mono_width: Option<f32>,
1247        hinting: Hinting,
1248    ) -> Vec<LayoutLine> {
1249        let mut lines = Vec::with_capacity(1);
1250        self.layout_to_buffer(
1251            &mut ShapeBuffer::default(),
1252            font_size,
1253            width_opt,
1254            wrap,
1255            align,
1256            &mut lines,
1257            match_mono_width,
1258            hinting,
1259        );
1260        lines
1261    }
1262
1263    pub fn layout_to_buffer(
1264        &self,
1265        scratch: &mut ShapeBuffer,
1266        font_size: f32,
1267        width_opt: Option<f32>,
1268        wrap: Wrap,
1269        align: Option<Align>,
1270        layout_lines: &mut Vec<LayoutLine>,
1271        match_mono_width: Option<f32>,
1272        hinting: Hinting,
1273    ) {
1274        fn add_to_visual_line(
1275            vl: &mut VisualLine,
1276            span_index: usize,
1277            start: (usize, usize),
1278            end: (usize, usize),
1279            width: f32,
1280            number_of_blanks: u32,
1281        ) {
1282            if end == start {
1283                return;
1284            }
1285
1286            vl.ranges.push((span_index, start, end));
1287            vl.w += width;
1288            vl.spaces += number_of_blanks;
1289        }
1290
1291        // For each visual line a list of  (span index,  and range of words in that span)
1292        // Note that a BiDi visual line could have multiple spans or parts of them
1293        // let mut vl_range_of_spans = Vec::with_capacity(1);
1294        let mut visual_lines = mem::take(&mut scratch.visual_lines);
1295        let mut cached_visual_lines = mem::take(&mut scratch.cached_visual_lines);
1296        cached_visual_lines.clear();
1297        cached_visual_lines.extend(visual_lines.drain(..).map(|mut l| {
1298            l.clear();
1299            l
1300        }));
1301
1302        // Cache glyph sets in reverse order so they will ideally be reused in exactly the same lines.
1303        let mut cached_glyph_sets = mem::take(&mut scratch.glyph_sets);
1304        cached_glyph_sets.clear();
1305        cached_glyph_sets.extend(layout_lines.drain(..).rev().map(|mut v| {
1306            v.glyphs.clear();
1307            v.glyphs
1308        }));
1309
1310        // This would keep the maximum number of spans that would fit on a visual line
1311        // If one span is too large, this variable will hold the range of words inside that span
1312        // that fits on a line.
1313        // let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
1314        let mut current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1315
1316        if wrap == Wrap::None {
1317            for (span_index, span) in self.spans.iter().enumerate() {
1318                let mut word_range_width = 0.;
1319                let mut number_of_blanks: u32 = 0;
1320                for word in &span.words {
1321                    let word_width = word.width(font_size);
1322                    word_range_width += word_width;
1323                    if word.blank {
1324                        number_of_blanks += 1;
1325                    }
1326                }
1327                add_to_visual_line(
1328                    &mut current_visual_line,
1329                    span_index,
1330                    (0, 0),
1331                    (span.words.len(), 0),
1332                    word_range_width,
1333                    number_of_blanks,
1334                );
1335            }
1336        } else {
1337            for (span_index, span) in self.spans.iter().enumerate() {
1338                let mut word_range_width = 0.;
1339                let mut width_before_last_blank = 0.;
1340                let mut number_of_blanks: u32 = 0;
1341
1342                // Create the word ranges that fits in a visual line
1343                if self.rtl != span.level.is_rtl() {
1344                    // incongruent directions
1345                    let mut fitting_start = (span.words.len(), 0);
1346                    for (i, word) in span.words.iter().enumerate().rev() {
1347                        let word_width = word.width(font_size);
1348
1349                        // Addition in the same order used to compute the final width, so that
1350                        // relayouts with that width as the `line_width` will produce the same
1351                        // wrapping results.
1352                        if current_visual_line.w + (word_range_width + word_width)
1353                            <= width_opt.unwrap_or(f32::INFINITY)
1354                            // Include one blank word over the width limit since it won't be
1355                            // counted in the final width
1356                            || (word.blank
1357                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
1358                        {
1359                            // fits
1360                            if word.blank {
1361                                number_of_blanks += 1;
1362                                width_before_last_blank = word_range_width;
1363                            }
1364                            word_range_width += word_width;
1365                        } else if wrap == Wrap::Glyph
1366                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
1367                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
1368                        {
1369                            // Commit the current line so that the word starts on the next line.
1370                            if word_range_width > 0.
1371                                && wrap == Wrap::WordOrGlyph
1372                                && word_width > width_opt.unwrap_or(f32::INFINITY)
1373                            {
1374                                add_to_visual_line(
1375                                    &mut current_visual_line,
1376                                    span_index,
1377                                    (i + 1, 0),
1378                                    fitting_start,
1379                                    word_range_width,
1380                                    number_of_blanks,
1381                                );
1382
1383                                visual_lines.push(current_visual_line);
1384                                current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1385
1386                                number_of_blanks = 0;
1387                                word_range_width = 0.;
1388
1389                                fitting_start = (i, 0);
1390                            }
1391
1392                            for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
1393                                let glyph_width = glyph.width(font_size);
1394                                if current_visual_line.w + (word_range_width + glyph_width)
1395                                    <= width_opt.unwrap_or(f32::INFINITY)
1396                                {
1397                                    word_range_width += glyph_width;
1398                                } else {
1399                                    add_to_visual_line(
1400                                        &mut current_visual_line,
1401                                        span_index,
1402                                        (i, glyph_i + 1),
1403                                        fitting_start,
1404                                        word_range_width,
1405                                        number_of_blanks,
1406                                    );
1407                                    visual_lines.push(current_visual_line);
1408                                    current_visual_line =
1409                                        cached_visual_lines.pop().unwrap_or_default();
1410
1411                                    number_of_blanks = 0;
1412                                    word_range_width = glyph_width;
1413                                    fitting_start = (i, glyph_i + 1);
1414                                }
1415                            }
1416                        } else {
1417                            // Wrap::Word, Wrap::WordOrGlyph
1418
1419                            // If we had a previous range, commit that line before the next word.
1420                            if word_range_width > 0. {
1421                                // Current word causing a wrap is not whitespace, so we ignore the
1422                                // previous word if it's a whitespace
1423                                let trailing_blank = span
1424                                    .words
1425                                    .get(i + 1)
1426                                    .is_some_and(|previous_word| previous_word.blank);
1427
1428                                if trailing_blank {
1429                                    number_of_blanks = number_of_blanks.saturating_sub(1);
1430                                    add_to_visual_line(
1431                                        &mut current_visual_line,
1432                                        span_index,
1433                                        (i + 2, 0),
1434                                        fitting_start,
1435                                        width_before_last_blank,
1436                                        number_of_blanks,
1437                                    );
1438                                } else {
1439                                    add_to_visual_line(
1440                                        &mut current_visual_line,
1441                                        span_index,
1442                                        (i + 1, 0),
1443                                        fitting_start,
1444                                        word_range_width,
1445                                        number_of_blanks,
1446                                    );
1447                                }
1448
1449                                visual_lines.push(current_visual_line);
1450                                current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1451                                number_of_blanks = 0;
1452                            }
1453
1454                            if word.blank {
1455                                word_range_width = 0.;
1456                                fitting_start = (i, 0);
1457                            } else {
1458                                word_range_width = word_width;
1459                                fitting_start = (i + 1, 0);
1460                            }
1461                        }
1462                    }
1463                    add_to_visual_line(
1464                        &mut current_visual_line,
1465                        span_index,
1466                        (0, 0),
1467                        fitting_start,
1468                        word_range_width,
1469                        number_of_blanks,
1470                    );
1471                } else {
1472                    // congruent direction
1473                    let mut fitting_start = (0, 0);
1474                    for (i, word) in span.words.iter().enumerate() {
1475                        let word_width = word.width(font_size);
1476                        if current_visual_line.w + (word_range_width + word_width)
1477                            <= width_opt.unwrap_or(f32::INFINITY)
1478                            // Include one blank word over the width limit since it won't be
1479                            // counted in the final width.
1480                            || (word.blank
1481                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
1482                        {
1483                            // fits
1484                            if word.blank {
1485                                number_of_blanks += 1;
1486                                width_before_last_blank = word_range_width;
1487                            }
1488                            word_range_width += word_width;
1489                        } else if wrap == Wrap::Glyph
1490                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
1491                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
1492                        {
1493                            // Commit the current line so that the word starts on the next line.
1494                            if word_range_width > 0.
1495                                && wrap == Wrap::WordOrGlyph
1496                                && word_width > width_opt.unwrap_or(f32::INFINITY)
1497                            {
1498                                add_to_visual_line(
1499                                    &mut current_visual_line,
1500                                    span_index,
1501                                    fitting_start,
1502                                    (i, 0),
1503                                    word_range_width,
1504                                    number_of_blanks,
1505                                );
1506
1507                                visual_lines.push(current_visual_line);
1508                                current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1509
1510                                number_of_blanks = 0;
1511                                word_range_width = 0.;
1512
1513                                fitting_start = (i, 0);
1514                            }
1515
1516                            for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
1517                                let glyph_width = glyph.width(font_size);
1518                                if current_visual_line.w + (word_range_width + glyph_width)
1519                                    <= width_opt.unwrap_or(f32::INFINITY)
1520                                {
1521                                    word_range_width += glyph_width;
1522                                } else {
1523                                    add_to_visual_line(
1524                                        &mut current_visual_line,
1525                                        span_index,
1526                                        fitting_start,
1527                                        (i, glyph_i),
1528                                        word_range_width,
1529                                        number_of_blanks,
1530                                    );
1531                                    visual_lines.push(current_visual_line);
1532                                    current_visual_line =
1533                                        cached_visual_lines.pop().unwrap_or_default();
1534
1535                                    number_of_blanks = 0;
1536                                    word_range_width = glyph_width;
1537                                    fitting_start = (i, glyph_i);
1538                                }
1539                            }
1540                        } else {
1541                            // Wrap::Word, Wrap::WordOrGlyph
1542
1543                            // If we had a previous range, commit that line before the next word.
1544                            if word_range_width > 0. {
1545                                // Current word causing a wrap is not whitespace, so we ignore the
1546                                // previous word if it's a whitespace.
1547                                let trailing_blank = i > 0 && span.words[i - 1].blank;
1548
1549                                if trailing_blank {
1550                                    number_of_blanks = number_of_blanks.saturating_sub(1);
1551                                    add_to_visual_line(
1552                                        &mut current_visual_line,
1553                                        span_index,
1554                                        fitting_start,
1555                                        (i - 1, 0),
1556                                        width_before_last_blank,
1557                                        number_of_blanks,
1558                                    );
1559                                } else {
1560                                    add_to_visual_line(
1561                                        &mut current_visual_line,
1562                                        span_index,
1563                                        fitting_start,
1564                                        (i, 0),
1565                                        word_range_width,
1566                                        number_of_blanks,
1567                                    );
1568                                }
1569
1570                                visual_lines.push(current_visual_line);
1571                                current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1572                                number_of_blanks = 0;
1573                            }
1574
1575                            if word.blank {
1576                                word_range_width = 0.;
1577                                fitting_start = (i + 1, 0);
1578                            } else {
1579                                word_range_width = word_width;
1580                                fitting_start = (i, 0);
1581                            }
1582                        }
1583                    }
1584                    add_to_visual_line(
1585                        &mut current_visual_line,
1586                        span_index,
1587                        fitting_start,
1588                        (span.words.len(), 0),
1589                        word_range_width,
1590                        number_of_blanks,
1591                    );
1592                }
1593            }
1594        }
1595
1596        if current_visual_line.ranges.is_empty() {
1597            current_visual_line.clear();
1598            cached_visual_lines.push(current_visual_line);
1599        } else {
1600            visual_lines.push(current_visual_line);
1601        }
1602
1603        // Create the LayoutLines using the ranges inside visual lines
1604        let align = align.unwrap_or(if self.rtl { Align::Right } else { Align::Left });
1605
1606        let line_width = width_opt.map_or_else(
1607            || {
1608                let mut width: f32 = 0.0;
1609                for visual_line in &visual_lines {
1610                    width = width.max(visual_line.w);
1611                }
1612                width
1613            },
1614            |width| width,
1615        );
1616
1617        let start_x = if self.rtl { line_width } else { 0.0 };
1618
1619        let number_of_visual_lines = visual_lines.len();
1620        for (index, visual_line) in visual_lines.iter().enumerate() {
1621            if visual_line.ranges.is_empty() {
1622                continue;
1623            }
1624            let new_order = self.reorder(&visual_line.ranges);
1625            let mut glyphs = cached_glyph_sets
1626                .pop()
1627                .unwrap_or_else(|| Vec::with_capacity(1));
1628            let mut x = start_x;
1629            let mut y = 0.;
1630            let mut max_ascent: f32 = 0.;
1631            let mut max_descent: f32 = 0.;
1632            let alignment_correction = match (align, self.rtl) {
1633                (Align::Left, true) => line_width - visual_line.w,
1634                (Align::Left, false) => 0.,
1635                (Align::Right, true) => 0.,
1636                (Align::Right, false) => line_width - visual_line.w,
1637                (Align::Center, _) => (line_width - visual_line.w) / 2.0,
1638                (Align::End, _) => line_width - visual_line.w,
1639                (Align::Justified, _) => 0.,
1640            };
1641
1642            if self.rtl {
1643                x -= alignment_correction;
1644            } else {
1645                x += alignment_correction;
1646            }
1647
1648            if hinting == Hinting::Enabled {
1649                x = x.round();
1650            }
1651
1652            // TODO: Only certain `is_whitespace` chars are typically expanded but this is what is
1653            // currently used to compute `visual_line.spaces`.
1654            //
1655            // https://www.unicode.org/reports/tr14/#Introduction
1656            // > When expanding or compressing interword space according to common
1657            // > typographical practice, only the spaces marked by U+0020 SPACE and U+00A0
1658            // > NO-BREAK SPACE are subject to compression, and only spaces marked by U+0020
1659            // > SPACE, U+00A0 NO-BREAK SPACE, and occasionally spaces marked by U+2009 THIN
1660            // > SPACE are subject to expansion. All other space characters normally have
1661            // > fixed width.
1662            //
1663            // (also some spaces aren't followed by potential linebreaks but they could
1664            //  still be expanded)
1665
1666            // Amount of extra width added to each blank space within a line.
1667            let justification_expansion = if matches!(align, Align::Justified)
1668                && visual_line.spaces > 0
1669                // Don't justify the last line in a paragraph.
1670                && index != number_of_visual_lines - 1
1671            {
1672                (line_width - visual_line.w) / visual_line.spaces as f32
1673            } else {
1674                0.
1675            };
1676
1677            let mut process_range = |range: Range<usize>| {
1678                for &(span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)) in
1679                    &visual_line.ranges[range]
1680                {
1681                    let span = &self.spans[span_index];
1682                    // If ending_glyph is not 0 we need to include glyphs from the ending_word
1683                    for i in starting_word..ending_word + usize::from(ending_glyph != 0) {
1684                        let word = &span.words[i];
1685                        let included_glyphs = match (i == starting_word, i == ending_word) {
1686                            (false, false) => &word.glyphs[..],
1687                            (true, false) => &word.glyphs[starting_glyph..],
1688                            (false, true) => &word.glyphs[..ending_glyph],
1689                            (true, true) => &word.glyphs[starting_glyph..ending_glyph],
1690                        };
1691
1692                        for glyph in included_glyphs {
1693                            // Use overridden font size
1694                            let font_size = glyph.metrics_opt.map_or(font_size, |x| x.font_size);
1695
1696                            let match_mono_em_width = match_mono_width.map(|w| w / font_size);
1697
1698                            let glyph_font_size = match (
1699                                match_mono_em_width,
1700                                glyph.font_monospace_em_width,
1701                            ) {
1702                                (Some(match_em_width), Some(glyph_em_width))
1703                                    if glyph_em_width != match_em_width =>
1704                                {
1705                                    let glyph_to_match_factor = glyph_em_width / match_em_width;
1706                                    let glyph_font_size = math::roundf(glyph_to_match_factor)
1707                                        .max(1.0)
1708                                        / glyph_to_match_factor
1709                                        * font_size;
1710                                    log::trace!(
1711                                        "Adjusted glyph font size ({font_size} => {glyph_font_size})"
1712                                    );
1713                                    glyph_font_size
1714                                }
1715                                _ => font_size,
1716                            };
1717
1718                            let mut x_advance = glyph_font_size.mul_add(
1719                                glyph.x_advance,
1720                                if word.blank {
1721                                    justification_expansion
1722                                } else {
1723                                    0.0
1724                                },
1725                            );
1726                            if let Some(match_em_width) = match_mono_em_width {
1727                                // Round to nearest monospace width
1728                                x_advance = ((x_advance / match_em_width).round()) * match_em_width;
1729                            }
1730                            if hinting == Hinting::Enabled {
1731                                x_advance = x_advance.round();
1732                            }
1733                            if self.rtl {
1734                                x -= x_advance;
1735                            }
1736                            let y_advance = glyph_font_size * glyph.y_advance;
1737                            glyphs.push(glyph.layout(
1738                                glyph_font_size,
1739                                glyph.metrics_opt.map(|x| x.line_height),
1740                                x,
1741                                y,
1742                                x_advance,
1743                                span.level,
1744                            ));
1745                            if !self.rtl {
1746                                x += x_advance;
1747                            }
1748                            y += y_advance;
1749                            max_ascent = max_ascent.max(glyph_font_size * glyph.ascent);
1750                            max_descent = max_descent.max(glyph_font_size * glyph.descent);
1751                        }
1752                    }
1753                }
1754            };
1755
1756            if self.rtl {
1757                for range in new_order.into_iter().rev() {
1758                    process_range(range);
1759                }
1760            } else {
1761                /* LTR */
1762                for range in new_order {
1763                    process_range(range);
1764                }
1765            }
1766
1767            let mut line_height_opt: Option<f32> = None;
1768            for glyph in &glyphs {
1769                if let Some(glyph_line_height) = glyph.line_height_opt {
1770                    line_height_opt = line_height_opt
1771                        .map_or(Some(glyph_line_height), |line_height| {
1772                            Some(line_height.max(glyph_line_height))
1773                        });
1774                }
1775            }
1776
1777            layout_lines.push(LayoutLine {
1778                w: if align != Align::Justified {
1779                    visual_line.w
1780                } else if self.rtl {
1781                    start_x - x
1782                } else {
1783                    x
1784                },
1785                max_ascent,
1786                max_descent,
1787                line_height_opt,
1788                glyphs,
1789            });
1790        }
1791
1792        // This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
1793        if layout_lines.is_empty() {
1794            layout_lines.push(LayoutLine {
1795                w: 0.0,
1796                max_ascent: 0.0,
1797                max_descent: 0.0,
1798                line_height_opt: self.metrics_opt.map(|x| x.line_height),
1799                glyphs: Vec::default(),
1800            });
1801        }
1802
1803        // Restore the buffer to the scratch set to prevent reallocations.
1804        scratch.visual_lines = visual_lines;
1805        scratch.visual_lines.append(&mut cached_visual_lines);
1806        scratch.cached_visual_lines = cached_visual_lines;
1807        scratch.glyph_sets = cached_glyph_sets;
1808    }
1809}