cosmic_text/
shape.rs

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