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(line[start_run..end_run].char_indices().enumerate().map(
452        |(i, (chr_idx, codepoint))| {
453            let glyph_id = charmap.map(codepoint);
454            let x_advance = glyph_metrics.advance_width(glyph_id);
455            let attrs = attrs_list.get_span(start_run + chr_idx);
456
457            ShapeGlyph {
458                start: i,
459                end: i + 1,
460                x_advance,
461                y_advance: 0.0,
462                x_offset: 0.0,
463                y_offset: 0.0,
464                ascent,
465                descent,
466                font_monospace_em_width,
467                font_id,
468                glyph_id,
469                color_opt: attrs.color_opt,
470                metadata: attrs.metadata,
471                cache_key_flags: attrs.cache_key_flags,
472                metrics_opt: attrs.metrics_opt.map(|x| x.into()),
473            }
474        },
475    ));
476}
477
478/// A shaped glyph
479#[derive(Clone, Debug)]
480pub struct ShapeGlyph {
481    pub start: usize,
482    pub end: usize,
483    pub x_advance: f32,
484    pub y_advance: f32,
485    pub x_offset: f32,
486    pub y_offset: f32,
487    pub ascent: f32,
488    pub descent: f32,
489    pub font_monospace_em_width: Option<f32>,
490    pub font_id: fontdb::ID,
491    pub glyph_id: u16,
492    pub color_opt: Option<Color>,
493    pub metadata: usize,
494    pub cache_key_flags: CacheKeyFlags,
495    pub metrics_opt: Option<Metrics>,
496}
497
498impl ShapeGlyph {
499    fn layout(
500        &self,
501        font_size: f32,
502        line_height_opt: Option<f32>,
503        x: f32,
504        y: f32,
505        w: f32,
506        level: unicode_bidi::Level,
507    ) -> LayoutGlyph {
508        LayoutGlyph {
509            start: self.start,
510            end: self.end,
511            font_size,
512            line_height_opt,
513            font_id: self.font_id,
514            glyph_id: self.glyph_id,
515            x,
516            y,
517            w,
518            level,
519            x_offset: self.x_offset,
520            y_offset: self.y_offset,
521            color_opt: self.color_opt,
522            metadata: self.metadata,
523            cache_key_flags: self.cache_key_flags,
524        }
525    }
526
527    /// Get the width of the [`ShapeGlyph`] in pixels, either using the provided font size
528    /// or the [`ShapeGlyph::metrics_opt`] override.
529    pub fn width(&self, font_size: f32) -> f32 {
530        self.metrics_opt.map_or(font_size, |x| x.font_size) * self.x_advance
531    }
532}
533
534/// A shaped word (for word wrapping)
535#[derive(Clone, Debug)]
536pub struct ShapeWord {
537    pub blank: bool,
538    pub glyphs: Vec<ShapeGlyph>,
539}
540
541impl ShapeWord {
542    /// Creates an empty word.
543    ///
544    /// The returned word is in an invalid state until [`Self::build_in_buffer`] is called.
545    pub(crate) fn empty() -> Self {
546        Self {
547            blank: true,
548            glyphs: Vec::default(),
549        }
550    }
551
552    /// Shape a word into a set of glyphs.
553    #[allow(clippy::too_many_arguments)]
554    pub fn new(
555        font_system: &mut FontSystem,
556        line: &str,
557        attrs_list: &AttrsList,
558        word_range: Range<usize>,
559        level: unicode_bidi::Level,
560        blank: bool,
561        shaping: Shaping,
562    ) -> Self {
563        let mut empty = Self::empty();
564        empty.build(
565            font_system,
566            line,
567            attrs_list,
568            word_range,
569            level,
570            blank,
571            shaping,
572        );
573        empty
574    }
575
576    /// See [`Self::new`].
577    ///
578    /// Reuses as much of the pre-existing internal allocations as possible.
579    #[allow(clippy::too_many_arguments)]
580    pub fn build(
581        &mut self,
582        font_system: &mut FontSystem,
583        line: &str,
584        attrs_list: &AttrsList,
585        word_range: Range<usize>,
586        level: unicode_bidi::Level,
587        blank: bool,
588        shaping: Shaping,
589    ) {
590        let word = &line[word_range.clone()];
591
592        log::trace!(
593            "      Word{}: '{}'",
594            if blank { " BLANK" } else { "" },
595            word
596        );
597
598        let mut glyphs = mem::take(&mut self.glyphs);
599        glyphs.clear();
600
601        let span_rtl = level.is_rtl();
602
603        let mut start_run = word_range.start;
604        let mut attrs = attrs_list.defaults();
605        for (egc_i, _egc) in word.grapheme_indices(true) {
606            let start_egc = word_range.start + egc_i;
607            let attrs_egc = attrs_list.get_span(start_egc);
608            if !attrs.compatible(&attrs_egc) {
609                shaping.run(
610                    &mut glyphs,
611                    font_system,
612                    line,
613                    attrs_list,
614                    start_run,
615                    start_egc,
616                    span_rtl,
617                );
618
619                start_run = start_egc;
620                attrs = attrs_egc;
621            }
622        }
623        if start_run < word_range.end {
624            shaping.run(
625                &mut glyphs,
626                font_system,
627                line,
628                attrs_list,
629                start_run,
630                word_range.end,
631                span_rtl,
632            );
633        }
634
635        self.blank = blank;
636        self.glyphs = glyphs;
637    }
638
639    /// Get the width of the [`ShapeWord`] in pixels, using the [`ShapeGlyph::width`] function.
640    pub fn width(&self, font_size: f32) -> f32 {
641        let mut width = 0.0;
642        for glyph in self.glyphs.iter() {
643            width += glyph.width(font_size);
644        }
645        width
646    }
647}
648
649/// A shaped span (for bidirectional processing)
650#[derive(Clone, Debug)]
651pub struct ShapeSpan {
652    pub level: unicode_bidi::Level,
653    pub words: Vec<ShapeWord>,
654}
655
656impl ShapeSpan {
657    /// Creates an empty span.
658    ///
659    /// The returned span is in an invalid state until [`Self::build_in_buffer`] is called.
660    pub(crate) fn empty() -> Self {
661        Self {
662            level: unicode_bidi::Level::ltr(),
663            words: Vec::default(),
664        }
665    }
666
667    /// Shape a span into a set of words.
668    pub fn new(
669        font_system: &mut FontSystem,
670        line: &str,
671        attrs_list: &AttrsList,
672        span_range: Range<usize>,
673        line_rtl: bool,
674        level: unicode_bidi::Level,
675        shaping: Shaping,
676    ) -> Self {
677        let mut empty = Self::empty();
678        empty.build(
679            font_system,
680            line,
681            attrs_list,
682            span_range,
683            line_rtl,
684            level,
685            shaping,
686        );
687        empty
688    }
689
690    /// See [`Self::new`].
691    ///
692    /// Reuses as much of the pre-existing internal allocations as possible.
693    pub fn build(
694        &mut self,
695        font_system: &mut FontSystem,
696        line: &str,
697        attrs_list: &AttrsList,
698        span_range: Range<usize>,
699        line_rtl: bool,
700        level: unicode_bidi::Level,
701        shaping: Shaping,
702    ) {
703        let span = &line[span_range.start..span_range.end];
704
705        log::trace!(
706            "  Span {}: '{}'",
707            if level.is_rtl() { "RTL" } else { "LTR" },
708            span
709        );
710
711        let mut words = mem::take(&mut self.words);
712
713        // Cache the shape words in reverse order so they can be popped for reuse in the same order.
714        let mut cached_words = mem::take(&mut font_system.shape_buffer.words);
715        cached_words.clear();
716        if line_rtl != level.is_rtl() {
717            // Un-reverse previous words so the internal glyph counts match accurately when rewriting memory.
718            cached_words.append(&mut words);
719        } else {
720            cached_words.extend(words.drain(..).rev());
721        }
722
723        let mut start_word = 0;
724        for (end_lb, _) in unicode_linebreak::linebreaks(span) {
725            let mut start_lb = end_lb;
726            for (i, c) in span[start_word..end_lb].char_indices().rev() {
727                // TODO: Not all whitespace characters are linebreakable, e.g. 00A0 (No-break
728                // space)
729                // https://www.unicode.org/reports/tr14/#GL
730                // https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt
731                if c.is_whitespace() {
732                    start_lb = start_word + i;
733                } else {
734                    break;
735                }
736            }
737            if start_word < start_lb {
738                let mut word = cached_words.pop().unwrap_or_else(ShapeWord::empty);
739                word.build(
740                    font_system,
741                    line,
742                    attrs_list,
743                    (span_range.start + start_word)..(span_range.start + start_lb),
744                    level,
745                    false,
746                    shaping,
747                );
748                words.push(word);
749            }
750            if start_lb < end_lb {
751                for (i, c) in span[start_lb..end_lb].char_indices() {
752                    // assert!(c.is_whitespace());
753                    let mut word = cached_words.pop().unwrap_or_else(ShapeWord::empty);
754                    word.build(
755                        font_system,
756                        line,
757                        attrs_list,
758                        (span_range.start + start_lb + i)
759                            ..(span_range.start + start_lb + i + c.len_utf8()),
760                        level,
761                        true,
762                        shaping,
763                    );
764                    words.push(word);
765                }
766            }
767            start_word = end_lb;
768        }
769
770        // Reverse glyphs in RTL lines
771        if line_rtl {
772            for word in &mut words {
773                word.glyphs.reverse();
774            }
775        }
776
777        // Reverse words in spans that do not match line direction
778        if line_rtl != level.is_rtl() {
779            words.reverse();
780        }
781
782        self.level = level;
783        self.words = words;
784
785        // Cache buffer for future reuse.
786        font_system.shape_buffer.words = cached_words;
787    }
788}
789
790/// A shaped line (or paragraph)
791#[derive(Clone, Debug)]
792pub struct ShapeLine {
793    pub rtl: bool,
794    pub spans: Vec<ShapeSpan>,
795    pub metrics_opt: Option<Metrics>,
796}
797
798// Visual Line Ranges: (span_index, (first_word_index, first_glyph_index), (last_word_index, last_glyph_index))
799type VlRange = (usize, (usize, usize), (usize, usize));
800
801#[derive(Default)]
802struct VisualLine {
803    ranges: Vec<VlRange>,
804    spaces: u32,
805    w: f32,
806}
807
808impl VisualLine {
809    fn clear(&mut self) {
810        self.ranges.clear();
811        self.spaces = 0;
812        self.w = 0.;
813    }
814}
815
816impl ShapeLine {
817    /// Creates an empty line.
818    ///
819    /// The returned line is in an invalid state until [`Self::build_in_buffer`] is called.
820    pub(crate) fn empty() -> Self {
821        Self {
822            rtl: false,
823            spans: Vec::default(),
824            metrics_opt: None,
825        }
826    }
827
828    /// Shape a line into a set of spans, using a scratch buffer. If [`unicode_bidi::BidiInfo`]
829    /// detects multiple paragraphs, they will be joined.
830    ///
831    /// # Panics
832    ///
833    /// Will panic if `line` contains multiple paragraphs that do not have matching direction
834    pub fn new(
835        font_system: &mut FontSystem,
836        line: &str,
837        attrs_list: &AttrsList,
838        shaping: Shaping,
839        tab_width: u16,
840    ) -> Self {
841        let mut empty = Self::empty();
842        empty.build(font_system, line, attrs_list, shaping, tab_width);
843        empty
844    }
845
846    /// See [`Self::new`].
847    ///
848    /// Reuses as much of the pre-existing internal allocations as possible.
849    ///
850    /// # Panics
851    ///
852    /// Will panic if `line` contains multiple paragraphs that do not have matching direction
853    pub fn build(
854        &mut self,
855        font_system: &mut FontSystem,
856        line: &str,
857        attrs_list: &AttrsList,
858        shaping: Shaping,
859        tab_width: u16,
860    ) {
861        let mut spans = mem::take(&mut self.spans);
862
863        // Cache the shape spans in reverse order so they can be popped for reuse in the same order.
864        let mut cached_spans = mem::take(&mut font_system.shape_buffer.spans);
865        cached_spans.clear();
866        cached_spans.extend(spans.drain(..).rev());
867
868        let bidi = unicode_bidi::BidiInfo::new(line, None);
869        let rtl = if bidi.paragraphs.is_empty() {
870            false
871        } else {
872            bidi.paragraphs[0].level.is_rtl()
873        };
874
875        log::trace!("Line {}: '{}'", if rtl { "RTL" } else { "LTR" }, line);
876
877        for para_info in bidi.paragraphs.iter() {
878            let line_rtl = para_info.level.is_rtl();
879            assert_eq!(line_rtl, rtl);
880
881            let line_range = para_info.range.clone();
882            let levels = Self::adjust_levels(&unicode_bidi::Paragraph::new(&bidi, para_info));
883
884            // Find consecutive level runs. We use this to create Spans.
885            // Each span is a set of characters with equal levels.
886            let mut start = line_range.start;
887            let mut run_level = levels[start];
888            spans.reserve(line_range.end - start + 1);
889
890            for (i, &new_level) in levels
891                .iter()
892                .enumerate()
893                .take(line_range.end)
894                .skip(start + 1)
895            {
896                if new_level != run_level {
897                    // End of the previous run, start of a new one.
898                    let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
899                    span.build(
900                        font_system,
901                        line,
902                        attrs_list,
903                        start..i,
904                        line_rtl,
905                        run_level,
906                        shaping,
907                    );
908                    spans.push(span);
909                    start = i;
910                    run_level = new_level;
911                }
912            }
913            let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
914            span.build(
915                font_system,
916                line,
917                attrs_list,
918                start..line_range.end,
919                line_rtl,
920                run_level,
921                shaping,
922            );
923            spans.push(span);
924        }
925
926        // Adjust for tabs
927        let mut x = 0.0;
928        for span in spans.iter_mut() {
929            for word in span.words.iter_mut() {
930                for glyph in word.glyphs.iter_mut() {
931                    if line.get(glyph.start..glyph.end) == Some("\t") {
932                        // Tabs are shaped as spaces, so they will always have the x_advance of a space.
933                        let tab_x_advance = (tab_width as f32) * glyph.x_advance;
934                        let tab_stop = (math::floorf(x / tab_x_advance) + 1.0) * tab_x_advance;
935                        glyph.x_advance = tab_stop - x;
936                    }
937                    x += glyph.x_advance;
938                }
939            }
940        }
941
942        self.rtl = rtl;
943        self.spans = spans;
944        self.metrics_opt = attrs_list.defaults().metrics_opt.map(|x| x.into());
945
946        // Return the buffer for later reuse.
947        font_system.shape_buffer.spans = cached_spans;
948    }
949
950    // A modified version of first part of unicode_bidi::bidi_info::visual_run
951    fn adjust_levels(para: &unicode_bidi::Paragraph) -> Vec<unicode_bidi::Level> {
952        use unicode_bidi::BidiClass::*;
953        let text = para.info.text;
954        let levels = &para.info.levels;
955        let original_classes = &para.info.original_classes;
956
957        let mut levels = levels.clone();
958        let line_classes = &original_classes[..];
959        let line_levels = &mut levels[..];
960
961        // Reset some whitespace chars to paragraph level.
962        // <http://www.unicode.org/reports/tr9/#L1>
963        let mut reset_from: Option<usize> = Some(0);
964        let mut reset_to: Option<usize> = None;
965        for (i, c) in text.char_indices() {
966            match line_classes[i] {
967                // Ignored by X9
968                RLE | LRE | RLO | LRO | PDF | BN => {}
969                // Segment separator, Paragraph separator
970                B | S => {
971                    assert_eq!(reset_to, None);
972                    reset_to = Some(i + c.len_utf8());
973                    if reset_from.is_none() {
974                        reset_from = Some(i);
975                    }
976                }
977                // Whitespace, isolate formatting
978                WS | FSI | LRI | RLI | PDI => {
979                    if reset_from.is_none() {
980                        reset_from = Some(i);
981                    }
982                }
983                _ => {
984                    reset_from = None;
985                }
986            }
987            if let (Some(from), Some(to)) = (reset_from, reset_to) {
988                for level in &mut line_levels[from..to] {
989                    *level = para.para.level;
990                }
991                reset_from = None;
992                reset_to = None;
993            }
994        }
995        if let Some(from) = reset_from {
996            for level in &mut line_levels[from..] {
997                *level = para.para.level;
998            }
999        }
1000        levels
1001    }
1002
1003    // A modified version of second part of unicode_bidi::bidi_info::visual run
1004    fn reorder(&self, line_range: &[VlRange]) -> Vec<Range<usize>> {
1005        let line: Vec<unicode_bidi::Level> = line_range
1006            .iter()
1007            .map(|(span_index, _, _)| self.spans[*span_index].level)
1008            .collect();
1009        // Find consecutive level runs.
1010        let mut runs = Vec::new();
1011        let mut start = 0;
1012        let mut run_level = line[start];
1013        let mut min_level = run_level;
1014        let mut max_level = run_level;
1015
1016        for (i, &new_level) in line.iter().enumerate().skip(start + 1) {
1017            if new_level != run_level {
1018                // End of the previous run, start of a new one.
1019                runs.push(start..i);
1020                start = i;
1021                run_level = new_level;
1022                min_level = min(run_level, min_level);
1023                max_level = max(run_level, max_level);
1024            }
1025        }
1026        runs.push(start..line.len());
1027
1028        let run_count = runs.len();
1029
1030        // Re-order the odd runs.
1031        // <http://www.unicode.org/reports/tr9/#L2>
1032
1033        // Stop at the lowest *odd* level.
1034        min_level = min_level.new_lowest_ge_rtl().expect("Level error");
1035
1036        while max_level >= min_level {
1037            // Look for the start of a sequence of consecutive runs of max_level or higher.
1038            let mut seq_start = 0;
1039            while seq_start < run_count {
1040                if line[runs[seq_start].start] < max_level {
1041                    seq_start += 1;
1042                    continue;
1043                }
1044
1045                // Found the start of a sequence. Now find the end.
1046                let mut seq_end = seq_start + 1;
1047                while seq_end < run_count {
1048                    if line[runs[seq_end].start] < max_level {
1049                        break;
1050                    }
1051                    seq_end += 1;
1052                }
1053
1054                // Reverse the runs within this sequence.
1055                runs[seq_start..seq_end].reverse();
1056
1057                seq_start = seq_end;
1058            }
1059            max_level
1060                .lower(1)
1061                .expect("Lowering embedding level below zero");
1062        }
1063
1064        runs
1065    }
1066
1067    pub fn layout(
1068        &self,
1069        font_size: f32,
1070        width_opt: Option<f32>,
1071        wrap: Wrap,
1072        align: Option<Align>,
1073        match_mono_width: Option<f32>,
1074    ) -> Vec<LayoutLine> {
1075        let mut lines = Vec::with_capacity(1);
1076        self.layout_to_buffer(
1077            &mut ShapeBuffer::default(),
1078            font_size,
1079            width_opt,
1080            wrap,
1081            align,
1082            &mut lines,
1083            match_mono_width,
1084        );
1085        lines
1086    }
1087
1088    pub fn layout_to_buffer(
1089        &self,
1090        scratch: &mut ShapeBuffer,
1091        font_size: f32,
1092        width_opt: Option<f32>,
1093        wrap: Wrap,
1094        align: Option<Align>,
1095        layout_lines: &mut Vec<LayoutLine>,
1096        match_mono_width: Option<f32>,
1097    ) {
1098        // For each visual line a list of  (span index,  and range of words in that span)
1099        // Note that a BiDi visual line could have multiple spans or parts of them
1100        // let mut vl_range_of_spans = Vec::with_capacity(1);
1101        let mut visual_lines = mem::take(&mut scratch.visual_lines);
1102        let mut cached_visual_lines = mem::take(&mut scratch.cached_visual_lines);
1103        cached_visual_lines.clear();
1104        cached_visual_lines.extend(visual_lines.drain(..).map(|mut l| {
1105            l.clear();
1106            l
1107        }));
1108
1109        // Cache glyph sets in reverse order so they will ideally be reused in exactly the same lines.
1110        let mut cached_glyph_sets = mem::take(&mut scratch.glyph_sets);
1111        cached_glyph_sets.clear();
1112        cached_glyph_sets.extend(layout_lines.drain(..).rev().map(|mut v| {
1113            v.glyphs.clear();
1114            v.glyphs
1115        }));
1116
1117        fn add_to_visual_line(
1118            vl: &mut VisualLine,
1119            span_index: usize,
1120            start: (usize, usize),
1121            end: (usize, usize),
1122            width: f32,
1123            number_of_blanks: u32,
1124        ) {
1125            if end == start {
1126                return;
1127            }
1128
1129            vl.ranges.push((span_index, start, end));
1130            vl.w += width;
1131            vl.spaces += number_of_blanks;
1132        }
1133
1134        // This would keep the maximum number of spans that would fit on a visual line
1135        // If one span is too large, this variable will hold the range of words inside that span
1136        // that fits on a line.
1137        // let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
1138        let mut current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1139
1140        if wrap == Wrap::None {
1141            for (span_index, span) in self.spans.iter().enumerate() {
1142                let mut word_range_width = 0.;
1143                let mut number_of_blanks: u32 = 0;
1144                for word in span.words.iter() {
1145                    let word_width = word.width(font_size);
1146                    word_range_width += word_width;
1147                    if word.blank {
1148                        number_of_blanks += 1;
1149                    }
1150                }
1151                add_to_visual_line(
1152                    &mut current_visual_line,
1153                    span_index,
1154                    (0, 0),
1155                    (span.words.len(), 0),
1156                    word_range_width,
1157                    number_of_blanks,
1158                );
1159            }
1160        } else {
1161            for (span_index, span) in self.spans.iter().enumerate() {
1162                let mut word_range_width = 0.;
1163                let mut width_before_last_blank = 0.;
1164                let mut number_of_blanks: u32 = 0;
1165
1166                // Create the word ranges that fits in a visual line
1167                if self.rtl != span.level.is_rtl() {
1168                    // incongruent directions
1169                    let mut fitting_start = (span.words.len(), 0);
1170                    for (i, word) in span.words.iter().enumerate().rev() {
1171                        let word_width = word.width(font_size);
1172
1173                        // Addition in the same order used to compute the final width, so that
1174                        // relayouts with that width as the `line_width` will produce the same
1175                        // wrapping results.
1176                        if current_visual_line.w + (word_range_width + word_width)
1177                            <= width_opt.unwrap_or(f32::INFINITY)
1178                            // Include one blank word over the width limit since it won't be
1179                            // counted in the final width
1180                            || (word.blank
1181                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
1182                        {
1183                            // fits
1184                            if word.blank {
1185                                number_of_blanks += 1;
1186                                width_before_last_blank = word_range_width;
1187                            }
1188                            word_range_width += word_width;
1189                            continue;
1190                        } else if wrap == Wrap::Glyph
1191                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
1192                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
1193                        {
1194                            // Commit the current line so that the word starts on the next line.
1195                            if word_range_width > 0.
1196                                && wrap == Wrap::WordOrGlyph
1197                                && word_width > width_opt.unwrap_or(f32::INFINITY)
1198                            {
1199                                add_to_visual_line(
1200                                    &mut current_visual_line,
1201                                    span_index,
1202                                    (i + 1, 0),
1203                                    fitting_start,
1204                                    word_range_width,
1205                                    number_of_blanks,
1206                                );
1207
1208                                visual_lines.push(current_visual_line);
1209                                current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1210
1211                                number_of_blanks = 0;
1212                                word_range_width = 0.;
1213
1214                                fitting_start = (i, 0);
1215                            }
1216
1217                            for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
1218                                let glyph_width = glyph.width(font_size);
1219                                if current_visual_line.w + (word_range_width + glyph_width)
1220                                    <= width_opt.unwrap_or(f32::INFINITY)
1221                                {
1222                                    word_range_width += glyph_width;
1223                                    continue;
1224                                } else {
1225                                    add_to_visual_line(
1226                                        &mut current_visual_line,
1227                                        span_index,
1228                                        (i, glyph_i + 1),
1229                                        fitting_start,
1230                                        word_range_width,
1231                                        number_of_blanks,
1232                                    );
1233                                    visual_lines.push(current_visual_line);
1234                                    current_visual_line =
1235                                        cached_visual_lines.pop().unwrap_or_default();
1236
1237                                    number_of_blanks = 0;
1238                                    word_range_width = glyph_width;
1239                                    fitting_start = (i, glyph_i + 1);
1240                                }
1241                            }
1242                        } else {
1243                            // Wrap::Word, Wrap::WordOrGlyph
1244
1245                            // If we had a previous range, commit that line before the next word.
1246                            if word_range_width > 0. {
1247                                // Current word causing a wrap is not whitespace, so we ignore the
1248                                // previous word if it's a whitespace
1249                                let trailing_blank = span
1250                                    .words
1251                                    .get(i + 1)
1252                                    .is_some_and(|previous_word| previous_word.blank);
1253
1254                                if trailing_blank {
1255                                    number_of_blanks = number_of_blanks.saturating_sub(1);
1256                                    add_to_visual_line(
1257                                        &mut current_visual_line,
1258                                        span_index,
1259                                        (i + 2, 0),
1260                                        fitting_start,
1261                                        width_before_last_blank,
1262                                        number_of_blanks,
1263                                    );
1264                                } else {
1265                                    add_to_visual_line(
1266                                        &mut current_visual_line,
1267                                        span_index,
1268                                        (i + 1, 0),
1269                                        fitting_start,
1270                                        word_range_width,
1271                                        number_of_blanks,
1272                                    );
1273                                }
1274
1275                                visual_lines.push(current_visual_line);
1276                                current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1277                                number_of_blanks = 0;
1278                            }
1279
1280                            if word.blank {
1281                                word_range_width = 0.;
1282                                fitting_start = (i, 0);
1283                            } else {
1284                                word_range_width = word_width;
1285                                fitting_start = (i + 1, 0);
1286                            }
1287                        }
1288                    }
1289                    add_to_visual_line(
1290                        &mut current_visual_line,
1291                        span_index,
1292                        (0, 0),
1293                        fitting_start,
1294                        word_range_width,
1295                        number_of_blanks,
1296                    );
1297                } else {
1298                    // congruent direction
1299                    let mut fitting_start = (0, 0);
1300                    for (i, word) in span.words.iter().enumerate() {
1301                        let word_width = word.width(font_size);
1302                        if current_visual_line.w + (word_range_width + word_width)
1303                            <= width_opt.unwrap_or(f32::INFINITY)
1304                            // Include one blank word over the width limit since it won't be
1305                            // counted in the final width.
1306                            || (word.blank
1307                                && (current_visual_line.w + word_range_width) <= width_opt.unwrap_or(f32::INFINITY))
1308                        {
1309                            // fits
1310                            if word.blank {
1311                                number_of_blanks += 1;
1312                                width_before_last_blank = word_range_width;
1313                            }
1314                            word_range_width += word_width;
1315                            continue;
1316                        } else if wrap == Wrap::Glyph
1317                            // Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
1318                            || (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
1319                        {
1320                            // Commit the current line so that the word starts on the next line.
1321                            if word_range_width > 0.
1322                                && wrap == Wrap::WordOrGlyph
1323                                && word_width > width_opt.unwrap_or(f32::INFINITY)
1324                            {
1325                                add_to_visual_line(
1326                                    &mut current_visual_line,
1327                                    span_index,
1328                                    fitting_start,
1329                                    (i, 0),
1330                                    word_range_width,
1331                                    number_of_blanks,
1332                                );
1333
1334                                visual_lines.push(current_visual_line);
1335                                current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1336
1337                                number_of_blanks = 0;
1338                                word_range_width = 0.;
1339
1340                                fitting_start = (i, 0);
1341                            }
1342
1343                            for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
1344                                let glyph_width = glyph.width(font_size);
1345                                if current_visual_line.w + (word_range_width + glyph_width)
1346                                    <= width_opt.unwrap_or(f32::INFINITY)
1347                                {
1348                                    word_range_width += glyph_width;
1349                                    continue;
1350                                } else {
1351                                    add_to_visual_line(
1352                                        &mut current_visual_line,
1353                                        span_index,
1354                                        fitting_start,
1355                                        (i, glyph_i),
1356                                        word_range_width,
1357                                        number_of_blanks,
1358                                    );
1359                                    visual_lines.push(current_visual_line);
1360                                    current_visual_line =
1361                                        cached_visual_lines.pop().unwrap_or_default();
1362
1363                                    number_of_blanks = 0;
1364                                    word_range_width = glyph_width;
1365                                    fitting_start = (i, glyph_i);
1366                                }
1367                            }
1368                        } else {
1369                            // Wrap::Word, Wrap::WordOrGlyph
1370
1371                            // If we had a previous range, commit that line before the next word.
1372                            if word_range_width > 0. {
1373                                // Current word causing a wrap is not whitespace, so we ignore the
1374                                // previous word if it's a whitespace.
1375                                let trailing_blank = i > 0 && span.words[i - 1].blank;
1376
1377                                if trailing_blank {
1378                                    number_of_blanks = number_of_blanks.saturating_sub(1);
1379                                    add_to_visual_line(
1380                                        &mut current_visual_line,
1381                                        span_index,
1382                                        fitting_start,
1383                                        (i - 1, 0),
1384                                        width_before_last_blank,
1385                                        number_of_blanks,
1386                                    );
1387                                } else {
1388                                    add_to_visual_line(
1389                                        &mut current_visual_line,
1390                                        span_index,
1391                                        fitting_start,
1392                                        (i, 0),
1393                                        word_range_width,
1394                                        number_of_blanks,
1395                                    );
1396                                }
1397
1398                                visual_lines.push(current_visual_line);
1399                                current_visual_line = cached_visual_lines.pop().unwrap_or_default();
1400                                number_of_blanks = 0;
1401                            }
1402
1403                            if word.blank {
1404                                word_range_width = 0.;
1405                                fitting_start = (i + 1, 0);
1406                            } else {
1407                                word_range_width = word_width;
1408                                fitting_start = (i, 0);
1409                            }
1410                        }
1411                    }
1412                    add_to_visual_line(
1413                        &mut current_visual_line,
1414                        span_index,
1415                        fitting_start,
1416                        (span.words.len(), 0),
1417                        word_range_width,
1418                        number_of_blanks,
1419                    );
1420                }
1421            }
1422        }
1423
1424        if !current_visual_line.ranges.is_empty() {
1425            visual_lines.push(current_visual_line);
1426        } else {
1427            current_visual_line.clear();
1428            cached_visual_lines.push(current_visual_line);
1429        }
1430
1431        // Create the LayoutLines using the ranges inside visual lines
1432        let align = align.unwrap_or({
1433            if self.rtl {
1434                Align::Right
1435            } else {
1436                Align::Left
1437            }
1438        });
1439
1440        let line_width = match width_opt {
1441            Some(width) => width,
1442            None => {
1443                let mut width: f32 = 0.0;
1444                for visual_line in visual_lines.iter() {
1445                    width = width.max(visual_line.w);
1446                }
1447                width
1448            }
1449        };
1450
1451        let start_x = if self.rtl { line_width } else { 0.0 };
1452
1453        let number_of_visual_lines = visual_lines.len();
1454        for (index, visual_line) in visual_lines.iter().enumerate() {
1455            if visual_line.ranges.is_empty() {
1456                continue;
1457            }
1458            let new_order = self.reorder(&visual_line.ranges);
1459            let mut glyphs = cached_glyph_sets
1460                .pop()
1461                .unwrap_or_else(|| Vec::with_capacity(1));
1462            let mut x = start_x;
1463            let mut y = 0.;
1464            let mut max_ascent: f32 = 0.;
1465            let mut max_descent: f32 = 0.;
1466            let alignment_correction = match (align, self.rtl) {
1467                (Align::Left, true) => line_width - visual_line.w,
1468                (Align::Left, false) => 0.,
1469                (Align::Right, true) => 0.,
1470                (Align::Right, false) => line_width - visual_line.w,
1471                (Align::Center, _) => (line_width - visual_line.w) / 2.0,
1472                (Align::End, _) => line_width - visual_line.w,
1473                (Align::Justified, _) => 0.,
1474            };
1475
1476            if self.rtl {
1477                x -= alignment_correction;
1478            } else {
1479                x += alignment_correction;
1480            }
1481
1482            // TODO: Only certain `is_whitespace` chars are typically expanded but this is what is
1483            // currently used to compute `visual_line.spaces`.
1484            //
1485            // https://www.unicode.org/reports/tr14/#Introduction
1486            // > When expanding or compressing interword space according to common
1487            // > typographical practice, only the spaces marked by U+0020 SPACE and U+00A0
1488            // > NO-BREAK SPACE are subject to compression, and only spaces marked by U+0020
1489            // > SPACE, U+00A0 NO-BREAK SPACE, and occasionally spaces marked by U+2009 THIN
1490            // > SPACE are subject to expansion. All other space characters normally have
1491            // > fixed width.
1492            //
1493            // (also some spaces aren't followed by potential linebreaks but they could
1494            //  still be expanded)
1495
1496            // Amount of extra width added to each blank space within a line.
1497            let justification_expansion = if matches!(align, Align::Justified)
1498                && visual_line.spaces > 0
1499                // Don't justify the last line in a paragraph.
1500                && index != number_of_visual_lines - 1
1501            {
1502                (line_width - visual_line.w) / visual_line.spaces as f32
1503            } else {
1504                0.
1505            };
1506
1507            let mut process_range = |range: Range<usize>| {
1508                for &(span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)) in
1509                    visual_line.ranges[range.clone()].iter()
1510                {
1511                    let span = &self.spans[span_index];
1512                    // If ending_glyph is not 0 we need to include glyphs from the ending_word
1513                    for i in starting_word..ending_word + usize::from(ending_glyph != 0) {
1514                        let word = &span.words[i];
1515                        let included_glyphs = match (i == starting_word, i == ending_word) {
1516                            (false, false) => &word.glyphs[..],
1517                            (true, false) => &word.glyphs[starting_glyph..],
1518                            (false, true) => &word.glyphs[..ending_glyph],
1519                            (true, true) => &word.glyphs[starting_glyph..ending_glyph],
1520                        };
1521
1522                        for glyph in included_glyphs {
1523                            // Use overridden font size
1524                            let font_size = glyph.metrics_opt.map_or(font_size, |x| x.font_size);
1525
1526                            let match_mono_em_width = match_mono_width.map(|w| w / font_size);
1527
1528                            let glyph_font_size = match (
1529                                match_mono_em_width,
1530                                glyph.font_monospace_em_width,
1531                            ) {
1532                                (Some(match_em_width), Some(glyph_em_width))
1533                                    if glyph_em_width != match_em_width =>
1534                                {
1535                                    let glyph_to_match_factor = glyph_em_width / match_em_width;
1536                                    let glyph_font_size = math::roundf(glyph_to_match_factor)
1537                                        .max(1.0)
1538                                        / glyph_to_match_factor
1539                                        * font_size;
1540                                    log::trace!("Adjusted glyph font size ({font_size} => {glyph_font_size})");
1541                                    glyph_font_size
1542                                }
1543                                _ => font_size,
1544                            };
1545
1546                            let x_advance = glyph_font_size * glyph.x_advance
1547                                + if word.blank {
1548                                    justification_expansion
1549                                } else {
1550                                    0.0
1551                                };
1552                            if self.rtl {
1553                                x -= x_advance;
1554                            }
1555                            let y_advance = glyph_font_size * glyph.y_advance;
1556                            glyphs.push(glyph.layout(
1557                                glyph_font_size,
1558                                glyph.metrics_opt.map(|x| x.line_height),
1559                                x,
1560                                y,
1561                                x_advance,
1562                                span.level,
1563                            ));
1564                            if !self.rtl {
1565                                x += x_advance;
1566                            }
1567                            y += y_advance;
1568                            max_ascent = max_ascent.max(glyph_font_size * glyph.ascent);
1569                            max_descent = max_descent.max(glyph_font_size * glyph.descent);
1570                        }
1571                    }
1572                }
1573            };
1574
1575            if self.rtl {
1576                for range in new_order.into_iter().rev() {
1577                    process_range(range);
1578                }
1579            } else {
1580                /* LTR */
1581                for range in new_order {
1582                    process_range(range);
1583                }
1584            }
1585
1586            let mut line_height_opt: Option<f32> = None;
1587            for glyph in glyphs.iter() {
1588                if let Some(glyph_line_height) = glyph.line_height_opt {
1589                    line_height_opt = match line_height_opt {
1590                        Some(line_height) => Some(line_height.max(glyph_line_height)),
1591                        None => Some(glyph_line_height),
1592                    };
1593                }
1594            }
1595
1596            layout_lines.push(LayoutLine {
1597                w: if align != Align::Justified {
1598                    visual_line.w
1599                } else if self.rtl {
1600                    start_x - x
1601                } else {
1602                    x
1603                },
1604                max_ascent,
1605                max_descent,
1606                line_height_opt,
1607                glyphs,
1608            });
1609        }
1610
1611        // This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
1612        if layout_lines.is_empty() {
1613            layout_lines.push(LayoutLine {
1614                w: 0.0,
1615                max_ascent: 0.0,
1616                max_descent: 0.0,
1617                line_height_opt: self.metrics_opt.map(|x| x.line_height),
1618                glyphs: Default::default(),
1619            });
1620        }
1621
1622        // Restore the buffer to the scratch set to prevent reallocations.
1623        scratch.visual_lines = visual_lines;
1624        scratch.visual_lines.append(&mut cached_visual_lines);
1625        scratch.cached_visual_lines = cached_visual_lines;
1626        scratch.glyph_sets = cached_glyph_sets;
1627    }
1628}