1use std::{
2 borrow::Cow,
3 cell::{Cell, RefCell},
4 hash::Hash,
5 ops::Range,
6 rc::Rc,
7};
8
9use cranpose_core::{NodeId, collections::pass_aged::PassAgedCache};
10use web_time::Instant;
11
12use super::{
13 layout_options::{TextLayoutOptions, TextOverflow},
14 paragraph::{Hyphens, LineBreak},
15 style::TextStyle,
16};
17use crate::{font_scale::FontScaleCurve, text_layout_result::TextLayoutResult};
18
19const ELLIPSIS: &str = "\u{2026}";
20const DEFAULT_FONT_SIZE_SP: f32 = 14.0;
21const WRAP_EPSILON: f32 = 0.5;
22const SCALE_DOWN_SEARCH_STEPS: usize = 14;
23const AUTO_HYPHEN_MIN_SEGMENT_CHARS: usize = 2;
24const AUTO_HYPHEN_MIN_TRAILING_CHARS: usize = 3;
25const AUTO_HYPHEN_PREFERRED_TRAILING_CHARS: usize = 4;
26const TEXT_SERVICE_CACHE_CAPACITY: usize = 8192;
27const TEXT_PREPARED_CACHE_CAPACITY: usize = 1024;
32const TEXT_LAYOUT_TELEMETRY_ENV: &str = "CRANPOSE_TEXT_LAYOUT_TELEMETRY";
33
34fn text_layout_telemetry_enabled() -> bool {
35 cranpose_core::env_flag!(TEXT_LAYOUT_TELEMETRY_ENV)
36}
37
38#[derive(Clone, Copy, Debug, PartialEq)]
39pub struct TextMetrics {
40 pub width: f32,
41 pub height: f32,
42 pub line_height: f32,
44 pub line_count: usize,
46}
47
48#[derive(Clone, Debug)]
49pub struct PreparedTextLayout {
50 pub text: Rc<crate::text::AnnotatedString>,
52 pub visual_style: std::sync::Arc<TextStyle>,
55 pub metrics: TextMetrics,
56 pub alignment_lines: cranpose_ui_layout::AlignmentLines,
58 pub did_overflow: bool,
59 pub render_text: std::cell::OnceCell<std::sync::Arc<crate::text::RenderString>>,
62 pub(crate) wrap_hold: Option<WrapHold>,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq)]
71pub(crate) struct WrapHold {
72 fits: f32,
73 pulls_up: f32,
74}
75
76impl WrapHold {
77 const ANY: Self = Self {
79 fits: f32::NEG_INFINITY,
80 pulls_up: f32::INFINITY,
81 };
82
83 fn narrow(&mut self, fits: f32, pulls_up: f32) {
84 self.fits = self.fits.max(fits);
85 self.pulls_up = self.pulls_up.min(pulls_up);
86 }
87
88 fn holds(self, width: f32) -> bool {
89 self.fits <= width + WRAP_EPSILON && width + WRAP_EPSILON < self.pulls_up
90 }
91
92 fn fit_whole(hold: &mut Option<Self>, width: f32) {
94 if let Some(hold) = hold {
95 hold.narrow(width, f32::INFINITY);
96 }
97 }
98
99 fn settle(hold: Option<Self>, measured_width: f32, max_width: Option<f32>) -> Option<Self> {
103 let mut hold = hold?;
104 hold.narrow(measured_width + WRAP_EPSILON, f32::INFINITY);
105 max_width.filter(|width| hold.holds(*width)).map(|_| hold)
106 }
107}
108
109impl PreparedTextLayout {
110 pub fn render_text(&self) -> std::sync::Arc<crate::text::RenderString> {
114 if let Some(converted) = self.render_text.get() {
115 return std::sync::Arc::clone(converted);
116 }
117 let converted = std::sync::Arc::new(self.text.render_string());
118 let _ = self.render_text.set(std::sync::Arc::clone(&converted));
119 converted
120 }
121}
122
123impl PartialEq for PreparedTextLayout {
124 fn eq(&self, other: &Self) -> bool {
125 self.text == other.text
126 && self.visual_style == other.visual_style
127 && self.metrics == other.metrics
128 && self.alignment_lines == other.alignment_lines
129 && self.did_overflow == other.did_overflow
130 }
131}
132
133#[derive(Clone, Debug, PartialEq)]
134pub struct TextLinePrefixWidths {
135 prefix_widths: Vec<f32>,
136 separator_before: Vec<f32>,
137 non_empty_overhang: f32,
138}
139
140impl TextLinePrefixWidths {
141 pub fn from_parts(
142 prefix_widths: Vec<f32>,
143 separator_before: Vec<f32>,
144 non_empty_overhang: f32,
145 ) -> Option<Self> {
146 if prefix_widths.is_empty() || prefix_widths.len() != separator_before.len() + 1 {
147 return None;
148 }
149 if prefix_widths
150 .iter()
151 .chain(separator_before.iter())
152 .any(|value| !value.is_finite())
153 {
154 return None;
155 }
156 let non_empty_overhang = non_empty_overhang.max(0.0);
157 if !non_empty_overhang.is_finite() {
158 return None;
159 }
160 Some(Self {
161 prefix_widths,
162 separator_before,
163 non_empty_overhang,
164 })
165 }
166
167 pub fn monospaced(char_count: usize, char_width: f32, letter_spacing: f32) -> Option<Self> {
168 if !char_width.is_finite() || !letter_spacing.is_finite() {
169 return None;
170 }
171 let char_width = char_width.max(0.0);
172 let letter_spacing = letter_spacing.max(0.0);
173 let mut prefix_widths = Vec::with_capacity(char_count + 1);
174 let mut separator_before = Vec::with_capacity(char_count);
175 let mut width = 0.0f32;
176 prefix_widths.push(width);
177 for _ in 0..char_count {
178 separator_before.push(0.0);
179 width += char_width + letter_spacing;
180 prefix_widths.push(width);
181 }
182 Self::from_parts(prefix_widths, separator_before, 0.0)
183 }
184
185 pub fn char_count(&self) -> usize {
186 self.separator_before.len()
187 }
188
189 pub fn width_for_char_range(&self, start: usize, end: usize) -> Option<f32> {
190 if start > end || end > self.char_count() {
191 return None;
192 }
193 if start == end {
194 return Some(0.0);
195 }
196 let separator = self.separator_before.get(start).copied().unwrap_or(0.0);
197 Some(
198 (self.prefix_widths[end] - self.prefix_widths[start] - separator).max(0.0)
199 + self.non_empty_overhang,
200 )
201 }
202}
203
204pub trait TextMeasurer: 'static {
205 fn measure(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> TextMetrics;
206
207 fn begin_layout_pass(&self) {}
211
212 fn measure_for_node(
213 &self,
214 node_id: Option<NodeId>,
215 text: &crate::text::AnnotatedString,
216 style: &TextStyle,
217 ) -> TextMetrics {
218 let _ = node_id;
219 self.measure(text, style)
220 }
221
222 fn measure_subsequence(
223 &self,
224 text: &crate::text::AnnotatedString,
225 range: Range<usize>,
226 style: &TextStyle,
227 ) -> TextMetrics {
228 self.measure(&text.subsequence(range), style)
229 }
230
231 fn measure_subsequence_for_node(
232 &self,
233 node_id: Option<NodeId>,
234 text: &crate::text::AnnotatedString,
235 range: Range<usize>,
236 style: &TextStyle,
237 ) -> TextMetrics {
238 let _ = node_id;
239 self.measure_subsequence(text, range, style)
240 }
241
242 fn measure_line_prefix_widths(
244 &self,
245 text: &crate::text::AnnotatedString,
246 line_range: Range<usize>,
247 style: &TextStyle,
248 ) -> Option<TextLinePrefixWidths> {
249 let _ = text;
250 let _ = line_range;
251 let _ = style;
252 None
253 }
254
255 fn measure_line_width(
256 &self,
257 text: &crate::text::AnnotatedString,
258 line_range: Range<usize>,
259 style: &TextStyle,
260 ) -> Option<f32> {
261 let _ = text;
262 let _ = line_range;
263 let _ = style;
264 None
265 }
266
267 fn line_height(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> f32 {
268 self.measure(text, style).line_height
269 }
270
271 fn glyph_line_box(&self, style: &TextStyle) -> Option<(f32, f32)> {
280 let _ = style;
281 None
282 }
283
284 fn first_baseline(&self, style: &TextStyle) -> Option<f32> {
290 let _ = style;
291 None
292 }
293
294 fn line_box(&self, style: &TextStyle) -> Option<crate::text::LineBox> {
301 let _ = style;
302 None
303 }
304
305 fn visit_line_boxes(
310 &self,
311 text: &crate::text::AnnotatedString,
312 style: &TextStyle,
313 visit: &mut dyn FnMut(crate::text::LineBox),
314 ) -> Option<()> {
315 if !text.span_styles.is_empty() {
316 return None;
317 }
318 let line_box = self.line_box(style)?;
319 for _ in text.text.split('\n') {
320 visit(line_box);
321 }
322 Some(())
323 }
324
325 fn line_height_for_node(
326 &self,
327 node_id: Option<NodeId>,
328 text: &crate::text::AnnotatedString,
329 style: &TextStyle,
330 ) -> f32 {
331 let _ = node_id;
332 self.line_height(text, style)
333 }
334
335 fn get_offset_for_position(
336 &self,
337 text: &crate::text::AnnotatedString,
338 style: &TextStyle,
339 x: f32,
340 y: f32,
341 ) -> usize;
342
343 fn get_cursor_x_for_offset(
344 &self,
345 text: &crate::text::AnnotatedString,
346 style: &TextStyle,
347 offset: usize,
348 ) -> f32;
349
350 fn layout(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> TextLayoutResult;
351
352 fn choose_auto_hyphen_break(
358 &self,
359 _line: &str,
360 _style: &TextStyle,
361 _segment_start_char: usize,
362 _measured_break_char: usize,
363 ) -> Option<usize> {
364 None
365 }
366
367 fn measure_with_options(
368 &self,
369 text: &crate::text::AnnotatedString,
370 style: &TextStyle,
371 options: TextLayoutOptions,
372 max_width: Option<f32>,
373 ) -> TextMetrics {
374 self.prepare_with_options(text, style, options, max_width)
375 .metrics
376 }
377
378 fn measure_with_options_for_node(
379 &self,
380 node_id: Option<NodeId>,
381 text: &crate::text::AnnotatedString,
382 style: &TextStyle,
383 options: TextLayoutOptions,
384 max_width: Option<f32>,
385 ) -> TextMetrics {
386 self.prepare_with_options_for_node(node_id, text, style, options, max_width)
387 .metrics
388 }
389
390 fn prepare_with_options(
391 &self,
392 text: &crate::text::AnnotatedString,
393 style: &TextStyle,
394 options: TextLayoutOptions,
395 max_width: Option<f32>,
396 ) -> PreparedTextLayout {
397 self.prepare_with_options_fallback(text, style, options, max_width)
398 }
399
400 fn prepare_with_options_for_node(
401 &self,
402 node_id: Option<NodeId>,
403 text: &crate::text::AnnotatedString,
404 style: &TextStyle,
405 options: TextLayoutOptions,
406 max_width: Option<f32>,
407 ) -> PreparedTextLayout {
408 prepare_text_layout_with_measurer_for_node(self, node_id, text, style, options, max_width)
409 }
410
411 fn prepare_with_options_fallback(
412 &self,
413 text: &crate::text::AnnotatedString,
414 style: &TextStyle,
415 options: TextLayoutOptions,
416 max_width: Option<f32>,
417 ) -> PreparedTextLayout {
418 prepare_text_layout_fallback(self, text, style, options, max_width)
419 }
420}
421
422#[derive(Default)]
423struct MonospacedTextMeasurer;
424
425impl MonospacedTextMeasurer {
426 const DEFAULT_SIZE: f32 = 14.0;
427 const CHAR_WIDTH_RATIO: f32 = 0.6;
428
429 fn get_metrics(style: &TextStyle) -> (f32, f32) {
430 let font_size = style.resolve_font_size(Self::DEFAULT_SIZE);
431 let line_height = style.resolve_line_height(Self::DEFAULT_SIZE, font_size);
432 let letter_spacing = style.resolve_letter_spacing(Self::DEFAULT_SIZE).max(0.0);
433 (
434 (font_size * Self::CHAR_WIDTH_RATIO) + letter_spacing,
435 line_height,
436 )
437 }
438}
439
440impl TextMeasurer for MonospacedTextMeasurer {
441 fn measure(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> TextMetrics {
442 let (char_width, line_height) = Self::get_metrics(style);
443
444 let lines: Vec<&str> = text.text.split('\n').collect();
445 let line_count = lines.len().max(1);
446
447 let width = lines
448 .iter()
449 .map(|line| line.chars().count() as f32 * char_width)
450 .fold(0.0_f32, f32::max);
451
452 TextMetrics {
453 width,
454 height: line_count as f32 * line_height,
455 line_height,
456 line_count,
457 }
458 }
459
460 fn measure_subsequence(
461 &self,
462 text: &crate::text::AnnotatedString,
463 range: Range<usize>,
464 style: &TextStyle,
465 ) -> TextMetrics {
466 let (char_width, line_height) = Self::get_metrics(style);
467 let slice = &text.text[range];
468 let line_count = slice.split('\n').count().max(1);
469 let width = slice
470 .split('\n')
471 .map(|line| line.chars().count() as f32 * char_width)
472 .fold(0.0_f32, f32::max);
473
474 TextMetrics {
475 width,
476 height: line_count as f32 * line_height,
477 line_height,
478 line_count,
479 }
480 }
481
482 fn measure_line_prefix_widths(
483 &self,
484 text: &crate::text::AnnotatedString,
485 line_range: Range<usize>,
486 style: &TextStyle,
487 ) -> Option<TextLinePrefixWidths> {
488 let font_size = style.resolve_font_size(Self::DEFAULT_SIZE);
489 let letter_spacing = style.resolve_letter_spacing(Self::DEFAULT_SIZE);
490 TextLinePrefixWidths::monospaced(
491 text.text[line_range].chars().count(),
492 font_size * Self::CHAR_WIDTH_RATIO,
493 letter_spacing,
494 )
495 }
496
497 fn measure_line_width(
498 &self,
499 text: &crate::text::AnnotatedString,
500 line_range: Range<usize>,
501 style: &TextStyle,
502 ) -> Option<f32> {
503 Some(self.measure_subsequence(text, line_range, style).width)
504 }
505
506 fn line_height(&self, _text: &crate::text::AnnotatedString, style: &TextStyle) -> f32 {
507 let (_, line_height) = Self::get_metrics(style);
508 line_height
509 }
510
511 fn get_offset_for_position(
512 &self,
513 text: &crate::text::AnnotatedString,
514 style: &TextStyle,
515 x: f32,
516 y: f32,
517 ) -> usize {
518 let (char_width, line_height) = Self::get_metrics(style);
519
520 if text.text.is_empty() {
521 return 0;
522 }
523
524 let line_index = (y / line_height).floor().max(0.0) as usize;
525 let lines: Vec<&str> = text.text.split('\n').collect();
526 let target_line = line_index.min(lines.len().saturating_sub(1));
527
528 let mut line_start_byte = 0;
529 for line in lines.iter().take(target_line) {
530 line_start_byte += line.len() + 1;
531 }
532
533 let line_text = lines.get(target_line).unwrap_or(&"");
534 let char_index = (x / char_width).round() as usize;
535 let line_char_count = line_text.chars().count();
536 let clamped_index = char_index.min(line_char_count);
537
538 let offset_in_line = line_text
539 .char_indices()
540 .nth(clamped_index)
541 .map_or(line_text.len(), |(i, _)| i);
542
543 line_start_byte + offset_in_line
544 }
545
546 fn get_cursor_x_for_offset(
547 &self,
548 text: &crate::text::AnnotatedString,
549 style: &TextStyle,
550 offset: usize,
551 ) -> f32 {
552 let (char_width, _) = Self::get_metrics(style);
553
554 let clamped_offset = offset.min(text.text.len());
555 let char_count = text.text[..clamped_offset].chars().count();
556 char_count as f32 * char_width
557 }
558
559 fn layout(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> TextLayoutResult {
560 let (char_width, line_height) = Self::get_metrics(style);
561 TextLayoutResult::monospaced(&text.text, char_width, line_height)
562 }
563}
564
565#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
566struct TextBaseCacheKey {
567 text_hash: u64,
568 style_hash: u64,
569}
570
571#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
572struct TextOptionsCacheKey {
573 base: TextBaseCacheKey,
574 options: TextLayoutOptions,
575 max_width_bits: Option<u32>,
576}
577
578#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
579struct TextPreparedCacheKey {
580 base: TextOptionsCacheKey,
581 visual_hash: u64,
582}
583
584pub(crate) struct TextService {
585 generation: Cell<u64>,
586 measurer: RefCell<Rc<dyn TextMeasurer>>,
587 metrics_cache: RefCell<PassAgedCache<TextBaseCacheKey, TextMetrics>>,
588 options_metrics_cache: RefCell<PassAgedCache<TextOptionsCacheKey, TextMetrics>>,
589 prepared_cache: RefCell<PassAgedCache<TextPreparedCacheKey, Rc<PreparedTextLayout>>>,
590 layout_cache: RefCell<PassAgedCache<TextBaseCacheKey, TextLayoutResult>>,
591}
592
593impl TextService {
594 pub(crate) fn new() -> Self {
595 Self::from_measurer(Rc::new(MonospacedTextMeasurer))
596 }
597
598 pub(crate) fn from_measurer(measurer: Rc<dyn TextMeasurer>) -> Self {
599 Self {
600 generation: Cell::new(1),
601 measurer: RefCell::new(measurer),
602 metrics_cache: RefCell::new(PassAgedCache::with_capacity_at_least_one(
603 TEXT_SERVICE_CACHE_CAPACITY,
604 )),
605 options_metrics_cache: RefCell::new(PassAgedCache::with_capacity_at_least_one(
606 TEXT_SERVICE_CACHE_CAPACITY,
607 )),
608 prepared_cache: RefCell::new(PassAgedCache::with_capacity_at_least_one(
609 TEXT_PREPARED_CACHE_CAPACITY,
610 )),
611 layout_cache: RefCell::new(PassAgedCache::with_capacity_at_least_one(
612 TEXT_SERVICE_CACHE_CAPACITY,
613 )),
614 }
615 }
616
617 pub(crate) fn set_measurer(&self, measurer: Rc<dyn TextMeasurer>) {
618 *self.measurer.borrow_mut() = measurer;
619 self.clear_caches();
620 }
621
622 pub(crate) fn generation(&self) -> u64 {
623 self.generation.get()
624 }
625
626 pub(crate) fn current_measurer(&self) -> Rc<dyn TextMeasurer> {
627 Rc::clone(&self.measurer.borrow())
628 }
629
630 pub(crate) fn with_measurer<R>(&self, f: impl FnOnce(&dyn TextMeasurer) -> R) -> R {
631 let measurer = self.current_measurer();
632 f(&*measurer)
633 }
634
635 pub(crate) fn measure(
636 &self,
637 node_id: Option<NodeId>,
638 text: &crate::text::AnnotatedString,
639 style: &TextStyle,
640 ) -> TextMetrics {
641 let key = text_base_cache_key(text, style);
642 if let Some(metrics) = self.metrics_cache.borrow_mut().get(&key).copied() {
643 return metrics;
644 }
645 let metrics = self.with_measurer(|m| m.measure_for_node(node_id, text, style));
646 self.metrics_cache.borrow_mut().push(key, metrics);
647 metrics
648 }
649
650 pub(crate) fn measure_with_options(
651 &self,
652 node_id: Option<NodeId>,
653 text: &crate::text::AnnotatedString,
654 style: &TextStyle,
655 options: TextLayoutOptions,
656 max_width: Option<f32>,
657 ) -> TextMetrics {
658 let key = text_options_cache_key(text, style, options.normalized(), max_width);
659 if let Some(metrics) = self.options_metrics_cache.borrow_mut().get(&key).copied() {
660 return metrics;
661 }
662 let prepared_key = TextPreparedCacheKey {
664 base: key,
665 visual_hash: style.render_hash(),
666 };
667 if let Some(prepared) = self.prepared_cache.borrow_mut().get(&prepared_key) {
668 return prepared.metrics;
669 }
670 let metrics = self.with_measurer(|m| {
671 m.measure_with_options_for_node(node_id, text, style, options.normalized(), max_width)
672 });
673 self.options_metrics_cache.borrow_mut().push(key, metrics);
674 metrics
675 }
676
677 pub(crate) fn prepare_with_options(
681 &self,
682 node_id: Option<NodeId>,
683 text: &crate::text::AnnotatedString,
684 style: &TextStyle,
685 options: TextLayoutOptions,
686 max_width: Option<f32>,
687 ) -> Rc<PreparedTextLayout> {
688 let key = TextPreparedCacheKey {
689 base: text_options_cache_key(text, style, options.normalized(), max_width),
690 visual_hash: style.render_hash(),
691 };
692 if let Some(prepared) = self.prepared_cache.borrow_mut().get(&key).map(Rc::clone) {
693 return prepared;
694 }
695 let prepared = Rc::new(self.with_measurer(|m| {
696 m.prepare_with_options_for_node(node_id, text, style, options.normalized(), max_width)
697 }));
698 self.prepared_cache
699 .borrow_mut()
700 .push(key, Rc::clone(&prepared));
701 prepared
702 }
703
704 pub(crate) fn layout(
705 &self,
706 text: &crate::text::AnnotatedString,
707 style: &TextStyle,
708 ) -> TextLayoutResult {
709 let key = text_base_cache_key(text, style);
710 if let Some(layout) = self.layout_cache.borrow_mut().get(&key).cloned() {
711 return layout;
712 }
713 let layout = self.with_measurer(|m| m.layout(text, style));
714 self.layout_cache.borrow_mut().push(key, layout.clone());
715 layout
716 }
717
718 pub(crate) fn begin_layout_pass(&self) {
721 self.with_measurer(TextMeasurer::begin_layout_pass);
722 self.metrics_cache.borrow_mut().begin_pass(drop);
723 self.options_metrics_cache.borrow_mut().begin_pass(drop);
724 self.prepared_cache.borrow_mut().begin_pass(drop);
725 self.layout_cache.borrow_mut().begin_pass(drop);
726 }
727
728 fn clear_caches(&self) {
729 self.generation
730 .set(self.generation.get().wrapping_add(1).max(1));
731 self.metrics_cache.borrow_mut().clear();
732 self.options_metrics_cache.borrow_mut().clear();
733 self.prepared_cache.borrow_mut().clear();
734 self.layout_cache.borrow_mut().clear();
735 }
736}
737
738fn text_base_cache_key(text: &crate::text::AnnotatedString, style: &TextStyle) -> TextBaseCacheKey {
739 TextBaseCacheKey {
740 text_hash: text.render_hash(),
741 style_hash: style.measurement_hash(),
742 }
743}
744
745fn text_options_cache_key(
746 text: &crate::text::AnnotatedString,
747 style: &TextStyle,
748 options: TextLayoutOptions,
749 max_width: Option<f32>,
750) -> TextOptionsCacheKey {
751 TextOptionsCacheKey {
752 base: text_base_cache_key(text, style),
753 options: options.normalized(),
754 max_width_bits: normalize_max_width(max_width).map(f32::to_bits),
755 }
756}
757
758pub fn set_text_measurer<M: TextMeasurer>(measurer: M) {
759 crate::render_state::set_current_text_measurer(Rc::new(measurer));
760}
761
762pub(crate) fn current_text_generation() -> u64 {
763 crate::render_state::with_text_service(TextService::generation)
764}
765
766pub fn measure_text(text: &crate::text::AnnotatedString, style: &TextStyle) -> TextMetrics {
767 with_system_font_scale(text, style, |text, style| {
768 crate::render_state::with_text_service(|service| service.measure(None, text, style))
769 })
770}
771
772pub(crate) fn measure_resolved_text(
773 text: &crate::text::AnnotatedString,
774 style: &TextStyle,
775) -> TextMetrics {
776 crate::render_state::with_text_service(|service| service.measure(None, text, style))
777}
778
779pub(crate) fn resolved_first_baseline(style: &TextStyle) -> Option<f32> {
780 crate::render_state::with_text_service(|service| {
781 service.with_measurer(|m| m.first_baseline(style))
782 })
783}
784
785pub(crate) fn resolved_line_box(style: &TextStyle) -> Option<crate::text::LineBox> {
786 crate::render_state::current_app_context()?;
787 crate::render_state::with_text_service(|service| service.with_measurer(|m| m.line_box(style)))
788}
789
790pub fn glyph_line_box(style: &TextStyle, line_height: f32) -> (f32, f32) {
794 let style = scale_text_style_font_sizes(style, crate::current_font_scale_curve());
795 crate::render_state::with_text_service(|service| {
796 service.with_measurer(|m| m.glyph_line_box(&style))
797 })
798 .map_or((0.0, line_height), |(off, h)| {
799 (off.min(line_height), h.min(line_height))
800 })
801}
802
803pub fn text_line_box(style: &TextStyle) -> Option<crate::text::LineBox> {
807 let style = scale_text_style_font_sizes(style, crate::current_font_scale_curve());
808 crate::render_state::with_text_service(|service| service.with_measurer(|m| m.line_box(&style)))
809}
810
811pub fn first_baseline(style: &TextStyle) -> Option<f32> {
815 let style = scale_text_style_font_sizes(style, crate::current_font_scale_curve());
816 crate::render_state::with_text_service(|service| {
817 service.with_measurer(|m| m.first_baseline(&style))
818 })
819}
820
821pub fn measure_text_for_node(
822 node_id: Option<NodeId>,
823 text: &crate::text::AnnotatedString,
824 style: &TextStyle,
825) -> TextMetrics {
826 with_system_font_scale(text, style, |text, style| {
827 crate::render_state::with_text_service(|service| service.measure(node_id, text, style))
828 })
829}
830
831pub fn measure_text_with_options(
832 text: &crate::text::AnnotatedString,
833 style: &TextStyle,
834 options: TextLayoutOptions,
835 max_width: Option<f32>,
836) -> TextMetrics {
837 with_system_font_scale(text, style, |text, style| {
838 crate::render_state::with_text_service(|service| {
839 service.measure_with_options(None, text, style, options.normalized(), max_width)
840 })
841 })
842}
843
844pub fn measure_text_with_options_for_node(
845 node_id: Option<NodeId>,
846 text: &crate::text::AnnotatedString,
847 style: &TextStyle,
848 options: TextLayoutOptions,
849 max_width: Option<f32>,
850) -> TextMetrics {
851 with_system_font_scale(text, style, |text, style| {
852 crate::render_state::with_text_service(|service| {
853 service.measure_with_options(node_id, text, style, options.normalized(), max_width)
854 })
855 })
856}
857
858pub fn prepare_text_layout(
859 text: &crate::text::AnnotatedString,
860 style: &TextStyle,
861 options: TextLayoutOptions,
862 max_width: Option<f32>,
863) -> PreparedTextLayout {
864 Rc::unwrap_or_clone(prepare_text_layout_for_node(
865 None, text, style, options, max_width,
866 ))
867}
868
869pub fn prepare_text_layout_for_node(
870 node_id: Option<NodeId>,
871 text: &crate::text::AnnotatedString,
872 style: &TextStyle,
873 options: TextLayoutOptions,
874 max_width: Option<f32>,
875) -> Rc<PreparedTextLayout> {
876 with_system_font_scale(text, style, |text, style| {
877 crate::render_state::with_text_service(|service| {
878 service.prepare_with_options(node_id, text, style, options.normalized(), max_width)
879 })
880 })
881}
882
883pub fn get_offset_for_position(
884 text: &crate::text::AnnotatedString,
885 style: &TextStyle,
886 x: f32,
887 y: f32,
888) -> usize {
889 with_system_font_scale(text, style, |text, style| {
890 crate::render_state::with_text_measurer(|m| m.get_offset_for_position(text, style, x, y))
891 })
892}
893
894pub fn offset_for_position_wrapped(
908 text: &str,
909 style: &TextStyle,
910 node_id: Option<NodeId>,
911 wrap_width: Option<f32>,
912 line_height: f32,
913 x: f32,
914 y: f32,
915) -> usize {
916 if text.is_empty() {
917 return 0;
918 }
919 let annotated = crate::text::AnnotatedString::from(text);
920 let line_ranges = wrapped_line_ranges(
921 node_id,
922 &annotated,
923 style,
924 TextLayoutOptions::default(),
925 wrap_width,
926 );
927 if line_ranges.is_empty() {
928 return 0;
929 }
930 let line_idx = if line_height > 0.0 {
931 (y / line_height).floor().max(0.0) as usize
932 } else {
933 0
934 }
935 .min(line_ranges.len() - 1);
936 let range = &line_ranges[line_idx];
937 let line = &text[range.start..range.end];
938 let within = get_offset_for_position(&crate::text::AnnotatedString::from(line), style, x, 0.0);
939 range.start + within.min(line.len())
940}
941
942pub fn get_cursor_x_for_offset(
943 text: &crate::text::AnnotatedString,
944 style: &TextStyle,
945 offset: usize,
946) -> f32 {
947 with_system_font_scale(text, style, |text, style| {
948 crate::render_state::with_text_measurer(|m| m.get_cursor_x_for_offset(text, style, offset))
949 })
950}
951
952pub fn layout_text(text: &crate::text::AnnotatedString, style: &TextStyle) -> TextLayoutResult {
953 with_system_font_scale(text, style, |text, style| {
954 crate::render_state::with_text_service(|service| service.layout(text, style))
955 })
956}
957
958pub fn wrapped_line_ranges(
971 node_id: Option<NodeId>,
972 text: &crate::text::AnnotatedString,
973 style: &TextStyle,
974 options: TextLayoutOptions,
975 max_width: Option<f32>,
976) -> Vec<Range<usize>> {
977 with_system_font_scale(text, style, |text, style| {
978 crate::render_state::with_text_measurer(|m| {
979 wrapped_line_ranges_with_measurer(m, node_id, text, style, options, max_width)
980 })
981 })
982}
983
984fn wrapped_line_ranges_with_measurer<M: TextMeasurer + ?Sized>(
985 measurer: &M,
986 _node_id: Option<NodeId>,
987 text: &crate::text::AnnotatedString,
988 style: &TextStyle,
989 options: TextLayoutOptions,
990 max_width: Option<f32>,
991) -> Vec<Range<usize>> {
992 let opts = options.normalized();
993 let max_width = normalize_max_width(max_width);
994 let wrap_width = (opts.soft_wrap && opts.overflow != TextOverflow::Visible)
995 .then_some(max_width)
996 .flatten();
997 let line_break_mode = style
998 .paragraph_style
999 .line_break
1000 .take_or_else(|| LineBreak::Simple);
1001 let hyphens_mode = style.paragraph_style.hyphens.take_or_else(|| Hyphens::None);
1002
1003 let line_ranges = split_line_ranges(text.text.as_str());
1004 let Some(width_limit) = wrap_width else {
1005 return line_ranges;
1006 };
1007 let mut lines = Vec::with_capacity(line_ranges.len());
1008 for line_range in line_ranges {
1009 wrap_line_to_width(
1010 measurer,
1011 text,
1012 line_range,
1013 style,
1014 (width_limit, &mut None),
1015 (line_break_mode, hyphens_mode),
1016 &mut lines,
1017 );
1018 }
1019 lines.into_iter().map(|line| line.source_range).collect()
1020}
1021
1022fn prepare_text_layout_fallback<M: TextMeasurer + ?Sized>(
1023 measurer: &M,
1024 text: &crate::text::AnnotatedString,
1025 style: &TextStyle,
1026 options: TextLayoutOptions,
1027 max_width: Option<f32>,
1028) -> PreparedTextLayout {
1029 prepare_text_layout_with_measurer_for_node(measurer, None, text, style, options, max_width)
1030}
1031
1032pub fn prepare_text_layout_with_measurer_for_node<M: TextMeasurer + ?Sized>(
1033 measurer: &M,
1034 node_id: Option<NodeId>,
1035 text: &crate::text::AnnotatedString,
1036 style: &TextStyle,
1037 options: TextLayoutOptions,
1038 max_width: Option<f32>,
1039) -> PreparedTextLayout {
1040 let telemetry = text_layout_telemetry_enabled();
1041 let total_start = telemetry.then(Instant::now);
1042 let opts = options.normalized();
1043 let max_width = normalize_max_width(max_width);
1044 if let Some(min_font_size_sp) = opts.overflow.scale_down_min_font_size_sp() {
1045 return prepare_scale_down_text_layout(
1046 measurer,
1047 node_id,
1048 text,
1049 style,
1050 opts,
1051 max_width,
1052 min_font_size_sp,
1053 );
1054 }
1055
1056 let wrap_width = (opts.soft_wrap && opts.overflow != TextOverflow::Visible)
1057 .then_some(max_width)
1058 .flatten();
1059 let line_break_mode = style
1060 .paragraph_style
1061 .line_break
1062 .take_or_else(|| LineBreak::Simple);
1063 let hyphens_mode = style.paragraph_style.hyphens.take_or_else(|| Hyphens::None);
1064
1065 let wrap_start = telemetry.then(Instant::now);
1066 let line_ranges = split_line_ranges(text.text.as_str());
1067 let source_line_count = line_ranges.len();
1068 let mut visible_lines: Vec<DisplayLine>;
1069 let mut wrap_hold = None;
1070 if let Some(width_limit) = wrap_width {
1071 (visible_lines, wrap_hold) = wrap_lines(
1072 measurer,
1073 text,
1074 line_ranges,
1075 style,
1076 width_limit,
1077 (line_break_mode, hyphens_mode),
1078 );
1079 } else {
1080 visible_lines = line_ranges
1081 .into_iter()
1082 .map(DisplayLine::from_source_range)
1083 .collect();
1084 }
1085 let wrap_ms = wrap_start.map(|start| start.elapsed().as_secs_f64() * 1000.0);
1086
1087 let overflow_start = telemetry.then(Instant::now);
1088 let did_overflow = apply_overflow(
1089 measurer,
1090 node_id,
1091 text,
1092 style,
1093 opts,
1094 max_width,
1095 &mut visible_lines,
1096 );
1097 let overflow_ms = overflow_start.map(|start| start.elapsed().as_secs_f64() * 1000.0);
1098
1099 let build_start = telemetry.then(Instant::now);
1100 let display_annotated = build_display_annotated(text, &visible_lines);
1101 debug_assert_eq!(
1102 display_annotated.text,
1103 join_display_line_text(text, &visible_lines)
1104 );
1105 let build_ms = build_start.map(|start| start.elapsed().as_secs_f64() * 1000.0);
1106
1107 let metrics_start = telemetry.then(Instant::now);
1108 let display_line_count = visible_lines.len().max(1);
1109 let layout_line_count = display_line_count.max(opts.min_lines);
1110
1111 let measured_width = if visible_lines.is_empty() {
1112 0.0
1113 } else {
1114 visible_lines
1115 .iter()
1116 .map(|line| line.measure_width(measurer, node_id, text, style))
1117 .fold(0.0_f32, f32::max)
1118 };
1119 let metrics_ms = metrics_start.map(|start| start.elapsed().as_secs_f64() * 1000.0);
1120 let width = if opts.overflow == TextOverflow::Visible {
1121 measured_width
1122 } else if let Some(width_limit) = max_width {
1123 measured_width.min(width_limit)
1124 } else {
1125 measured_width
1126 };
1127 let wrap_hold = WrapHold::settle(wrap_hold, measured_width, wrap_width);
1128
1129 let vertical = prepared_line_metrics(
1130 measurer,
1131 node_id,
1132 text,
1133 &display_annotated,
1134 style,
1135 opts.min_lines,
1136 );
1137 let prepared = PreparedTextLayout {
1138 text: Rc::new(display_annotated),
1139 visual_style: std::sync::Arc::new(style.clone()),
1140 alignment_lines: vertical.alignment_lines,
1141 metrics: TextMetrics {
1142 width,
1143 height: vertical.height,
1144 line_height: vertical.line_height,
1145 line_count: layout_line_count,
1146 },
1147 did_overflow,
1148 render_text: Default::default(),
1149 wrap_hold,
1150 };
1151
1152 if let Some(start) = total_start {
1153 eprintln!(
1154 "[text-layout-telemetry] bytes={} spans={} source_lines={} display_lines={} wrap={} max_width={:?} wrap_ms={:.2} overflow_ms={:.2} build_ms={:.2} metrics_ms={:.2} total_ms={:.2}",
1155 text.text.len(),
1156 text.span_styles.len(),
1157 source_line_count,
1158 display_line_count,
1159 wrap_width.is_some(),
1160 max_width,
1161 wrap_ms.unwrap_or(0.0),
1162 overflow_ms.unwrap_or(0.0),
1163 build_ms.unwrap_or(0.0),
1164 metrics_ms.unwrap_or(0.0),
1165 start.elapsed().as_secs_f64() * 1000.0,
1166 );
1167 }
1168
1169 prepared
1170}
1171
1172struct PreparedLineMetrics {
1173 height: f32,
1174 line_height: f32,
1175 alignment_lines: cranpose_ui_layout::AlignmentLines,
1176}
1177
1178fn prepared_line_metrics<M: TextMeasurer + ?Sized>(
1179 measurer: &M,
1180 node_id: Option<NodeId>,
1181 source: &crate::text::AnnotatedString,
1182 display: &crate::text::AnnotatedString,
1183 style: &TextStyle,
1184 min_lines: usize,
1185) -> PreparedLineMetrics {
1186 let base_box = measurer.line_box(style);
1187 if !display.span_styles.is_empty() {
1188 let mut top = 0.0;
1189 let mut trim_bottom = 0.0;
1190 let mut first = None;
1191 let mut last = None;
1192 let mut line_height = 0.0_f32;
1193 let resolved = measurer.visit_line_boxes(display, style, &mut |line| {
1194 if first.is_none() {
1195 top = -line.trim_top;
1196 first = Some(top + line.baseline);
1197 }
1198 last = Some(top + line.baseline);
1199 top += line.height;
1200 trim_bottom = line.trim_bottom;
1201 line_height = line_height.max(line.height);
1202 });
1203 if resolved.is_some() {
1204 let min_height = if min_lines > 1 {
1205 base_box.map_or(0.0, |line| line.block_height(min_lines))
1206 } else {
1207 0.0
1208 };
1209 return PreparedLineMetrics {
1210 height: (top - trim_bottom).max(min_height),
1211 line_height,
1212 alignment_lines: cranpose_ui_layout::AlignmentLines::new(first, last),
1213 };
1214 }
1215 }
1216 let measured_text = if source.span_styles.is_empty() {
1217 source
1218 } else {
1219 display
1220 };
1221 let line_height = measurer
1222 .line_height_for_node(node_id, measured_text, style)
1223 .max(0.0);
1224 let first = base_box
1225 .map(crate::text::LineBox::first_baseline)
1226 .or_else(|| measurer.first_baseline(style));
1227 let displayed_lines = display.text.split('\n').count().max(1);
1228 let layout_line_count = displayed_lines.max(min_lines);
1229 let edges = base_box.unwrap_or_else(|| crate::text::LineBox::untrimmed(line_height, 0.0));
1230 PreparedLineMetrics {
1231 height: (layout_line_count as f32 * line_height - edges.trim_top - edges.trim_bottom)
1232 .max(0.0),
1233 line_height,
1234 alignment_lines: cranpose_ui_layout::AlignmentLines::new(
1235 first,
1236 first.map(|first| first + (displayed_lines - 1) as f32 * line_height),
1237 ),
1238 }
1239}
1240
1241fn prepare_scale_down_text_layout<M: TextMeasurer + ?Sized>(
1242 measurer: &M,
1243 node_id: Option<NodeId>,
1244 text: &crate::text::AnnotatedString,
1245 style: &TextStyle,
1246 options: TextLayoutOptions,
1247 max_width: Option<f32>,
1248 min_font_size_sp: f32,
1249) -> PreparedTextLayout {
1250 let clipped_options = TextLayoutOptions {
1251 overflow: TextOverflow::Clip,
1252 ..options
1253 }
1254 .normalized();
1255
1256 let full_size = prepare_scaled_text_layout(
1257 measurer,
1258 node_id,
1259 text,
1260 style,
1261 clipped_options,
1262 max_width,
1263 FontScaleCurve::linear(1.0),
1264 );
1265 let Some(width_limit) = max_width else {
1266 return full_size;
1267 };
1268 if !full_size.did_overflow {
1269 return full_size;
1270 }
1271
1272 let base_font_size = style.resolve_font_size(DEFAULT_FONT_SIZE_SP);
1273 if !base_font_size.is_finite() || base_font_size <= 0.0 {
1274 return full_size;
1275 }
1276 let min_scale = (min_font_size_sp.min(base_font_size) / base_font_size).clamp(0.0, 1.0);
1277 if min_scale >= 1.0 {
1278 return full_size;
1279 }
1280
1281 let min_size = prepare_scaled_text_layout(
1282 measurer,
1283 node_id,
1284 text,
1285 style,
1286 clipped_options,
1287 Some(width_limit),
1288 FontScaleCurve::linear(min_scale),
1289 );
1290 if min_size.did_overflow {
1291 return min_size;
1292 }
1293
1294 let mut low = min_scale;
1295 let mut high = 1.0;
1296 let mut best = min_size;
1297 for _ in 0..SCALE_DOWN_SEARCH_STEPS {
1298 let mid = (low + high) * 0.5;
1299 let candidate = prepare_scaled_text_layout(
1300 measurer,
1301 node_id,
1302 text,
1303 style,
1304 clipped_options,
1305 Some(width_limit),
1306 FontScaleCurve::linear(mid),
1307 );
1308 if candidate.did_overflow {
1309 high = mid;
1310 } else {
1311 low = mid;
1312 best = candidate;
1313 }
1314 }
1315
1316 best
1317}
1318
1319fn prepare_scaled_text_layout<M: TextMeasurer + ?Sized>(
1320 measurer: &M,
1321 node_id: Option<NodeId>,
1322 text: &crate::text::AnnotatedString,
1323 style: &TextStyle,
1324 options: TextLayoutOptions,
1325 max_width: Option<f32>,
1326 shrink: FontScaleCurve,
1327) -> PreparedTextLayout {
1328 let visual_style = scale_text_style_font_sizes(style, shrink);
1329 let visual_text = scale_annotated_font_sizes(text, shrink);
1330 prepare_text_layout_with_measurer_for_node(
1331 measurer,
1332 node_id,
1333 visual_text.as_ref(),
1334 &visual_style,
1335 options,
1336 max_width,
1337 )
1338}
1339
1340fn scale_annotated_font_sizes(
1341 text: &crate::text::AnnotatedString,
1342 curve: FontScaleCurve,
1343) -> Cow<'_, crate::text::AnnotatedString> {
1344 if curve.is_identity() || !annotated_text_needs_scaling(text) {
1345 return Cow::Borrowed(text);
1346 }
1347
1348 let mut scaled = text.clone();
1349 for span in &mut scaled.span_styles {
1350 span.item = scale_span_style_font_sizes(&span.item, curve, None);
1351 }
1352 Cow::Owned(scaled)
1353}
1354
1355fn scale_text_style_font_sizes(style: &TextStyle, curve: FontScaleCurve) -> TextStyle {
1356 if curve.is_identity() {
1357 return style.clone();
1358 }
1359
1360 let mut scaled = style.clone();
1361 scaled.span_style =
1362 scale_span_style_font_sizes(&style.span_style, curve, Some(DEFAULT_FONT_SIZE_SP));
1363 scaled.paragraph_style.line_height =
1364 scale_text_unit_sp(scaled.paragraph_style.line_height, curve);
1365 if let Some(mut indent) = scaled.paragraph_style.text_indent {
1366 indent.first_line = scale_text_unit_sp(indent.first_line, curve);
1367 indent.rest_line = scale_text_unit_sp(indent.rest_line, curve);
1368 scaled.paragraph_style.text_indent = Some(indent);
1369 }
1370 scaled
1371}
1372
1373fn with_system_font_scale<R>(
1374 text: &crate::text::AnnotatedString,
1375 style: &TextStyle,
1376 block: impl FnOnce(&crate::text::AnnotatedString, &TextStyle) -> R,
1377) -> R {
1378 let curve = crate::current_font_scale_curve();
1379 let visual_style = scale_text_style_font_sizes(style, curve);
1380 let visual_text = scale_annotated_font_sizes(text, curve);
1381 block(visual_text.as_ref(), &visual_style)
1382}
1383
1384fn scale_span_style_font_sizes(
1385 style: &crate::text::SpanStyle,
1386 curve: FontScaleCurve,
1387 default_font_size_sp: Option<f32>,
1388) -> crate::text::SpanStyle {
1389 let factor = curve.scale();
1390 let mut scaled = style.clone();
1391 scaled.font_size = match (style.font_size, default_font_size_sp) {
1392 (crate::text::TextUnit::Unspecified, Some(default_size)) => {
1393 crate::text::TextUnit::Sp(curve.sp_to_dp(default_size))
1394 }
1395 (unit, Some(_)) => scale_text_unit_sp_and_em(unit, curve),
1396 (unit, None) => scale_text_unit_sp(unit, curve),
1397 };
1398 scaled.letter_spacing = scale_text_unit_sp(scaled.letter_spacing, curve);
1399 if let Some(mut shadow) = scaled.shadow {
1400 shadow.offset.x = scale_finite_dimension(shadow.offset.x, factor);
1401 shadow.offset.y = scale_finite_dimension(shadow.offset.y, factor);
1402 shadow.blur_radius = scale_finite_dimension(shadow.blur_radius, factor);
1403 scaled.shadow = Some(shadow);
1404 }
1405 if let Some(crate::text::TextDrawStyle::Stroke { width }) = scaled.draw_style {
1406 scaled.draw_style = Some(crate::text::TextDrawStyle::Stroke {
1407 width: width * factor,
1408 });
1409 }
1410 scaled
1411}
1412
1413fn annotated_text_needs_scaling(text: &crate::text::AnnotatedString) -> bool {
1414 text.span_styles
1415 .iter()
1416 .any(|span| span_style_needs_scaling(&span.item))
1417}
1418
1419fn span_style_needs_scaling(style: &crate::text::SpanStyle) -> bool {
1420 matches!(style.font_size, crate::text::TextUnit::Sp(value) if value.is_finite())
1421 || matches!(style.letter_spacing, crate::text::TextUnit::Sp(value) if value.is_finite())
1422 || matches!(
1423 style.draw_style,
1424 Some(crate::text::TextDrawStyle::Stroke { .. })
1425 )
1426 || style.shadow.is_some()
1427}
1428
1429fn scale_text_unit_sp(unit: crate::text::TextUnit, curve: FontScaleCurve) -> crate::text::TextUnit {
1430 match unit {
1431 crate::text::TextUnit::Sp(value) if value.is_finite() => {
1432 crate::text::TextUnit::Sp(curve.sp_to_dp(value))
1433 }
1434 other => other,
1435 }
1436}
1437
1438fn scale_text_unit_sp_and_em(
1439 unit: crate::text::TextUnit,
1440 curve: FontScaleCurve,
1441) -> crate::text::TextUnit {
1442 match unit {
1443 crate::text::TextUnit::Sp(_) => scale_text_unit_sp(unit, curve),
1444 crate::text::TextUnit::Em(value) if value.is_finite() => {
1445 crate::text::TextUnit::Em(value * curve.scale())
1446 }
1447 other => other,
1448 }
1449}
1450
1451fn scale_finite_dimension(value: f32, factor: f32) -> f32 {
1452 if value.is_finite() {
1453 value * factor
1454 } else {
1455 value
1456 }
1457}
1458
1459#[derive(Clone, Debug)]
1460enum DisplayLineText {
1461 Source,
1462 Ellipsized(crate::text::AnnotatedString),
1463}
1464
1465#[derive(Clone, Debug)]
1466struct DisplayLine {
1467 source_range: Range<usize>,
1468 text: DisplayLineText,
1469 measured_width: Option<f32>,
1470}
1471
1472impl DisplayLine {
1473 fn from_source_range(source_range: Range<usize>) -> Self {
1474 Self {
1475 source_range,
1476 text: DisplayLineText::Source,
1477 measured_width: None,
1478 }
1479 }
1480
1481 fn from_measured_source_range(source_range: Range<usize>, measured_width: f32) -> Self {
1482 Self {
1483 source_range,
1484 text: DisplayLineText::Source,
1485 measured_width: measured_width
1486 .is_finite()
1487 .then_some(measured_width.max(0.0)),
1488 }
1489 }
1490
1491 fn display_text<'a>(&'a self, source: &'a crate::text::AnnotatedString) -> &'a str {
1492 match &self.text {
1493 DisplayLineText::Source => &source.text[self.source_range.clone()],
1494 DisplayLineText::Ellipsized(annotated) => annotated.text.as_str(),
1495 }
1496 }
1497
1498 fn measure_width<M: TextMeasurer + ?Sized>(
1499 &self,
1500 measurer: &M,
1501 node_id: Option<NodeId>,
1502 source: &crate::text::AnnotatedString,
1503 style: &TextStyle,
1504 ) -> f32 {
1505 self.measured_width.unwrap_or_else(|| match &self.text {
1506 DisplayLineText::Source => {
1507 measurer
1508 .measure_subsequence_for_node(node_id, source, self.source_range.clone(), style)
1509 .width
1510 }
1511 DisplayLineText::Ellipsized(annotated) => {
1512 measurer.measure_for_node(node_id, annotated, style).width
1513 }
1514 })
1515 }
1516
1517 fn extend_to_paragraph_end(&mut self, source: &crate::text::AnnotatedString) {
1518 let start = self.source_range.start;
1519 let end = source.text[start..]
1520 .find('\n')
1521 .map_or(source.text.len(), |offset| start + offset);
1522 self.source_range = start..end;
1523 self.text = DisplayLineText::Source;
1524 self.measured_width = None;
1525 }
1526
1527 fn ellipsize<M: TextMeasurer + ?Sized>(
1528 &mut self,
1529 measurer: &M,
1530 node_id: Option<NodeId>,
1531 source: &crate::text::AnnotatedString,
1532 style: &TextStyle,
1533 max_width: Option<f32>,
1534 placement: EllipsisPlacement,
1535 ) {
1536 *self = fit_ellipsis(
1537 measurer,
1538 node_id,
1539 source,
1540 self.source_range.clone(),
1541 style,
1542 max_width,
1543 placement,
1544 );
1545 }
1546}
1547
1548fn split_line_ranges(text: &str) -> Vec<Range<usize>> {
1549 if text.is_empty() {
1550 return single_line_range(0..0);
1551 }
1552
1553 let mut ranges = Vec::new();
1554 let mut start = 0usize;
1555 for (idx, ch) in text.char_indices() {
1556 if ch == '\n' {
1557 ranges.push(start..idx);
1558 start = idx + ch.len_utf8();
1559 }
1560 }
1561 ranges.push(start..text.len());
1562 ranges
1563}
1564
1565fn build_display_annotated(
1566 source: &crate::text::AnnotatedString,
1567 lines: &[DisplayLine],
1568) -> crate::text::AnnotatedString {
1569 if lines.is_empty() {
1570 return crate::text::AnnotatedString::from("");
1571 }
1572
1573 let mut builder = crate::text::AnnotatedString::builder();
1574 for (idx, line) in lines.iter().enumerate() {
1575 builder = match &line.text {
1576 DisplayLineText::Source => {
1577 builder.append_annotated_subsequence(source, line.source_range.clone())
1578 }
1579 DisplayLineText::Ellipsized(annotated) => builder.append_annotated(annotated),
1580 };
1581 if idx + 1 < lines.len() {
1582 builder = builder.append("\n");
1583 }
1584 }
1585 builder.to_annotated_string()
1586}
1587
1588fn join_display_line_text(source: &crate::text::AnnotatedString, lines: &[DisplayLine]) -> String {
1589 let mut text = String::new();
1590 for (idx, line) in lines.iter().enumerate() {
1591 text.push_str(line.display_text(source));
1592 if idx + 1 < lines.len() {
1593 text.push('\n');
1594 }
1595 }
1596 text
1597}
1598
1599fn trim_segment_end_whitespace(line: &str, start: usize, mut end: usize) -> usize {
1600 while end > start {
1601 let Some((idx, ch)) = line[start..end].char_indices().next_back() else {
1602 break;
1603 };
1604 if ch.is_whitespace() {
1605 end = start + idx;
1606 } else {
1607 break;
1608 }
1609 }
1610 end
1611}
1612
1613#[derive(Clone, Copy, Debug, PartialEq)]
1616pub(crate) enum PreparedWidths {
1617 Exact(Option<u32>),
1619 AtLeast(f32),
1622 Wrapped(WrapHold),
1624}
1625
1626impl PreparedWidths {
1627 pub(crate) fn of(
1629 text: &crate::text::AnnotatedString,
1630 options: TextLayoutOptions,
1631 max_width: Option<f32>,
1632 prepared: &PreparedTextLayout,
1633 ) -> Self {
1634 let max_width = normalize_max_width(max_width);
1635 let exact = Self::Exact(max_width.map(f32::to_bits));
1636 let wrapped = prepared.text.text.matches('\n').count() != text.text.matches('\n').count();
1637 let trailing_space = text
1640 .text
1641 .split('\n')
1642 .any(|line| line.ends_with(char::is_whitespace));
1643 if options
1644 .normalized()
1645 .overflow
1646 .scale_down_min_font_size_sp()
1647 .is_some()
1648 || prepared.did_overflow
1649 || trailing_space
1650 {
1651 return exact;
1652 }
1653 if wrapped {
1654 return match (prepared.wrap_hold, max_width) {
1655 (Some(hold), Some(width)) if hold.holds(width) => Self::Wrapped(hold),
1656 _ => exact,
1657 };
1658 }
1659 match max_width {
1660 Some(width) if prepared.metrics.width >= width => exact,
1661 _ => Self::AtLeast(prepared.metrics.width),
1662 }
1663 }
1664
1665 pub(crate) fn hold(self, max_width: Option<f32>) -> bool {
1667 let max_width = normalize_max_width(max_width);
1668 match self {
1669 Self::Exact(bits) => max_width.map(f32::to_bits) == bits,
1670 Self::AtLeast(min) => max_width.is_none_or(|width| width >= min),
1671 Self::Wrapped(hold) => max_width.is_some_and(|width| hold.holds(width)),
1672 }
1673 }
1674}
1675
1676fn normalize_max_width(max_width: Option<f32>) -> Option<f32> {
1677 match max_width {
1678 Some(width) if width.is_finite() && width > 0.0 => Some(width),
1679 _ => None,
1680 }
1681}
1682
1683fn absolute_range_from_start(base_start: usize, relative: Range<usize>) -> Range<usize> {
1684 (base_start + relative.start)..(base_start + relative.end)
1685}
1686
1687fn boundary_index_for_byte(boundaries: &[usize], byte_offset: usize) -> usize {
1688 boundaries
1689 .binary_search(&byte_offset)
1690 .unwrap_or_else(|index| index.min(boundaries.len().saturating_sub(1)))
1691}
1692
1693fn single_line_range(range: Range<usize>) -> Vec<Range<usize>> {
1694 std::iter::once(range).collect()
1695}
1696
1697struct LineMeasureContext<'a, M: TextMeasurer + ?Sized> {
1698 measurer: &'a M,
1699 text: &'a crate::text::AnnotatedString,
1700 style: &'a TextStyle,
1701 line_start: usize,
1702 prefix_widths: Option<TextLinePrefixWidths>,
1703}
1704
1705impl<'a, M: TextMeasurer + ?Sized> LineMeasureContext<'a, M> {
1706 fn new(
1707 measurer: &'a M,
1708 text: &'a crate::text::AnnotatedString,
1709 line_range: &Range<usize>,
1710 style: &'a TextStyle,
1711 boundary_count: usize,
1712 ) -> Self {
1713 let expected_chars = boundary_count.saturating_sub(1);
1714 let prefix_widths = measurer
1715 .measure_line_prefix_widths(text, line_range.clone(), style)
1716 .filter(|widths| widths.char_count() == expected_chars);
1717 Self {
1718 measurer,
1719 text,
1720 style,
1721 line_start: line_range.start,
1722 prefix_widths,
1723 }
1724 }
1725
1726 fn measure_char_range(&self, boundaries: &[usize], start_idx: usize, end_idx: usize) -> f32 {
1727 if let Some(width) = self.prefix_width_for_char_range(start_idx, end_idx) {
1728 return width;
1729 }
1730 let segment_range =
1731 absolute_range_from_start(self.line_start, boundaries[start_idx]..boundaries[end_idx]);
1732 self.measurer
1733 .measure_subsequence(self.text, segment_range, self.style)
1734 .width
1735 }
1736
1737 fn prefix_width_for_char_range(&self, start_idx: usize, end_idx: usize) -> Option<f32> {
1738 if let Some(prefix_widths) = &self.prefix_widths
1739 && let Some(width) = prefix_widths.width_for_char_range(start_idx, end_idx)
1740 {
1741 return Some(width);
1742 }
1743 None
1744 }
1745
1746 fn display_line_for_char_range(
1747 &self,
1748 boundaries: &[usize],
1749 start_idx: usize,
1750 end_idx: usize,
1751 ) -> DisplayLine {
1752 let source_range =
1753 absolute_range_from_start(self.line_start, boundaries[start_idx]..boundaries[end_idx]);
1754 let measured_width = self.measure_char_range(boundaries, start_idx, end_idx);
1755 DisplayLine::from_measured_source_range(source_range, measured_width)
1756 }
1757}
1758
1759fn wrap_lines<M: TextMeasurer + ?Sized>(
1762 measurer: &M,
1763 text: &crate::text::AnnotatedString,
1764 line_ranges: Vec<Range<usize>>,
1765 style: &TextStyle,
1766 max_width: f32,
1767 modes: (LineBreak, Hyphens),
1768) -> (Vec<DisplayLine>, Option<WrapHold>) {
1769 let source_lines = line_ranges.len();
1770 let mut lines = Vec::with_capacity(source_lines);
1771 let mut hold = Some(WrapHold::ANY);
1772 for line_range in line_ranges {
1773 wrap_line_to_width(
1774 measurer,
1775 text,
1776 line_range,
1777 style,
1778 (max_width, &mut hold),
1779 modes,
1780 &mut lines,
1781 );
1782 }
1783 let wrapped = lines.len() != source_lines;
1784 (lines, hold.filter(|_| wrapped))
1785}
1786
1787fn wrap_line_to_width<M: TextMeasurer + ?Sized>(
1792 measurer: &M,
1793 text: &crate::text::AnnotatedString,
1794 line_range: Range<usize>,
1795 style: &TextStyle,
1796 (max_width, hold): (f32, &mut Option<WrapHold>),
1797 (line_break, hyphens): (LineBreak, Hyphens),
1798 out: &mut Vec<DisplayLine>,
1799) {
1800 let line_text = &text.text[line_range.clone()];
1801 if line_text.is_empty() {
1802 out.push(DisplayLine::from_source_range(
1803 line_range.start..line_range.start,
1804 ));
1805 return;
1806 }
1807
1808 if let Some(measured_width) = measurer.measure_line_width(text, line_range.clone(), style)
1809 && measured_width <= max_width + WRAP_EPSILON
1810 {
1811 WrapHold::fit_whole(hold, measured_width);
1812 out.push(DisplayLine::from_measured_source_range(
1813 line_range,
1814 measured_width,
1815 ));
1816 return;
1817 }
1818
1819 if matches!(line_break, LineBreak::Heading | LineBreak::Paragraph)
1820 && line_text.chars().any(char::is_whitespace)
1821 && wrap_line_with_word_balance(
1822 measurer,
1823 text,
1824 line_range.clone(),
1825 style,
1826 max_width,
1827 line_break,
1828 out,
1829 )
1830 {
1831 *hold = None;
1832 return;
1833 }
1834
1835 wrap_line_greedy(
1836 measurer,
1837 text,
1838 line_range,
1839 style,
1840 (max_width, hold),
1841 (line_break, hyphens),
1842 out,
1843 );
1844}
1845
1846fn wrap_line_greedy<M: TextMeasurer + ?Sized>(
1847 measurer: &M,
1848 text: &crate::text::AnnotatedString,
1849 line_range: Range<usize>,
1850 style: &TextStyle,
1851 (max_width, hold): (f32, &mut Option<WrapHold>),
1852 (line_break, hyphens): (LineBreak, Hyphens),
1853 out: &mut Vec<DisplayLine>,
1854) {
1855 let line_text = &text.text[line_range.clone()];
1856 let boundaries = char_boundaries(line_text);
1857 let measure_context =
1858 LineMeasureContext::new(measurer, text, &line_range, style, boundaries.len());
1859 if let Some(measured_width) =
1860 measure_context.prefix_width_for_char_range(0, boundaries.len() - 1)
1861 && measured_width <= max_width + WRAP_EPSILON
1862 {
1863 WrapHold::fit_whole(hold, measured_width);
1864 out.push(DisplayLine::from_measured_source_range(
1865 line_range,
1866 measured_width,
1867 ));
1868 return;
1869 }
1870 let first = out.len();
1871 let mut start_idx = 0usize;
1872
1873 while start_idx < boundaries.len() - 1 {
1874 let mut low = start_idx + 1;
1875 let mut high = boundaries.len() - 1;
1876 let mut best = start_idx + 1;
1877
1878 while low <= high {
1879 let mid = (low + high) / 2;
1880 let width = measure_context.measure_char_range(&boundaries, start_idx, mid);
1881 if width <= max_width + WRAP_EPSILON || mid == start_idx + 1 {
1882 best = mid;
1883 low = mid + 1;
1884 } else {
1885 if mid == 0 {
1886 break;
1887 }
1888 high = mid - 1;
1889 }
1890 }
1891
1892 let wrap_idx = choose_wrap_break(line_text, &boundaries, start_idx, best, line_break);
1893 let mut effective_wrap_idx = wrap_idx;
1894 let can_hyphenate = hyphens == Hyphens::Auto
1895 && wrap_idx == best
1896 && best < boundaries.len() - 1
1897 && is_break_inside_word(line_text, &boundaries, wrap_idx);
1898 narrow_to_break(
1899 hold,
1900 &measure_context,
1901 (line_text, &boundaries),
1902 (start_idx, best),
1903 can_hyphenate,
1904 );
1905 if can_hyphenate {
1906 effective_wrap_idx = resolve_auto_hyphen_break(
1907 measurer,
1908 line_text,
1909 style,
1910 &boundaries,
1911 start_idx,
1912 wrap_idx,
1913 );
1914 }
1915
1916 let broke_at_word_boundary = effective_wrap_idx > start_idx
1917 && line_text[boundaries[effective_wrap_idx - 1]..boundaries[effective_wrap_idx]]
1918 .chars()
1919 .all(char::is_whitespace);
1920 let segment_start = boundaries[start_idx];
1921 let mut segment_end = boundaries[effective_wrap_idx];
1922 if wrap_idx != best || broke_at_word_boundary {
1923 segment_end = trim_segment_end_whitespace(line_text, segment_start, segment_end);
1924 }
1925 let segment_end_idx = boundary_index_for_byte(&boundaries, segment_end);
1926 out.push(measure_context.display_line_for_char_range(
1927 &boundaries,
1928 start_idx,
1929 segment_end_idx,
1930 ));
1931
1932 start_idx = if wrap_idx != best || broke_at_word_boundary {
1933 skip_leading_whitespace(line_text, &boundaries, wrap_idx)
1934 } else {
1935 effective_wrap_idx
1936 };
1937 }
1938
1939 if out.len() == first {
1940 out.push(DisplayLine::from_source_range(
1941 line_range.start..line_range.start,
1942 ));
1943 }
1944}
1945
1946fn wrap_line_with_word_balance<M: TextMeasurer + ?Sized>(
1949 measurer: &M,
1950 text: &crate::text::AnnotatedString,
1951 line_range: Range<usize>,
1952 style: &TextStyle,
1953 max_width: f32,
1954 line_break: LineBreak,
1955 out: &mut Vec<DisplayLine>,
1956) -> bool {
1957 let line_text = &text.text[line_range.clone()];
1958 let boundaries = char_boundaries(line_text);
1959 let measure_context =
1960 LineMeasureContext::new(measurer, text, &line_range, style, boundaries.len());
1961 if let Some(measured_width) =
1962 measure_context.prefix_width_for_char_range(0, boundaries.len() - 1)
1963 && measured_width <= max_width + WRAP_EPSILON
1964 {
1965 out.push(DisplayLine::from_measured_source_range(
1966 line_range,
1967 measured_width,
1968 ));
1969 return true;
1970 }
1971 let breakpoints = collect_word_breakpoints(line_text, &boundaries);
1972 if breakpoints.len() <= 2 {
1973 return false;
1974 }
1975
1976 let node_count = breakpoints.len();
1977 let mut best_cost = vec![f32::INFINITY; node_count];
1978 let mut next_index = vec![None; node_count];
1979 best_cost[node_count - 1] = 0.0;
1980
1981 for start in (0..node_count - 1).rev() {
1982 for end in start + 1..node_count {
1983 let start_byte = boundaries[breakpoints[start]];
1984 let end_byte = boundaries[breakpoints[end]];
1985 let trimmed_end = trim_segment_end_whitespace(line_text, start_byte, end_byte);
1986 if trimmed_end <= start_byte {
1987 continue;
1988 }
1989 let segment_start_idx = breakpoints[start];
1990 let segment_end_idx = boundary_index_for_byte(&boundaries, trimmed_end);
1991 let segment_width =
1992 measure_context.measure_char_range(&boundaries, segment_start_idx, segment_end_idx);
1993 if segment_width > max_width + WRAP_EPSILON {
1994 continue;
1995 }
1996 if !best_cost[end].is_finite() {
1997 continue;
1998 }
1999 let slack = (max_width - segment_width).max(0.0);
2000 let is_last = end == node_count - 1;
2001 let segment_cost = match line_break {
2002 LineBreak::Heading => slack * slack,
2003 LineBreak::Paragraph => {
2004 if is_last {
2005 slack * slack * 0.16
2006 } else {
2007 slack * slack
2008 }
2009 }
2010 LineBreak::Simple | LineBreak::Unspecified => slack * slack,
2011 };
2012 let candidate = segment_cost + best_cost[end];
2013 if candidate < best_cost[start] {
2014 best_cost[start] = candidate;
2015 next_index[start] = Some(end);
2016 }
2017 }
2018 }
2019
2020 let first = out.len();
2021 let mut current = 0usize;
2022 while current < node_count - 1 {
2023 let Some(next) = next_index[current] else {
2024 out.truncate(first);
2025 return false;
2026 };
2027 let start_byte = boundaries[breakpoints[current]];
2028 let end_byte = boundaries[breakpoints[next]];
2029 let trimmed_end = trim_segment_end_whitespace(line_text, start_byte, end_byte);
2030 if trimmed_end <= start_byte {
2031 out.truncate(first);
2032 return false;
2033 }
2034 let segment_start_idx = breakpoints[current];
2035 let segment_end_idx = boundary_index_for_byte(&boundaries, trimmed_end);
2036 out.push(measure_context.display_line_for_char_range(
2037 &boundaries,
2038 segment_start_idx,
2039 segment_end_idx,
2040 ));
2041 current = next;
2042 }
2043 out.len() > first
2044}
2045
2046fn collect_word_breakpoints(line: &str, boundaries: &[usize]) -> Vec<usize> {
2047 let mut points = vec![0usize];
2048 for idx in 1..boundaries.len() - 1 {
2049 let prev = &line[boundaries[idx - 1]..boundaries[idx]];
2050 let current = &line[boundaries[idx]..boundaries[idx + 1]];
2051 if prev.chars().all(char::is_whitespace) && !current.chars().all(char::is_whitespace) {
2052 points.push(idx);
2053 }
2054 }
2055 let end = boundaries.len() - 1;
2056 if points.last().copied() != Some(end) {
2057 points.push(end);
2058 }
2059 points
2060}
2061
2062fn choose_wrap_break(
2063 line: &str,
2064 boundaries: &[usize],
2065 start_idx: usize,
2066 best: usize,
2067 _line_break: LineBreak,
2068) -> usize {
2069 if best >= boundaries.len() - 1 {
2070 return best;
2071 }
2072
2073 if best <= start_idx + 1 {
2074 return best;
2075 }
2076
2077 for idx in (start_idx + 1..=best).rev() {
2078 let prev = &line[boundaries[idx - 1]..boundaries[idx]];
2079 if prev.chars().all(char::is_whitespace) {
2080 return idx;
2081 }
2082 }
2083 best
2084}
2085
2086fn narrow_to_break<M: TextMeasurer + ?Sized>(
2090 hold: &mut Option<WrapHold>,
2091 measure_context: &LineMeasureContext<'_, M>,
2092 (line, boundaries): (&str, &[usize]),
2093 (start_idx, best): (usize, usize),
2094 hyphenated: bool,
2095) {
2096 if hyphenated {
2097 *hold = None;
2098 }
2099 let Some(hold) = hold else {
2100 return;
2101 };
2102 let (fits, pulls_up) = wrap_break_widths(line, boundaries, start_idx, best);
2103 let width = |idx| measure_context.measure_char_range(boundaries, start_idx, idx);
2104 hold.narrow(
2105 fits.map_or(f32::NEG_INFINITY, width),
2106 pulls_up.map_or(f32::INFINITY, width),
2107 );
2108}
2109
2110fn wrap_break_widths(
2116 line: &str,
2117 boundaries: &[usize],
2118 start_idx: usize,
2119 best: usize,
2120) -> (Option<usize>, Option<usize>) {
2121 let end = boundaries.len() - 1;
2122 if best <= start_idx + 1 {
2124 return (None, (best < end).then_some(best + 1));
2125 }
2126 if best >= end {
2127 return (Some(end), None);
2128 }
2129 let after_space = |idx: usize| {
2130 line[boundaries[idx - 1]..boundaries[idx]]
2131 .chars()
2132 .all(char::is_whitespace)
2133 };
2134 match (start_idx + 1..=best).rev().find(|&idx| after_space(idx)) {
2135 Some(wrap_idx) => {
2136 let next = (best + 1..end).find(|&idx| after_space(idx)).unwrap_or(end);
2137 (Some(wrap_idx), Some(next))
2138 }
2139 None => (Some(best), Some(best + 1)),
2140 }
2141}
2142
2143fn is_break_inside_word(line: &str, boundaries: &[usize], break_idx: usize) -> bool {
2144 if break_idx == 0 || break_idx >= boundaries.len() - 1 {
2145 return false;
2146 }
2147 let prev = &line[boundaries[break_idx - 1]..boundaries[break_idx]];
2148 let next = &line[boundaries[break_idx]..boundaries[break_idx + 1]];
2149 !prev.chars().all(char::is_whitespace) && !next.chars().all(char::is_whitespace)
2150}
2151
2152fn resolve_auto_hyphen_break<M: TextMeasurer + ?Sized>(
2153 measurer: &M,
2154 line: &str,
2155 style: &TextStyle,
2156 boundaries: &[usize],
2157 start_idx: usize,
2158 break_idx: usize,
2159) -> usize {
2160 if let Some(candidate) = measurer.choose_auto_hyphen_break(line, style, start_idx, break_idx)
2161 && is_valid_auto_hyphen_break(line, boundaries, start_idx, break_idx, candidate)
2162 {
2163 return candidate;
2164 }
2165 choose_auto_hyphen_break_fallback(boundaries, start_idx, break_idx)
2166}
2167
2168fn is_valid_auto_hyphen_break(
2169 line: &str,
2170 boundaries: &[usize],
2171 start_idx: usize,
2172 break_idx: usize,
2173 candidate_idx: usize,
2174) -> bool {
2175 let end_idx = boundaries.len().saturating_sub(1);
2176 candidate_idx > start_idx
2177 && candidate_idx < end_idx
2178 && candidate_idx <= break_idx
2179 && candidate_idx >= start_idx + AUTO_HYPHEN_MIN_SEGMENT_CHARS
2180 && is_break_inside_word(line, boundaries, candidate_idx)
2181}
2182
2183fn choose_auto_hyphen_break_fallback(
2184 boundaries: &[usize],
2185 start_idx: usize,
2186 break_idx: usize,
2187) -> usize {
2188 let end_idx = boundaries.len().saturating_sub(1);
2189 if break_idx >= end_idx {
2190 return break_idx;
2191 }
2192 let trailing_len = end_idx.saturating_sub(break_idx);
2193 if trailing_len > 2 || break_idx <= start_idx + AUTO_HYPHEN_MIN_SEGMENT_CHARS {
2194 return break_idx;
2195 }
2196
2197 let min_break = start_idx + AUTO_HYPHEN_MIN_SEGMENT_CHARS;
2198 let max_break = break_idx.saturating_sub(1);
2199 if min_break > max_break {
2200 return break_idx;
2201 }
2202
2203 let mut best_break = break_idx;
2204 let mut best_penalty = usize::MAX;
2205 for idx in min_break..=max_break {
2206 let candidate_trailing_len = end_idx.saturating_sub(idx);
2207 let candidate_prefix_len = idx.saturating_sub(start_idx);
2208 if candidate_prefix_len < AUTO_HYPHEN_MIN_SEGMENT_CHARS
2209 || candidate_trailing_len < AUTO_HYPHEN_MIN_TRAILING_CHARS
2210 {
2211 continue;
2212 }
2213
2214 let penalty = candidate_trailing_len.abs_diff(AUTO_HYPHEN_PREFERRED_TRAILING_CHARS);
2215 if penalty < best_penalty {
2216 best_penalty = penalty;
2217 best_break = idx;
2218 if penalty == 0 {
2219 break;
2220 }
2221 }
2222 }
2223 best_break
2224}
2225
2226fn skip_leading_whitespace(line: &str, boundaries: &[usize], mut idx: usize) -> usize {
2227 while idx < boundaries.len() - 1 {
2228 let ch = &line[boundaries[idx]..boundaries[idx + 1]];
2229 if !ch.chars().all(char::is_whitespace) {
2230 break;
2231 }
2232 idx += 1;
2233 }
2234 idx
2235}
2236
2237fn apply_overflow<M: TextMeasurer + ?Sized>(
2238 measurer: &M,
2239 node_id: Option<NodeId>,
2240 text: &crate::text::AnnotatedString,
2241 style: &TextStyle,
2242 options: TextLayoutOptions,
2243 max_width: Option<f32>,
2244 visible_lines: &mut Vec<DisplayLine>,
2245) -> bool {
2246 if options.overflow == TextOverflow::Visible {
2247 return false;
2248 }
2249 let ellipsis = EllipsisPlacement::for_options(options);
2250 let mut did_overflow = false;
2251 if visible_lines.len() > options.max_lines {
2252 did_overflow = true;
2253 visible_lines.truncate(options.max_lines);
2254 if let (Some(placement), Some(last_line)) = (ellipsis, visible_lines.last_mut()) {
2255 last_line.extend_to_paragraph_end(text);
2256 last_line.ellipsize(measurer, node_id, text, style, max_width, placement);
2257 }
2258 }
2259
2260 let Some(width_limit) = max_width else {
2261 return did_overflow;
2262 };
2263 let visible_len = visible_lines.len();
2264 for (line_index, line) in visible_lines.iter_mut().enumerate() {
2265 if line.measure_width(measurer, node_id, text, style) <= width_limit + WRAP_EPSILON {
2266 continue;
2267 }
2268 did_overflow = true;
2269 if line_index + 1 == visible_len
2270 && let Some(placement) = ellipsis
2271 {
2272 line.ellipsize(measurer, node_id, text, style, max_width, placement);
2273 }
2274 }
2275 did_overflow
2276}
2277
2278#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2279enum EllipsisPlacement {
2280 End,
2281 Start,
2282 Middle,
2283}
2284
2285impl EllipsisPlacement {
2286 fn split(self, kept_chars: usize) -> (usize, usize) {
2288 match self {
2289 Self::End => (kept_chars, 0),
2290 Self::Start => (0, kept_chars),
2291 Self::Middle => (kept_chars.div_ceil(2), kept_chars / 2),
2292 }
2293 }
2294
2295 fn estimated_kept_chars(
2300 self,
2301 prefix_widths: &TextLinePrefixWidths,
2302 ellipsis_width: f32,
2303 width_limit: f32,
2304 ) -> Option<usize> {
2305 let char_count = prefix_widths.char_count();
2306 let width = |kept_chars: usize| {
2307 let (head_chars, tail_chars) = self.split(kept_chars);
2308 Some(
2309 prefix_widths.width_for_char_range(0, head_chars)?
2310 + ellipsis_width
2311 + prefix_widths.width_for_char_range(char_count - tail_chars, char_count)?,
2312 )
2313 };
2314 let (mut fitting, mut overflowing) = (0usize, char_count + 1);
2315 while fitting + 1 < overflowing {
2316 let kept_chars = fitting + (overflowing - fitting) / 2;
2317 if width(kept_chars)? <= width_limit + WRAP_EPSILON {
2318 fitting = kept_chars;
2319 } else {
2320 overflowing = kept_chars;
2321 }
2322 }
2323 Some(fitting)
2324 }
2325
2326 fn for_options(options: TextLayoutOptions) -> Option<Self> {
2327 let single_line = options.max_lines == 1;
2328 match options.overflow {
2329 TextOverflow::Ellipsis => Some(Self::End),
2330 TextOverflow::StartEllipsis if single_line => Some(Self::Start),
2331 TextOverflow::MiddleEllipsis if single_line => Some(Self::Middle),
2332 TextOverflow::StartEllipsis
2333 | TextOverflow::MiddleEllipsis
2334 | TextOverflow::Clip
2335 | TextOverflow::Visible
2336 | TextOverflow::ScaleDown { .. } => None,
2337 }
2338 }
2339
2340 fn elide(
2341 self,
2342 source: &crate::text::AnnotatedString,
2343 source_range: Range<usize>,
2344 boundaries: &[usize],
2345 kept_chars: usize,
2346 ) -> crate::text::AnnotatedString {
2347 let char_count = boundaries.len() - 1;
2348 let (head_chars, tail_chars) = self.split(kept_chars);
2349 let head_end = source_range.start + boundaries[head_chars];
2350 let tail_start = source_range.start + boundaries[char_count - tail_chars];
2351 crate::text::AnnotatedString::builder()
2352 .append_annotated_subsequence(source, source_range.start..head_end)
2353 .append(ELLIPSIS)
2354 .append_annotated_subsequence(source, tail_start..source_range.end)
2355 .to_annotated_string()
2356 }
2357}
2358
2359fn fit_ellipsis<M: TextMeasurer + ?Sized>(
2360 measurer: &M,
2361 node_id: Option<NodeId>,
2362 source: &crate::text::AnnotatedString,
2363 source_range: Range<usize>,
2364 style: &TextStyle,
2365 max_width: Option<f32>,
2366 placement: EllipsisPlacement,
2367) -> DisplayLine {
2368 let width_limit = max_width.unwrap_or(f32::INFINITY);
2369 let fitting_line = |text: DisplayLineText| {
2370 let mut line = DisplayLine {
2371 source_range: source_range.clone(),
2372 text,
2373 measured_width: None,
2374 };
2375 let width = line.measure_width(measurer, node_id, source, style);
2376 line.measured_width = Some(width);
2377 (width <= width_limit + WRAP_EPSILON).then_some(line)
2378 };
2379 if placement != EllipsisPlacement::End
2380 && let Some(line) = fitting_line(DisplayLineText::Source)
2381 {
2382 return line;
2383 }
2384
2385 let boundaries = char_boundaries(&source.text[source_range.clone()]);
2386 let elided_line = |kept_chars: usize| {
2387 fitting_line(DisplayLineText::Ellipsized(placement.elide(
2388 source,
2389 source_range.clone(),
2390 &boundaries,
2391 kept_chars,
2392 )))
2393 };
2394 let Some(mut best) = elided_line(0) else {
2395 return DisplayLine {
2396 source_range: source_range.clone(),
2397 text: DisplayLineText::Ellipsized(crate::text::AnnotatedString::default()),
2398 measured_width: None,
2399 };
2400 };
2401
2402 let mut fitting = 0usize;
2403 let mut overflowing = boundaries.len();
2404 let guess = best.measured_width.and_then(|ellipsis_width| {
2408 measurer
2409 .measure_line_prefix_widths(source, source_range.clone(), style)
2410 .filter(|widths| widths.char_count() + 1 == boundaries.len())
2411 .and_then(|widths| placement.estimated_kept_chars(&widths, ellipsis_width, width_limit))
2412 });
2413 if let Some(guess) = guess.filter(|guess| *guess > 0) {
2414 for kept_chars in [guess, guess + 1] {
2415 if kept_chars <= fitting || kept_chars >= overflowing {
2416 break;
2417 }
2418 match elided_line(kept_chars) {
2419 Some(line) => {
2420 fitting = kept_chars;
2421 best = line;
2422 }
2423 None => overflowing = kept_chars,
2424 }
2425 }
2426 }
2427 while fitting + 1 < overflowing {
2428 let kept_chars = fitting + (overflowing - fitting) / 2;
2429 match elided_line(kept_chars) {
2430 Some(line) => {
2431 fitting = kept_chars;
2432 best = line;
2433 }
2434 None => overflowing = kept_chars,
2435 }
2436 }
2437 best
2438}
2439
2440fn char_boundaries(text: &str) -> Vec<usize> {
2441 let mut out = Vec::with_capacity(text.chars().count() + 1);
2442 out.push(0);
2443 for (idx, _) in text.char_indices() {
2444 if idx != 0 {
2445 out.push(idx);
2446 }
2447 }
2448 out.push(text.len());
2449 out
2450}
2451
2452#[cfg(test)]
2453#[path = "tests/measure_tests.rs"]
2454mod tests;