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#[derive(Default)]
48pub struct TextLayouter {
49 itemizer: Itemizer,
50 shaper: Shaper,
51 breaker: LineBreaker,
52 balancer: LineBalancer,
53 items: Vec<ShapedItem>,
54 measures: ParagraphMeasures,
57 lines: Vec<BrokenLine>,
58 pieces: Vec<Piece>,
59 glyphs: ShapedGlyphs,
62 faces: SmallVec<[Typeface; 1]>,
65 line_box: LastLineBox,
66 #[cfg(feature = "hyphenation")]
67 hyphenator: Hyphenator,
68 #[cfg(feature = "hyphenation")]
71 hyphenated: Vec<BreakOpportunity>,
72 #[cfg(feature = "hyphenation")]
73 points: Vec<usize>,
74}
75
76const SCRATCH_GLYPHS: usize = 4096;
79const PARAGRAPH_LAST_LINE: f32 = 0.16;
82
83impl TextLayouter {
84 pub fn layout(&mut self, input: &LayoutInput<'_>, resolver: &FontFamilyResolver) -> TextLayout {
86 self.layout_taking(input, resolver, None)
87 }
88
89 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 #[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 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 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 #[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 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 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 let fits = unbroken.filter(|&line| line <= wrap.first);
207 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 #[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 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 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 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 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 fn keep_glyph_room(&mut self) {
358 if self.glyphs.capacity() > SCRATCH_GLYPHS {
359 self.glyphs = ShapedGlyphs::default();
360 }
361 }
362
363 fn finish_lines(
368 &mut self,
369 input: &LayoutInput<'_>,
370 options: &TextLayoutOptions,
371 mut top: f32,
372 layout: &mut LayoutBody,
373 ) {
374 if layout.glyphs.len() != self.glyphs.len() {
376 layout.glyphs = LaidGlyphs::new(&self.glyphs);
377 }
378 self.keep_glyph_room();
379 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 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 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 pub fn set_memory_headroom(&mut self, headroom: MemoryHeadroom) {
422 self.shaper.words.set_headroom(headroom);
423 }
424
425 #[cfg(feature = "hyphenation")]
427 pub fn hyphenator_mut(&mut self) -> &mut Hyphenator {
428 &mut self.hyphenator
429 }
430
431 #[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 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
996fn lines_f32(count: usize) -> f32 {
998 f32::from(u16::try_from(count).unwrap_or(u16::MAX))
999}