Skip to main content

cranpose_ui/text/
measure.rs

1use std::{
2    borrow::Cow,
3    cell::{Cell, RefCell},
4    hash::Hash,
5    ops::Range,
6    rc::Rc,
7    sync::Arc,
8};
9
10use cranpose_core::{NodeId, collections::pass_aged::PassAgedCache};
11use web_time::Instant;
12
13use super::{
14    layout_options::{TextLayoutOptions, TextOverflow},
15    paragraph::{Hyphens, LineBreak},
16    style::TextStyle,
17};
18use crate::{font_scale::FontScaleCurve, text_layout_result::TextLayoutResult};
19
20const ELLIPSIS: &str = "\u{2026}";
21const DEFAULT_FONT_SIZE_SP: f32 = 14.0;
22const WRAP_EPSILON: f32 = 0.5;
23const SCALE_DOWN_SEARCH_STEPS: usize = 14;
24const AUTO_HYPHEN_MIN_SEGMENT_CHARS: usize = 2;
25const AUTO_HYPHEN_MIN_TRAILING_CHARS: usize = 3;
26const AUTO_HYPHEN_PREFERRED_TRAILING_CHARS: usize = 4;
27const TEXT_SERVICE_CACHE_CAPACITY: usize = 8192;
28/// Prepared layouts kept across nodes. Each node keeps its own, so this
29/// serves the items a list composes again, a few screens of them; an entry
30/// holds a whole visual style, and a screen laid out again at a new width
31/// every frame fills every entry with widths it never asks for again.
32const TEXT_PREPARED_CACHE_CAPACITY: usize = 1024;
33const TEXT_LAYOUT_TELEMETRY_ENV: &str = "CRANPOSE_TEXT_LAYOUT_TELEMETRY";
34
35fn text_layout_telemetry_enabled() -> bool {
36    cranpose_core::env_flag!(TEXT_LAYOUT_TELEMETRY_ENV)
37}
38
39#[derive(Clone, Copy, Debug, PartialEq)]
40pub struct TextMetrics {
41    pub width: f32,
42    pub height: f32,
43    /// Height of a single line of text
44    pub line_height: f32,
45    /// Number of lines in the text
46    pub line_count: usize,
47}
48
49#[derive(Clone, Debug)]
50pub struct PreparedTextLayout {
51    /// Shared display text after wrapping and overflow have been resolved.
52    pub text: Rc<crate::text::AnnotatedString>,
53    /// The style the text draws in, shared with every render node drawn
54    /// from this layout.
55    pub visual_style: std::sync::Arc<TextStyle>,
56    pub metrics: TextMetrics,
57    /// The first and last drawn baselines, excluding blank height added by `min_lines`.
58    pub alignment_lines: cranpose_ui_layout::AlignmentLines,
59    pub did_overflow: bool,
60    /// `text` as a renderer draws it, converted on first use: see
61    /// [`PreparedTextLayout::render_text`].
62    pub render_text: std::cell::OnceCell<std::sync::Arc<crate::text::RenderString>>,
63    /// The max widths the layout's greedy wrap breaks the same lines at and
64    /// cuts its ellipsis at the same character, when it wrapped: `None` when
65    /// it did not, broke lines another way, or a line overflowed its width.
66    pub(crate) wrap_hold: Option<WrapHold>,
67}
68
69/// The max widths a greedy wrap breaks a text's lines the same at: every
70/// width `w` with `fits <= w + WRAP_EPSILON < pulls_up`. Below `fits` a break
71/// no longer fits; from `pulls_up` a line takes up its next word.
72#[derive(Clone, Copy, Debug, PartialEq)]
73pub(crate) struct WrapHold {
74    fits: f32,
75    pulls_up: f32,
76}
77
78impl WrapHold {
79    /// Every width, before any line narrows it.
80    const ANY: Self = Self {
81        fits: f32::NEG_INFINITY,
82        pulls_up: f32::INFINITY,
83    };
84
85    fn narrow(&mut self, fits: f32, pulls_up: f32) {
86        self.fits = self.fits.max(fits);
87        self.pulls_up = self.pulls_up.min(pulls_up);
88    }
89
90    fn holds(self, width: f32) -> bool {
91        self.fits <= width + WRAP_EPSILON && width + WRAP_EPSILON < self.pulls_up
92    }
93
94    /// Narrows `hold` to the widths a line `width` wide fits whole at.
95    fn fit_whole(hold: &mut Option<Self>, width: f32) {
96        if let Some(hold) = hold {
97            hold.narrow(width, f32::INFINITY);
98        }
99    }
100
101    /// `hold` narrowed to the widths its layout reports `measured_width` at,
102    /// and kept only when it holds `max_width`, the width it was made at: a
103    /// layout wider than its limit reports the limit.
104    fn settle(hold: Option<Self>, measured_width: f32, max_width: Option<f32>) -> Option<Self> {
105        let mut hold = hold?;
106        hold.narrow(measured_width + WRAP_EPSILON, f32::INFINITY);
107        max_width.filter(|width| hold.holds(*width)).map(|_| hold)
108    }
109}
110
111impl PreparedTextLayout {
112    /// The display text as a renderer draws it. Converted once for the layout
113    /// and shared after, so every frame and scene rebuild drawing this layout
114    /// hands over the same allocation.
115    pub fn render_text(&self) -> std::sync::Arc<crate::text::RenderString> {
116        if let Some(converted) = self.render_text.get() {
117            return std::sync::Arc::clone(converted);
118        }
119        let converted = std::sync::Arc::new(self.text.render_string());
120        let _ = self.render_text.set(std::sync::Arc::clone(&converted));
121        converted
122    }
123}
124
125impl PartialEq for PreparedTextLayout {
126    fn eq(&self, other: &Self) -> bool {
127        self.text == other.text
128            && self.visual_style == other.visual_style
129            && self.metrics == other.metrics
130            && self.alignment_lines == other.alignment_lines
131            && self.did_overflow == other.did_overflow
132    }
133}
134
135#[derive(Clone, Debug, PartialEq)]
136pub struct TextLinePrefixWidths {
137    prefix_widths: Vec<f32>,
138    separator_before: Vec<f32>,
139    non_empty_overhang: f32,
140}
141
142impl TextLinePrefixWidths {
143    pub fn from_parts(
144        prefix_widths: Vec<f32>,
145        separator_before: Vec<f32>,
146        non_empty_overhang: f32,
147    ) -> Option<Self> {
148        if prefix_widths.is_empty() || prefix_widths.len() != separator_before.len() + 1 {
149            return None;
150        }
151        if prefix_widths
152            .iter()
153            .chain(separator_before.iter())
154            .any(|value| !value.is_finite())
155        {
156            return None;
157        }
158        let non_empty_overhang = non_empty_overhang.max(0.0);
159        if !non_empty_overhang.is_finite() {
160            return None;
161        }
162        Some(Self {
163            prefix_widths,
164            separator_before,
165            non_empty_overhang,
166        })
167    }
168
169    pub fn monospaced(char_count: usize, char_width: f32, letter_spacing: f32) -> Option<Self> {
170        if !char_width.is_finite() || !letter_spacing.is_finite() {
171            return None;
172        }
173        let char_width = char_width.max(0.0);
174        let letter_spacing = letter_spacing.max(0.0);
175        let mut prefix_widths = Vec::with_capacity(char_count + 1);
176        let mut separator_before = Vec::with_capacity(char_count);
177        let mut width = 0.0f32;
178        prefix_widths.push(width);
179        for _ in 0..char_count {
180            separator_before.push(0.0);
181            width += char_width + letter_spacing;
182            prefix_widths.push(width);
183        }
184        Self::from_parts(prefix_widths, separator_before, 0.0)
185    }
186
187    pub fn char_count(&self) -> usize {
188        self.separator_before.len()
189    }
190
191    pub fn width_for_char_range(&self, start: usize, end: usize) -> Option<f32> {
192        if start > end || end > self.char_count() {
193            return None;
194        }
195        if start == end {
196            return Some(0.0);
197        }
198        let separator = self.separator_before.get(start).copied().unwrap_or(0.0);
199        Some(
200            (self.prefix_widths[end] - self.prefix_widths[start] - separator).max(0.0)
201                + self.non_empty_overhang,
202        )
203    }
204}
205
206pub trait TextMeasurer: 'static {
207    fn measure(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> TextMetrics;
208
209    /// Called as each layout pass starts. A measurer that caches
210    /// measurements can age them by passes and drop the ones recent passes
211    /// did not use.
212    fn begin_layout_pass(&self) {}
213
214    fn measure_for_node(
215        &self,
216        node_id: Option<NodeId>,
217        text: &crate::text::AnnotatedString,
218        style: &TextStyle,
219    ) -> TextMetrics {
220        let _ = node_id;
221        self.measure(text, style)
222    }
223
224    fn measure_subsequence(
225        &self,
226        text: &crate::text::AnnotatedString,
227        range: Range<usize>,
228        style: &TextStyle,
229    ) -> TextMetrics {
230        self.measure(&text.subsequence(range), style)
231    }
232
233    fn measure_subsequence_for_node(
234        &self,
235        node_id: Option<NodeId>,
236        text: &crate::text::AnnotatedString,
237        range: Range<usize>,
238        style: &TextStyle,
239    ) -> TextMetrics {
240        let _ = node_id;
241        self.measure_subsequence(text, range, style)
242    }
243
244    /// The widths of every prefix of one line.
245    fn measure_line_prefix_widths(
246        &self,
247        text: &crate::text::AnnotatedString,
248        line_range: Range<usize>,
249        style: &TextStyle,
250    ) -> Option<TextLinePrefixWidths> {
251        let _ = text;
252        let _ = line_range;
253        let _ = style;
254        None
255    }
256
257    fn measure_line_width(
258        &self,
259        text: &crate::text::AnnotatedString,
260        line_range: Range<usize>,
261        style: &TextStyle,
262    ) -> Option<f32> {
263        let _ = text;
264        let _ = line_range;
265        let _ = style;
266        None
267    }
268
269    fn line_height(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> f32 {
270        self.measure(text, style).line_height
271    }
272
273    /// The tight glyph box of a text line inside its `line_height` slot:
274    /// `(top_offset, height)` in logical units, where `height` is the font's
275    /// natural ascent+descent extent and `top_offset` positions it within the
276    /// slot (glyph rows are vertically centered). Selection chrome — the
277    /// highlight, the caret and the finger handles — anchors to THIS box,
278    /// not the full slot: with a paragraph line height above the natural one
279    /// the reference shows gaps between highlighted lines and handles riding
280    /// the glyphs. `None` means the box fills the slot.
281    fn glyph_line_box(&self, style: &TextStyle) -> Option<(f32, f32)> {
282        let _ = style;
283        None
284    }
285
286    /// Distance from the top of a line slot down to that line's baseline, in
287    /// logical units — the number the rasterizer places glyph origins at.
288    ///
289    /// Callers that position text by baseline (rather than by its box) need
290    /// this; `None` means the measurer has no font metrics to answer with.
291    fn first_baseline(&self, style: &TextStyle) -> Option<f32> {
292        let _ = style;
293        None
294    }
295
296    /// One line's whole box for a style: its advance, its baseline and what a
297    /// paragraph of it gives back at its edges, with no string to measure.
298    ///
299    /// A layout that stacks rows of a known style needs the row pitch before it
300    /// has any text to put in them, and a paragraph's height and first baseline
301    /// both depend on the edges. `None` when the measurer has no font metrics.
302    fn line_box(&self, style: &TextStyle) -> Option<crate::text::LineBox> {
303        let _ = style;
304        None
305    }
306
307    /// Visits each displayed line's resolved font box, including styled spans.
308    /// The text must already contain its wrapping newlines. Returns `None` when
309    /// the measurer does not provide these metrics; no callbacks run in that case.
310    /// The default handles plain text and defers annotated spans to `line_height`.
311    fn visit_line_boxes(
312        &self,
313        text: &crate::text::AnnotatedString,
314        style: &TextStyle,
315        visit: &mut dyn FnMut(crate::text::LineBox),
316    ) -> Option<()> {
317        if !text.span_styles.is_empty() {
318            return None;
319        }
320        let line_box = self.line_box(style)?;
321        for _ in text.text.split('\n') {
322            visit(line_box);
323        }
324        Some(())
325    }
326
327    fn line_height_for_node(
328        &self,
329        node_id: Option<NodeId>,
330        text: &crate::text::AnnotatedString,
331        style: &TextStyle,
332    ) -> f32 {
333        let _ = node_id;
334        self.line_height(text, style)
335    }
336
337    fn get_offset_for_position(
338        &self,
339        text: &crate::text::AnnotatedString,
340        style: &TextStyle,
341        x: f32,
342        y: f32,
343    ) -> usize;
344
345    fn get_cursor_x_for_offset(
346        &self,
347        text: &crate::text::AnnotatedString,
348        style: &TextStyle,
349        offset: usize,
350    ) -> f32;
351
352    fn layout(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> TextLayoutResult;
353
354    /// Returns an alternate break boundary for `Hyphens::Auto` when a greedy break
355    /// split lands in the middle of a word.
356    ///
357    /// `segment_start_char` and `measured_break_char` are character-boundary indices
358    /// in `line` (not byte offsets). Return `None` to delegate to fallback behavior.
359    fn choose_auto_hyphen_break(
360        &self,
361        _line: &str,
362        _style: &TextStyle,
363        _segment_start_char: usize,
364        _measured_break_char: usize,
365    ) -> Option<usize> {
366        None
367    }
368
369    fn measure_with_options(
370        &self,
371        text: &crate::text::AnnotatedString,
372        style: &TextStyle,
373        options: TextLayoutOptions,
374        max_width: Option<f32>,
375    ) -> TextMetrics {
376        self.prepare_with_options(text, style, options, max_width)
377            .metrics
378    }
379
380    fn measure_with_options_for_node(
381        &self,
382        node_id: Option<NodeId>,
383        text: &crate::text::AnnotatedString,
384        style: &TextStyle,
385        options: TextLayoutOptions,
386        max_width: Option<f32>,
387    ) -> TextMetrics {
388        prepare_text_layout_with_measurer_for_node(self, node_id, text, style, options, max_width)
389            .metrics
390    }
391
392    fn prepare_with_options(
393        &self,
394        text: &crate::text::AnnotatedString,
395        style: &TextStyle,
396        options: TextLayoutOptions,
397        max_width: Option<f32>,
398    ) -> PreparedTextLayout {
399        self.prepare_with_options_fallback(text, style, options, max_width)
400    }
401
402    /// Lays `text` out in `style` for a node that holds both: a layout that
403    /// leaves them as they are shares them instead of copying them.
404    fn prepare_with_options_for_node(
405        &self,
406        node_id: Option<NodeId>,
407        text: &Rc<crate::text::AnnotatedString>,
408        style: &Arc<TextStyle>,
409        options: TextLayoutOptions,
410        max_width: Option<f32>,
411    ) -> PreparedTextLayout {
412        prepare_layout(
413            self,
414            node_id,
415            LayoutSource::Shared { text, style },
416            options,
417            max_width,
418        )
419    }
420
421    fn prepare_with_options_fallback(
422        &self,
423        text: &crate::text::AnnotatedString,
424        style: &TextStyle,
425        options: TextLayoutOptions,
426        max_width: Option<f32>,
427    ) -> PreparedTextLayout {
428        prepare_text_layout_fallback(self, text, style, options, max_width)
429    }
430}
431
432#[derive(Default)]
433struct MonospacedTextMeasurer;
434
435impl MonospacedTextMeasurer {
436    const DEFAULT_SIZE: f32 = 14.0;
437    const CHAR_WIDTH_RATIO: f32 = 0.6;
438
439    fn get_metrics(style: &TextStyle) -> (f32, f32) {
440        let font_size = style.resolve_font_size(Self::DEFAULT_SIZE);
441        let line_height = style.resolve_line_height(Self::DEFAULT_SIZE, font_size);
442        let letter_spacing = style.resolve_letter_spacing(Self::DEFAULT_SIZE).max(0.0);
443        (
444            (font_size * Self::CHAR_WIDTH_RATIO) + letter_spacing,
445            line_height,
446        )
447    }
448}
449
450impl TextMeasurer for MonospacedTextMeasurer {
451    fn measure(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> TextMetrics {
452        let (char_width, line_height) = Self::get_metrics(style);
453
454        let lines: Vec<&str> = text.text.split('\n').collect();
455        let line_count = lines.len().max(1);
456
457        let width = lines
458            .iter()
459            .map(|line| line.chars().count() as f32 * char_width)
460            .fold(0.0_f32, f32::max);
461
462        TextMetrics {
463            width,
464            height: line_count as f32 * line_height,
465            line_height,
466            line_count,
467        }
468    }
469
470    fn measure_subsequence(
471        &self,
472        text: &crate::text::AnnotatedString,
473        range: Range<usize>,
474        style: &TextStyle,
475    ) -> TextMetrics {
476        let (char_width, line_height) = Self::get_metrics(style);
477        let slice = &text.text[range];
478        let line_count = slice.split('\n').count().max(1);
479        let width = slice
480            .split('\n')
481            .map(|line| line.chars().count() as f32 * char_width)
482            .fold(0.0_f32, f32::max);
483
484        TextMetrics {
485            width,
486            height: line_count as f32 * line_height,
487            line_height,
488            line_count,
489        }
490    }
491
492    fn measure_line_prefix_widths(
493        &self,
494        text: &crate::text::AnnotatedString,
495        line_range: Range<usize>,
496        style: &TextStyle,
497    ) -> Option<TextLinePrefixWidths> {
498        let font_size = style.resolve_font_size(Self::DEFAULT_SIZE);
499        let letter_spacing = style.resolve_letter_spacing(Self::DEFAULT_SIZE);
500        TextLinePrefixWidths::monospaced(
501            text.text[line_range].chars().count(),
502            font_size * Self::CHAR_WIDTH_RATIO,
503            letter_spacing,
504        )
505    }
506
507    fn measure_line_width(
508        &self,
509        text: &crate::text::AnnotatedString,
510        line_range: Range<usize>,
511        style: &TextStyle,
512    ) -> Option<f32> {
513        Some(self.measure_subsequence(text, line_range, style).width)
514    }
515
516    fn line_height(&self, _text: &crate::text::AnnotatedString, style: &TextStyle) -> f32 {
517        let (_, line_height) = Self::get_metrics(style);
518        line_height
519    }
520
521    fn get_offset_for_position(
522        &self,
523        text: &crate::text::AnnotatedString,
524        style: &TextStyle,
525        x: f32,
526        y: f32,
527    ) -> usize {
528        let (char_width, line_height) = Self::get_metrics(style);
529
530        if text.text.is_empty() {
531            return 0;
532        }
533
534        let line_index = (y / line_height).floor().max(0.0) as usize;
535        let lines: Vec<&str> = text.text.split('\n').collect();
536        let target_line = line_index.min(lines.len().saturating_sub(1));
537
538        let mut line_start_byte = 0;
539        for line in lines.iter().take(target_line) {
540            line_start_byte += line.len() + 1;
541        }
542
543        let line_text = lines.get(target_line).unwrap_or(&"");
544        let char_index = (x / char_width).round() as usize;
545        let line_char_count = line_text.chars().count();
546        let clamped_index = char_index.min(line_char_count);
547
548        let offset_in_line = line_text
549            .char_indices()
550            .nth(clamped_index)
551            .map_or(line_text.len(), |(i, _)| i);
552
553        line_start_byte + offset_in_line
554    }
555
556    fn get_cursor_x_for_offset(
557        &self,
558        text: &crate::text::AnnotatedString,
559        style: &TextStyle,
560        offset: usize,
561    ) -> f32 {
562        let (char_width, _) = Self::get_metrics(style);
563
564        let clamped_offset = offset.min(text.text.len());
565        let char_count = text.text[..clamped_offset].chars().count();
566        char_count as f32 * char_width
567    }
568
569    fn layout(&self, text: &crate::text::AnnotatedString, style: &TextStyle) -> TextLayoutResult {
570        let (char_width, line_height) = Self::get_metrics(style);
571        TextLayoutResult::monospaced(&text.text, char_width, line_height)
572    }
573}
574
575#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
576struct TextBaseCacheKey {
577    text_hash: u64,
578    style_hash: u64,
579}
580
581#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
582struct TextOptionsCacheKey {
583    base: TextBaseCacheKey,
584    options: TextLayoutOptions,
585    max_width_bits: Option<u32>,
586}
587
588#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
589struct TextPreparedCacheKey {
590    base: TextOptionsCacheKey,
591    visual_hash: u64,
592}
593
594pub(crate) struct TextService {
595    generation: Cell<u64>,
596    measurer: RefCell<Rc<dyn TextMeasurer>>,
597    metrics_cache: RefCell<PassAgedCache<TextBaseCacheKey, TextMetrics>>,
598    options_metrics_cache: RefCell<PassAgedCache<TextOptionsCacheKey, TextMetrics>>,
599    prepared_cache: RefCell<PassAgedCache<TextPreparedCacheKey, Rc<PreparedTextLayout>>>,
600    layout_cache: RefCell<PassAgedCache<TextBaseCacheKey, TextLayoutResult>>,
601}
602
603impl TextService {
604    pub(crate) fn new() -> Self {
605        Self::from_measurer(Rc::new(MonospacedTextMeasurer))
606    }
607
608    pub(crate) fn from_measurer(measurer: Rc<dyn TextMeasurer>) -> Self {
609        Self {
610            generation: Cell::new(1),
611            measurer: RefCell::new(measurer),
612            metrics_cache: RefCell::new(PassAgedCache::with_capacity_at_least_one(
613                TEXT_SERVICE_CACHE_CAPACITY,
614            )),
615            options_metrics_cache: RefCell::new(PassAgedCache::with_capacity_at_least_one(
616                TEXT_SERVICE_CACHE_CAPACITY,
617            )),
618            prepared_cache: RefCell::new(PassAgedCache::with_capacity_at_least_one(
619                TEXT_PREPARED_CACHE_CAPACITY,
620            )),
621            layout_cache: RefCell::new(PassAgedCache::with_capacity_at_least_one(
622                TEXT_SERVICE_CACHE_CAPACITY,
623            )),
624        }
625    }
626
627    pub(crate) fn set_measurer(&self, measurer: Rc<dyn TextMeasurer>) {
628        *self.measurer.borrow_mut() = measurer;
629        self.clear_caches();
630    }
631
632    pub(crate) fn generation(&self) -> u64 {
633        self.generation.get()
634    }
635
636    pub(crate) fn current_measurer(&self) -> Rc<dyn TextMeasurer> {
637        Rc::clone(&self.measurer.borrow())
638    }
639
640    pub(crate) fn with_measurer<R>(&self, f: impl FnOnce(&dyn TextMeasurer) -> R) -> R {
641        let measurer = self.current_measurer();
642        f(&*measurer)
643    }
644
645    pub(crate) fn measure(
646        &self,
647        node_id: Option<NodeId>,
648        text: &crate::text::AnnotatedString,
649        style: &TextStyle,
650    ) -> TextMetrics {
651        let key = text_base_cache_key(text, style);
652        if let Some(key) = key
653            && let Some(metrics) = self.metrics_cache.borrow_mut().get(&key).copied()
654        {
655            return metrics;
656        }
657        let metrics = self.with_measurer(|m| m.measure_for_node(node_id, text, style));
658        if let Some(key) = key {
659            self.metrics_cache.borrow_mut().push(key, metrics);
660        }
661        metrics
662    }
663
664    pub(crate) fn measure_with_options(
665        &self,
666        node_id: Option<NodeId>,
667        text: &crate::text::AnnotatedString,
668        style: &TextStyle,
669        options: TextLayoutOptions,
670        max_width: Option<f32>,
671    ) -> TextMetrics {
672        let key = text_options_cache_key(text, style, options.normalized(), max_width);
673        if let Some(key) = key {
674            if let Some(metrics) = self.options_metrics_cache.borrow_mut().get(&key).copied() {
675                return metrics;
676            }
677            let prepared_key = TextPreparedCacheKey {
678                base: key,
679                visual_hash: style.render_hash(),
680            };
681            if let Some(prepared) = self.prepared_cache.borrow_mut().get(&prepared_key) {
682                return prepared.metrics;
683            }
684        }
685        let metrics = self.with_measurer(|m| {
686            m.measure_with_options_for_node(node_id, text, style, options.normalized(), max_width)
687        });
688        if let Some(key) = key {
689            self.options_metrics_cache.borrow_mut().push(key, metrics);
690        }
691        metrics
692    }
693
694    /// The layout of `text` at `max_width`, shared with the cache: a layout
695    /// animated through widths misses on every frame, and copying each one
696    /// into and out of the cache cost more than laying it out.
697    pub(crate) fn prepare_with_options(
698        &self,
699        node_id: Option<NodeId>,
700        text: &Rc<crate::text::AnnotatedString>,
701        style: &Arc<TextStyle>,
702        options: TextLayoutOptions,
703        max_width: Option<f32>,
704    ) -> Rc<PreparedTextLayout> {
705        let key =
706            text_options_cache_key(text, style, options.normalized(), max_width).map(|base| {
707                TextPreparedCacheKey {
708                    base,
709                    visual_hash: style.render_hash(),
710                }
711            });
712        if let Some(key) = key
713            && let Some(prepared) = self.prepared_cache.borrow_mut().get(&key).map(Rc::clone)
714        {
715            return prepared;
716        }
717        let prepared = Rc::new(self.with_measurer(|m| {
718            m.prepare_with_options_for_node(node_id, text, style, options.normalized(), max_width)
719        }));
720        if let Some(key) = key {
721            self.prepared_cache
722                .borrow_mut()
723                .push(key, Rc::clone(&prepared));
724        }
725        prepared
726    }
727
728    pub(crate) fn layout(
729        &self,
730        text: &crate::text::AnnotatedString,
731        style: &TextStyle,
732    ) -> TextLayoutResult {
733        let key = text_base_cache_key(text, style);
734        if let Some(key) = key
735            && let Some(layout) = self.layout_cache.borrow_mut().get(&key).cloned()
736        {
737            return layout;
738        }
739        let layout = self.with_measurer(|m| m.layout(text, style));
740        if let Some(key) = key {
741            self.layout_cache.borrow_mut().push(key, layout.clone());
742        }
743        layout
744    }
745
746    /// Starts a layout pass: the measurer and these caches drop what recent
747    /// passes did not use.
748    pub(crate) fn begin_layout_pass(&self) {
749        self.with_measurer(TextMeasurer::begin_layout_pass);
750        self.metrics_cache.borrow_mut().begin_pass(drop);
751        self.options_metrics_cache.borrow_mut().begin_pass(drop);
752        self.prepared_cache.borrow_mut().begin_pass(drop);
753        self.layout_cache.borrow_mut().begin_pass(drop);
754    }
755
756    fn clear_caches(&self) {
757        self.generation
758            .set(self.generation.get().wrapping_add(1).max(1));
759        self.metrics_cache.borrow_mut().clear();
760        self.options_metrics_cache.borrow_mut().clear();
761        self.prepared_cache.borrow_mut().clear();
762        self.layout_cache.borrow_mut().clear();
763    }
764}
765
766fn text_base_cache_key(
767    text: &crate::text::AnnotatedString,
768    style: &TextStyle,
769) -> Option<TextBaseCacheKey> {
770    (text.string_annotations.is_empty() && text.link_annotations.is_empty()).then(|| {
771        TextBaseCacheKey {
772            text_hash: text.render_hash(),
773            style_hash: style.measurement_hash(),
774        }
775    })
776}
777
778fn text_options_cache_key(
779    text: &crate::text::AnnotatedString,
780    style: &TextStyle,
781    options: TextLayoutOptions,
782    max_width: Option<f32>,
783) -> Option<TextOptionsCacheKey> {
784    Some(TextOptionsCacheKey {
785        base: text_base_cache_key(text, style)?,
786        options: options.normalized(),
787        max_width_bits: normalize_max_width(max_width).map(f32::to_bits),
788    })
789}
790
791pub fn set_text_measurer<M: TextMeasurer>(measurer: M) {
792    crate::render_state::set_current_text_measurer(Rc::new(measurer));
793}
794
795pub fn measure_text(text: &crate::text::AnnotatedString, style: &TextStyle) -> TextMetrics {
796    with_system_font_scale(text, style, |text, style| {
797        crate::render_state::with_text_service(|service| service.measure(None, text, style))
798    })
799}
800
801pub(crate) fn measure_resolved_text(
802    text: &crate::text::AnnotatedString,
803    style: &TextStyle,
804) -> TextMetrics {
805    crate::render_state::with_text_service(|service| service.measure(None, text, style))
806}
807
808pub(crate) fn resolved_first_baseline(style: &TextStyle) -> Option<f32> {
809    crate::render_state::with_text_service(|service| {
810        service.with_measurer(|m| m.first_baseline(style))
811    })
812}
813
814pub(crate) fn resolved_line_box(style: &TextStyle) -> Option<crate::text::LineBox> {
815    crate::render_state::current_app_context()?;
816    crate::render_state::with_text_service(|service| service.with_measurer(|m| m.line_box(style)))
817}
818
819/// The tight glyph box `(top_offset, height)` of a `style` text line inside
820/// its line slot (see [`TextMeasurer::glyph_line_box`]). Falls back to the
821/// full slot when the active measurer has no font metrics.
822pub fn glyph_line_box(style: &TextStyle, line_height: f32) -> (f32, f32) {
823    let style = system_scaled_style(style);
824    crate::render_state::with_text_service(|service| {
825        service.with_measurer(|m| m.glyph_line_box(&style))
826    })
827    .map_or((0.0, line_height), |(off, h)| {
828        (off.min(line_height), h.min(line_height))
829    })
830}
831
832/// The paragraph line box of `style` (see [`TextMeasurer::line_box`]): its
833/// advance, its baseline and what a paragraph gives back at its edges. `None`
834/// when the active measurer carries no font metrics.
835pub fn text_line_box(style: &TextStyle) -> Option<crate::text::LineBox> {
836    let style = system_scaled_style(style);
837    crate::render_state::with_text_service(|service| service.with_measurer(|m| m.line_box(&style)))
838}
839
840/// Distance from the top of a `style` line slot down to its baseline (see
841/// [`TextMeasurer::first_baseline`]). `None` when the active measurer carries
842/// no font metrics.
843pub fn first_baseline(style: &TextStyle) -> Option<f32> {
844    let style = system_scaled_style(style);
845    crate::render_state::with_text_service(|service| {
846        service.with_measurer(|m| m.first_baseline(&style))
847    })
848}
849
850pub fn measure_text_for_node(
851    node_id: Option<NodeId>,
852    text: &crate::text::AnnotatedString,
853    style: &TextStyle,
854) -> TextMetrics {
855    with_system_font_scale(text, style, |text, style| {
856        crate::render_state::with_text_service(|service| service.measure(node_id, text, style))
857    })
858}
859
860pub fn measure_text_with_options(
861    text: &crate::text::AnnotatedString,
862    style: &TextStyle,
863    options: TextLayoutOptions,
864    max_width: Option<f32>,
865) -> TextMetrics {
866    with_system_font_scale(text, style, |text, style| {
867        crate::render_state::with_text_service(|service| {
868            service.measure_with_options(None, text, style, options.normalized(), max_width)
869        })
870    })
871}
872
873pub fn measure_text_with_options_for_node(
874    node_id: Option<NodeId>,
875    text: &crate::text::AnnotatedString,
876    style: &TextStyle,
877    options: TextLayoutOptions,
878    max_width: Option<f32>,
879) -> TextMetrics {
880    with_system_font_scale(text, style, |text, style| {
881        crate::render_state::with_text_service(|service| {
882            service.measure_with_options(node_id, text, style, options.normalized(), max_width)
883        })
884    })
885}
886
887pub fn prepare_text_layout(
888    text: &crate::text::AnnotatedString,
889    style: &TextStyle,
890    options: TextLayoutOptions,
891    max_width: Option<f32>,
892) -> PreparedTextLayout {
893    Rc::unwrap_or_clone(prepare_text_layout_for_node(
894        None,
895        &Rc::new(text.clone()),
896        &Arc::new(style.clone()),
897        options,
898        max_width,
899    ))
900}
901
902/// Lays out `text` in `style` for `node_id`. The layout shares the text and
903/// style when it leaves them as they are.
904pub fn prepare_text_layout_for_node(
905    node_id: Option<NodeId>,
906    text: &Rc<crate::text::AnnotatedString>,
907    style: &Arc<TextStyle>,
908    options: TextLayoutOptions,
909    max_width: Option<f32>,
910) -> Rc<PreparedTextLayout> {
911    let prepare = |text: &Rc<crate::text::AnnotatedString>, style: &Arc<TextStyle>| {
912        crate::render_state::with_text_service(|service| {
913            service.prepare_with_options(node_id, text, style, options.normalized(), max_width)
914        })
915    };
916    let Some(curve) = crate::render_state::current_scaling_font_scale_curve() else {
917        return prepare(text, style);
918    };
919    let scaled_text = match scale_annotated_font_sizes(text, curve) {
920        Cow::Borrowed(_) => Rc::clone(text),
921        Cow::Owned(scaled) => Rc::new(scaled),
922    };
923    prepare(
924        &scaled_text,
925        &Arc::new(scale_text_style_font_sizes(style, curve).into_owned()),
926    )
927}
928
929pub fn get_offset_for_position(
930    text: &crate::text::AnnotatedString,
931    style: &TextStyle,
932    x: f32,
933    y: f32,
934) -> usize {
935    with_system_font_scale(text, style, |text, style| {
936        crate::render_state::with_text_measurer(|m| m.get_offset_for_position(text, style, x, y))
937    })
938}
939
940/// Byte offset nearest the local content position (`x`, `y`), **wrap-aware** —
941/// the single hit-test every editable-text pointer path uses (tap-to-place,
942/// drag-select, and selection-handle drag).
943///
944/// It is the inverse of the drawn caret and [`wrapped_line_ranges`]: `y` selects
945/// the VISUAL (wrapped) line (`floor(y / line_height)`), then `x` picks the
946/// nearest char boundary WITHIN that line (delegated to the measurer with `y`
947/// forced to 0). `x`/`y` must already be in text space (padding- and
948/// pan-adjusted). The plain [`get_offset_for_position`] maps `y` through the
949/// measurer's logical `\n` layout, so on wrapped text it lands on the wrong line
950/// (an error that grows with each wrapped line above the finger). With
951/// `wrap_width == None` (single-line fields) this reduces to the one logical
952/// line.
953pub fn offset_for_position_wrapped(
954    text: &str,
955    style: &TextStyle,
956    node_id: Option<NodeId>,
957    wrap_width: Option<f32>,
958    line_height: f32,
959    x: f32,
960    y: f32,
961) -> usize {
962    if text.is_empty() {
963        return 0;
964    }
965    let annotated = crate::text::AnnotatedString::from(text);
966    let line_ranges = wrapped_line_ranges(
967        node_id,
968        &annotated,
969        style,
970        TextLayoutOptions::default(),
971        wrap_width,
972    );
973    if line_ranges.is_empty() {
974        return 0;
975    }
976    let line_idx = if line_height > 0.0 {
977        (y / line_height).floor().max(0.0) as usize
978    } else {
979        0
980    }
981    .min(line_ranges.len() - 1);
982    let range = &line_ranges[line_idx];
983    let line = &text[range.start..range.end];
984    let within = get_offset_for_position(&crate::text::AnnotatedString::from(line), style, x, 0.0);
985    range.start + within.min(line.len())
986}
987
988pub fn get_cursor_x_for_offset(
989    text: &crate::text::AnnotatedString,
990    style: &TextStyle,
991    offset: usize,
992) -> f32 {
993    with_system_font_scale(text, style, |text, style| {
994        crate::render_state::with_text_measurer(|m| m.get_cursor_x_for_offset(text, style, offset))
995    })
996}
997
998pub fn layout_text(text: &crate::text::AnnotatedString, style: &TextStyle) -> TextLayoutResult {
999    with_system_font_scale(text, style, |text, style| {
1000        crate::render_state::with_text_service(|service| service.layout(text, style))
1001    })
1002}
1003
1004/// Returns the source-text byte range covered by each **visual** (wrapped) line
1005/// when `text` is laid out at `max_width` with `options`, matching the wrapping
1006/// the renderer performs. Each range excludes the trailing `\n`. With
1007/// `max_width == None` (or soft-wrap disabled) this is just the logical
1008/// `\n`-delimited lines.
1009///
1010/// The text field uses this to place its caret and selection handles on the
1011/// correct visual line for wrapped text: the in-content caret otherwise counts
1012/// only logical `\n` lines, so a caret on a wrapped line's second visual line is
1013/// drawn on the first (and its x, being the whole logical-line prefix width,
1014/// runs off the right edge and is clipped), while typing and the magnifier land
1015/// on the correct spot.
1016pub fn wrapped_line_ranges(
1017    node_id: Option<NodeId>,
1018    text: &crate::text::AnnotatedString,
1019    style: &TextStyle,
1020    options: TextLayoutOptions,
1021    max_width: Option<f32>,
1022) -> Vec<Range<usize>> {
1023    with_system_font_scale(text, style, |text, style| {
1024        crate::render_state::with_text_measurer(|m| {
1025            wrapped_line_ranges_with_measurer(m, node_id, text, style, options, max_width)
1026        })
1027    })
1028}
1029
1030fn wrapped_line_ranges_with_measurer<M: TextMeasurer + ?Sized>(
1031    measurer: &M,
1032    _node_id: Option<NodeId>,
1033    text: &crate::text::AnnotatedString,
1034    style: &TextStyle,
1035    options: TextLayoutOptions,
1036    max_width: Option<f32>,
1037) -> Vec<Range<usize>> {
1038    let opts = options.normalized();
1039    let max_width = normalize_max_width(max_width);
1040    let wrap_width = (opts.soft_wrap && opts.overflow != TextOverflow::Visible)
1041        .then_some(max_width)
1042        .flatten();
1043    let line_break_mode = style
1044        .paragraph_style
1045        .line_break
1046        .take_or_else(|| LineBreak::Simple);
1047    let hyphens_mode = style.paragraph_style.hyphens.take_or_else(|| Hyphens::None);
1048
1049    let line_ranges = split_line_ranges(text.text.as_str());
1050    let Some(width_limit) = wrap_width else {
1051        return line_ranges.into_vec();
1052    };
1053    let mut lines = DisplayLines::with_capacity(line_ranges.len());
1054    for line_range in line_ranges {
1055        wrap_line_to_width(
1056            measurer,
1057            text,
1058            line_range,
1059            style,
1060            (width_limit, LineLimit::NONE, &mut None),
1061            (line_break_mode, hyphens_mode),
1062            &mut lines,
1063        );
1064    }
1065    lines.into_iter().map(|line| line.source_range).collect()
1066}
1067
1068fn prepare_text_layout_fallback<M: TextMeasurer + ?Sized>(
1069    measurer: &M,
1070    text: &crate::text::AnnotatedString,
1071    style: &TextStyle,
1072    options: TextLayoutOptions,
1073    max_width: Option<f32>,
1074) -> PreparedTextLayout {
1075    prepare_text_layout_with_measurer_for_node(measurer, None, text, style, options, max_width)
1076}
1077
1078pub fn prepare_text_layout_with_measurer_for_node<M: TextMeasurer + ?Sized>(
1079    measurer: &M,
1080    node_id: Option<NodeId>,
1081    text: &crate::text::AnnotatedString,
1082    style: &TextStyle,
1083    options: TextLayoutOptions,
1084    max_width: Option<f32>,
1085) -> PreparedTextLayout {
1086    prepare_layout(
1087        measurer,
1088        node_id,
1089        LayoutSource::Borrowed { text, style },
1090        options,
1091        max_width,
1092    )
1093}
1094
1095/// The text and style a layout is prepared from: borrowed, which the layout
1096/// copies, or shared with the node that holds them.
1097#[derive(Clone, Copy)]
1098enum LayoutSource<'a> {
1099    Borrowed {
1100        text: &'a crate::text::AnnotatedString,
1101        style: &'a TextStyle,
1102    },
1103    Shared {
1104        text: &'a Rc<crate::text::AnnotatedString>,
1105        style: &'a Arc<TextStyle>,
1106    },
1107}
1108
1109impl LayoutSource<'_> {
1110    fn text(&self) -> &crate::text::AnnotatedString {
1111        match self {
1112            Self::Borrowed { text, .. } => text,
1113            Self::Shared { text, .. } => text,
1114        }
1115    }
1116
1117    fn style(&self) -> &TextStyle {
1118        match self {
1119            Self::Borrowed { style, .. } => style,
1120            Self::Shared { style, .. } => style,
1121        }
1122    }
1123
1124    /// The source text as the layout's display text, when the layout left it
1125    /// as it is: shared, or built from `lines` when it cannot be shared.
1126    fn display_text(
1127        &self,
1128        lines: &[DisplayLine],
1129        unchanged: bool,
1130    ) -> Rc<crate::text::AnnotatedString> {
1131        match self {
1132            Self::Shared { text, .. } if unchanged => Rc::clone(text),
1133            _ => Rc::new(build_display_annotated(self.text(), lines)),
1134        }
1135    }
1136
1137    fn visual_style(&self) -> Arc<TextStyle> {
1138        match self {
1139            Self::Borrowed { style, .. } => Arc::new((*style).clone()),
1140            Self::Shared { style, .. } => Arc::clone(style),
1141        }
1142    }
1143}
1144
1145fn prepare_layout<M: TextMeasurer + ?Sized>(
1146    measurer: &M,
1147    node_id: Option<NodeId>,
1148    source: LayoutSource<'_>,
1149    options: TextLayoutOptions,
1150    max_width: Option<f32>,
1151) -> PreparedTextLayout {
1152    let (text, style) = (source.text(), source.style());
1153    let telemetry = text_layout_telemetry_enabled();
1154    let total_start = telemetry.then(Instant::now);
1155    let opts = options.normalized();
1156    let max_width = normalize_max_width(max_width);
1157    if let Some(min_font_size_sp) = opts.overflow.scale_down_min_font_size_sp() {
1158        return prepare_scale_down_text_layout(
1159            measurer,
1160            node_id,
1161            text,
1162            style,
1163            opts,
1164            max_width,
1165            min_font_size_sp,
1166        );
1167    }
1168
1169    let wrap_width = (opts.soft_wrap && opts.overflow != TextOverflow::Visible)
1170        .then_some(max_width)
1171        .flatten();
1172    let line_break_mode = style
1173        .paragraph_style
1174        .line_break
1175        .take_or_else(|| LineBreak::Simple);
1176    let hyphens_mode = style.paragraph_style.hyphens.take_or_else(|| Hyphens::None);
1177
1178    let wrap_start = telemetry.then(Instant::now);
1179    let line_ranges = split_line_ranges(text.text.as_str());
1180    let source_line_count = line_ranges.len();
1181    let mut visible_lines: DisplayLines;
1182    let mut wrap_hold = None;
1183    if let Some(width_limit) = wrap_width {
1184        (visible_lines, wrap_hold) = wrap_lines(
1185            measurer,
1186            text,
1187            line_ranges,
1188            style,
1189            (width_limit, LineLimit::of(opts)),
1190            (line_break_mode, hyphens_mode),
1191        );
1192    } else {
1193        visible_lines = line_ranges
1194            .into_iter()
1195            .map(DisplayLine::from_source_range)
1196            .collect();
1197    }
1198    let wrap_ms = wrap_start.map(|start| start.elapsed().as_secs_f64() * 1000.0);
1199
1200    let overflow_start = telemetry.then(Instant::now);
1201    let did_overflow = apply_overflow(
1202        measurer,
1203        node_id,
1204        (text, style),
1205        opts,
1206        max_width,
1207        (&mut visible_lines, &mut wrap_hold),
1208    );
1209    let overflow_ms = overflow_start.map(|start| start.elapsed().as_secs_f64() * 1000.0);
1210
1211    let build_start = telemetry.then(Instant::now);
1212    // No line wrapped, was cut or elided: the display text is the source.
1213    let unchanged = !did_overflow
1214        && visible_lines.len() == source_line_count
1215        && visible_lines
1216            .iter()
1217            .all(|line| matches!(line.text, DisplayLineText::Source));
1218    let display_annotated = source.display_text(&visible_lines, unchanged);
1219    debug_assert_eq!(
1220        display_annotated.text,
1221        join_display_line_text(text, &visible_lines)
1222    );
1223    let build_ms = build_start.map(|start| start.elapsed().as_secs_f64() * 1000.0);
1224
1225    let metrics_start = telemetry.then(Instant::now);
1226    let display_line_count = visible_lines.len().max(1);
1227    let layout_line_count = display_line_count.max(opts.min_lines);
1228
1229    let measured_width = if visible_lines.is_empty() {
1230        0.0
1231    } else {
1232        visible_lines
1233            .iter_mut()
1234            .map(|line| line.measure_width(measurer, node_id, text, style))
1235            .fold(0.0_f32, f32::max)
1236    };
1237    let metrics_ms = metrics_start.map(|start| start.elapsed().as_secs_f64() * 1000.0);
1238    let width = if opts.overflow == TextOverflow::Visible {
1239        measured_width
1240    } else if let Some(width_limit) = max_width {
1241        measured_width.min(width_limit)
1242    } else {
1243        measured_width
1244    };
1245    let wrap_hold = WrapHold::settle(wrap_hold, measured_width, wrap_width);
1246
1247    let vertical = prepared_line_metrics(
1248        measurer,
1249        node_id,
1250        text,
1251        &display_annotated,
1252        style,
1253        opts.min_lines,
1254    );
1255    let prepared = PreparedTextLayout {
1256        text: display_annotated,
1257        visual_style: source.visual_style(),
1258        alignment_lines: vertical.alignment_lines,
1259        metrics: TextMetrics {
1260            width,
1261            height: vertical.height,
1262            line_height: vertical.line_height,
1263            line_count: layout_line_count,
1264        },
1265        did_overflow,
1266        render_text: Default::default(),
1267        wrap_hold,
1268    };
1269
1270    if let Some(start) = total_start {
1271        eprintln!(
1272            "[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}",
1273            text.text.len(),
1274            text.span_styles.len(),
1275            source_line_count,
1276            display_line_count,
1277            wrap_width.is_some(),
1278            max_width,
1279            wrap_ms.unwrap_or(0.0),
1280            overflow_ms.unwrap_or(0.0),
1281            build_ms.unwrap_or(0.0),
1282            metrics_ms.unwrap_or(0.0),
1283            start.elapsed().as_secs_f64() * 1000.0,
1284        );
1285    }
1286
1287    prepared
1288}
1289
1290struct PreparedLineMetrics {
1291    height: f32,
1292    line_height: f32,
1293    alignment_lines: cranpose_ui_layout::AlignmentLines,
1294}
1295
1296fn prepared_line_metrics<M: TextMeasurer + ?Sized>(
1297    measurer: &M,
1298    node_id: Option<NodeId>,
1299    source: &crate::text::AnnotatedString,
1300    display: &crate::text::AnnotatedString,
1301    style: &TextStyle,
1302    min_lines: usize,
1303) -> PreparedLineMetrics {
1304    let base_box = measurer.line_box(style);
1305    if !display.span_styles.is_empty() {
1306        let mut top = 0.0;
1307        let mut trim_bottom = 0.0;
1308        let mut first = None;
1309        let mut last = None;
1310        let mut line_height = 0.0_f32;
1311        let resolved = measurer.visit_line_boxes(display, style, &mut |line| {
1312            if first.is_none() {
1313                top = -line.trim_top;
1314                first = Some(top + line.baseline);
1315            }
1316            last = Some(top + line.baseline);
1317            top += line.height;
1318            trim_bottom = line.trim_bottom;
1319            line_height = line_height.max(line.height);
1320        });
1321        if resolved.is_some() {
1322            let min_height = if min_lines > 1 {
1323                base_box.map_or(0.0, |line| line.block_height(min_lines))
1324            } else {
1325                0.0
1326            };
1327            return PreparedLineMetrics {
1328                height: (top - trim_bottom).max(min_height),
1329                line_height,
1330                alignment_lines: cranpose_ui_layout::AlignmentLines::new(first, last),
1331            };
1332        }
1333    }
1334    let measured_text = if source.span_styles.is_empty() {
1335        source
1336    } else {
1337        display
1338    };
1339    let line_height = measurer
1340        .line_height_for_node(node_id, measured_text, style)
1341        .max(0.0);
1342    let first = base_box
1343        .map(crate::text::LineBox::first_baseline)
1344        .or_else(|| measurer.first_baseline(style));
1345    let displayed_lines = display.text.split('\n').count().max(1);
1346    let layout_line_count = displayed_lines.max(min_lines);
1347    let edges = base_box.unwrap_or_else(|| crate::text::LineBox::untrimmed(line_height, 0.0));
1348    PreparedLineMetrics {
1349        height: (layout_line_count as f32 * line_height - edges.trim_top - edges.trim_bottom)
1350            .max(0.0),
1351        line_height,
1352        alignment_lines: cranpose_ui_layout::AlignmentLines::new(
1353            first,
1354            first.map(|first| first + (displayed_lines - 1) as f32 * line_height),
1355        ),
1356    }
1357}
1358
1359fn prepare_scale_down_text_layout<M: TextMeasurer + ?Sized>(
1360    measurer: &M,
1361    node_id: Option<NodeId>,
1362    text: &crate::text::AnnotatedString,
1363    style: &TextStyle,
1364    options: TextLayoutOptions,
1365    max_width: Option<f32>,
1366    min_font_size_sp: f32,
1367) -> PreparedTextLayout {
1368    let clipped_options = TextLayoutOptions {
1369        overflow: TextOverflow::Clip,
1370        ..options
1371    }
1372    .normalized();
1373
1374    let full_size = prepare_scaled_text_layout(
1375        measurer,
1376        node_id,
1377        text,
1378        style,
1379        clipped_options,
1380        max_width,
1381        FontScaleCurve::linear(1.0),
1382    );
1383    let Some(width_limit) = max_width else {
1384        return full_size;
1385    };
1386    if !full_size.did_overflow {
1387        return full_size;
1388    }
1389
1390    let base_font_size = style.resolve_font_size(DEFAULT_FONT_SIZE_SP);
1391    if !base_font_size.is_finite() || base_font_size <= 0.0 {
1392        return full_size;
1393    }
1394    let min_scale = (min_font_size_sp.min(base_font_size) / base_font_size).clamp(0.0, 1.0);
1395    if min_scale >= 1.0 {
1396        return full_size;
1397    }
1398
1399    let min_size = prepare_scaled_text_layout(
1400        measurer,
1401        node_id,
1402        text,
1403        style,
1404        clipped_options,
1405        Some(width_limit),
1406        FontScaleCurve::linear(min_scale),
1407    );
1408    if min_size.did_overflow {
1409        return min_size;
1410    }
1411
1412    let mut low = min_scale;
1413    let mut high = 1.0;
1414    let mut best = min_size;
1415    for _ in 0..SCALE_DOWN_SEARCH_STEPS {
1416        let mid = (low + high) * 0.5;
1417        let candidate = prepare_scaled_text_layout(
1418            measurer,
1419            node_id,
1420            text,
1421            style,
1422            clipped_options,
1423            Some(width_limit),
1424            FontScaleCurve::linear(mid),
1425        );
1426        if candidate.did_overflow {
1427            high = mid;
1428        } else {
1429            low = mid;
1430            best = candidate;
1431        }
1432    }
1433
1434    best
1435}
1436
1437fn prepare_scaled_text_layout<M: TextMeasurer + ?Sized>(
1438    measurer: &M,
1439    node_id: Option<NodeId>,
1440    text: &crate::text::AnnotatedString,
1441    style: &TextStyle,
1442    options: TextLayoutOptions,
1443    max_width: Option<f32>,
1444    shrink: FontScaleCurve,
1445) -> PreparedTextLayout {
1446    let visual_style = scale_text_style_font_sizes(style, shrink);
1447    let visual_text = scale_annotated_font_sizes(text, shrink);
1448    prepare_text_layout_with_measurer_for_node(
1449        measurer,
1450        node_id,
1451        visual_text.as_ref(),
1452        &visual_style,
1453        options,
1454        max_width,
1455    )
1456}
1457
1458fn scale_annotated_font_sizes(
1459    text: &crate::text::AnnotatedString,
1460    curve: FontScaleCurve,
1461) -> Cow<'_, crate::text::AnnotatedString> {
1462    if curve.is_identity() || !annotated_text_needs_scaling(text) {
1463        return Cow::Borrowed(text);
1464    }
1465
1466    let mut scaled = text.clone();
1467    for span in &mut scaled.span_styles {
1468        scale_span_style_font_sizes(&mut span.item, curve, None);
1469    }
1470    Cow::Owned(scaled)
1471}
1472
1473fn scale_text_style_font_sizes(style: &TextStyle, curve: FontScaleCurve) -> Cow<'_, TextStyle> {
1474    if curve.is_identity() {
1475        return Cow::Borrowed(style);
1476    }
1477
1478    let mut scaled = style.clone();
1479    scale_span_style_font_sizes(&mut scaled.span_style, curve, Some(DEFAULT_FONT_SIZE_SP));
1480    scaled.paragraph_style.line_height =
1481        scale_text_unit_sp(scaled.paragraph_style.line_height, curve);
1482    if let Some(mut indent) = scaled.paragraph_style.text_indent {
1483        indent.first_line = scale_text_unit_sp(indent.first_line, curve);
1484        indent.rest_line = scale_text_unit_sp(indent.rest_line, curve);
1485        scaled.paragraph_style.text_indent = Some(indent);
1486    }
1487    Cow::Owned(scaled)
1488}
1489
1490/// `style` at the running app's font scale.
1491fn system_scaled_style(style: &TextStyle) -> Cow<'_, TextStyle> {
1492    match crate::render_state::current_scaling_font_scale_curve() {
1493        Some(curve) => scale_text_style_font_sizes(style, curve),
1494        None => Cow::Borrowed(style),
1495    }
1496}
1497
1498fn with_system_font_scale<R>(
1499    text: &crate::text::AnnotatedString,
1500    style: &TextStyle,
1501    block: impl FnOnce(&crate::text::AnnotatedString, &TextStyle) -> R,
1502) -> R {
1503    let Some(curve) = crate::render_state::current_scaling_font_scale_curve() else {
1504        return block(text, style);
1505    };
1506    let visual_style = scale_text_style_font_sizes(style, curve);
1507    let visual_text = scale_annotated_font_sizes(text, curve);
1508    block(visual_text.as_ref(), &visual_style)
1509}
1510
1511/// Scales `scaled`'s sizes in place by `curve`.
1512fn scale_span_style_font_sizes(
1513    scaled: &mut crate::text::SpanStyle,
1514    curve: FontScaleCurve,
1515    default_font_size_sp: Option<f32>,
1516) {
1517    let factor = curve.scale();
1518    scaled.font_size = match (scaled.font_size, default_font_size_sp) {
1519        (crate::text::TextUnit::Unspecified, Some(default_size)) => {
1520            crate::text::TextUnit::Sp(curve.sp_to_dp(default_size))
1521        }
1522        (unit, Some(_)) => scale_text_unit_sp_and_em(unit, curve),
1523        (unit, None) => scale_text_unit_sp(unit, curve),
1524    };
1525    scaled.letter_spacing = scale_text_unit_sp(scaled.letter_spacing, curve);
1526    if let Some(mut shadow) = scaled.shadow {
1527        shadow.offset.x = scale_finite_dimension(shadow.offset.x, factor);
1528        shadow.offset.y = scale_finite_dimension(shadow.offset.y, factor);
1529        shadow.blur_radius = scale_finite_dimension(shadow.blur_radius, factor);
1530        scaled.shadow = Some(shadow);
1531    }
1532    if let Some(crate::text::TextDrawStyle::Stroke { width }) = scaled.draw_style {
1533        scaled.draw_style = Some(crate::text::TextDrawStyle::Stroke {
1534            width: width * factor,
1535        });
1536    }
1537}
1538
1539fn annotated_text_needs_scaling(text: &crate::text::AnnotatedString) -> bool {
1540    text.span_styles
1541        .iter()
1542        .any(|span| span_style_needs_scaling(&span.item))
1543}
1544
1545fn span_style_needs_scaling(style: &crate::text::SpanStyle) -> bool {
1546    matches!(style.font_size, crate::text::TextUnit::Sp(value) if value.is_finite())
1547        || matches!(style.letter_spacing, crate::text::TextUnit::Sp(value) if value.is_finite())
1548        || matches!(
1549            style.draw_style,
1550            Some(crate::text::TextDrawStyle::Stroke { .. })
1551        )
1552        || style.shadow.is_some()
1553}
1554
1555fn scale_text_unit_sp(unit: crate::text::TextUnit, curve: FontScaleCurve) -> crate::text::TextUnit {
1556    match unit {
1557        crate::text::TextUnit::Sp(value) if value.is_finite() => {
1558            crate::text::TextUnit::Sp(curve.sp_to_dp(value))
1559        }
1560        other => other,
1561    }
1562}
1563
1564fn scale_text_unit_sp_and_em(
1565    unit: crate::text::TextUnit,
1566    curve: FontScaleCurve,
1567) -> crate::text::TextUnit {
1568    match unit {
1569        crate::text::TextUnit::Sp(_) => scale_text_unit_sp(unit, curve),
1570        crate::text::TextUnit::Em(value) if value.is_finite() => {
1571            crate::text::TextUnit::Em(value * curve.scale())
1572        }
1573        other => other,
1574    }
1575}
1576
1577fn scale_finite_dimension(value: f32, factor: f32) -> f32 {
1578    if value.is_finite() {
1579        value * factor
1580    } else {
1581        value
1582    }
1583}
1584
1585#[derive(Clone, Debug)]
1586enum DisplayLineText {
1587    Source,
1588    Ellipsized(crate::text::AnnotatedString),
1589}
1590
1591#[derive(Clone, Debug)]
1592struct DisplayLine {
1593    source_range: Range<usize>,
1594    text: DisplayLineText,
1595    measured_width: Option<f32>,
1596}
1597
1598impl DisplayLine {
1599    fn from_source_range(source_range: Range<usize>) -> Self {
1600        Self {
1601            source_range,
1602            text: DisplayLineText::Source,
1603            measured_width: None,
1604        }
1605    }
1606
1607    fn from_measured_source_range(source_range: Range<usize>, measured_width: f32) -> Self {
1608        Self {
1609            source_range,
1610            text: DisplayLineText::Source,
1611            measured_width: measured_width
1612                .is_finite()
1613                .then_some(measured_width.max(0.0)),
1614        }
1615    }
1616
1617    fn display_text<'a>(&'a self, source: &'a crate::text::AnnotatedString) -> &'a str {
1618        match &self.text {
1619            DisplayLineText::Source => &source.text[self.source_range.clone()],
1620            DisplayLineText::Ellipsized(annotated) => annotated.text.as_str(),
1621        }
1622    }
1623
1624    /// The line's width, measured once and kept.
1625    fn measure_width<M: TextMeasurer + ?Sized>(
1626        &mut self,
1627        measurer: &M,
1628        node_id: Option<NodeId>,
1629        source: &crate::text::AnnotatedString,
1630        style: &TextStyle,
1631    ) -> f32 {
1632        *self.measured_width.get_or_insert_with(|| match &self.text {
1633            DisplayLineText::Source => {
1634                measurer
1635                    .measure_subsequence_for_node(node_id, source, self.source_range.clone(), style)
1636                    .width
1637            }
1638            DisplayLineText::Ellipsized(annotated) => {
1639                measurer.measure_for_node(node_id, annotated, style).width
1640            }
1641        })
1642    }
1643
1644    fn extend_to_paragraph_end(&mut self, source: &crate::text::AnnotatedString) {
1645        let start = self.source_range.start;
1646        let end = source.text[start..]
1647            .find('\n')
1648            .map_or(source.text.len(), |offset| start + offset);
1649        self.source_range = start..end;
1650        self.text = DisplayLineText::Source;
1651        self.measured_width = None;
1652    }
1653
1654    /// Elides the line to fit `max_width` and returns the widths that cut
1655    /// it at the same character.
1656    fn ellipsize<M: TextMeasurer + ?Sized>(
1657        &mut self,
1658        measurer: &M,
1659        node_id: Option<NodeId>,
1660        source: &crate::text::AnnotatedString,
1661        style: &TextStyle,
1662        max_width: Option<f32>,
1663        placement: EllipsisPlacement,
1664    ) -> WrapHold {
1665        let (line, cut) = fit_ellipsis(
1666            measurer,
1667            node_id,
1668            source,
1669            self.source_range.clone(),
1670            style,
1671            max_width,
1672            placement,
1673        );
1674        *self = line;
1675        cut
1676    }
1677}
1678
1679/// A text's lines, which for most texts is one, kept without an allocation.
1680type LineRanges = smallvec::SmallVec<[Range<usize>; 1]>;
1681/// A layout's display lines, kept without an allocation for one line.
1682type DisplayLines = smallvec::SmallVec<[DisplayLine; 1]>;
1683
1684fn split_line_ranges(text: &str) -> LineRanges {
1685    if text.is_empty() {
1686        return smallvec::smallvec![0..0];
1687    }
1688
1689    let mut ranges = LineRanges::new();
1690    let mut start = 0usize;
1691    for (idx, ch) in text.char_indices() {
1692        if ch == '\n' {
1693            ranges.push(start..idx);
1694            start = idx + ch.len_utf8();
1695        }
1696    }
1697    ranges.push(start..text.len());
1698    ranges
1699}
1700
1701fn build_display_annotated(
1702    source: &crate::text::AnnotatedString,
1703    lines: &[DisplayLine],
1704) -> crate::text::AnnotatedString {
1705    if lines.is_empty() {
1706        return crate::text::AnnotatedString::from("");
1707    }
1708
1709    let mut builder = crate::text::AnnotatedString::builder();
1710    for (idx, line) in lines.iter().enumerate() {
1711        builder = match &line.text {
1712            DisplayLineText::Source => {
1713                builder.append_annotated_subsequence(source, line.source_range.clone())
1714            }
1715            DisplayLineText::Ellipsized(annotated) => builder.append_annotated(annotated),
1716        };
1717        if idx + 1 < lines.len() {
1718            builder = builder.append("\n");
1719        }
1720    }
1721    builder.to_annotated_string()
1722}
1723
1724fn join_display_line_text(source: &crate::text::AnnotatedString, lines: &[DisplayLine]) -> String {
1725    let mut text = String::new();
1726    for (idx, line) in lines.iter().enumerate() {
1727        text.push_str(line.display_text(source));
1728        if idx + 1 < lines.len() {
1729            text.push('\n');
1730        }
1731    }
1732    text
1733}
1734
1735fn trim_segment_end_whitespace(line: &str, start: usize, mut end: usize) -> usize {
1736    while end > start {
1737        let Some((idx, ch)) = line[start..end].char_indices().next_back() else {
1738            break;
1739        };
1740        if ch.is_whitespace() {
1741            end = start + idx;
1742        } else {
1743            break;
1744        }
1745    }
1746    end
1747}
1748
1749/// The max widths a prepared layout comes out the same for, so a node whose
1750/// width moves can keep one layout instead of preparing it again.
1751#[derive(Clone, Copy, Debug, PartialEq)]
1752pub(crate) enum PreparedWidths {
1753    /// Only the width it was prepared at; `None` is unconstrained.
1754    Exact(Option<u32>),
1755    /// No line wrapped or overflowed: unconstrained, and every width from
1756    /// its measured width up. A narrower width may wrap, so it is not held.
1757    AtLeast(f32),
1758    /// Lines wrapped greedily, and every width that breaks them and cuts the
1759    /// last kept line's ellipsis the same.
1760    Wrapped(WrapHold),
1761}
1762
1763impl PreparedWidths {
1764    /// The widths `prepared`, made from `text` at `max_width`, holds for.
1765    pub(crate) fn of(
1766        text: &crate::text::AnnotatedString,
1767        options: TextLayoutOptions,
1768        max_width: Option<f32>,
1769        prepared: &PreparedTextLayout,
1770    ) -> Self {
1771        let max_width = normalize_max_width(max_width);
1772        let exact = Self::Exact(max_width.map(f32::to_bits));
1773        let wrapped = prepared.text.text.matches('\n').count() != text.text.matches('\n').count();
1774        // A line's trailing spaces count when it is fitted but not in the
1775        // width it reports, so such a line may wrap at its own width.
1776        let trailing_space = text
1777            .text
1778            .split('\n')
1779            .any(|line| line.ends_with(char::is_whitespace));
1780        if options
1781            .normalized()
1782            .overflow
1783            .scale_down_min_font_size_sp()
1784            .is_some()
1785            || trailing_space
1786        {
1787            return exact;
1788        }
1789        if wrapped || prepared.did_overflow {
1790            return match (prepared.wrap_hold, max_width) {
1791                (Some(hold), Some(width)) if hold.holds(width) => Self::Wrapped(hold),
1792                _ => exact,
1793            };
1794        }
1795        match max_width {
1796            Some(width) if prepared.metrics.width >= width => exact,
1797            _ => Self::AtLeast(prepared.metrics.width),
1798        }
1799    }
1800
1801    /// Whether preparing at `max_width` gives the same layout.
1802    pub(crate) fn hold(self, max_width: Option<f32>) -> bool {
1803        let max_width = normalize_max_width(max_width);
1804        match self {
1805            Self::Exact(bits) => max_width.map(f32::to_bits) == bits,
1806            Self::AtLeast(min) => max_width.is_none_or(|width| width >= min),
1807            Self::Wrapped(hold) => max_width.is_some_and(|width| hold.holds(width)),
1808        }
1809    }
1810}
1811
1812fn normalize_max_width(max_width: Option<f32>) -> Option<f32> {
1813    match max_width {
1814        Some(width) if width.is_finite() && width > 0.0 => Some(width),
1815        _ => None,
1816    }
1817}
1818
1819fn absolute_range_from_start(base_start: usize, relative: Range<usize>) -> Range<usize> {
1820    (base_start + relative.start)..(base_start + relative.end)
1821}
1822
1823fn boundary_index_for_byte(boundaries: &[usize], byte_offset: usize) -> usize {
1824    boundaries
1825        .binary_search(&byte_offset)
1826        .unwrap_or_else(|index| index.min(boundaries.len().saturating_sub(1)))
1827}
1828
1829struct LineMeasureContext<'a, M: TextMeasurer + ?Sized> {
1830    measurer: &'a M,
1831    text: &'a crate::text::AnnotatedString,
1832    style: &'a TextStyle,
1833    line_start: usize,
1834    prefix_widths: Option<TextLinePrefixWidths>,
1835}
1836
1837impl<'a, M: TextMeasurer + ?Sized> LineMeasureContext<'a, M> {
1838    fn new(
1839        measurer: &'a M,
1840        text: &'a crate::text::AnnotatedString,
1841        line_range: &Range<usize>,
1842        style: &'a TextStyle,
1843        boundary_count: usize,
1844    ) -> Self {
1845        let expected_chars = boundary_count.saturating_sub(1);
1846        let prefix_widths = measurer
1847            .measure_line_prefix_widths(text, line_range.clone(), style)
1848            .filter(|widths| widths.char_count() == expected_chars);
1849        Self {
1850            measurer,
1851            text,
1852            style,
1853            line_start: line_range.start,
1854            prefix_widths,
1855        }
1856    }
1857
1858    fn measure_char_range(&self, boundaries: &[usize], start_idx: usize, end_idx: usize) -> f32 {
1859        if let Some(width) = self.prefix_width_for_char_range(start_idx, end_idx) {
1860            return width;
1861        }
1862        let segment_range =
1863            absolute_range_from_start(self.line_start, boundaries[start_idx]..boundaries[end_idx]);
1864        self.measurer
1865            .measure_subsequence(self.text, segment_range, self.style)
1866            .width
1867    }
1868
1869    fn prefix_width_for_char_range(&self, start_idx: usize, end_idx: usize) -> Option<f32> {
1870        if let Some(prefix_widths) = &self.prefix_widths
1871            && let Some(width) = prefix_widths.width_for_char_range(start_idx, end_idx)
1872        {
1873            return Some(width);
1874        }
1875        None
1876    }
1877
1878    fn display_line_for_char_range(
1879        &self,
1880        boundaries: &[usize],
1881        start_idx: usize,
1882        end_idx: usize,
1883    ) -> DisplayLine {
1884        let source_range =
1885            absolute_range_from_start(self.line_start, boundaries[start_idx]..boundaries[end_idx]);
1886        let measured_width = self.measure_char_range(boundaries, start_idx, end_idx);
1887        DisplayLine::from_measured_source_range(source_range, measured_width)
1888    }
1889}
1890
1891/// The display lines `line_ranges` wrap into at `max_width`, and when any
1892/// wrapped greedily, the widths that wrap them the same.
1893/// How many lines a wrap keeps, and whether the last kept line runs on to
1894/// its paragraph's end to be elided when text is cut after it.
1895#[derive(Clone, Copy, Debug)]
1896struct LineLimit {
1897    lines: usize,
1898    elides_last: bool,
1899}
1900
1901impl LineLimit {
1902    const NONE: Self = Self {
1903        lines: usize::MAX,
1904        elides_last: false,
1905    };
1906
1907    fn of(options: TextLayoutOptions) -> Self {
1908        Self {
1909            lines: options.max_lines,
1910            elides_last: EllipsisPlacement::for_options(options).is_some(),
1911        }
1912    }
1913
1914    /// Whether a wrap with `lines` lines so far has reached the limit: it
1915    /// then ends with one line that stands for all the cut text.
1916    fn reached(self, lines: usize) -> bool {
1917        lines >= self.lines
1918    }
1919
1920    /// Whether the next line after `lines` lines is the last kept line and
1921    /// is elided, so where it breaks does not change the layout.
1922    fn elides_next(self, lines: usize) -> bool {
1923        self.elides_last && lines + 1 == self.lines
1924    }
1925}
1926
1927fn wrap_lines<M: TextMeasurer + ?Sized>(
1928    measurer: &M,
1929    text: &crate::text::AnnotatedString,
1930    line_ranges: LineRanges,
1931    style: &TextStyle,
1932    (max_width, limit): (f32, LineLimit),
1933    modes: (LineBreak, Hyphens),
1934) -> (DisplayLines, Option<WrapHold>) {
1935    let source_lines = line_ranges.len();
1936    let mut lines = DisplayLines::with_capacity(source_lines.min(limit.lines.saturating_add(1)));
1937    let mut hold = Some(WrapHold::ANY);
1938    for line_range in line_ranges {
1939        if limit.reached(lines.len()) {
1940            if lines.len() == limit.lines {
1941                lines.push(DisplayLine::from_source_range(line_range));
1942            }
1943            break;
1944        }
1945        wrap_line_to_width(
1946            measurer,
1947            text,
1948            line_range,
1949            style,
1950            (max_width, limit, &mut hold),
1951            modes,
1952            &mut lines,
1953        );
1954    }
1955    let wrapped = lines.len() != source_lines;
1956    (lines, hold.filter(|_| wrapped))
1957}
1958
1959/// Appends the display lines `line_range` wraps into at `max_width` to
1960/// `out`: most lines fit whole, and take no allocation of their own. Narrows
1961/// `hold` to the widths that wrap it the same, or clears it when the wrap is
1962/// not greedy.
1963fn wrap_line_to_width<M: TextMeasurer + ?Sized>(
1964    measurer: &M,
1965    text: &crate::text::AnnotatedString,
1966    line_range: Range<usize>,
1967    style: &TextStyle,
1968    (max_width, limit, hold): (f32, LineLimit, &mut Option<WrapHold>),
1969    (line_break, hyphens): (LineBreak, Hyphens),
1970    out: &mut DisplayLines,
1971) {
1972    let line_text = &text.text[line_range.clone()];
1973    if line_text.is_empty() {
1974        out.push(DisplayLine::from_source_range(
1975            line_range.start..line_range.start,
1976        ));
1977        return;
1978    }
1979
1980    if let Some(measured_width) = measurer.measure_line_width(text, line_range.clone(), style)
1981        && measured_width <= max_width + WRAP_EPSILON
1982    {
1983        WrapHold::fit_whole(hold, measured_width);
1984        out.push(DisplayLine::from_measured_source_range(
1985            line_range,
1986            measured_width,
1987        ));
1988        return;
1989    }
1990
1991    if matches!(line_break, LineBreak::Heading | LineBreak::Paragraph)
1992        && line_text.chars().any(char::is_whitespace)
1993        && wrap_line_with_word_balance(
1994            measurer,
1995            text,
1996            line_range.clone(),
1997            style,
1998            max_width,
1999            line_break,
2000            out,
2001        )
2002    {
2003        *hold = None;
2004        return;
2005    }
2006
2007    wrap_line_greedy(
2008        measurer,
2009        text,
2010        line_range,
2011        style,
2012        (max_width, limit, hold),
2013        (line_break, hyphens),
2014        out,
2015    );
2016}
2017
2018fn wrap_line_greedy<M: TextMeasurer + ?Sized>(
2019    measurer: &M,
2020    text: &crate::text::AnnotatedString,
2021    line_range: Range<usize>,
2022    style: &TextStyle,
2023    (max_width, limit, hold): (f32, LineLimit, &mut Option<WrapHold>),
2024    (line_break, hyphens): (LineBreak, Hyphens),
2025    out: &mut DisplayLines,
2026) {
2027    let line_text = &text.text[line_range.clone()];
2028    let boundaries = char_boundaries(line_text);
2029    let measure_context =
2030        LineMeasureContext::new(measurer, text, &line_range, style, boundaries.len());
2031    if let Some(measured_width) =
2032        measure_context.prefix_width_for_char_range(0, boundaries.len() - 1)
2033        && measured_width <= max_width + WRAP_EPSILON
2034    {
2035        WrapHold::fit_whole(hold, measured_width);
2036        out.push(DisplayLine::from_measured_source_range(
2037            line_range,
2038            measured_width,
2039        ));
2040        return;
2041    }
2042    let first = out.len();
2043    let mut start_idx = 0usize;
2044
2045    while start_idx < boundaries.len() - 1 {
2046        if limit.reached(out.len()) {
2047            out.push(DisplayLine::from_source_range(
2048                line_range.start + boundaries[start_idx]..line_range.end,
2049            ));
2050            return;
2051        }
2052        let mut low = start_idx + 1;
2053        let mut high = boundaries.len() - 1;
2054        let mut best = start_idx + 1;
2055
2056        while low <= high {
2057            let mid = (low + high) / 2;
2058            let width = measure_context.measure_char_range(&boundaries, start_idx, mid);
2059            if width <= max_width + WRAP_EPSILON || mid == start_idx + 1 {
2060                best = mid;
2061                low = mid + 1;
2062            } else {
2063                if mid == 0 {
2064                    break;
2065                }
2066                high = mid - 1;
2067            }
2068        }
2069
2070        let wrap_idx = choose_wrap_break(line_text, &boundaries, start_idx, best, line_break);
2071        let mut effective_wrap_idx = wrap_idx;
2072        let can_hyphenate = hyphens == Hyphens::Auto
2073            && wrap_idx == best
2074            && best < boundaries.len() - 1
2075            && is_break_inside_word(line_text, &boundaries, wrap_idx);
2076        narrow_to_break(
2077            hold,
2078            &measure_context,
2079            (line_text, &boundaries),
2080            (start_idx, best),
2081            (can_hyphenate, limit.elides_next(out.len())),
2082        );
2083        if can_hyphenate {
2084            effective_wrap_idx = resolve_auto_hyphen_break(
2085                measurer,
2086                line_text,
2087                style,
2088                &boundaries,
2089                start_idx,
2090                wrap_idx,
2091            );
2092        }
2093
2094        let broke_at_word_boundary = effective_wrap_idx > start_idx
2095            && line_text[boundaries[effective_wrap_idx - 1]..boundaries[effective_wrap_idx]]
2096                .chars()
2097                .all(char::is_whitespace);
2098        let segment_start = boundaries[start_idx];
2099        let mut segment_end = boundaries[effective_wrap_idx];
2100        if wrap_idx != best || broke_at_word_boundary {
2101            segment_end = trim_segment_end_whitespace(line_text, segment_start, segment_end);
2102        }
2103        let segment_end_idx = boundary_index_for_byte(&boundaries, segment_end);
2104        out.push(measure_context.display_line_for_char_range(
2105            &boundaries,
2106            start_idx,
2107            segment_end_idx,
2108        ));
2109
2110        start_idx = if wrap_idx != best || broke_at_word_boundary {
2111            skip_leading_whitespace(line_text, &boundaries, wrap_idx)
2112        } else {
2113            effective_wrap_idx
2114        };
2115    }
2116
2117    if out.len() == first {
2118        out.push(DisplayLine::from_source_range(
2119            line_range.start..line_range.start,
2120        ));
2121    }
2122}
2123
2124/// Appends the word-balanced lines of `line_range` to `out` and returns
2125/// whether it could balance them; when it cannot, `out` is left as it was.
2126fn wrap_line_with_word_balance<M: TextMeasurer + ?Sized>(
2127    measurer: &M,
2128    text: &crate::text::AnnotatedString,
2129    line_range: Range<usize>,
2130    style: &TextStyle,
2131    max_width: f32,
2132    line_break: LineBreak,
2133    out: &mut DisplayLines,
2134) -> bool {
2135    let line_text = &text.text[line_range.clone()];
2136    let boundaries = char_boundaries(line_text);
2137    let measure_context =
2138        LineMeasureContext::new(measurer, text, &line_range, style, boundaries.len());
2139    if let Some(measured_width) =
2140        measure_context.prefix_width_for_char_range(0, boundaries.len() - 1)
2141        && measured_width <= max_width + WRAP_EPSILON
2142    {
2143        out.push(DisplayLine::from_measured_source_range(
2144            line_range,
2145            measured_width,
2146        ));
2147        return true;
2148    }
2149    let breakpoints = collect_word_breakpoints(line_text, &boundaries);
2150    if breakpoints.len() <= 2 {
2151        return false;
2152    }
2153
2154    let node_count = breakpoints.len();
2155    let mut best_cost = vec![f32::INFINITY; node_count];
2156    let mut next_index = vec![None; node_count];
2157    best_cost[node_count - 1] = 0.0;
2158
2159    for start in (0..node_count - 1).rev() {
2160        for end in start + 1..node_count {
2161            let start_byte = boundaries[breakpoints[start]];
2162            let end_byte = boundaries[breakpoints[end]];
2163            let trimmed_end = trim_segment_end_whitespace(line_text, start_byte, end_byte);
2164            if trimmed_end <= start_byte {
2165                continue;
2166            }
2167            let segment_start_idx = breakpoints[start];
2168            let segment_end_idx = boundary_index_for_byte(&boundaries, trimmed_end);
2169            let segment_width =
2170                measure_context.measure_char_range(&boundaries, segment_start_idx, segment_end_idx);
2171            if segment_width > max_width + WRAP_EPSILON {
2172                continue;
2173            }
2174            if !best_cost[end].is_finite() {
2175                continue;
2176            }
2177            let slack = (max_width - segment_width).max(0.0);
2178            let is_last = end == node_count - 1;
2179            let segment_cost = match line_break {
2180                LineBreak::Heading => slack * slack,
2181                LineBreak::Paragraph => {
2182                    if is_last {
2183                        slack * slack * 0.16
2184                    } else {
2185                        slack * slack
2186                    }
2187                }
2188                LineBreak::Simple | LineBreak::Unspecified => slack * slack,
2189            };
2190            let candidate = segment_cost + best_cost[end];
2191            if candidate < best_cost[start] {
2192                best_cost[start] = candidate;
2193                next_index[start] = Some(end);
2194            }
2195        }
2196    }
2197
2198    let first = out.len();
2199    let mut current = 0usize;
2200    while current < node_count - 1 {
2201        let Some(next) = next_index[current] else {
2202            out.truncate(first);
2203            return false;
2204        };
2205        let start_byte = boundaries[breakpoints[current]];
2206        let end_byte = boundaries[breakpoints[next]];
2207        let trimmed_end = trim_segment_end_whitespace(line_text, start_byte, end_byte);
2208        if trimmed_end <= start_byte {
2209            out.truncate(first);
2210            return false;
2211        }
2212        let segment_start_idx = breakpoints[current];
2213        let segment_end_idx = boundary_index_for_byte(&boundaries, trimmed_end);
2214        out.push(measure_context.display_line_for_char_range(
2215            &boundaries,
2216            segment_start_idx,
2217            segment_end_idx,
2218        ));
2219        current = next;
2220    }
2221    out.len() > first
2222}
2223
2224fn collect_word_breakpoints(line: &str, boundaries: &[usize]) -> Vec<usize> {
2225    let mut points = vec![0usize];
2226    for idx in 1..boundaries.len() - 1 {
2227        let prev = &line[boundaries[idx - 1]..boundaries[idx]];
2228        let current = &line[boundaries[idx]..boundaries[idx + 1]];
2229        if prev.chars().all(char::is_whitespace) && !current.chars().all(char::is_whitespace) {
2230            points.push(idx);
2231        }
2232    }
2233    let end = boundaries.len() - 1;
2234    if points.last().copied() != Some(end) {
2235        points.push(end);
2236    }
2237    points
2238}
2239
2240fn choose_wrap_break(
2241    line: &str,
2242    boundaries: &[usize],
2243    start_idx: usize,
2244    best: usize,
2245    _line_break: LineBreak,
2246) -> usize {
2247    if best >= boundaries.len() - 1 {
2248        return best;
2249    }
2250
2251    if best <= start_idx + 1 {
2252        return best;
2253    }
2254
2255    for idx in (start_idx + 1..=best).rev() {
2256        let prev = &line[boundaries[idx - 1]..boundaries[idx]];
2257        if prev.chars().all(char::is_whitespace) {
2258            return idx;
2259        }
2260    }
2261    best
2262}
2263
2264/// Narrows `hold` to the widths a line from `start_idx` breaks the same at
2265/// as it does where `best` characters fit, or clears it when the break was
2266/// hyphenated, which no width range describes.
2267/// Narrows `hold` to the widths that break the line from `start_idx` at
2268/// `best` the same. A line that runs on to be elided holds while the rest of
2269/// its paragraph does not fit on it, wherever it breaks.
2270fn narrow_to_break<M: TextMeasurer + ?Sized>(
2271    hold: &mut Option<WrapHold>,
2272    measure_context: &LineMeasureContext<'_, M>,
2273    (line, boundaries): (&str, &[usize]),
2274    (start_idx, best): (usize, usize),
2275    (hyphenated, elided): (bool, bool),
2276) {
2277    let end = boundaries.len() - 1;
2278    if elided && best < end {
2279        if let Some(hold) = hold {
2280            let rest = measure_context.measure_char_range(boundaries, start_idx, end);
2281            hold.narrow(f32::NEG_INFINITY, rest);
2282        }
2283        return;
2284    }
2285    if hyphenated {
2286        *hold = None;
2287    }
2288    let Some(hold) = hold else {
2289        return;
2290    };
2291    let (fits, pulls_up) = wrap_break_widths(line, boundaries, start_idx, best);
2292    let width = |idx| measure_context.measure_char_range(boundaries, start_idx, idx);
2293    hold.narrow(
2294        fits.map_or(f32::NEG_INFINITY, width),
2295        pulls_up.map_or(f32::INFINITY, width),
2296    );
2297}
2298
2299/// The character counts, from `start_idx`, whose widths bound the widths
2300/// [`choose_wrap_break`] picks the same break at as it does for `best`: the
2301/// first a line must fit to keep its break (`None` when any width does),
2302/// and the first that would take the break past it (`None` when none
2303/// would).
2304fn wrap_break_widths(
2305    line: &str,
2306    boundaries: &[usize],
2307    start_idx: usize,
2308    best: usize,
2309) -> (Option<usize>, Option<usize>) {
2310    let end = boundaries.len() - 1;
2311    // A line takes its first character whether it fits or not.
2312    if best <= start_idx + 1 {
2313        return (None, (best < end).then_some(best + 1));
2314    }
2315    if best >= end {
2316        return (Some(end), None);
2317    }
2318    let after_space = |idx: usize| {
2319        line[boundaries[idx - 1]..boundaries[idx]]
2320            .chars()
2321            .all(char::is_whitespace)
2322    };
2323    match (start_idx + 1..=best).rev().find(|&idx| after_space(idx)) {
2324        Some(wrap_idx) => {
2325            let next = (best + 1..end).find(|&idx| after_space(idx)).unwrap_or(end);
2326            (Some(wrap_idx), Some(next))
2327        }
2328        None => (Some(best), Some(best + 1)),
2329    }
2330}
2331
2332fn is_break_inside_word(line: &str, boundaries: &[usize], break_idx: usize) -> bool {
2333    if break_idx == 0 || break_idx >= boundaries.len() - 1 {
2334        return false;
2335    }
2336    let prev = &line[boundaries[break_idx - 1]..boundaries[break_idx]];
2337    let next = &line[boundaries[break_idx]..boundaries[break_idx + 1]];
2338    !prev.chars().all(char::is_whitespace) && !next.chars().all(char::is_whitespace)
2339}
2340
2341fn resolve_auto_hyphen_break<M: TextMeasurer + ?Sized>(
2342    measurer: &M,
2343    line: &str,
2344    style: &TextStyle,
2345    boundaries: &[usize],
2346    start_idx: usize,
2347    break_idx: usize,
2348) -> usize {
2349    if let Some(candidate) = measurer.choose_auto_hyphen_break(line, style, start_idx, break_idx)
2350        && is_valid_auto_hyphen_break(line, boundaries, start_idx, break_idx, candidate)
2351    {
2352        return candidate;
2353    }
2354    choose_auto_hyphen_break_fallback(boundaries, start_idx, break_idx)
2355}
2356
2357fn is_valid_auto_hyphen_break(
2358    line: &str,
2359    boundaries: &[usize],
2360    start_idx: usize,
2361    break_idx: usize,
2362    candidate_idx: usize,
2363) -> bool {
2364    let end_idx = boundaries.len().saturating_sub(1);
2365    candidate_idx > start_idx
2366        && candidate_idx < end_idx
2367        && candidate_idx <= break_idx
2368        && candidate_idx >= start_idx + AUTO_HYPHEN_MIN_SEGMENT_CHARS
2369        && is_break_inside_word(line, boundaries, candidate_idx)
2370}
2371
2372fn choose_auto_hyphen_break_fallback(
2373    boundaries: &[usize],
2374    start_idx: usize,
2375    break_idx: usize,
2376) -> usize {
2377    let end_idx = boundaries.len().saturating_sub(1);
2378    if break_idx >= end_idx {
2379        return break_idx;
2380    }
2381    let trailing_len = end_idx.saturating_sub(break_idx);
2382    if trailing_len > 2 || break_idx <= start_idx + AUTO_HYPHEN_MIN_SEGMENT_CHARS {
2383        return break_idx;
2384    }
2385
2386    let min_break = start_idx + AUTO_HYPHEN_MIN_SEGMENT_CHARS;
2387    let max_break = break_idx.saturating_sub(1);
2388    if min_break > max_break {
2389        return break_idx;
2390    }
2391
2392    let mut best_break = break_idx;
2393    let mut best_penalty = usize::MAX;
2394    for idx in min_break..=max_break {
2395        let candidate_trailing_len = end_idx.saturating_sub(idx);
2396        let candidate_prefix_len = idx.saturating_sub(start_idx);
2397        if candidate_prefix_len < AUTO_HYPHEN_MIN_SEGMENT_CHARS
2398            || candidate_trailing_len < AUTO_HYPHEN_MIN_TRAILING_CHARS
2399        {
2400            continue;
2401        }
2402
2403        let penalty = candidate_trailing_len.abs_diff(AUTO_HYPHEN_PREFERRED_TRAILING_CHARS);
2404        if penalty < best_penalty {
2405            best_penalty = penalty;
2406            best_break = idx;
2407            if penalty == 0 {
2408                break;
2409            }
2410        }
2411    }
2412    best_break
2413}
2414
2415fn skip_leading_whitespace(line: &str, boundaries: &[usize], mut idx: usize) -> usize {
2416    while idx < boundaries.len() - 1 {
2417        let ch = &line[boundaries[idx]..boundaries[idx + 1]];
2418        if !ch.chars().all(char::is_whitespace) {
2419            break;
2420        }
2421        idx += 1;
2422    }
2423    idx
2424}
2425
2426/// Cuts `visible_lines` to the options' line limit and elides what does not
2427/// fit, and returns whether anything did not. Narrows `hold` to the widths
2428/// that cut the ellipsis at the same character, or clears it when a line
2429/// overflows its width.
2430fn apply_overflow<M: TextMeasurer + ?Sized>(
2431    measurer: &M,
2432    node_id: Option<NodeId>,
2433    (text, style): (&crate::text::AnnotatedString, &TextStyle),
2434    options: TextLayoutOptions,
2435    max_width: Option<f32>,
2436    (visible_lines, hold): (&mut DisplayLines, &mut Option<WrapHold>),
2437) -> bool {
2438    if options.overflow == TextOverflow::Visible {
2439        return false;
2440    }
2441    let ellipsis = EllipsisPlacement::for_options(options);
2442    let mut did_overflow = false;
2443    if visible_lines.len() > options.max_lines {
2444        did_overflow = true;
2445        visible_lines.truncate(options.max_lines);
2446        if let (Some(placement), Some(last_line)) = (ellipsis, visible_lines.last_mut()) {
2447            last_line.extend_to_paragraph_end(text);
2448            let cut = last_line.ellipsize(measurer, node_id, text, style, max_width, placement);
2449            if let Some(hold) = hold {
2450                hold.narrow(cut.fits, cut.pulls_up);
2451            }
2452        }
2453    }
2454
2455    let Some(width_limit) = max_width else {
2456        return did_overflow;
2457    };
2458    let visible_len = visible_lines.len();
2459    for (line_index, line) in visible_lines.iter_mut().enumerate() {
2460        if line.measure_width(measurer, node_id, text, style) <= width_limit + WRAP_EPSILON {
2461            continue;
2462        }
2463        did_overflow = true;
2464        *hold = None;
2465        if line_index + 1 == visible_len
2466            && let Some(placement) = ellipsis
2467        {
2468            line.ellipsize(measurer, node_id, text, style, max_width, placement);
2469        }
2470    }
2471    did_overflow
2472}
2473
2474#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2475enum EllipsisPlacement {
2476    End,
2477    Start,
2478    Middle,
2479}
2480
2481impl EllipsisPlacement {
2482    /// How many of `kept_chars` stay before the ellipsis and how many after.
2483    fn split(self, kept_chars: usize) -> (usize, usize) {
2484        match self {
2485            Self::End => (kept_chars, 0),
2486            Self::Start => (0, kept_chars),
2487            Self::Middle => (kept_chars.div_ceil(2), kept_chars / 2),
2488        }
2489    }
2490
2491    /// The most characters an elided line can keep within `width_limit`,
2492    /// estimated from the line's prefix widths and the ellipsis's width
2493    /// without measuring any elided string. Shaping across the cut can move
2494    /// the real width a little either way, so callers confirm it.
2495    fn estimated_kept_chars(
2496        self,
2497        prefix_widths: &TextLinePrefixWidths,
2498        ellipsis_width: f32,
2499        width_limit: f32,
2500    ) -> Option<usize> {
2501        let char_count = prefix_widths.char_count();
2502        let width = |kept_chars: usize| {
2503            let (head_chars, tail_chars) = self.split(kept_chars);
2504            Some(
2505                prefix_widths.width_for_char_range(0, head_chars)?
2506                    + ellipsis_width
2507                    + prefix_widths.width_for_char_range(char_count - tail_chars, char_count)?,
2508            )
2509        };
2510        let (mut fitting, mut overflowing) = (0usize, char_count + 1);
2511        while fitting + 1 < overflowing {
2512            let kept_chars = fitting + (overflowing - fitting) / 2;
2513            if width(kept_chars)? <= width_limit + WRAP_EPSILON {
2514                fitting = kept_chars;
2515            } else {
2516                overflowing = kept_chars;
2517            }
2518        }
2519        Some(fitting)
2520    }
2521
2522    fn for_options(options: TextLayoutOptions) -> Option<Self> {
2523        let single_line = options.max_lines == 1;
2524        match options.overflow {
2525            TextOverflow::Ellipsis => Some(Self::End),
2526            TextOverflow::StartEllipsis if single_line => Some(Self::Start),
2527            TextOverflow::MiddleEllipsis if single_line => Some(Self::Middle),
2528            TextOverflow::StartEllipsis
2529            | TextOverflow::MiddleEllipsis
2530            | TextOverflow::Clip
2531            | TextOverflow::Visible
2532            | TextOverflow::ScaleDown { .. } => None,
2533        }
2534    }
2535
2536    fn elide(
2537        self,
2538        source: &crate::text::AnnotatedString,
2539        source_range: Range<usize>,
2540        boundaries: &[usize],
2541        kept_chars: usize,
2542    ) -> crate::text::AnnotatedString {
2543        let char_count = boundaries.len() - 1;
2544        let (head_chars, tail_chars) = self.split(kept_chars);
2545        let head_end = source_range.start + boundaries[head_chars];
2546        let tail_start = source_range.start + boundaries[char_count - tail_chars];
2547        crate::text::AnnotatedString::builder()
2548            .append_annotated_subsequence(source, source_range.start..head_end)
2549            .append(ELLIPSIS)
2550            .append_annotated_subsequence(source, tail_start..source_range.end)
2551            .to_annotated_string()
2552    }
2553}
2554
2555/// The line `source_range` elided at `placement` to fit `max_width`, and the
2556/// widths the same elision fits: from its own width up to the width of the
2557/// elision keeping one character more.
2558fn fit_ellipsis<M: TextMeasurer + ?Sized>(
2559    measurer: &M,
2560    node_id: Option<NodeId>,
2561    source: &crate::text::AnnotatedString,
2562    source_range: Range<usize>,
2563    style: &TextStyle,
2564    max_width: Option<f32>,
2565    placement: EllipsisPlacement,
2566) -> (DisplayLine, WrapHold) {
2567    let width_limit = max_width.unwrap_or(f32::INFINITY);
2568    // The line when it fits, or the width it overflows at.
2569    let fitting_line = |text: DisplayLineText| {
2570        let mut line = DisplayLine {
2571            source_range: source_range.clone(),
2572            text,
2573            measured_width: None,
2574        };
2575        let width = line.measure_width(measurer, node_id, source, style);
2576        if width <= width_limit + WRAP_EPSILON {
2577            Ok(line)
2578        } else {
2579            Err(width)
2580        }
2581    };
2582    // Keeping every character elides nothing, so the whole line bounds the
2583    // widths an elision holds.
2584    let whole = if placement == EllipsisPlacement::End {
2585        f32::INFINITY
2586    } else {
2587        match fitting_line(DisplayLineText::Source) {
2588            Ok(line) => return ElisionSearch::found(line, f32::INFINITY),
2589            Err(width) => width,
2590        }
2591    };
2592
2593    let boundaries = char_boundaries(&source.text[source_range.clone()]);
2594    let elided_line = |kept_chars: usize| {
2595        fitting_line(DisplayLineText::Ellipsized(placement.elide(
2596            source,
2597            source_range.clone(),
2598            &boundaries,
2599            kept_chars,
2600        )))
2601    };
2602    let best = match elided_line(0) {
2603        Ok(line) => line,
2604        Err(ellipsis_width) => {
2605            let empty = DisplayLine {
2606                source_range: source_range.clone(),
2607                text: DisplayLineText::Ellipsized(crate::text::AnnotatedString::default()),
2608                measured_width: None,
2609            };
2610            return ElisionSearch::found(empty, ellipsis_width);
2611        }
2612    };
2613
2614    // The line's prefix widths place the cut without measuring an elided
2615    // string per guess; measuring the guess and the one past it confirms it,
2616    // and the search below only runs when shaping across the cut moved it.
2617    let guess = best.measured_width.and_then(|ellipsis_width| {
2618        measurer
2619            .measure_line_prefix_widths(source, source_range.clone(), style)
2620            .filter(|widths| widths.char_count() + 1 == boundaries.len())
2621            .and_then(|widths| placement.estimated_kept_chars(&widths, ellipsis_width, width_limit))
2622    });
2623    let mut search = ElisionSearch {
2624        best,
2625        fitting: 0,
2626        overflowing: boundaries.len(),
2627        overflow_width: whole,
2628    };
2629    if let Some(guess) = guess.filter(|guess| *guess > 0) {
2630        for kept_chars in [guess, guess + 1] {
2631            if kept_chars <= search.fitting || kept_chars >= search.overflowing {
2632                break;
2633            }
2634            search.probe(kept_chars, elided_line(kept_chars));
2635        }
2636    }
2637    while search.fitting + 1 < search.overflowing {
2638        let kept_chars = search.fitting + (search.overflowing - search.fitting) / 2;
2639        search.probe(kept_chars, elided_line(kept_chars));
2640    }
2641    ElisionSearch::found(search.best, search.overflow_width)
2642}
2643
2644/// The most characters an elision was found to keep within the width, and
2645/// the fewest it was found to overflow at.
2646struct ElisionSearch {
2647    best: DisplayLine,
2648    fitting: usize,
2649    overflowing: usize,
2650    overflow_width: f32,
2651}
2652
2653impl ElisionSearch {
2654    fn probe(&mut self, kept_chars: usize, line: Result<DisplayLine, f32>) {
2655        match line {
2656            Ok(line) => (self.fitting, self.best) = (kept_chars, line),
2657            Err(width) => (self.overflowing, self.overflow_width) = (kept_chars, width),
2658        }
2659    }
2660
2661    /// `line`, and the widths it fits while the next wider elision, which is
2662    /// `overflow_width` wide, does not.
2663    fn found(line: DisplayLine, overflow_width: f32) -> (DisplayLine, WrapHold) {
2664        let cut = WrapHold {
2665            fits: line.measured_width.unwrap_or(f32::NEG_INFINITY),
2666            pulls_up: overflow_width,
2667        };
2668        (line, cut)
2669    }
2670}
2671
2672fn char_boundaries(text: &str) -> Vec<usize> {
2673    let mut out = Vec::with_capacity(text.chars().count() + 1);
2674    out.push(0);
2675    for (idx, _) in text.char_indices() {
2676        if idx != 0 {
2677            out.push(idx);
2678        }
2679    }
2680    out.push(text.len());
2681    out
2682}
2683
2684#[cfg(test)]
2685#[path = "tests/measure_tests.rs"]
2686mod tests;