cosmic_text/
shape.rs

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