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::{fitting_hold, wrapped_hold},
23    line_runs::{line_extent, place_runs},
24    line_text::{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 = if options.soft_wrap {
195            WrapWidths {
196                first: width - lengths.first_line_indent,
197                rest: width - lengths.rest_line_indent,
198            }
199        } else {
200            WrapWidths {
201                first: f32::INFINITY,
202                rest: f32::INFINITY,
203            }
204        };
205        // Most text is one line that fits; it needs no break opportunities.
206        let fits = unbroken.filter(|&line| line <= wrap.first);
207        // A lone item that fits with no ellipsis is the line, and needs no
208        // cluster widths.
209        if let Some(piece) =
210            lone.take_if(|piece| fits.is_some() && piece.width <= width - lengths.first_line_indent)
211        {
212            let line = piece.width + lengths.first_line_indent;
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 when it does, and lays them out. Out of line: most text is
230    // one line that `fill` lays out itself, and a frame that lays out only
231    // such text runs none of this.
232    #[inline(never)]
233    fn fill_lines(
234        &mut self,
235        input: &LayoutInput<'_>,
236        (options, wrap, width): (&TextLayoutOptions, WrapWidths, f32),
237        resolver: &FontFamilyResolver,
238        fits: Option<f32>,
239        layout: &mut LayoutBody,
240    ) {
241        let text = input.text;
242        if let Some(line) = fits {
243            layout.hold = Some(fitting_hold(
244                options,
245                line + input.lengths.first_line_indent,
246            ));
247            self.lines.clear();
248            self.lines.push(BrokenLine::plain(0..text.len()));
249        } else {
250            self.wrap_lines(input, options, wrap, layout);
251        }
252        layout.did_overflow_height = self.lines.len() > options.max_lines;
253        if layout.did_overflow_height && ellipsis_at(options).is_some() {
254            take_rest_of_paragraph(&mut self.lines, text, options.max_lines - 1);
255        }
256        self.lines.truncate(options.max_lines);
257        self.build_lines(input, options, resolver, width, layout);
258    }
259
260    // Breaks `input` into lines of the `wrap` widths, at the opportunities
261    // its line break style takes, and sets the layout's hold.
262    fn wrap_lines(
263        &mut self,
264        input: &LayoutInput<'_>,
265        options: &TextLayoutOptions,
266        wrap: WrapWidths,
267        layout: &mut LayoutBody,
268    ) {
269        let text = input.text;
270        let line_break = input.paragraph.line_break;
271        self.breaker
272            .opportunities(text, line_break, &mut self.measures.breaks);
273        #[cfg(feature = "hyphenation")]
274        let breaks =
275            if input.paragraph.hyphens == Hyphens::Auto && self.hyphenate(input, wrap, layout) {
276                &self.hyphenated
277            } else {
278                &self.measures.breaks
279            };
280        #[cfg(not(feature = "hyphenation"))]
281        let breaks = &self.measures.breaks;
282        let wrapping = Wrapping {
283            text,
284            widths: &self.measures.widths,
285            wrap,
286        };
287        match line_break {
288            LineBreak::Paragraph => {
289                self.balancer
290                    .balance(&wrapping, breaks, PARAGRAPH_LAST_LINE, &mut self.lines);
291            }
292            LineBreak::Heading => {
293                self.balancer
294                    .balance(&wrapping, breaks, 1.0, &mut self.lines);
295            }
296            LineBreak::Simple | LineBreak::Unspecified => {
297                break_lines(&wrapping, breaks, &mut self.lines);
298            }
299        }
300        layout.hold = wrapped_hold(&self.lines, input, options, &wrapping, breaks);
301    }
302
303    /// The intrinsic widths of `input`, laid out with the typefaces of
304    /// `resolver`; its width and options do not count.
305    pub fn intrinsics(
306        &mut self,
307        input: &LayoutInput<'_>,
308        resolver: &FontFamilyResolver,
309    ) -> ParagraphIntrinsics {
310        let mut faces = std::mem::take(&mut self.faces);
311        faces.clear();
312        self.shape(input, resolver, None, &mut faces);
313        self.measure_widths(input, &faces);
314        self.faces = faces;
315        self.keep_glyph_room();
316        let text = input.text;
317        self.breaker
318            .opportunities(text, input.paragraph.line_break, &mut self.measures.breaks);
319        let max_intrinsic_width = self.widest_paragraph(input);
320        // An empty text, or one that ends with a line break, ends with an
321        // empty line.
322        let empty_last = self
323            .measures
324            .breaks
325            .last()
326            .is_none_or(|opportunity| opportunity.kind == BreakKind::Hard);
327        let mut min_intrinsic_width = if empty_last {
328            input.lengths.first_line_indent
329        } else {
330            0.0
331        };
332        let mut word = 0;
333        for opportunity in &self.measures.breaks {
334            let width = self
335                .measures
336                .widths
337                .width(word, visible_end(text, word, opportunity.offset));
338            min_intrinsic_width = min_intrinsic_width.max(width + indent(input, word));
339            word = opportunity.offset;
340        }
341        ParagraphIntrinsics {
342            min_intrinsic_width,
343            max_intrinsic_width,
344        }
345    }
346
347    // Measures the widths of the clusters shaped last, in storage of the
348    // layouter's own.
349    fn measure_widths(&mut self, input: &LayoutInput<'_>, faces: &[Typeface]) {
350        self.measures
351            .widths
352            .measure(&self.items, input, faces, &self.glyphs);
353    }
354
355    // Gives back the storage of the glyphs when it grew past what a label or
356    // a paragraph needs.
357    fn keep_glyph_room(&mut self) {
358        if self.glyphs.capacity() > SCRATCH_GLYPHS {
359            self.glyphs = ShapedGlyphs::default();
360        }
361    }
362
363    // Ends a layout whose lines reach down to `top`: the layout takes the
364    // glyphs, a text short of its minimum lines takes the height of that
365    // many of its first line, and its lines align across the paragraph's
366    // width.
367    fn finish_lines(
368        &mut self,
369        input: &LayoutInput<'_>,
370        options: &TextLayoutOptions,
371        mut top: f32,
372        layout: &mut LayoutBody,
373    ) {
374        // A relayout that added no glyphs keeps those it took.
375        if layout.glyphs.len() != self.glyphs.len() {
376            layout.glyphs = LaidGlyphs::new(&self.glyphs);
377        }
378        self.keep_glyph_room();
379        // Each missing line adds a line's advance, as Compose's
380        // `heightInLines` adds the distance between two lines' baselines.
381        if layout.lines.len() < options.min_lines {
382            let missing = options.min_lines - layout.lines.len();
383            top += layout.advance * lines_f32(missing);
384        }
385        layout.height = top;
386        let grid = input.lengths.grid;
387        layout.line_width = whole::ceil(layout.widest * grid) / grid;
388        // New lines start at their indents, unstretched.
389        let align = input.paragraph.text_align;
390        if !layout.keeps_lines_at_indent(align) {
391            let width = layout.width_between(input.min_width, input.max_width);
392            layout.align_lines(width, align);
393        }
394    }
395
396    // The widest paragraph of the text measured last on one line, with its
397    // first line's indent.
398    fn widest_paragraph(&self, input: &LayoutInput<'_>) -> f32 {
399        let text = input.text;
400        let mut widest: f32 = 0.0;
401        let mut start = 0;
402        for (at, ch) in text.char_indices() {
403            if hard_break(ch) {
404                widest = widest.max(
405                    self.measures
406                        .widths
407                        .width(start, visible_end(text, start, at)),
408                );
409                start = at + ch.len_utf8();
410            }
411        }
412        let last = self
413            .measures
414            .widths
415            .width(start, visible_end(text, start, text.len()));
416        widest.max(last) + input.lengths.first_line_indent
417    }
418
419    /// Sizes the caches the layouter keeps by how much memory the device
420    /// has left: tight memory takes them back to their least.
421    pub fn set_memory_headroom(&mut self, headroom: MemoryHeadroom) {
422        self.shaper.words.set_headroom(headroom);
423    }
424
425    /// The dictionaries `Hyphens.Auto` hyphenates words by.
426    #[cfg(feature = "hyphenation")]
427    pub fn hyphenator_mut(&mut self) -> &mut Hyphenator {
428        &mut self.hyphenator
429    }
430
431    // Gathers in `hyphenated` the break opportunities with the hyphenation
432    // points of the words a line may break inside: every word for even
433    // breaking, and for greedy breaking the words wider than a line, the
434    // only ones it hyphenates. False when lines take any width, so none
435    // breaks inside a word.
436    #[cfg(feature = "hyphenation")]
437    fn hyphenate(
438        &mut self,
439        input: &LayoutInput<'_>,
440        wrap: WrapWidths,
441        layout: &mut LayoutBody,
442    ) -> bool {
443        if !wrap.first.is_finite() {
444            return false;
445        }
446        let wider_than = match input.paragraph.line_break {
447            LineBreak::Paragraph | LineBreak::Heading => f32::NEG_INFINITY,
448            LineBreak::Simple | LineBreak::Unspecified => wrap.first.min(wrap.rest),
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 * wrap.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::Past);
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 = 0.0;
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                room: width - indent,
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            room: width - indent,
666        };
667        let edges = LineEdges {
668            top: 0.0,
669            first: true,
670            last: true,
671            ellipsized: false,
672        };
673        let (top, reach) = self.push_line(input, resolver, line, edges, layout);
674        self.shaper.note_ink_reach(reach);
675        self.finish_lines(input, options, top, layout);
676    }
677
678    // Places the pieces just collected for `line` as its runs, adds the
679    // line 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    ) -> (f32, 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 line_box = self.line_box.of(LineBoxInputs::of(
692            input.paragraph,
693            extent,
694            asked,
695            input.lengths.grid,
696        ));
697        let trim_top = if edges.first { line_box.trim_top } else { 0.0 };
698        let trim_bottom = if edges.last {
699            line_box.trim_bottom
700        } else {
701            0.0
702        };
703        if edges.first {
704            layout.advance = line_box.height;
705        }
706        let top = edges.top;
707        let bottom = top + line_box.height - trim_top - trim_bottom;
708        let level = self.paragraph_level(input, line.range.start);
709        layout.rtl |= level.is_rtl();
710        layout.lines.push(LayoutLine {
711            level,
712            text: line.range.into(),
713            visible_end: narrow(line.visible),
714            runs: runs.into(),
715            top,
716            baseline: top + line_box.baseline - trim_top,
717            bottom,
718            left: line.indent,
719            indent: line.indent,
720            width,
721            stretch: 0.0,
722            ellipsized: edges.ellipsized,
723            ends_paragraph: true,
724        });
725        layout.widest = layout.widest.max(width + line.indent);
726        let band = || line_band(&self.pieces, &layout.faces);
727        let reach = InkReach::of_line(line_box.baseline - trim_top, bottom - top, (extent, band));
728        (bottom, reach)
729    }
730
731    // Collects the pieces of line `index`, with an ellipsis where it does
732    // not fit and the options ask for one, or the hyphen of a hyphenated
733    // line, and narrows the layout's hold to the widths that keep the
734    // ellipsis where it is. True when the line shows an ellipsis.
735    fn collect_line(
736        &mut self,
737        input: &LayoutInput<'_>,
738        options: &TextLayoutOptions,
739        resolver: &FontFamilyResolver,
740        (index, line): (usize, &LineText),
741        layout: &mut LayoutBody,
742    ) -> bool {
743        self.pieces.clear();
744        let last = index + 1 == self.lines.len();
745        let visible_width = self.measures.widths.width(line.range.start, line.visible);
746        let overflows = visible_width > line.room;
747        let ellipsis = ellipsis_at(options).filter(|_| last);
748        let shown = ellipsis.filter(|_| overflows || (last && layout.did_overflow_height));
749        if let Some(hold) = &mut layout.hold {
750            if ellipsis.is_some() && shown.is_none() {
751                hold.narrow(visible_width + line.indent, f32::INFINITY);
752            }
753            if shown.is_some() && overflows {
754                hold.narrow(f32::NEG_INFINITY, visible_width + line.indent);
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.room - ellipsis.width).max(0.0);
793        let text = input.text;
794        let (head, tail) = match at {
795            EllipsisAt::End => {
796                let cut = self
797                    .measures
798                    .widths
799                    .last_fitting(range.start, visible, room);
800                if let Some(hold) = &mut layout.hold {
801                    // The cut stays while its text fits and one more cluster
802                    // does not.
803                    let edge = line.indent + ellipsis.width;
804                    let min = if cut > range.start {
805                        self.measures.widths.width(range.start, cut) + edge
806                    } else {
807                        f32::NEG_INFINITY
808                    };
809                    let max = match self.measures.widths.next_boundary(cut) {
810                        Some(next) if cut < visible => {
811                            self.measures.widths.width(range.start, next) + edge
812                        }
813                        _ => f32::INFINITY,
814                    };
815                    hold.narrow(min, max);
816                }
817                (visible_end(text, range.start, cut), range.end)
818            }
819            EllipsisAt::Start => {
820                layout.hold = None;
821                (
822                    range.start,
823                    self.measures
824                        .widths
825                        .first_fitting(range.start, visible, room),
826                )
827            }
828            EllipsisAt::Middle => {
829                // The end takes up to half the room and the start the rest,
830                // as Android cuts a middle ellipsis.
831                layout.hold = None;
832                let tail = self
833                    .measures
834                    .widths
835                    .first_fitting(range.start, visible, room / 2.0);
836                let used = self.measures.widths.width(tail, visible);
837                (
838                    self.measures
839                        .widths
840                        .last_fitting(range.start, tail, room - used),
841                    tail,
842                )
843            }
844        };
845        set_cluster(&mut self.glyphs.glyphs_mut()[ellipsis.glyphs.clone()], head);
846        ellipsis.text = head..head;
847        self.collect_pieces(input, range.start..head, visible, layout);
848        self.pieces.push(ellipsis);
849        if tail < line.drawn {
850            self.collect_pieces(input, tail..line.drawn, visible, layout);
851        }
852        true
853    }
854
855    // The ellipsis in the typeface that `span` resolves it to, at `level`.
856    fn ellipsis_piece(
857        &mut self,
858        input: &LayoutInput<'_>,
859        resolver: &FontFamilyResolver,
860        span: usize,
861        level: BidiLevel,
862        layout: &mut LayoutBody,
863    ) -> Option<Piece> {
864        let style = &input.spans[span];
865        let mut typeface = None;
866        resolver.typeface_runs(ELLIPSIS, style.font, |_, face| {
867            typeface.get_or_insert(face);
868        });
869        let typeface = &resolver.typefaces()[typeface?];
870        let item = TextItem {
871            range: 0..ELLIPSIS.len(),
872            level,
873            paragraph_level: level,
874            script: Script::COMMON,
875            typeface,
876            span,
877        };
878        let start = self.glyphs.len();
879        let width = shape_inserted(&mut self.shaper, input, &item, ELLIPSIS, &mut self.glyphs);
880        Some(Piece {
881            width,
882            text: 0..0,
883            glyphs: start..self.glyphs.len(),
884            face: face_index(&mut layout.faces, typeface),
885            size: style.size,
886            scale: scale(style, typeface),
887            spacing: 0.0,
888            baseline_shift: style.baseline_shift,
889            level,
890        })
891    }
892
893    // Adds the hyphen of a line hyphenated at `at`, in the typeface and
894    // settings of the text before it, as Android draws it.
895    fn push_hyphen(&mut self, input: &LayoutInput<'_>, at: usize, layout: &mut LayoutBody) {
896        let Some(item) = self.items.get(item_at(&self.items, at.saturating_sub(1))) else {
897            return;
898        };
899        let span = &input.spans[item.span];
900        let face = &layout.faces[item.face];
901        let hyphen = hyphen(face);
902        let start = self.glyphs.len();
903        let width = shape_inserted(
904            &mut self.shaper,
905            input,
906            &item.inserted(hyphen, face),
907            hyphen,
908            &mut self.glyphs,
909        );
910        set_cluster(&mut self.glyphs.glyphs_mut()[start..], at);
911        self.pieces.push(Piece {
912            width,
913            text: at..at,
914            glyphs: start..self.glyphs.len(),
915            face: item.face,
916            size: span.size,
917            scale: scale(span, face),
918            spacing: 0.0,
919            baseline_shift: span.baseline_shift,
920            level: item.level,
921        });
922    }
923
924    // Adds the parts of the items inside `part` of a line, in text order,
925    // the trailing whitespace from `visible` on at the paragraph's level
926    // (rule L1 of UAX #9).
927    fn collect_pieces(
928        &mut self,
929        input: &LayoutInput<'_>,
930        part: Range<usize>,
931        visible: usize,
932        layout: &mut LayoutBody,
933    ) {
934        let Self {
935            items,
936            shaper,
937            pieces,
938            glyphs: all,
939            ..
940        } = self;
941        let first = items.partition_point(|item| item.text.end <= part.start);
942        for item in &items[first..] {
943            if item.text.start >= part.end {
944                break;
945            }
946            let start = item.text.start.max(part.start);
947            let end = item.text.end.min(part.end);
948            let middle = visible.clamp(start, end);
949            for (sub, level) in [
950                (start..middle, item.level),
951                (middle..end, item.paragraph_level),
952            ] {
953                if sub.is_empty() {
954                    continue;
955                }
956                let glyphs = part_glyphs(
957                    shaper,
958                    input,
959                    item,
960                    sub.clone(),
961                    part.clone(),
962                    &layout.faces,
963                    all,
964                );
965                let scale = scale(&input.spans[item.span], &layout.faces[item.face]);
966                pieces.push(Piece {
967                    width: spaced_width(&all[glyphs.clone()], scale, item.spacing),
968                    text: sub,
969                    glyphs,
970                    face: item.face,
971                    size: input.spans[item.span].size,
972                    scale,
973                    spacing: item.spacing,
974                    baseline_shift: input.spans[item.span].baseline_shift,
975                    level,
976                });
977            }
978        }
979    }
980
981    // The base level of the paragraph that holds `offset`.
982    fn paragraph_level(&self, input: &LayoutInput<'_>, offset: usize) -> BidiLevel {
983        self.items
984            .get(item_at(&self.items, offset))
985            .or_else(|| self.items.last())
986            .map_or_else(
987                || match input.paragraph.text_direction.resolve("") {
988                    ResolvedTextDirection::Ltr => BidiLevel::LTR,
989                    ResolvedTextDirection::Rtl => BidiLevel::RTL,
990                },
991                |item| item.paragraph_level,
992            )
993    }
994}
995
996// A line count as a float; counts beyond `u16` saturate.
997fn lines_f32(count: usize) -> f32 {
998    f32::from(u16::try_from(count).unwrap_or(u16::MAX))
999}