Skip to main content

cranpose_text/
text_layouter.rs

1use std::ops::Range;
2
3use cranpose_ui_graphics::{MemoryHeadroom, whole};
4use smallvec::SmallVec;
5
6use crate::{
7    bidi_level::{BidiLevel, reorder_visually},
8    ellipsis_at::{ELLIPSIS, EllipsisAt, ellipsis_at, take_rest_of_paragraph},
9    font_family_resolver::FontFamilyResolver,
10    glyph_ink::{InkReach, line_band},
11    index_range::narrow,
12    itemizer::{Itemizer, TextItem},
13    laid_glyphs::LaidGlyphs,
14    layout_body::LayoutBody,
15    layout_input::LayoutInput,
16    layout_options::TextLayoutOptions,
17    line_box::{LastLineBox, LineBoxInputs},
18    line_breaker::{
19        BreakKind, BrokenLine, LineBalancer, LineBreaker, LineEnd, WrapWidths, Wrapping,
20        break_lines, hard_break, has_hard_break, starts_paragraph, visible_end,
21    },
22    line_hold::{end_ellipsis_hold, fitting_hold, wrapped_hold},
23    line_runs::{line_extent, place_runs},
24    line_text::{LineDepth, LineEdges, LineText, before_hard_break, indent},
25    paragraph::{LineBreak, ResolvedTextDirection},
26    paragraph_intrinsics::ParagraphIntrinsics,
27    paragraph_measures::ParagraphMeasures,
28    script::Script,
29    shaped_glyphs::ShapedGlyphs,
30    shaped_item::{
31        Piece, ShapedItem, face_index, hyphen, item_at, part_glyphs, scale, set_cluster,
32        shape_inserted, spaced_width,
33    },
34    shaper::Shaper,
35    text_layout::{LayoutLine, TextLayout},
36    typeface::Typeface,
37};
38#[cfg(feature = "hyphenation")]
39use crate::{
40    hyphenator::Hyphenator,
41    line_breaker::BreakOpportunity,
42    paragraph::{Hyphens, TextAlign},
43};
44
45/// Lays paragraphs out. Keeps its itemizer, shaper, line breaker and scratch
46/// storage from one layout to the next.
47#[derive(Default)]
48pub struct TextLayouter {
49    itemizer: Itemizer,
50    shaper: Shaper,
51    breaker: LineBreaker,
52    balancer: LineBalancer,
53    items: Vec<ShapedItem>,
54    // The cluster widths and break opportunities of the text laid out last,
55    // which keep their storage from one text to the next.
56    measures: ParagraphMeasures,
57    lines: Vec<BrokenLine>,
58    pieces: Vec<Piece>,
59    // The glyphs of the text being laid out, which keep their storage from
60    // one text to the next; a layout takes them once they are all placed.
61    glyphs: ShapedGlyphs,
62    // The faces intrinsic widths are measured in, which keep their storage
63    // from one text to the next.
64    faces: SmallVec<[Typeface; 1]>,
65    line_box: LastLineBox,
66    #[cfg(feature = "hyphenation")]
67    hyphenator: Hyphenator,
68    // The break opportunities with the hyphenation points among them, and
69    // a word's points.
70    #[cfg(feature = "hyphenation")]
71    hyphenated: Vec<BreakOpportunity>,
72    #[cfg(feature = "hyphenation")]
73    points: Vec<usize>,
74}
75
76// The most glyphs the layouter keeps room for between texts: the storage of
77// a longer text goes once the text is laid out.
78const SCRATCH_GLYPHS: usize = 4096;
79// How much the free width of a paragraph's last line counts in even breaking
80// for `LineBreak.Paragraph`, which lets the last line stay short.
81const PARAGRAPH_LAST_LINE: f32 = 0.16;
82
83impl TextLayouter {
84    /// Lays `input` out with the typefaces of `resolver`.
85    pub fn layout(&mut self, input: &LayoutInput<'_>, resolver: &FontFamilyResolver) -> TextLayout {
86        self.layout_taking(input, resolver, None)
87    }
88
89    /// Lays `input` out as [`TextLayouter::layout`] does, with the glyphs
90    /// `shaped` placed for the same text in the same typefaces in place of
91    /// shaping it again.
92    pub fn relayout(
93        &mut self,
94        input: &LayoutInput<'_>,
95        resolver: &FontFamilyResolver,
96        shaped: &TextLayout,
97    ) -> TextLayout {
98        self.layout_taking(input, resolver, Some(shaped))
99    }
100
101    fn layout_taking(
102        &mut self,
103        input: &LayoutInput<'_>,
104        resolver: &FontFamilyResolver,
105        shaped: Option<&TextLayout>,
106    ) -> TextLayout {
107        let mut faces = SmallVec::new();
108        self.shape(input, resolver, shaped, &mut faces);
109        self.place(input, resolver, shaped, faces)
110    }
111
112    // Places the items just shaped in `faces` into a new layout. Out of
113    // line, so the layout and the placing stay off the stack of the
114    // shaping, which runs deepest.
115    #[inline(never)]
116    fn place(
117        &mut self,
118        input: &LayoutInput<'_>,
119        resolver: &FontFamilyResolver,
120        shaped: Option<&TextLayout>,
121        faces: SmallVec<[Typeface; 1]>,
122    ) -> TextLayout {
123        let mut body = LayoutBody {
124            faces,
125            ..LayoutBody::default()
126        };
127        let width = self.fill(input, resolver, shaped, &mut body);
128        // A layout kept holds each part in storage of its own length.
129        body.shrink_to_fit();
130        let reach = self.shaper.take_ink_reach();
131        TextLayout::new(body, width, (input.min_width, input.max_width), reach)
132    }
133
134    /// Lays `input` out into `layout` in place of what it held, in the
135    /// storage of its lines and runs when no copy of it shares them.
136    pub fn layout_into(
137        &mut self,
138        input: &LayoutInput<'_>,
139        resolver: &FontFamilyResolver,
140        layout: &mut TextLayout,
141    ) {
142        layout.refill((input.min_width, input.max_width), |body| {
143            body.clear();
144            self.shape(input, resolver, None, &mut body.faces);
145            let width = self.fill(input, resolver, None, body);
146            (width, self.shaper.take_ink_reach())
147        });
148    }
149
150    // Lays the items just shaped of `input` out into `layout`, with what
151    // `shaped` holds when given, and returns the width its lines are placed
152    // across. Out of line, as `place` is.
153    #[inline(never)]
154    fn fill(
155        &mut self,
156        input: &LayoutInput<'_>,
157        resolver: &FontFamilyResolver,
158        shaped: Option<&TextLayout>,
159        layout: &mut LayoutBody,
160    ) -> f32 {
161        let options = input.options.normalized();
162        layout.shaped = self.glyphs.len();
163        layout.glyphs = shaped
164            .and_then(|shaped| shaped.body().shaped_glyphs())
165            .cloned()
166            .unwrap_or_default();
167        let lengths = input.lengths;
168        let text = input.text;
169        // The piece of a text of one item with no line break and no
170        // whitespace at its end, which is its line when it fits.
171        let mut lone = (self.items.len() == 1
172            && !has_hard_break(text)
173            && visible_end(text, 0, text.len()) == text.len())
174        .then(|| self.lone_piece(input, layout));
175        // The width up to the trailing whitespace of a text with no line
176        // break: its one paragraph.
177        let unbroken = if let Some(piece) = &lone {
178            Some(piece.width)
179        } else {
180            self.measure_widths(input, &layout.faces);
181            (!has_hard_break(text)).then(|| {
182                self.measures
183                    .widths
184                    .width(0, visible_end(text, 0, text.len()))
185            })
186        };
187        let widest = unbroken.map_or_else(
188            || self.widest_paragraph(input),
189            |width| width.max(0.0) + lengths.first_line_indent,
190        );
191        layout.content_width = whole::ceil(widest * lengths.grid) / lengths.grid;
192        layout.follows_max = options.soft_wrap || options.overflow.is_ellipsis();
193        let width = layout.wrap_width(input.min_width, input.max_width);
194        let wrap = WrapWidths {
195            width: if options.soft_wrap {
196                width
197            } else {
198                f32::INFINITY
199            },
200            first_indent: lengths.first_line_indent,
201            rest_indent: lengths.rest_line_indent,
202        };
203        // Most text is one line that fits, its width with its indent; it
204        // needs no break opportunities.
205        let fits = unbroken
206            .map(|line| line + wrap.first_indent)
207            .filter(|&line| line <= wrap.width);
208        // A lone item that fits with no ellipsis is the line, and needs no
209        // cluster widths.
210        if let Some(line) = fits.filter(|&line| line <= width)
211            && let Some(piece) = lone.take()
212        {
213            let mut hold = fitting_hold(&options, line);
214            if ellipsis_at(&options).is_some() {
215                hold.narrow(line, f32::INFINITY);
216            }
217            layout.hold = Some(hold);
218            self.lay_out_one_line(input, &options, resolver, piece, width, layout);
219            return width;
220        }
221        if lone.is_some() {
222            self.measure_widths(input, &layout.faces);
223        }
224        self.fill_lines(input, (&options, wrap, width), resolver, fits, layout);
225        width
226    }
227
228    // Breaks the text `fill` measured into lines of `width`, the one line it
229    // `fits` on, that wide with its indent, when it does, and lays them out.
230    // Out of line: most text is
231    // one line that `fill` lays out itself, and a frame that lays out only
232    // such text runs none of this.
233    #[inline(never)]
234    fn fill_lines(
235        &mut self,
236        input: &LayoutInput<'_>,
237        (options, wrap, width): (&TextLayoutOptions, WrapWidths, f32),
238        resolver: &FontFamilyResolver,
239        fits: Option<f32>,
240        layout: &mut LayoutBody,
241    ) {
242        let text = input.text;
243        if let Some(line) = fits {
244            layout.hold = Some(fitting_hold(options, line));
245            self.lines.clear();
246            self.lines.push(BrokenLine::plain(0..text.len()));
247        } else {
248            self.wrap_lines(input, options, wrap, layout);
249        }
250        layout.did_overflow_height = self.lines.len() > options.max_lines;
251        if layout.did_overflow_height && ellipsis_at(options).is_some() {
252            take_rest_of_paragraph(&mut self.lines, text, options.max_lines - 1);
253        }
254        self.lines.truncate(options.max_lines);
255        self.build_lines(input, options, resolver, width, layout);
256    }
257
258    // Breaks `input` into lines of the `wrap` widths, at the opportunities
259    // its line break style takes, and sets the layout's hold.
260    fn wrap_lines(
261        &mut self,
262        input: &LayoutInput<'_>,
263        options: &TextLayoutOptions,
264        wrap: WrapWidths,
265        layout: &mut LayoutBody,
266    ) {
267        let text = input.text;
268        let line_break = input.paragraph.line_break;
269        self.breaker
270            .opportunities(text, line_break, &mut self.measures.breaks);
271        #[cfg(feature = "hyphenation")]
272        let breaks =
273            if input.paragraph.hyphens == Hyphens::Auto && self.hyphenate(input, wrap, layout) {
274                &self.hyphenated
275            } else {
276                &self.measures.breaks
277            };
278        #[cfg(not(feature = "hyphenation"))]
279        let breaks = &self.measures.breaks;
280        let wrapping = Wrapping {
281            text,
282            widths: &self.measures.widths,
283            wrap,
284        };
285        match line_break {
286            LineBreak::Paragraph => {
287                self.balancer
288                    .balance(&wrapping, breaks, PARAGRAPH_LAST_LINE, &mut self.lines);
289            }
290            LineBreak::Heading => {
291                self.balancer
292                    .balance(&wrapping, breaks, 1.0, &mut self.lines);
293            }
294            LineBreak::Simple | LineBreak::Unspecified => {
295                break_lines(&wrapping, breaks, &mut self.lines);
296            }
297        }
298        layout.hold = wrapped_hold(&self.lines, input, options, &wrapping, breaks);
299    }
300
301    /// The intrinsic widths of `input`, laid out with the typefaces of
302    /// `resolver`; its width and options do not count.
303    pub fn intrinsics(
304        &mut self,
305        input: &LayoutInput<'_>,
306        resolver: &FontFamilyResolver,
307    ) -> ParagraphIntrinsics {
308        let mut faces = std::mem::take(&mut self.faces);
309        faces.clear();
310        self.shape(input, resolver, None, &mut faces);
311        self.measure_widths(input, &faces);
312        self.faces = faces;
313        self.keep_glyph_room();
314        let text = input.text;
315        self.breaker
316            .opportunities(text, input.paragraph.line_break, &mut self.measures.breaks);
317        let max_intrinsic_width = self.widest_paragraph(input);
318        // An empty text, or one that ends with a line break, ends with an
319        // empty line.
320        let empty_last = self
321            .measures
322            .breaks
323            .last()
324            .is_none_or(|opportunity| opportunity.kind == BreakKind::Hard);
325        let mut min_intrinsic_width = if empty_last {
326            input.lengths.first_line_indent
327        } else {
328            0.0
329        };
330        let mut word = 0;
331        for opportunity in &self.measures.breaks {
332            let width = self
333                .measures
334                .widths
335                .width(word, visible_end(text, word, opportunity.offset));
336            min_intrinsic_width = min_intrinsic_width.max(width + indent(input, word));
337            word = opportunity.offset;
338        }
339        ParagraphIntrinsics {
340            min_intrinsic_width,
341            max_intrinsic_width,
342        }
343    }
344
345    // Measures the widths of the clusters shaped last, in storage of the
346    // layouter's own.
347    fn measure_widths(&mut self, input: &LayoutInput<'_>, faces: &[Typeface]) {
348        self.measures
349            .widths
350            .measure(&self.items, input, faces, &self.glyphs);
351    }
352
353    // Gives back the storage of the glyphs when it grew past what a label or
354    // a paragraph needs.
355    fn keep_glyph_room(&mut self) {
356        if self.glyphs.capacity() > SCRATCH_GLYPHS {
357            self.glyphs = ShapedGlyphs::default();
358        }
359    }
360
361    // Ends a layout whose lines reach down to `top`: the layout takes the
362    // glyphs, a text short of its minimum lines takes the height of that
363    // many of its first line, and its lines align across the paragraph's
364    // width.
365    fn finish_lines(
366        &mut self,
367        input: &LayoutInput<'_>,
368        options: &TextLayoutOptions,
369        bottom: LineDepth,
370        layout: &mut LayoutBody,
371    ) {
372        // A relayout that added no glyphs keeps those it took.
373        if layout.glyphs.len() != self.glyphs.len() {
374            layout.glyphs = LaidGlyphs::new(&self.glyphs);
375        }
376        self.keep_glyph_room();
377        // Each missing line adds a line's advance, whole pixels, as Compose's
378        // `heightInLines` adds the distance between two lines' baselines.
379        let grid = input.lengths.grid;
380        layout.height = if layout.lines.len() < options.min_lines {
381            let missing = options.min_lines - layout.lines.len();
382            (bottom.pixels + whole::round(layout.advance * grid) * lines_f32(missing)) / grid
383        } else {
384            bottom.layout
385        };
386        layout.line_width = whole::ceil(layout.widest * grid) / grid;
387        // New lines start at their indents, unstretched.
388        let align = input.paragraph.text_align;
389        if !layout.keeps_lines_at_indent(align) {
390            let width = layout.width_between(input.min_width, input.max_width);
391            layout.align_lines(width, align);
392        }
393    }
394
395    // The widest paragraph of the text measured last on one line, with its
396    // first line's indent.
397    fn widest_paragraph(&self, input: &LayoutInput<'_>) -> f32 {
398        let text = input.text;
399        let mut widest: f32 = 0.0;
400        let mut start = 0;
401        for (at, ch) in text.char_indices() {
402            if hard_break(ch) {
403                widest = widest.max(
404                    self.measures
405                        .widths
406                        .width(start, visible_end(text, start, at)),
407                );
408                start = at + ch.len_utf8();
409            }
410        }
411        let last = self
412            .measures
413            .widths
414            .width(start, visible_end(text, start, text.len()));
415        widest.max(last) + input.lengths.first_line_indent
416    }
417
418    /// Sizes the caches the layouter keeps by how much memory the device
419    /// has left: tight memory takes them back to their least.
420    pub fn set_memory_headroom(&mut self, headroom: MemoryHeadroom) {
421        self.shaper.words.set_headroom(headroom);
422    }
423
424    /// The dictionaries `Hyphens.Auto` hyphenates words by.
425    #[cfg(feature = "hyphenation")]
426    pub fn hyphenator_mut(&mut self) -> &mut Hyphenator {
427        &mut self.hyphenator
428    }
429
430    // Gathers in `hyphenated` the break opportunities with the hyphenation
431    // points of the words a line may break inside: every word for even
432    // breaking, and for greedy breaking the words wider than a line, the
433    // only ones it hyphenates. False when lines take any width, so none
434    // breaks inside a word.
435    #[cfg(feature = "hyphenation")]
436    fn hyphenate(
437        &mut self,
438        input: &LayoutInput<'_>,
439        wrap: WrapWidths,
440        layout: &mut LayoutBody,
441    ) -> bool {
442        if !wrap.width.is_finite() {
443            return false;
444        }
445        let first = wrap.width - wrap.first_indent;
446        let wider_than = match input.paragraph.line_break {
447            LineBreak::Paragraph | LineBreak::Heading => f32::NEG_INFINITY,
448            LineBreak::Simple | LineBreak::Unspecified => first.min(wrap.width - wrap.rest_indent),
449        };
450        // Android's penalty for a hyphen in even breaking: half the font size
451        // times the line width, a quarter of it in justified text.
452        let justified = if input.paragraph.text_align == TextAlign::Justify {
453            0.25
454        } else {
455            1.0
456        };
457        let penalty_per_size = 0.5 * first * justified;
458        self.hyphenated.clear();
459        let mut word_start = 0;
460        for index in 0..self.measures.breaks.len() {
461            let opportunity = self.measures.breaks[index];
462            let word_end = visible_end(input.text, word_start, opportunity.offset);
463            if self.measures.widths.width(word_start, word_end) > wider_than {
464                self.hyphenate_word(input, word_start..word_end, penalty_per_size, layout);
465            }
466            self.hyphenated.push(opportunity);
467            word_start = opportunity.offset;
468        }
469        true
470    }
471
472    // Adds the hyphenation points of the letters of `word`, without the
473    // punctuation around them, that fall between clusters, each with the
474    // width of its hyphen.
475    #[cfg(feature = "hyphenation")]
476    fn hyphenate_word(
477        &mut self,
478        input: &LayoutInput<'_>,
479        word: Range<usize>,
480        penalty_per_size: f32,
481        layout: &mut LayoutBody,
482    ) {
483        let Some(text) = input.text.get(word.clone()) else {
484            return;
485        };
486        let letters = text.trim_start_matches(|ch: char| !ch.is_alphabetic());
487        let start = word.start + (text.len() - letters.len());
488        let letters = letters.trim_end_matches(|ch: char| !ch.is_alphabetic());
489        let Some(first) = self.items.get(item_at(&self.items, start)) else {
490            return;
491        };
492        if letters.is_empty() || first.level.is_rtl() {
493            return;
494        }
495        self.points.clear();
496        self.hyphenator
497            .break_points(input.spans[first.span].language, letters, &mut self.points);
498        let mut hyphen_width: Option<(usize, f32)> = None;
499        for &point in &self.points {
500            let at = start + point;
501            if self.measures.widths.next_boundary(at.saturating_sub(1)) != Some(at) {
502                continue;
503            }
504            let index = item_at(&self.items, at.saturating_sub(1));
505            let item = &self.items[index];
506            let width = match hyphen_width {
507                Some((known, width)) if known == index => width,
508                _ => {
509                    let face = &layout.faces[item.face];
510                    let shaped = self.glyphs.len();
511                    let text = hyphen(face);
512                    let item = item.inserted(text, face);
513                    let width =
514                        shape_inserted(&mut self.shaper, input, &item, text, &mut self.glyphs);
515                    self.glyphs.truncate(shaped);
516                    hyphen_width = Some((index, width));
517                    width
518                }
519            };
520            self.hyphenated.push(BreakOpportunity {
521                offset: at,
522                kind: BreakKind::Hyphen {
523                    width,
524                    penalty: penalty_per_size * input.spans[item.span].size,
525                },
526            });
527        }
528    }
529
530    // Shapes the items of `input` with the typefaces it adds to `faces`, or
531    // takes the glyphs `shaped` placed for them, whose clusters fall in each
532    // item's text in item order.
533    fn shape(
534        &mut self,
535        input: &LayoutInput<'_>,
536        resolver: &FontFamilyResolver,
537        shaped: Option<&TextLayout>,
538        faces: &mut SmallVec<[Typeface; 1]>,
539    ) {
540        self.items.clear();
541        self.glyphs.clear();
542        self.shaper.take_ink_reach();
543        // A glyph takes at least a byte of text, so the glyphs grow once.
544        self.glyphs.reserve(input.text.len());
545        let text = input.text;
546        let (shaper, items, glyphs) = (&mut self.shaper, &mut self.items, &mut self.glyphs);
547        let direction = input.paragraph.text_direction;
548        let mut taken = shaped.map(|shaped| (shaped.body(), 0));
549        self.itemizer
550            .itemize(text, direction, input.spans, resolver, |item| {
551                let start = glyphs.len();
552                match &mut taken {
553                    Some((body, from)) => {
554                        *from = body.glyphs.take(*from..body.shaped, &item.range, glyphs);
555                        shaper.note_ink_reach(body.ink_reach);
556                        shaper.note_synthesis(&item, &input.spans[item.span]);
557                    }
558                    None => {
559                        shaper.shape(text, &item, &input.spans[item.span], 0..text.len(), glyphs);
560                    }
561                }
562                items.push(ShapedItem {
563                    text: item.range,
564                    glyphs: start..glyphs.len(),
565                    face: face_index(faces, item.typeface),
566                    span: item.span,
567                    level: item.level,
568                    paragraph_level: item.paragraph_level,
569                    script: item.script,
570                    spacing: if item.script.takes_letter_spacing() {
571                        let span = &input.spans[item.span];
572                        span.letter_spacing
573                            .unwrap_or_else(|| item.typeface.tracking(span.size))
574                    } else {
575                        0.0
576                    },
577                });
578            });
579        // Glyphs spaced closer than their advances overlap past a line's ends.
580        if self.items.iter().any(|item| item.spacing < 0.0) {
581            self.shaper.note_ink_reach(InkReach::Lines);
582        }
583    }
584
585    fn build_lines(
586        &mut self,
587        input: &LayoutInput<'_>,
588        options: &TextLayoutOptions,
589        resolver: &FontFamilyResolver,
590        width: f32,
591        layout: &mut LayoutBody,
592    ) {
593        let mut top = LineDepth::default();
594        let last = self.lines.len() - 1;
595        // Most lines are one run.
596        layout.lines.reserve_exact(self.lines.len());
597        layout.runs.reserve_exact(self.lines.len());
598        for index in 0..=last {
599            let range = self.lines[index].text.clone();
600            let indent = indent(input, range.start);
601            let line = LineText {
602                visible: visible_end(input.text, range.start, range.end),
603                drawn: before_hard_break(input.text, range.start, range.end),
604                indent,
605                width,
606                range,
607            };
608            let ellipsized = self.collect_line(input, options, resolver, (index, &line), layout);
609            reorder_visually(&mut self.pieces, |piece| piece.level);
610            let edges = LineEdges {
611                top,
612                first: index == 0,
613                last: index == last,
614                ellipsized,
615            };
616            let reach;
617            (top, reach) = self.push_line(input, resolver, line, edges, layout);
618            self.shaper.note_ink_reach(reach);
619        }
620        for index in 1..layout.lines.len() {
621            let starts = starts_paragraph(input.text, layout.lines[index].text.start());
622            layout.lines[index - 1].ends_paragraph = starts;
623        }
624        self.finish_lines(input, options, top, layout);
625    }
626
627    // The piece of the one item of a text: the whole item.
628    fn lone_piece(&self, input: &LayoutInput<'_>, layout: &LayoutBody) -> Piece {
629        let item = &self.items[0];
630        let span = &input.spans[item.span];
631        let scale = scale(span, &layout.faces[item.face]);
632        Piece {
633            width: spaced_width(&self.glyphs[item.glyphs.clone()], scale, item.spacing),
634            text: item.text.clone(),
635            glyphs: item.glyphs.clone(),
636            face: item.face,
637            size: span.size,
638            scale,
639            spacing: item.spacing,
640            baseline_shift: span.baseline_shift,
641            level: item.level,
642        }
643    }
644
645    // Lays out the one line of a text whose one item fits it to its end, as
646    // `build_lines` lays it out: the item's `piece` is the line's one piece.
647    fn lay_out_one_line(
648        &mut self,
649        input: &LayoutInput<'_>,
650        options: &TextLayoutOptions,
651        resolver: &FontFamilyResolver,
652        piece: Piece,
653        width: f32,
654        layout: &mut LayoutBody,
655    ) {
656        self.pieces.clear();
657        self.pieces.push(piece);
658        let end = input.text.len();
659        let indent = input.lengths.first_line_indent;
660        let line = LineText {
661            range: 0..end,
662            visible: end,
663            drawn: end,
664            indent,
665            width,
666        };
667        let edges = LineEdges {
668            top: LineDepth::default(),
669            first: true,
670            last: true,
671            ellipsized: false,
672        };
673        let (bottom, reach) = self.push_line(input, resolver, line, edges, layout);
674        self.shaper.note_ink_reach(reach);
675        self.finish_lines(input, options, bottom, layout);
676    }
677
678    // Places the pieces just collected for `line` as its runs, adds the line
679    // below `edges.top` and returns its bottom and how its ink reaches.
680    fn push_line(
681        &self,
682        input: &LayoutInput<'_>,
683        resolver: &FontFamilyResolver,
684        line: LineText,
685        edges: LineEdges,
686        layout: &mut LayoutBody,
687    ) -> (LineDepth, InkReach) {
688        let (runs, width) = place_runs(&self.pieces, line.visible, layout);
689        let extent = line_extent(&self.pieces, input, resolver, layout, line.range.start);
690        let asked = input.lengths.line_height.unwrap_or(f32::NAN);
691        let (top, grid) = (edges.top, input.lengths.grid);
692        let inputs = LineBoxInputs::of(input.paragraph, extent, asked, grid);
693        let line_box = self.line_box.of(inputs);
694        let trim_top = if edges.first { line_box.trim_top } else { 0.0 };
695        let trim_bottom = if edges.last {
696            line_box.trim_bottom
697        } else {
698            0.0
699        };
700        if edges.first {
701            layout.advance = line_box.height / grid;
702        }
703        // Whole device pixels add up exactly; each edge is divided once.
704        let baseline = (top.pixels + line_box.baseline - trim_top) / grid;
705        let bottom = LineDepth::at(top.pixels + line_box.height - trim_top - trim_bottom, grid);
706        let level = self.paragraph_level(input, line.range.start);
707        layout.rtl |= level.is_rtl();
708        layout.lines.push(LayoutLine {
709            level,
710            text: line.range.into(),
711            visible_end: narrow(line.visible),
712            runs: runs.into(),
713            top: top.layout,
714            baseline,
715            bottom: bottom.layout,
716            left: line.indent,
717            indent: line.indent,
718            width,
719            stretch: 0.0,
720            ellipsized: edges.ellipsized,
721            ends_paragraph: true,
722        });
723        layout.widest = layout.widest.max(width + line.indent);
724        let band = || line_band(&self.pieces, &layout.faces);
725        let height = bottom.layout - top.layout;
726        let reach = InkReach::of_line(baseline - top.layout, height, (extent, band));
727        (bottom, reach)
728    }
729
730    // Collects the pieces of line `index`, with an ellipsis where it does
731    // not fit and the options ask for one, or the hyphen of a hyphenated
732    // line, and narrows the layout's hold to the widths that keep the
733    // ellipsis where it is. True when the line shows an ellipsis.
734    fn collect_line(
735        &mut self,
736        input: &LayoutInput<'_>,
737        options: &TextLayoutOptions,
738        resolver: &FontFamilyResolver,
739        (index, line): (usize, &LineText),
740        layout: &mut LayoutBody,
741    ) -> bool {
742        self.pieces.clear();
743        let last = index + 1 == self.lines.len();
744        // The line's text fits from the width that is its width plus its
745        // indent up.
746        let fitting = self.measures.widths.width(line.range.start, line.visible) + line.indent;
747        let overflows = fitting > line.width;
748        let ellipsis = ellipsis_at(options).filter(|_| last);
749        let shown = ellipsis.filter(|_| overflows || (last && layout.did_overflow_height));
750        if let Some(hold) = layout.hold.as_mut().filter(|_| ellipsis.is_some()) {
751            if shown.is_none() {
752                hold.narrow(fitting, f32::INFINITY);
753            } else if overflows {
754                hold.narrow(f32::NEG_INFINITY, fitting);
755            }
756        }
757        let ellipsized = match shown {
758            Some(at) => self.ellipsize(input, resolver, at, line, layout),
759            None => false,
760        };
761        if !ellipsized {
762            self.collect_pieces(input, line.range.start..line.drawn, line.visible, layout);
763            if self.lines[index].end == LineEnd::Hyphen {
764                self.push_hyphen(input, line.range.end, layout);
765            }
766        }
767        ellipsized
768    }
769
770    // Collects the pieces of a line that does not fit with an ellipsis where
771    // `at` says, cutting its text at cluster boundaries so the rest and the
772    // ellipsis fit the width. False when no typeface draws an ellipsis.
773    fn ellipsize(
774        &mut self,
775        input: &LayoutInput<'_>,
776        resolver: &FontFamilyResolver,
777        at: EllipsisAt,
778        line: &LineText,
779        layout: &mut LayoutBody,
780    ) -> bool {
781        let (range, visible) = (line.range.clone(), line.visible);
782        // The ellipsis takes the style of the text its line starts with.
783        let span = input
784            .spans
785            .iter()
786            .position(|span| span.range.contains(&range.start))
787            .unwrap_or(0);
788        let level = self.paragraph_level(input, range.start);
789        let Some(mut ellipsis) = self.ellipsis_piece(input, resolver, span, level, layout) else {
790            return false;
791        };
792        let room = (line.width - line.indent - ellipsis.width).max(0.0);
793        let text = input.text;
794        let (head, tail) = match at {
795            EllipsisAt::End => {
796                let edge = ellipsis.width + line.indent;
797                let cut = self
798                    .measures
799                    .widths
800                    .last_fitting(range.start, visible, edge, line.width);
801                if let Some(hold) = &mut layout.hold {
802                    let kept = end_ellipsis_hold(&self.measures.widths, text, line, cut, edge);
803                    hold.narrow(kept.min, kept.max);
804                }
805                (visible_end(text, range.start, cut), range.end)
806            }
807            EllipsisAt::Start => {
808                layout.hold = None;
809                (
810                    range.start,
811                    self.measures
812                        .widths
813                        .first_fitting(range.start, visible, room),
814                )
815            }
816            EllipsisAt::Middle => {
817                // The end takes up to half the room and the start the rest,
818                // as Android cuts a middle ellipsis.
819                layout.hold = None;
820                let tail = self
821                    .measures
822                    .widths
823                    .first_fitting(range.start, visible, room / 2.0);
824                let used = self.measures.widths.width(tail, visible);
825                (
826                    self.measures
827                        .widths
828                        .last_fitting(range.start, tail, 0.0, room - used),
829                    tail,
830                )
831            }
832        };
833        set_cluster(&mut self.glyphs.glyphs_mut()[ellipsis.glyphs.clone()], head);
834        ellipsis.text = head..head;
835        self.collect_pieces(input, range.start..head, visible, layout);
836        self.pieces.push(ellipsis);
837        if tail < line.drawn {
838            self.collect_pieces(input, tail..line.drawn, visible, layout);
839        }
840        true
841    }
842
843    // The ellipsis in the typeface that `span` resolves it to, at `level`.
844    fn ellipsis_piece(
845        &mut self,
846        input: &LayoutInput<'_>,
847        resolver: &FontFamilyResolver,
848        span: usize,
849        level: BidiLevel,
850        layout: &mut LayoutBody,
851    ) -> Option<Piece> {
852        let style = &input.spans[span];
853        let mut typeface = None;
854        resolver.typeface_runs(ELLIPSIS, style.font, |_, face| {
855            typeface.get_or_insert(face);
856        });
857        let typeface = &resolver.typefaces()[typeface?];
858        let item = TextItem {
859            range: 0..ELLIPSIS.len(),
860            level,
861            paragraph_level: level,
862            script: Script::COMMON,
863            typeface,
864            span,
865        };
866        let start = self.glyphs.len();
867        let width = shape_inserted(&mut self.shaper, input, &item, ELLIPSIS, &mut self.glyphs);
868        Some(Piece {
869            width,
870            text: 0..0,
871            glyphs: start..self.glyphs.len(),
872            face: face_index(&mut layout.faces, typeface),
873            size: style.size,
874            scale: scale(style, typeface),
875            spacing: 0.0,
876            baseline_shift: style.baseline_shift,
877            level,
878        })
879    }
880
881    // Adds the hyphen of a line hyphenated at `at`, in the typeface and
882    // settings of the text before it, as Android draws it.
883    fn push_hyphen(&mut self, input: &LayoutInput<'_>, at: usize, layout: &mut LayoutBody) {
884        let Some(item) = self.items.get(item_at(&self.items, at.saturating_sub(1))) else {
885            return;
886        };
887        let span = &input.spans[item.span];
888        let face = &layout.faces[item.face];
889        let hyphen = hyphen(face);
890        let start = self.glyphs.len();
891        let width = shape_inserted(
892            &mut self.shaper,
893            input,
894            &item.inserted(hyphen, face),
895            hyphen,
896            &mut self.glyphs,
897        );
898        set_cluster(&mut self.glyphs.glyphs_mut()[start..], at);
899        self.pieces.push(Piece {
900            width,
901            text: at..at,
902            glyphs: start..self.glyphs.len(),
903            face: item.face,
904            size: span.size,
905            scale: scale(span, face),
906            spacing: 0.0,
907            baseline_shift: span.baseline_shift,
908            level: item.level,
909        });
910    }
911
912    // Adds the parts of the items inside `part` of a line, in text order,
913    // the trailing whitespace from `visible` on at the paragraph's level
914    // (rule L1 of UAX #9).
915    fn collect_pieces(
916        &mut self,
917        input: &LayoutInput<'_>,
918        part: Range<usize>,
919        visible: usize,
920        layout: &mut LayoutBody,
921    ) {
922        let Self {
923            items,
924            shaper,
925            pieces,
926            glyphs: all,
927            ..
928        } = self;
929        let first = items.partition_point(|item| item.text.end <= part.start);
930        for item in &items[first..] {
931            if item.text.start >= part.end {
932                break;
933            }
934            let start = item.text.start.max(part.start);
935            let end = item.text.end.min(part.end);
936            let middle = visible.clamp(start, end);
937            for (sub, level) in [
938                (start..middle, item.level),
939                (middle..end, item.paragraph_level),
940            ] {
941                if sub.is_empty() {
942                    continue;
943                }
944                let glyphs = part_glyphs(
945                    shaper,
946                    input,
947                    item,
948                    sub.clone(),
949                    part.clone(),
950                    &layout.faces,
951                    all,
952                );
953                let scale = scale(&input.spans[item.span], &layout.faces[item.face]);
954                pieces.push(Piece {
955                    width: spaced_width(&all[glyphs.clone()], scale, item.spacing),
956                    text: sub,
957                    glyphs,
958                    face: item.face,
959                    size: input.spans[item.span].size,
960                    scale,
961                    spacing: item.spacing,
962                    baseline_shift: input.spans[item.span].baseline_shift,
963                    level,
964                });
965            }
966        }
967    }
968
969    // The base level of the paragraph that holds `offset`.
970    fn paragraph_level(&self, input: &LayoutInput<'_>, offset: usize) -> BidiLevel {
971        self.items
972            .get(item_at(&self.items, offset))
973            .or_else(|| self.items.last())
974            .map_or_else(
975                || match input.paragraph.text_direction.resolve("") {
976                    ResolvedTextDirection::Ltr => BidiLevel::LTR,
977                    ResolvedTextDirection::Rtl => BidiLevel::RTL,
978                },
979                |item| item.paragraph_level,
980            )
981    }
982}
983
984// A line count as a float; counts beyond `u16` saturate.
985fn lines_f32(count: usize) -> f32 {
986    f32::from(u16::try_from(count).unwrap_or(u16::MAX))
987}