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