1use crate::pretext_layout_line_text::materialize_line;
4use crate::pretext_layout_measurement::{get_engine_profile, EngineProfile};
5use crate::{
6 LayoutCursor, LayoutLine, LayoutLineRange, LayoutLinesResult, LayoutResult, LineStats,
7 PreparedText, PreparedTextWithSegments, PretextLayout, SegmentBreakKind,
8};
9
10#[derive(Clone, Copy, Debug)]
11pub(crate) struct InternalLine {
12 pub start: LayoutCursor,
13 pub visible_end: LayoutCursor,
14 pub end: LayoutCursor,
15 pub width: f64,
16 pub append_hyphen: bool,
17}
18
19#[derive(Clone, Copy)]
20struct PendingBreak {
21 end_segment_index: usize,
22 fit_width: f64,
23 paint_width: f64,
24 kind: SegmentBreakKind,
25}
26
27struct LineState {
30 start: LayoutCursor,
31 end: LayoutCursor,
32 width: f64,
33 has_content: bool,
34 pending: Option<PendingBreak>,
35}
36
37impl LineState {
38 fn new(start: LayoutCursor) -> Self {
39 Self {
40 start,
41 end: start,
42 width: 0.0,
43 has_content: false,
44 pending: None,
45 }
46 }
47
48 fn start_segment(&mut self, segment_index: usize, width: f64, reset_start: bool) {
49 if reset_start {
50 self.start = cursor(segment_index, 0);
51 }
52 self.end = cursor(segment_index + 1, 0);
53 self.width = width;
54 self.has_content = true;
55 }
56
57 fn start_grapheme(
58 &mut self,
59 segment_index: usize,
60 grapheme_index: usize,
61 width: f64,
62 reset_start: bool,
63 ) {
64 if reset_start {
65 self.start = cursor(segment_index, grapheme_index);
66 }
67 self.end = cursor(segment_index, grapheme_index + 1);
68 self.width = width;
69 self.has_content = true;
70 }
71
72 fn append_whole(&mut self, segment_index: usize, width: f64, reset_start: bool) {
73 if self.has_content {
74 self.width += width;
75 self.end = cursor(segment_index + 1, 0);
76 } else {
77 self.start_segment(segment_index, width, reset_start);
78 }
79 }
80
81 fn update_pending(
82 &mut self,
83 prepared: &PreparedText,
84 segment_index: usize,
85 width: f64,
86 simple: bool,
87 ) {
88 let kind = prepared.kinds[segment_index];
89 if !can_break_after(kind) {
90 return;
91 }
92 let (fit, paint) = if simple {
93 (0.0, 0.0)
94 } else if kind == SegmentBreakKind::Tab {
95 (0.0, width)
96 } else {
97 (
98 prepared.line_end_fit_advances[segment_index],
99 prepared.line_end_paint_advances[segment_index],
100 )
101 };
102 self.pending = Some(PendingBreak {
103 end_segment_index: segment_index + 1,
104 fit_width: self.width - width + fit,
105 paint_width: self.width - width + paint,
106 kind,
107 });
108 }
109
110 fn finish(
111 &self,
112 end: Option<LayoutCursor>,
113 width: Option<f64>,
114 append_hyphen: bool,
115 ) -> Option<InternalLine> {
116 self.has_content.then(|| {
117 let end = end.unwrap_or(self.end);
118 InternalLine {
119 start: self.start,
120 visible_end: end,
121 end,
122 width: width.unwrap_or(self.width),
123 append_hyphen,
124 }
125 })
126 }
127
128 fn emit(
129 &mut self,
130 end: Option<LayoutCursor>,
131 width: Option<f64>,
132 append_hyphen: bool,
133 on_line: &mut impl FnMut(InternalLine),
134 count: &mut usize,
135 ) {
136 if let Some(line) = self.finish(end, width, append_hyphen) {
137 *count += 1;
138 on_line(line);
139 }
140 self.width = 0.0;
141 self.has_content = false;
142 self.pending = None;
143 }
144
145 fn is_after_pending(&self, pending: PendingBreak) -> bool {
146 self.end.segment_index > pending.end_segment_index
147 || (self.end.segment_index == pending.end_segment_index && self.end.grapheme_index > 0)
148 }
149}
150
151const fn cursor(segment_index: usize, grapheme_index: usize) -> LayoutCursor {
152 LayoutCursor {
153 segment_index,
154 grapheme_index,
155 }
156}
157
158impl PretextLayout {
159 pub fn layout(prepared: &PreparedText, max_width: f64, line_height: f64) -> LayoutResult {
161 let line_count = count_prepared_lines(prepared, max_width);
162 LayoutResult {
163 line_count,
164 height: line_count as f64 * line_height,
165 }
166 }
167
168 pub fn layout_with_lines(
170 prepared: &PreparedTextWithSegments,
171 max_width: f64,
172 line_height: f64,
173 ) -> LayoutLinesResult {
174 let mut lines = Vec::new();
175 let line_count = walk_prepared_lines(prepared, max_width, |line| {
176 lines.push(materialize_line(prepared, line))
177 });
178 LayoutLinesResult {
179 line_count,
180 height: line_count as f64 * line_height,
181 lines,
182 }
183 }
184
185 pub fn layout_next_line(
188 prepared: &PreparedTextWithSegments,
189 start: LayoutCursor,
190 max_width: f64,
191 ) -> Option<LayoutLine> {
192 try_step_line(prepared, start, max_width).map(|line| materialize_line(prepared, line))
193 }
194
195 pub fn layout_next_line_range(
197 prepared: &PreparedTextWithSegments,
198 start: LayoutCursor,
199 max_width: f64,
200 ) -> Option<LayoutLineRange> {
201 try_step_line(prepared, start, max_width).map(|line| LayoutLineRange {
202 width: line.width,
203 start: line.start,
204 end: line.end,
205 })
206 }
207
208 pub fn materialize_line_range(
211 prepared: &PreparedTextWithSegments,
212 line: &LayoutLineRange,
213 ) -> LayoutLine {
214 materialize_line(
215 prepared,
216 InternalLine {
217 start: line.start,
218 visible_end: line.end,
219 end: line.end,
220 width: line.width,
221 append_hyphen: false,
222 },
223 )
224 }
225
226 pub fn walk_line_ranges(
227 prepared: &PreparedTextWithSegments,
228 max_width: f64,
229 mut on_line: impl FnMut(LayoutLineRange),
230 ) -> usize {
231 let mut start = cursor(0, 0);
232 let mut count = 0;
233 while let Some(line) = Self::layout_next_line_range(prepared, start, max_width) {
234 start = line.end;
235 count += 1;
236 on_line(line);
237 }
238 count
239 }
240
241 pub fn measure_line_stats(prepared: &PreparedTextWithSegments, max_width: f64) -> LineStats {
242 let mut max_line_width = 0.0_f64;
243 let line_count = walk_prepared_lines(prepared, max_width, |line| {
244 max_line_width = max_line_width.max(line.width)
245 });
246 LineStats {
247 line_count,
248 max_line_width,
249 }
250 }
251
252 pub fn measure_natural_width(prepared: &PreparedTextWithSegments) -> f64 {
253 Self::measure_line_stats(prepared, f64::INFINITY).max_line_width
254 }
255}
256
257pub(crate) fn count_prepared_lines(prepared: &PreparedText, max_width: f64) -> usize {
258 walk_prepared_lines(prepared, max_width, |_| {})
259}
260
261pub(crate) fn walk_prepared_lines(
263 prepared: &PreparedText,
264 max_width: f64,
265 mut on_line: impl FnMut(InternalLine),
266) -> usize {
267 if prepared.simple_line_walk_fast_path {
268 walk_prepared_lines_simple(prepared, max_width, &mut on_line)
269 } else {
270 walk_prepared_lines_general(prepared, max_width, &mut on_line)
271 }
272}
273
274fn append_breakable_walk(
275 state: &mut LineState,
276 prepared: &PreparedText,
277 start: LayoutCursor,
278 max_width: f64,
279 profile: EngineProfile,
280 on_line: &mut impl FnMut(InternalLine),
281 count: &mut usize,
282) {
283 let segment_index = start.segment_index;
284 let start_grapheme_index = start.grapheme_index;
285 let Some(widths) = prepared.breakable_widths[segment_index].as_deref() else {
286 return;
287 };
288 let prefixes = prepared.breakable_prefix_widths[segment_index].as_deref();
289 for grapheme_index in start_grapheme_index..widths.len() {
290 let width = get_breakable_advance(
291 widths,
292 prefixes,
293 grapheme_index,
294 profile.prefer_prefix_widths_for_breakable_runs,
295 );
296 if !state.has_content {
297 state.start_grapheme(segment_index, grapheme_index, width, true);
298 } else if state.width + width > max_width + profile.line_fit_epsilon {
299 state.emit(None, None, false, on_line, count);
300 state.start_grapheme(segment_index, grapheme_index, width, true);
301 } else {
302 state.width += width;
303 state.end = cursor(segment_index, grapheme_index + 1);
304 }
305 }
306 if state.has_content && state.end == cursor(segment_index, widths.len()) {
307 state.end = cursor(segment_index + 1, 0);
308 }
309}
310
311fn walk_prepared_lines_simple(
312 prepared: &PreparedText,
313 max_width: f64,
314 on_line: &mut impl FnMut(InternalLine),
315) -> usize {
316 let profile = get_engine_profile();
317 let epsilon = profile.line_fit_epsilon;
318 let mut count = 0;
319 let mut state = LineState::new(cursor(0, 0));
320 let mut index = 0;
321 while index < prepared.widths.len() {
322 if !state.has_content {
323 while index < prepared.widths.len() && is_skippable_at_line_start(prepared.kinds[index])
324 {
325 index += 1;
326 }
327 if index == prepared.widths.len() {
328 break;
329 }
330 }
331 let width = prepared.widths[index];
332 let kind = prepared.kinds[index];
333 if !state.has_content {
334 if width > max_width + epsilon && prepared.breakable_widths[index].is_some() {
335 append_breakable_walk(
336 &mut state,
337 prepared,
338 cursor(index, 0),
339 max_width,
340 profile,
341 on_line,
342 &mut count,
343 );
344 } else {
345 state.start_segment(index, width, true);
346 }
347 state.update_pending(prepared, index, width, true);
348 index += 1;
349 continue;
350 }
351 if state.width + width > max_width + epsilon {
352 if can_break_after(kind) {
353 state.append_whole(index, width, true);
354 state.emit(
355 Some(cursor(index + 1, 0)),
356 Some(state.width - width),
357 false,
358 on_line,
359 &mut count,
360 );
361 index += 1;
362 continue;
363 }
364 if let Some(pending) = state.pending {
365 if state.is_after_pending(pending) {
366 state.emit(None, None, false, on_line, &mut count);
367 } else {
368 state.emit(
369 Some(cursor(pending.end_segment_index, 0)),
370 Some(pending.paint_width),
371 false,
372 on_line,
373 &mut count,
374 );
375 }
376 continue;
377 }
378 state.emit(None, None, false, on_line, &mut count);
379 if width > max_width + epsilon && prepared.breakable_widths[index].is_some() {
380 append_breakable_walk(
381 &mut state,
382 prepared,
383 cursor(index, 0),
384 max_width,
385 profile,
386 on_line,
387 &mut count,
388 );
389 index += 1;
390 }
391 continue;
392 }
393 state.append_whole(index, width, true);
394 state.update_pending(prepared, index, width, true);
395 index += 1;
396 }
397 state.emit(None, None, false, on_line, &mut count);
398 count
399}
400
401fn continue_soft_hyphen_walk(
402 state: &mut LineState,
403 prepared: &PreparedText,
404 index: usize,
405 max_width: f64,
406 profile: EngineProfile,
407 on_line: &mut impl FnMut(InternalLine),
408 count: &mut usize,
409) -> bool {
410 if !matches!(
411 state.pending,
412 Some(PendingBreak {
413 kind: SegmentBreakKind::SoftHyphen,
414 ..
415 })
416 ) {
417 return false;
418 }
419 let Some(widths) = prepared.breakable_widths[index].as_deref() else {
420 return false;
421 };
422 let prefixes = prepared.breakable_prefix_widths[index]
423 .as_deref()
424 .filter(|_| profile.prefer_prefix_widths_for_breakable_runs);
425 let (fit_count, fitted_width) = fit_soft_hyphen_break(
426 prefixes.unwrap_or(widths),
427 state.width,
428 max_width,
429 profile.line_fit_epsilon,
430 prepared.discretionary_hyphen_width,
431 prefixes.is_some(),
432 );
433 if fit_count == 0 {
434 return false;
435 }
436 state.width = fitted_width;
437 state.end = cursor(index, fit_count);
438 state.pending = None;
439 if fit_count == widths.len() {
440 state.end = cursor(index + 1, 0);
441 } else {
442 state.emit(
443 Some(cursor(index, fit_count)),
444 Some(fitted_width + prepared.discretionary_hyphen_width),
445 true,
446 on_line,
447 count,
448 );
449 append_breakable_walk(
450 state,
451 prepared,
452 cursor(index, fit_count),
453 max_width,
454 profile,
455 on_line,
456 count,
457 );
458 }
459 true
460}
461
462fn walk_prepared_lines_general(
463 prepared: &PreparedText,
464 max_width: f64,
465 on_line: &mut impl FnMut(InternalLine),
466) -> usize {
467 let profile = get_engine_profile();
468 let epsilon = profile.line_fit_epsilon;
469 let mut count = 0;
470 for chunk in &prepared.chunks {
471 if chunk.start_segment_index == chunk.end_segment_index {
472 let end = cursor(chunk.consumed_end_segment_index, 0);
473 on_line(InternalLine {
474 start: cursor(chunk.start_segment_index, 0),
475 visible_end: end,
476 end,
477 width: 0.0,
478 append_hyphen: false,
479 });
480 count += 1;
481 continue;
482 }
483 let mut state = LineState::new(cursor(chunk.start_segment_index, 0));
484 let mut index = chunk.start_segment_index;
485 while index < chunk.end_segment_index {
486 let kind = prepared.kinds[index];
487 let mut width = segment_advance(prepared, index, state.width);
488 if kind == SegmentBreakKind::SoftHyphen {
489 if state.has_content {
490 state.end = cursor(index + 1, 0);
491 let hyphen_width = state.width + prepared.discretionary_hyphen_width;
492 state.pending = Some(PendingBreak {
493 end_segment_index: index + 1,
494 fit_width: hyphen_width,
495 paint_width: hyphen_width,
496 kind,
497 });
498 }
499 index += 1;
500 continue;
501 }
502 if !state.has_content {
503 while index < chunk.end_segment_index
504 && is_skippable_at_line_start(prepared.kinds[index])
505 {
506 index += 1;
507 }
508 if index == chunk.end_segment_index {
509 break;
510 }
511 width = segment_advance(prepared, index, state.width);
512 if width > max_width + epsilon && prepared.breakable_widths[index].is_some() {
513 append_breakable_walk(
514 &mut state,
515 prepared,
516 cursor(index, 0),
517 max_width,
518 profile,
519 on_line,
520 &mut count,
521 );
522 } else {
523 state.start_segment(index, width, true);
524 }
525 state.update_pending(prepared, index, width, false);
526 index += 1;
527 continue;
528 }
529 if state.width + width > max_width + epsilon {
530 let current_fit = state.width
531 + if kind == SegmentBreakKind::Tab {
532 0.0
533 } else {
534 prepared.line_end_fit_advances[index]
535 };
536 let current_paint = state.width
537 + if kind == SegmentBreakKind::Tab {
538 width
539 } else {
540 prepared.line_end_paint_advances[index]
541 };
542 if let Some(pending) = state.pending.filter(|p| {
543 p.kind == SegmentBreakKind::SoftHyphen
544 && profile.prefer_early_soft_hyphen_break
545 && p.fit_width <= max_width + epsilon
546 }) {
547 state.emit(
548 Some(cursor(pending.end_segment_index, 0)),
549 Some(pending.paint_width),
550 true,
551 on_line,
552 &mut count,
553 );
554 continue;
555 }
556 if continue_soft_hyphen_walk(
557 &mut state, prepared, index, max_width, profile, on_line, &mut count,
558 ) {
559 index += 1;
560 continue;
561 }
562 if can_break_after(kind) && current_fit <= max_width + epsilon {
563 state.append_whole(index, width, true);
564 state.emit(
565 Some(cursor(index + 1, 0)),
566 Some(current_paint),
567 false,
568 on_line,
569 &mut count,
570 );
571 index += 1;
572 continue;
573 }
574 if let Some(pending) = state.pending.filter(|p| p.fit_width <= max_width + epsilon)
575 {
576 if state.is_after_pending(pending) {
577 state.emit(None, None, false, on_line, &mut count);
578 } else {
579 state.emit(
580 Some(cursor(pending.end_segment_index, 0)),
581 Some(pending.paint_width),
582 pending.kind == SegmentBreakKind::SoftHyphen,
583 on_line,
584 &mut count,
585 );
586 }
587 continue;
588 }
589 state.emit(None, None, false, on_line, &mut count);
590 if width > max_width + epsilon && prepared.breakable_widths[index].is_some() {
591 append_breakable_walk(
592 &mut state,
593 prepared,
594 cursor(index, 0),
595 max_width,
596 profile,
597 on_line,
598 &mut count,
599 );
600 index += 1;
601 }
602 continue;
603 }
604 state.append_whole(index, width, true);
605 state.update_pending(prepared, index, width, false);
606 index += 1;
607 }
608 let paint = state
609 .pending
610 .filter(|p| p.end_segment_index == chunk.consumed_end_segment_index)
611 .map_or(state.width, |p| p.paint_width);
612 state.emit(
613 Some(cursor(chunk.consumed_end_segment_index, 0)),
614 Some(paint),
615 false,
616 on_line,
617 &mut count,
618 );
619 }
620 count
621}
622
623pub(crate) fn try_step_line(
624 prepared: &PreparedText,
625 start: LayoutCursor,
626 max_width: f64,
627) -> Option<InternalLine> {
628 let normalized = normalize_line_start(prepared, start)?;
629 if prepared.simple_line_walk_fast_path {
630 try_step_line_simple(prepared, normalized, max_width)
631 } else {
632 try_step_line_general(prepared, normalized, max_width)
633 }
634}
635
636fn append_breakable_step(
637 state: &mut LineState,
638 prepared: &PreparedText,
639 index: usize,
640 first: usize,
641 max_width: f64,
642 profile: EngineProfile,
643) -> Option<InternalLine> {
644 let widths = prepared.breakable_widths[index].as_deref()?;
645 let prefixes = prepared.breakable_prefix_widths[index].as_deref();
646 for grapheme_index in first..widths.len() {
647 let width = get_breakable_advance(
648 widths,
649 prefixes,
650 grapheme_index,
651 profile.prefer_prefix_widths_for_breakable_runs,
652 );
653 if !state.has_content {
654 state.start_grapheme(index, grapheme_index, width, false);
655 } else if state.width + width > max_width + profile.line_fit_epsilon {
656 return state.finish(None, None, false);
657 } else {
658 state.width += width;
659 state.end = cursor(index, grapheme_index + 1);
660 }
661 }
662 if state.has_content && state.end == cursor(index, widths.len()) {
663 state.end = cursor(index + 1, 0);
664 }
665 None
666}
667
668fn try_step_line_simple(
669 prepared: &PreparedText,
670 start: LayoutCursor,
671 max_width: f64,
672) -> Option<InternalLine> {
673 let profile = get_engine_profile();
674 let epsilon = profile.line_fit_epsilon;
675 let mut state = LineState::new(start);
676 for index in start.segment_index..prepared.widths.len() {
677 let width = prepared.widths[index];
678 let kind = prepared.kinds[index];
679 let first = if index == start.segment_index {
680 start.grapheme_index
681 } else {
682 0
683 };
684 if !state.has_content {
685 if first > 0
686 || (width > max_width + epsilon && prepared.breakable_widths[index].is_some())
687 {
688 if let Some(line) =
689 append_breakable_step(&mut state, prepared, index, first, max_width, profile)
690 {
691 return Some(line);
692 }
693 } else {
694 state.start_segment(index, width, false);
695 }
696 state.update_pending(prepared, index, width, true);
697 continue;
698 }
699 if state.width + width > max_width + epsilon {
700 if can_break_after(kind) {
701 state.append_whole(index, width, false);
702 return state.finish(Some(cursor(index + 1, 0)), Some(state.width - width), false);
703 }
704 if let Some(pending) = state.pending {
705 return if state.is_after_pending(pending) {
706 state.finish(None, None, false)
707 } else {
708 state.finish(
709 Some(cursor(pending.end_segment_index, 0)),
710 Some(pending.paint_width),
711 false,
712 )
713 };
714 }
715 return state.finish(None, None, false);
716 }
717 state.append_whole(index, width, false);
718 state.update_pending(prepared, index, width, true);
719 }
720 state.finish(None, None, false)
721}
722
723fn maybe_finish_at_soft_hyphen(
724 state: &mut LineState,
725 prepared: &PreparedText,
726 index: usize,
727 max_width: f64,
728 profile: EngineProfile,
729) -> Option<InternalLine> {
730 let pending = state
731 .pending
732 .filter(|p| p.kind == SegmentBreakKind::SoftHyphen)?;
733 if let Some(widths) = prepared.breakable_widths[index].as_deref() {
734 let prefixes = prepared.breakable_prefix_widths[index]
735 .as_deref()
736 .filter(|_| profile.prefer_prefix_widths_for_breakable_runs);
737 let (fit_count, fitted_width) = fit_soft_hyphen_break(
738 prefixes.unwrap_or(widths),
739 state.width,
740 max_width,
741 profile.line_fit_epsilon,
742 prepared.discretionary_hyphen_width,
743 prefixes.is_some(),
744 );
745 if fit_count == widths.len() {
746 state.width = fitted_width;
747 state.end = cursor(index + 1, 0);
748 state.pending = None;
749 return None;
750 }
751 if fit_count > 0 {
752 return state.finish(
753 Some(cursor(index, fit_count)),
754 Some(fitted_width + prepared.discretionary_hyphen_width),
755 true,
756 );
757 }
758 }
759 if pending.fit_width <= max_width + profile.line_fit_epsilon {
760 return state.finish(
761 Some(cursor(pending.end_segment_index, 0)),
762 Some(pending.paint_width),
763 true,
764 );
765 }
766 None
767}
768
769fn try_step_line_general(
770 prepared: &PreparedText,
771 start: LayoutCursor,
772 max_width: f64,
773) -> Option<InternalLine> {
774 let chunk = &prepared.chunks[find_chunk_index_for_start(prepared, start.segment_index)?];
775 if chunk.start_segment_index == chunk.end_segment_index {
776 let end = cursor(chunk.consumed_end_segment_index, 0);
777 return Some(InternalLine {
778 start: cursor(chunk.start_segment_index, 0),
779 visible_end: end,
780 end,
781 width: 0.0,
782 append_hyphen: false,
783 });
784 }
785 let profile = get_engine_profile();
786 let epsilon = profile.line_fit_epsilon;
787 let mut state = LineState::new(start);
788 for index in start.segment_index..chunk.end_segment_index {
789 let kind = prepared.kinds[index];
790 let first = if index == start.segment_index {
791 start.grapheme_index
792 } else {
793 0
794 };
795 let width = segment_advance(prepared, index, state.width);
796 if kind == SegmentBreakKind::SoftHyphen && first == 0 {
797 if state.has_content {
798 state.end = cursor(index + 1, 0);
799 let hyphen_width = state.width + prepared.discretionary_hyphen_width;
800 state.pending = Some(PendingBreak {
801 end_segment_index: index + 1,
802 fit_width: hyphen_width,
803 paint_width: hyphen_width,
804 kind,
805 });
806 }
807 continue;
808 }
809 if !state.has_content {
810 if first > 0
811 || (width > max_width + epsilon && prepared.breakable_widths[index].is_some())
812 {
813 if let Some(line) =
814 append_breakable_step(&mut state, prepared, index, first, max_width, profile)
815 {
816 return Some(line);
817 }
818 } else {
819 state.start_segment(index, width, false);
820 }
821 state.update_pending(prepared, index, width, false);
822 continue;
823 }
824 if state.width + width > max_width + epsilon {
825 let current_fit = state.width
826 + if kind == SegmentBreakKind::Tab {
827 0.0
828 } else {
829 prepared.line_end_fit_advances[index]
830 };
831 let current_paint = state.width
832 + if kind == SegmentBreakKind::Tab {
833 width
834 } else {
835 prepared.line_end_paint_advances[index]
836 };
837 if let Some(pending) = state.pending.filter(|p| {
838 p.kind == SegmentBreakKind::SoftHyphen
839 && profile.prefer_early_soft_hyphen_break
840 && p.fit_width <= max_width + epsilon
841 }) {
842 return state.finish(
843 Some(cursor(pending.end_segment_index, 0)),
844 Some(pending.paint_width),
845 true,
846 );
847 }
848 if let Some(line) =
849 maybe_finish_at_soft_hyphen(&mut state, prepared, index, max_width, profile)
850 {
851 return Some(line);
852 }
853 if can_break_after(kind) && current_fit <= max_width + epsilon {
854 state.append_whole(index, width, false);
855 return state.finish(Some(cursor(index + 1, 0)), Some(current_paint), false);
856 }
857 if let Some(pending) = state.pending.filter(|p| p.fit_width <= max_width + epsilon) {
858 return if state.is_after_pending(pending) {
859 state.finish(None, None, false)
860 } else {
861 state.finish(
862 Some(cursor(pending.end_segment_index, 0)),
863 Some(pending.paint_width),
864 pending.kind == SegmentBreakKind::SoftHyphen,
865 )
866 };
867 }
868 return state.finish(None, None, false);
869 }
870 state.append_whole(index, width, false);
871 state.update_pending(prepared, index, width, false);
872 }
873 let paint = state
874 .pending
875 .filter(|p| {
876 p.end_segment_index == chunk.consumed_end_segment_index && state.end.grapheme_index == 0
877 })
878 .map_or(state.width, |p| p.paint_width);
879 state.finish(
880 Some(cursor(chunk.consumed_end_segment_index, 0)),
881 Some(paint),
882 false,
883 )
884}
885
886fn find_chunk_index_for_start(prepared: &PreparedText, segment_index: usize) -> Option<usize> {
887 let index = prepared
890 .chunks
891 .partition_point(|chunk| chunk.consumed_end_segment_index <= segment_index);
892 (index < prepared.chunks.len()).then_some(index)
893}
894
895fn normalize_line_start(prepared: &PreparedText, start: LayoutCursor) -> Option<LayoutCursor> {
896 if start.segment_index >= prepared.widths.len() {
897 return None;
898 }
899 if start.grapheme_index > 0 {
900 let widths = prepared.breakable_widths[start.segment_index].as_ref()?;
901 return (start.grapheme_index < widths.len()).then_some(start);
902 }
903 let chunk = &prepared.chunks[find_chunk_index_for_start(prepared, start.segment_index)?];
904 let mut index = start.segment_index.max(chunk.start_segment_index);
905 if chunk.start_segment_index == chunk.end_segment_index && index == chunk.start_segment_index {
906 return Some(cursor(index, 0));
907 }
908 while index < chunk.end_segment_index {
909 if !is_skippable_at_line_start(prepared.kinds[index]) {
910 return Some(cursor(index, 0));
911 }
912 index += 1;
913 }
914 (chunk.consumed_end_segment_index < prepared.widths.len())
915 .then_some(cursor(chunk.consumed_end_segment_index, 0))
916}
917
918pub(crate) fn is_skippable_at_line_start(kind: SegmentBreakKind) -> bool {
919 matches!(
920 kind,
921 SegmentBreakKind::Space | SegmentBreakKind::ZeroWidthBreak | SegmentBreakKind::SoftHyphen
922 )
923}
924
925fn get_breakable_advance(
926 widths: &[f64],
927 prefixes: Option<&[f64]>,
928 index: usize,
929 prefer_prefixes: bool,
930) -> f64 {
931 if let Some(prefixes) = prefixes.filter(|_| prefer_prefixes) {
932 prefixes[index] - if index > 0 { prefixes[index - 1] } else { 0.0 }
933 } else {
934 widths[index]
935 }
936}
937
938fn fit_soft_hyphen_break(
939 widths: &[f64],
940 initial_width: f64,
941 max_width: f64,
942 epsilon: f64,
943 hyphen_width: f64,
944 cumulative: bool,
945) -> (usize, f64) {
946 let mut fit_count = 0;
947 let mut fitted_width = initial_width;
948 while fit_count < widths.len() {
949 let next_width = if cumulative {
950 initial_width + widths[fit_count]
951 } else {
952 fitted_width + widths[fit_count]
953 };
954 let next_line_width = if fit_count + 1 < widths.len() {
955 next_width + hyphen_width
956 } else {
957 next_width
958 };
959 if next_line_width > max_width + epsilon {
960 break;
961 }
962 fitted_width = next_width;
963 fit_count += 1;
964 }
965 (fit_count, fitted_width)
966}
967
968pub(crate) fn can_break_after(kind: SegmentBreakKind) -> bool {
969 matches!(
970 kind,
971 SegmentBreakKind::Space
972 | SegmentBreakKind::PreservedSpace
973 | SegmentBreakKind::Tab
974 | SegmentBreakKind::ZeroWidthBreak
975 | SegmentBreakKind::SoftHyphen
976 )
977}
978
979fn segment_advance(prepared: &PreparedText, index: usize, line_width: f64) -> f64 {
980 if prepared.kinds[index] == SegmentBreakKind::Tab {
981 get_tab_advance(line_width, prepared.tab_stop_advance)
982 } else {
983 prepared.widths[index]
984 }
985}
986
987fn get_tab_advance(line_width: f64, stop: f64) -> f64 {
988 if stop <= 0.0 {
989 return 0.0;
990 }
991 let remainder = line_width % stop;
992 if remainder.abs() <= 1e-6 {
993 stop
994 } else {
995 stop - remainder
996 }
997}