Skip to main content

kas_text/display/
wrap_lines.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Text preparation: wrapping
7
8use super::{RunSpecial, TextDisplay};
9use crate::conv::{to_u32, to_usize};
10use crate::fonts::{self, FontLibrary};
11use crate::shaper::{GlyphRun, PartMetrics};
12use crate::{Align, Range, Vec2};
13use smallvec::SmallVec;
14use std::num::NonZeroUsize;
15use tinyvec::TinyVec;
16use unicode_bidi::{LTR_LEVEL, Level};
17
18#[derive(Clone, Debug, Default)]
19pub struct RunPart {
20    pub text_end: u32,
21    pub glyph_run: u32,
22    pub glyph_range: Range,
23    pub offset: Vec2,
24}
25
26/// Per-line data (post wrapping)
27#[derive(Clone, Debug, Default)]
28pub struct Line {
29    text_range: Range,           // range in text
30    pub(crate) run_range: Range, // range in wrapped_runs
31    pub(crate) top: f32,
32    pub(crate) bottom: f32,
33}
34
35impl Line {
36    /// Get the text range of line contents
37    #[inline]
38    pub fn text_range(&self) -> std::ops::Range<usize> {
39        self.text_range.to_std()
40    }
41
42    /// Get the upper bound of the line
43    #[inline]
44    pub fn top(&self) -> f32 {
45        self.top
46    }
47
48    /// Get the lower bound of the line
49    #[inline]
50    pub fn bottom(&self) -> f32 {
51        self.bottom
52    }
53}
54
55impl TextDisplay {
56    /// Measure required width, up to some `max_width`
57    ///
58    /// [Requires status][Self#status-of-preparation]: level runs have been
59    /// prepared.
60    ///
61    /// This method allows calculation of the width requirement of a text object
62    /// without full wrapping and glyph placement. Whenever the requirement
63    /// exceeds `max_width`, the algorithm stops early, returning `max_width`.
64    ///
65    /// The return value is unaffected by alignment and wrap configuration.
66    pub fn measure_width(&self, max_width: f32) -> f32 {
67        let mut max_line_len = 0.0f32;
68        let mut caret = 0.0;
69        let mut line_len = 0.0;
70
71        for run in self.runs.iter() {
72            let num_parts = run.num_parts();
73            let part = run.part_lengths(0..num_parts);
74
75            if part.len_no_space > 0.0 {
76                line_len = caret + part.len_no_space;
77                if line_len >= max_width {
78                    return max_width;
79                }
80            }
81            caret += part.len;
82
83            if run.special == RunSpecial::HardBreak {
84                max_line_len = max_line_len.max(line_len);
85                caret = 0.0;
86                line_len = 0.0;
87            }
88        }
89
90        max_line_len.max(line_len)
91    }
92
93    /// Measure required vertical height, wrapping as configured
94    ///
95    /// Stops after `max_lines`, if provided.
96    ///
97    /// [Requires status][Self#status-of-preparation]: level runs have been
98    /// prepared.
99    pub fn measure_height(&self, wrap_width: f32, max_lines: Option<NonZeroUsize>) -> f32 {
100        #[derive(Default)]
101        struct MeasureAdder {
102            parts: Vec<usize>, // run index for each part
103            lines: usize,
104            line_gap: f32,
105            vcaret: f32,
106        }
107
108        impl PartAccumulator for MeasureAdder {
109            fn num_parts(&self) -> usize {
110                self.parts.len()
111            }
112
113            fn num_lines(&self) -> usize {
114                self.lines
115            }
116
117            fn add_part(
118                &mut self,
119                _: &[GlyphRun],
120                run_index: usize,
121                _: std::ops::Range<usize>,
122                _: PartMetrics,
123                checkpoint: bool,
124            ) {
125                if checkpoint
126                    && let Some(index) = self.parts.last().cloned()
127                    && index == run_index
128                {
129                    return;
130                }
131
132                self.parts.push(run_index);
133            }
134
135            fn add_line(
136                &mut self,
137                fonts: &FontLibrary,
138                runs: &[GlyphRun],
139                parts_end: usize,
140                _: bool,
141            ) {
142                debug_assert!(parts_end > 0);
143                let parts = &mut self.parts[..parts_end];
144
145                // Iterate runs to determine max ascent, level, etc.
146                let mut last_run = usize::MAX;
147                let (mut ascent, mut descent, mut line_gap) = (0f32, 0f32, 0f32);
148                for run_index in parts.iter().cloned() {
149                    if last_run == run_index {
150                        continue;
151                    }
152                    last_run = run_index;
153                    let run = &runs[last_run];
154
155                    let scale_font = fonts.get_face(run.face_id).scale_by_dpu(run.dpu);
156                    ascent = ascent.max(scale_font.ascent());
157                    descent = descent.min(scale_font.descent());
158                    line_gap = line_gap.max(scale_font.line_gap());
159                }
160
161                if self.lines > 0 {
162                    self.vcaret += line_gap.max(self.line_gap);
163                }
164                self.vcaret += ascent - descent;
165                self.line_gap = line_gap;
166
167                // Vertically align lines to the nearest pixel (improves rendering):
168                self.vcaret = self.vcaret.round();
169
170                self.lines += 1;
171                self.parts.clear();
172            }
173        }
174
175        let mut adder = MeasureAdder::default();
176        let max_lines = max_lines.map(|n| n.get()).unwrap_or(0);
177        self.wrap_lines(&mut adder, wrap_width, max_lines);
178        adder.vcaret
179    }
180
181    /// Prepare lines ("wrap")
182    ///
183    /// [Requires status][Self#status-of-preparation]: level runs have been
184    /// prepared.
185    ///
186    /// This does text layout, including wrapping and horizontal alignment but
187    /// excluding vertical alignment.
188    ///
189    /// `wrap_width` causes line wrapping at the given width. Use
190    /// `f32::INFINITY` to disable wrapping.
191    ///
192    /// Text will be aligned within `0..width_bound` (see below); as such
193    /// `width_bound` must be finite. When `wrap_width` is finite it will often
194    /// make sense to use the same value; when not it might make sense to use
195    /// `width_bound = 0` then offset the result using [`Self::apply_offset`].
196    /// If `width_bound` is too small, text will escape `0..width_bound`
197    /// according to alignment.
198    ///
199    /// Alignment is applied according to `h_align`:
200    ///
201    /// -   [`Align::Default`]: LTR text is left-aligned, RTL text is right-aligned
202    /// -   [`Align::TL`]: text is left-aligned (left at `0`)
203    /// -   [`Align::BR`]: text is right-aligned (right at `width_bound`)
204    /// -   [`Align::Center`]: text is center-aligned (center at `width_bound / 2`)
205    /// -   [`Align::Stretch`]: this is the most complex mode. For lines which
206    ///     wrap and where the line length does not exceed `width_bound`, the
207    ///     text is aligned to `0` *and* `width_bound` (if possible) by
208    ///     stretching spaces within the text. Other lines are aligned in the
209    ///     same way as [`Align::Default`].
210    ///
211    /// Returns the required height.
212    pub fn prepare_lines(&mut self, wrap_width: f32, width_bound: f32, h_align: Align) -> f32 {
213        debug_assert!(width_bound.is_finite());
214        let mut adder = LineAdder::new(width_bound, h_align);
215
216        self.wrap_lines(&mut adder, wrap_width, 0);
217
218        self.wrapped_runs = adder.wrapped_runs;
219        self.lines = adder.lines;
220        #[cfg(feature = "num_glyphs")]
221        {
222            self.num_glyphs = adder.num_glyphs;
223        }
224        self.l_bound = adder.l_bound.min(adder.r_bound);
225        self.r_bound = adder.r_bound;
226        adder.vcaret
227    }
228
229    fn wrap_lines(
230        &self,
231        accumulator: &mut impl PartAccumulator,
232        wrap_width: f32,
233        max_lines: usize,
234    ) {
235        let fonts = fonts::library();
236
237        // Tuples: (index, part_index, num_parts)
238        let mut start = (0, 0, 0);
239        let mut end = start;
240
241        let mut caret = 0.0;
242        let mut run_index = start.0;
243
244        let end_index = self.runs.len();
245        'a: while run_index < end_index {
246            let run = &self.runs[run_index];
247            let num_parts = run.num_parts();
248
249            let hard_break = run.special == RunSpecial::HardBreak;
250            let allow_break = run.special != RunSpecial::NoBreak;
251            let tab = run.special == RunSpecial::HTab;
252
253            let mut last_part = start.1;
254            let mut part_index = last_part + 1;
255            while part_index <= num_parts {
256                let mut part = run.part_lengths(last_part..part_index);
257                if tab {
258                    // Tab runs have no glyph; instead we calculate part.len
259                    // based on the current line length.
260
261                    // TODO(bidi): we should really calculate this after
262                    // re-ordering the line based on full line contents,
263                    // then use a checkpoint reset if too long.
264
265                    let sf = fonts.get_face(run.face_id).scale_by_dpu(run.dpu);
266                    // TODO: custom tab sizes?
267                    let tab_size = sf.h_advance(sf.face().glyph_index(' ')) * 8.0;
268                    let stops = (caret / tab_size).floor() + 1.0;
269                    part.len = tab_size * stops - caret;
270                }
271
272                let line_len = caret + part.len_no_space;
273                if line_len > wrap_width && end.2 > 0 {
274                    // Add up to last valid break point then wrap and reset
275                    accumulator.add_line(fonts, &self.runs, end.2, true);
276
277                    if accumulator.num_lines() == max_lines {
278                        return;
279                    }
280
281                    end.2 = 0;
282                    start = end;
283                    caret = 0.0;
284                    run_index = start.0;
285                    continue 'a;
286                }
287                caret += part.len;
288                let checkpoint = part_index < num_parts || allow_break;
289                accumulator.add_part(
290                    &self.runs,
291                    run_index,
292                    last_part..part_index,
293                    part,
294                    checkpoint,
295                );
296                if checkpoint {
297                    end = (run_index, part_index, accumulator.num_parts());
298                }
299                last_part = part_index;
300                part_index += 1;
301            }
302
303            run_index += 1;
304            start.1 = 0;
305
306            if hard_break || run_index == end_index {
307                let num_parts = accumulator.num_parts();
308                if num_parts > 0 {
309                    // It should not be possible for a line to end with a no-break, so:
310                    debug_assert_eq!(num_parts, end.2);
311
312                    accumulator.add_line(fonts, &self.runs, num_parts, false);
313
314                    if accumulator.num_lines() == max_lines {
315                        return;
316                    }
317                }
318
319                start = (run_index, 0, 0);
320                end = start;
321
322                caret = 0.0;
323                run_index = start.0;
324            }
325        }
326    }
327
328    /// Add `offset` to all positioned content
329    ///
330    /// This is a low-level method which can be used for alignment in some
331    /// cases. Cost is `O(w)` where `w` is the number of wrapped runs.
332    pub fn apply_offset(&mut self, offset: Vec2) {
333        for run in &mut self.wrapped_runs {
334            run.offset += offset;
335        }
336        for line in &mut self.lines {
337            line.top += offset.1;
338            line.bottom += offset.1;
339        }
340        self.l_bound += offset.0;
341        self.r_bound += offset.0;
342    }
343
344    /// Vertically align lines
345    ///
346    /// [Requires status][Self#status-of-preparation]: lines have been wrapped.
347    pub fn vertically_align(&mut self, bound: f32, v_align: Align) {
348        debug_assert!(bound.is_finite());
349
350        if self.lines.is_empty() {
351            return;
352        }
353
354        let top = self.lines.first().unwrap().top;
355        let bottom = self.lines.last().unwrap().bottom;
356        let height = bottom - top;
357        let new_offset = match v_align {
358            _ if height >= bound => 0.0,
359            Align::Default | Align::TL | Align::Stretch => 0.0,
360            Align::Center => 0.5 * (bound - height),
361            Align::BR => bound - height,
362        };
363        let offset = new_offset - top;
364
365        if offset != 0.0 {
366            self.apply_offset(Vec2(0.0, offset));
367        }
368    }
369}
370
371trait PartAccumulator {
372    fn num_parts(&self) -> usize;
373    fn num_lines(&self) -> usize;
374
375    fn add_part(
376        &mut self,
377        runs: &[GlyphRun],
378        run_index: usize,
379        part_range: std::ops::Range<usize>,
380        part: PartMetrics,
381        checkpoint: bool,
382    );
383
384    fn add_line(&mut self, fonts: &FontLibrary, runs: &[GlyphRun], parts_end: usize, is_wrap: bool);
385}
386
387#[derive(Clone, Debug)]
388struct PartInfo {
389    run: u32,
390    offset: f32,
391    len: f32,
392    len_no_space: f32,
393    glyph_range: Range,
394    end_space: bool,
395}
396
397#[derive(Default)]
398struct LineAdder {
399    wrapped_runs: TinyVec<[RunPart; 1]>,
400    parts: Vec<PartInfo>,
401    lines: TinyVec<[Line; 1]>,
402    line_gap: f32,
403    l_bound: f32,
404    r_bound: f32,
405    vcaret: f32,
406    #[cfg(feature = "num_glyphs")]
407    num_glyphs: u32,
408    h_align: Align,
409    width_bound: f32,
410}
411impl LineAdder {
412    fn new(width_bound: f32, h_align: Align) -> Self {
413        LineAdder {
414            parts: Vec::with_capacity(16),
415            l_bound: width_bound,
416            h_align,
417            width_bound,
418            ..Default::default()
419        }
420    }
421}
422
423impl PartAccumulator for LineAdder {
424    fn num_parts(&self) -> usize {
425        self.parts.len()
426    }
427
428    fn num_lines(&self) -> usize {
429        self.lines.len()
430    }
431
432    fn add_part(
433        &mut self,
434        runs: &[GlyphRun],
435        run_index: usize,
436        part_range: std::ops::Range<usize>,
437        part: PartMetrics,
438        checkpoint: bool,
439    ) {
440        let glyph_run = &runs[run_index];
441        let run = to_u32(run_index);
442        let glyph_range = glyph_run.to_glyph_range(part_range);
443
444        let justify = self.h_align == Align::Stretch && part.len_no_space > 0.0;
445        if checkpoint
446            && !justify
447            && let Some(info) = self.parts.last_mut()
448            && info.run == run
449        {
450            // Merge into last part info (not strictly necessary)
451
452            if glyph_run.level.is_ltr() {
453                info.glyph_range.end = glyph_range.end;
454            } else {
455                info.offset = part.offset;
456                info.glyph_range.start = glyph_range.start;
457            }
458            debug_assert!(info.glyph_range.start <= info.glyph_range.end);
459
460            if part.len_no_space > 0.0 {
461                info.len_no_space = info.len + part.len_no_space;
462            }
463            info.len += part.len;
464
465            return;
466        }
467
468        self.parts.push(PartInfo {
469            run,
470            offset: part.offset,
471            len: part.len,
472            len_no_space: part.len_no_space,
473            glyph_range,
474            end_space: false, // set later
475        });
476    }
477
478    fn add_line(
479        &mut self,
480        fonts: &FontLibrary,
481        runs: &[GlyphRun],
482        parts_end: usize,
483        is_wrap: bool,
484    ) {
485        debug_assert!(parts_end > 0);
486        let line_start = self.wrapped_runs.len();
487        let parts = &mut self.parts[..parts_end];
488
489        // Iterate runs to determine max ascent, level, etc.
490        let mut last_run = u32::MAX;
491        let (mut ascent, mut descent, mut line_gap) = (0f32, 0f32, 0f32);
492        let mut line_level = Level::new(Level::max_implicit_depth()).unwrap();
493        let mut max_level = LTR_LEVEL;
494        for part in parts.iter() {
495            if last_run == part.run {
496                continue;
497            }
498            last_run = part.run;
499            let run = &runs[to_usize(last_run)];
500
501            let scale_font = fonts.get_face(run.face_id).scale_by_dpu(run.dpu);
502            ascent = ascent.max(scale_font.ascent());
503            descent = descent.min(scale_font.descent());
504            line_gap = line_gap.max(scale_font.line_gap());
505
506            line_level = line_level.min(run.level);
507            max_level = max_level.max(run.level);
508        }
509        let line_is_rtl = line_level.is_rtl();
510
511        if !self.lines.is_empty() {
512            self.vcaret += line_gap.max(self.line_gap);
513        }
514        self.vcaret += ascent;
515        self.line_gap = line_gap;
516
517        let line_text_start = {
518            let part = &parts[0];
519            let run = &runs[to_usize(part.run)];
520            if run.level.is_ltr() {
521                if part.glyph_range.start() < run.glyphs.len() {
522                    run.glyphs[part.glyph_range.start()].index
523                } else {
524                    run.range.start
525                }
526            } else {
527                let end = part.glyph_range.end();
528                if 0 < end && end < run.glyphs.len() {
529                    run.glyphs[end - 1].index
530                } else {
531                    run.range.start
532                }
533            }
534        };
535
536        // Adjust the (logical) tail: optionally exclude last glyph, calculate
537        // line_text_end, trim whitespace for the purposes of layout.
538        let mut line_text_end;
539        let mut line_len = 0.0;
540        {
541            let part = &mut parts[parts.len() - 1];
542            let run = &runs[to_usize(part.run)];
543
544            if is_wrap && part.len > part.len_no_space {
545                // When wrapping on whitespace: exclude the last glyph
546                // This excludes only one glyph; the main aim is to make the
547                // 'End' key exclude the wrapping position (which is also the
548                // next line's start). It also avoids highlighting whitespace
549                // when selecting wrapped bidi text, for a single space.
550                if part.glyph_range.start < part.glyph_range.end {
551                    if run.level.is_ltr() {
552                        part.glyph_range.end -= 1;
553                    } else {
554                        part.glyph_range.start += 1;
555                    }
556                }
557            }
558
559            line_text_end = run.range.end;
560            if run.level.is_ltr() {
561                if part.glyph_range.end() < run.glyphs.len() {
562                    line_text_end = run.glyphs[part.glyph_range.end()].index;
563                }
564            } else {
565                let start = part.glyph_range.start();
566                if 0 < start && start < run.glyphs.len() {
567                    line_text_end = run.glyphs[start - 1].index
568                }
569            }
570
571            // With bidi text, the logical end may not actually be at the end;
572            // we must not allow spaces here to move other content.
573            for part in parts.iter_mut().rev() {
574                part.end_space = true;
575
576                if part.len_no_space > 0.0 {
577                    break;
578                }
579            }
580            for part in parts.iter() {
581                line_len += if part.end_space {
582                    part.len_no_space
583                } else {
584                    part.len
585                };
586            }
587        }
588
589        // Unic TR#9 L2: reverse items on the line
590        // This implementation does not correspond directly to the Unicode
591        // algorithm, which assumes that shaping happens *after* re-arranging
592        // chars (but also *before*, in order to calculate line-wrap points).
593        // Our shaper(s) accept both LTR and RTL input; additionally, our line
594        // wrapping must explicitly handle both LTR and RTL lines; the missing
595        // step is to rearrange non-wrapped runs on the line.
596
597        {
598            let mut level = max_level;
599            while level > Level::ltr() {
600                let mut start = None;
601                for i in 0..parts.len() {
602                    let part_level = runs[to_usize(parts[i].run)].level;
603                    if let Some(s) = start {
604                        if part_level < level {
605                            parts[s..i].reverse();
606                            start = None;
607                        }
608                    } else if part_level >= level {
609                        start = Some(i);
610                    }
611                }
612                if let Some(s) = start {
613                    parts[s..].reverse();
614                }
615                level.lower(1).unwrap();
616            }
617        }
618
619        let spare = self.width_bound - line_len;
620        let mut is_gap = SmallVec::<[bool; 16]>::new();
621        let mut per_gap = 0.0;
622        let mut caret = match self.h_align {
623            Align::Default if line_is_rtl => spare,
624            Align::Default => 0.0,
625            Align::TL => 0.0,
626            Align::Center => 0.5 * spare,
627            Align::BR => spare,
628            Align::Stretch if is_wrap && spare > 0.0 => {
629                let len = parts.len();
630                is_gap.resize(len, false);
631                let mut num_gaps = 0;
632                for (i, part) in parts[..len - 1].iter().enumerate() {
633                    let run = &runs[to_usize(part.run)];
634                    let not_at_end = if run.level.is_ltr() {
635                        part.glyph_range.end() < run.glyphs.len()
636                    } else {
637                        part.glyph_range.start > 0
638                    };
639                    if not_at_end || run.special != RunSpecial::NoBreak {
640                        is_gap[i] = true;
641                        num_gaps += 1;
642                    }
643                }
644
645                // Apply per-level reversing to is_gap
646                let mut start = 0;
647                let mut level = runs[to_usize(parts[0].run)].level;
648                for i in 1..len {
649                    let new_level = runs[to_usize(parts[i].run)].level;
650                    if level != new_level {
651                        if level > line_level {
652                            is_gap[start..i].reverse();
653                        }
654                        start = i;
655                        level = new_level;
656                    }
657                }
658                if level > line_level {
659                    is_gap[start..len - 1].reverse();
660                }
661
662                // Shift left 1 part for RTL lines
663                if line_level.is_rtl() {
664                    is_gap.copy_within(1..len, 0);
665                }
666
667                if num_gaps == 0 {
668                    match line_is_rtl {
669                        false => 0.0,
670                        true => spare,
671                    }
672                } else {
673                    per_gap = spare / (num_gaps as f32);
674                    0.0
675                }
676            }
677            Align::Stretch if line_is_rtl => spare,
678            Align::Stretch => 0.0,
679        };
680
681        self.l_bound = self.l_bound.min(caret);
682        let mut end_caret = caret;
683
684        for (i, part) in parts.iter().enumerate() {
685            let run = &runs[to_usize(part.run)];
686
687            #[cfg(feature = "num_glyphs")]
688            {
689                self.num_glyphs += to_u32(part.glyph_range.len());
690            }
691
692            let mut text_end = run.range.end;
693            let mut offset = 0.0;
694            if run.level.is_ltr() {
695                if part.glyph_range.end() < run.glyphs.len() {
696                    text_end = run.glyphs[part.glyph_range.end()].index;
697                }
698            } else {
699                let start = part.glyph_range.start();
700                if 0 < start && start < run.glyphs.len() {
701                    text_end = run.glyphs[start - 1].index
702                }
703                offset = part.len_no_space - part.len;
704            }
705
706            let xoffset = if part.end_space {
707                end_caret - part.offset + offset
708            } else {
709                caret - part.offset
710            };
711            self.wrapped_runs.push(RunPart {
712                text_end,
713                glyph_run: part.run,
714                glyph_range: part.glyph_range,
715                offset: Vec2(xoffset, self.vcaret),
716            });
717            if part.end_space {
718                caret += part.len_no_space;
719                end_caret += part.len;
720            } else {
721                caret += part.len;
722                end_caret = caret;
723            }
724
725            if is_gap.len() > 0 && is_gap[i] {
726                caret += per_gap;
727                end_caret += per_gap;
728            }
729        }
730
731        self.r_bound = self.r_bound.max(caret);
732
733        // Other parts of this library expect runs to be in logical order, so
734        // we re-order now (does not affect display position).
735        // TODO: should we change this, e.g. for visual-order navigation?
736        self.wrapped_runs[line_start..].sort_by_key(|run| run.text_end);
737
738        let top = self.vcaret - ascent;
739        self.vcaret -= descent;
740        // Vertically align lines to the nearest pixel (improves rendering):
741        self.vcaret = self.vcaret.round();
742
743        self.lines.push(Line {
744            text_range: Range::from(line_text_start..line_text_end),
745            run_range: (line_start..self.wrapped_runs.len()).into(),
746            top,
747            bottom: self.vcaret,
748        });
749        self.parts.clear();
750    }
751}