1use std::{
2 cell::{Cell, OnceCell, RefCell},
3 ops::Range,
4 rc::Rc,
5 sync::Arc,
6};
7
8use cranpose_core::{
9 PackedU64,
10 collections::pass_aged::{IDLE_PASSES, PassAgedCache},
11};
12use cranpose_text::{
13 CaretDirection, FontExtent, FontFamily, FontFamilyResolver, FontRequest, FontStyle, FontWeight,
14 LayoutInput, ParagraphIntrinsics, TextLayout, TextLayouter, paragraph_line_box,
15};
16use cranpose_ui_graphics::{MemoryHeadroom, Size, TextLayoutHandle};
17
18use super::{
19 AnnotatedString, LineBox, RenderString, SpanStyle, TextAlign, TextDrawStyle, TextUnit,
20 layout_cache::{CacheAdmission, Cached, LayoutCache},
21 layout_input::{DEFAULT_FONT_SIZE_SP, ParagraphSpans, TextUnits},
22 layout_options::{TextLayoutOptions, TextOverflow},
23 style::TextStyle,
24 text_key::TextKey,
25};
26use crate::{density::Density, font_scale::FontScaleCurve, text_layout_result::TextLayoutResult};
27
28const TEXT_INTRINSICS_CACHE_CAPACITY: usize = 1024;
29const SCALE_DOWN_SEARCH_STEPS: usize = 14;
30
31#[derive(Clone, Copy, Debug, PartialEq)]
33pub struct TextMetrics {
34 pub width: f32,
36 pub height: f32,
38 pub line_height: f32,
40 pub line_count: usize,
42}
43
44#[derive(Clone, Debug)]
47pub struct PreparedTextLayout {
48 pub text: Rc<AnnotatedString>,
50 pub visual_style: Arc<TextStyle>,
53 pub layout: TextLayout,
55 max_width: f32,
58 min_lines: u32,
59 pub render_text: OnceCell<(Arc<RenderString>, PackedU64)>,
63 pub(crate) visual_style_hash: OnceCell<PackedU64>,
64 layout_handle: OnceCell<TextLayoutHandle>,
66}
67
68impl PreparedTextLayout {
69 fn new(
72 text: Rc<AnnotatedString>,
73 style: Arc<TextStyle>,
74 layout: TextLayout,
75 (max_width, min_lines): (f32, usize),
76 ) -> Self {
77 Self {
78 max_width,
79 min_lines: u32::try_from(min_lines).unwrap_or(u32::MAX),
80 text,
81 visual_style: style,
82 layout,
83 render_text: OnceCell::new(),
84 visual_style_hash: OnceCell::new(),
85 layout_handle: OnceCell::new(),
86 }
87 }
88
89 pub fn size(&self) -> Size {
92 Size::new(
93 self.layout.width().min(self.max_width),
94 self.layout.height(),
95 )
96 }
97
98 pub(crate) fn max_widths_kept(&self, min_width: f32) -> cranpose_ui_layout::BoundRange {
101 let width = self.layout.width();
102 if self.max_width < width {
105 return cranpose_ui_layout::BoundRange::exactly(self.max_width);
106 }
107 let (low, high) = self.layout.max_widths_kept(min_width);
108 cranpose_ui_layout::BoundRange {
109 low: low.max(width),
110 high,
111 }
112 }
113
114 pub fn metrics(&self) -> TextMetrics {
116 let layout = &self.layout;
117 let size = self.size();
118 TextMetrics {
119 width: size.width,
120 height: size.height,
121 line_height: layout.line_advance(),
122 line_count: layout
123 .line_count()
124 .max(usize::try_from(self.min_lines).unwrap_or(usize::MAX)),
125 }
126 }
127
128 pub fn alignment_lines(&self) -> cranpose_ui_layout::AlignmentLines {
130 let layout = &self.layout;
131 cranpose_ui_layout::AlignmentLines::new(
132 Some(layout.line_baseline(0)),
133 Some(layout.line_baseline(layout.line_count() - 1)),
134 )
135 }
136
137 pub fn did_overflow(&self) -> bool {
140 self.layout.did_overflow_height() || self.layout.did_overflow_width()
141 }
142
143 pub(crate) fn layout_handle(&self) -> TextLayoutHandle {
146 self.layout_handle
147 .get_or_init(|| TextLayoutHandle::from(Arc::new(self.layout.clone())))
148 .clone()
149 }
150
151 pub(crate) fn at_widths(
156 self: &Rc<Self>,
157 widths: (f32, f32),
158 align: TextAlign,
159 spare: Option<Rc<Self>>,
160 ) -> Rc<Self> {
161 let width = self.layout.width_between(widths.0, widths.1);
162 if width.to_bits() == self.layout.width().to_bits() {
163 return Rc::clone(self);
164 }
165 self.aligned_at(widths, align, spare)
166 }
167
168 #[inline(never)]
171 fn aligned_at(
172 self: &Rc<Self>,
173 widths: (f32, f32),
174 align: TextAlign,
175 spare: Option<Rc<Self>>,
176 ) -> Rc<Self> {
177 let mut aligned = Self {
178 min_lines: self.min_lines,
179 render_text: self.render_text.clone(),
180 visual_style_hash: self.visual_style_hash.clone(),
181 ..Self::new(
182 Rc::clone(&self.text),
183 Arc::clone(&self.visual_style),
184 self.layout.clone(),
185 (self.max_width, 0),
186 )
187 };
188 aligned.realign(widths, align);
189 if let Some(mut spare) = spare
190 && let Some(slot) = Rc::get_mut(&mut spare)
191 {
192 *slot = aligned;
193 return spare;
194 }
195 Rc::new(aligned)
196 }
197
198 pub(crate) fn realign(&mut self, (min_width, max_width): (f32, f32), align: TextAlign) {
201 self.layout.align(min_width, max_width, align);
202 self.max_width = max_width;
203 self.layout_handle = OnceCell::new();
204 }
205
206 pub(crate) fn try_realign(
209 &mut self,
210 (min_width, max_width): (f32, f32),
211 align: TextAlign,
212 ) -> bool {
213 let held = self.layout.try_align(min_width, max_width, align);
214 if held {
215 self.max_width = max_width;
216 self.layout_handle = OnceCell::new();
217 }
218 held
219 }
220
221 pub fn render_text(&self) -> Arc<RenderString> {
225 if let Some((converted, _)) = self.render_text.get() {
226 return Arc::clone(converted);
227 }
228 self.convert_render_text().0
229 }
230
231 pub fn render_text_hash(&self) -> u64 {
235 if let Some((_, hash)) = self.render_text.get() {
236 return hash.get();
237 }
238 self.convert_render_text().1
239 }
240
241 fn convert_render_text(&self) -> (Arc<RenderString>, u64) {
242 let converted = Arc::new(self.text.render_string());
243 let hash = converted.render_hash();
244 let _ = self
245 .render_text
246 .set((Arc::clone(&converted), PackedU64::new(hash)));
247 (converted, hash)
248 }
249
250 pub fn visual_style_hash(&self) -> u64 {
253 if let Some(hash) = self.visual_style_hash.get() {
254 return hash.get();
255 }
256 let hash = self.visual_style.render_hash();
257 let _ = self.visual_style_hash.set(PackedU64::new(hash));
258 hash
259 }
260
261 pub fn result(&self) -> TextLayoutResult {
263 TextLayoutResult::new(self.layout.clone())
264 }
265}
266
267impl PartialEq for PreparedTextLayout {
268 fn eq(&self, other: &Self) -> bool {
269 self.text == other.text
270 && self.visual_style == other.visual_style
271 && self.metrics() == other.metrics()
272 && self.alignment_lines() == other.alignment_lines()
273 && self.did_overflow() == other.did_overflow()
274 }
275}
276
277#[derive(Clone, Copy)]
280enum LayoutSource<'a> {
281 Borrowed {
282 text: &'a AnnotatedString,
283 style: &'a TextStyle,
284 },
285 Shared {
286 text: &'a SharedText<'a>,
287 shaped: Option<&'a TextLayout>,
290 },
291}
292
293pub(crate) struct SharedText<'a> {
295 pub(crate) text: &'a Rc<AnnotatedString>,
296 pub(crate) style: &'a Arc<TextStyle>,
297 pub(crate) style_hash: u64,
298}
299
300impl LayoutSource<'_> {
301 fn text(&self) -> &AnnotatedString {
302 match self {
303 Self::Borrowed { text, .. } => text,
304 Self::Shared { text, .. } => text.text,
305 }
306 }
307
308 fn style(&self) -> &TextStyle {
309 match self {
310 Self::Borrowed { style, .. } => style,
311 Self::Shared { text, .. } => text.style,
312 }
313 }
314
315 fn shared(&self) -> (Rc<AnnotatedString>, Arc<TextStyle>) {
316 match self {
317 Self::Borrowed { text, style } => {
318 (Rc::new((*text).clone()), Arc::new((*style).clone()))
319 }
320 Self::Shared { text, .. } => (Rc::clone(text.text), Arc::clone(text.style)),
321 }
322 }
323
324 fn style_hash(&self) -> u64 {
325 match self {
326 Self::Borrowed { style, .. } => style.render_hash(),
327 Self::Shared { text, .. } => text.style_hash,
328 }
329 }
330
331 fn shaped(&self) -> Option<&TextLayout> {
332 match self {
333 Self::Borrowed { .. } => None,
334 Self::Shared { shaped, .. } => *shaped,
335 }
336 }
337}
338
339#[derive(Clone, Copy, Debug, PartialEq)]
341pub(crate) struct TextFrame {
342 pub(crate) min_width: f32,
343 pub(crate) max_width: f32,
344 pub(crate) options: TextLayoutOptions,
345}
346
347impl TextFrame {
348 fn unbounded(options: TextLayoutOptions) -> Self {
350 Self {
351 min_width: 0.0,
352 max_width: f32::INFINITY,
353 options,
354 }
355 }
356
357 pub(crate) fn up_to(max_width: Option<f32>, options: TextLayoutOptions) -> Self {
360 Self {
361 max_width: max_width
362 .filter(|width| width.is_finite() && *width > 0.0)
363 .unwrap_or(f32::INFINITY),
364 ..Self::unbounded(options)
365 }
366 }
367}
368
369pub(crate) struct TextService {
372 generation: Cell<u64>,
373 pass_owed: Cell<bool>,
376 fonts: RefCell<FontFamilyResolver>,
377 layouter: RefCell<TextLayouter>,
378 layouts: RefCell<LayoutCache>,
379 intrinsics: RefCell<PassAgedCache<TextKey, ParagraphIntrinsics>>,
380}
381
382impl TextService {
383 pub(crate) fn new() -> Self {
384 Self {
385 generation: Cell::new(1),
386 pass_owed: Cell::new(false),
387 fonts: RefCell::new(default_fonts()),
388 layouter: RefCell::new(TextLayouter::default()),
389 layouts: RefCell::new(LayoutCache::new()),
390 intrinsics: RefCell::new(PassAgedCache::with_capacity_at_least_one(
391 TEXT_INTRINSICS_CACHE_CAPACITY,
392 )),
393 }
394 }
395
396 pub(crate) fn set_fonts(&self, fonts: FontFamilyResolver) {
398 *self.fonts.borrow_mut() = fonts;
399 self.generation
400 .set(self.generation.get().wrapping_add(1).max(1));
401 self.layouts.borrow_mut().clear();
402 self.intrinsics.borrow_mut().clear();
403 }
404
405 pub(crate) fn generation(&self) -> u64 {
406 self.generation.get()
407 }
408
409 fn prepare(
413 &self,
414 source: LayoutSource<'_>,
415 frame: TextFrame,
416 (units, admission): (TextUnits, CacheAdmission),
417 ) -> Rc<PreparedTextLayout> {
418 let (text, style) = (source.text(), source.style());
419 let key = TextKey::of(text, source.style_hash(), frame.options, units);
420 let cached = key.map(|key| {
421 let cached = self
422 .layouts
423 .borrow_mut()
424 .cached(key, frame, (style, admission));
425 (key, cached)
426 });
427 let missed = match cached {
428 Some((_, Cached::Held(prepared))) => return prepared,
429 Some((key, Cached::Missed(miss))) => Some((key, miss)),
430 None => None,
431 };
432 let (text, style) = source.shared();
433 let prepared = Rc::new(self.lay_out(text, style, frame, (units, source.shaped())));
434 if let Some((key, miss)) = missed {
435 self.layouts
436 .borrow_mut()
437 .keep(key, &prepared, (miss, admission));
438 }
439 prepared
440 }
441
442 fn lay_out(
445 &self,
446 text: Rc<AnnotatedString>,
447 style: Arc<TextStyle>,
448 frame: TextFrame,
449 (units, shaped): (TextUnits, Option<&TextLayout>),
450 ) -> PreparedTextLayout {
451 let options = frame.options.normalized();
452 let min_lines = options.min_lines;
453 let Some(min_font_size_sp) = options.overflow.scale_down_min_font_size_sp() else {
454 let layout = self.layout(&text, &style, frame, (units, shaped));
455 return PreparedTextLayout::new(text, style, layout, (frame.max_width, min_lines));
456 };
457 self.lay_out_scaled_down(text, style, frame, (units, shaped), min_font_size_sp)
458 }
459
460 #[inline(never)]
464 fn lay_out_scaled_down(
465 &self,
466 text: Rc<AnnotatedString>,
467 style: Arc<TextStyle>,
468 frame: TextFrame,
469 (units, shaped): (TextUnits, Option<&TextLayout>),
470 min_font_size_sp: f32,
471 ) -> PreparedTextLayout {
472 let options = frame.options.normalized();
473 let min_lines = options.min_lines;
474 let frame = TextFrame {
475 options: TextLayoutOptions {
476 overflow: TextOverflow::Clip,
477 ..options
478 },
479 ..frame
480 };
481 let fits =
482 |layout: &TextLayout| !layout.did_overflow_height() && !layout.did_overflow_width();
483 let full = self.layout(&text, &style, frame, (units, shaped));
484 let base = style.resolve_font_size(DEFAULT_FONT_SIZE_SP);
485 let min_factor = (min_font_size_sp.min(base) / base).clamp(0.0, 1.0);
486 if fits(&full) || !frame.max_width.is_finite() || min_factor >= 1.0 {
487 return PreparedTextLayout::new(text, style, full, (frame.max_width, min_lines));
488 }
489 let at = |factor: f32| {
492 let units = TextUnits {
493 factor: units.factor * factor,
494 ..units
495 };
496 self.layout(&text, &style, frame, (units, Some(&full)))
497 };
498 let (mut best, mut factor) = (at(min_factor), min_factor);
499 if fits(&best) {
500 let (mut low, mut high) = (min_factor, 1.0);
501 for _ in 0..SCALE_DOWN_SEARCH_STEPS {
502 let mid = (low + high) * 0.5;
503 let candidate = at(mid);
504 if fits(&candidate) {
505 low = mid;
506 (best, factor) = (candidate, mid);
507 } else {
508 high = mid;
509 }
510 }
511 }
512 let (text, style) = scaled_drawing(&text, &style, factor);
513 PreparedTextLayout::new(text, style, best, (frame.max_width, min_lines))
514 }
515
516 fn layout(
519 &self,
520 text: &AnnotatedString,
521 style: &TextStyle,
522 frame: TextFrame,
523 (units, shaped): (TextUnits, Option<&TextLayout>),
524 ) -> TextLayout {
525 let paragraph = ParagraphSpans::new(text, style, units);
526 let fonts = self.fonts.borrow();
527 let spans = paragraph.spans(&fonts);
528 let input = LayoutInput {
529 text: &text.text,
530 spans: &spans,
531 paragraph: &style.paragraph_style,
532 lengths: paragraph.lengths,
533 min_width: frame.min_width,
534 max_width: frame.max_width,
535 options: frame.options,
536 };
537 let mut layouter = self.layouter.borrow_mut();
538 match shaped {
539 Some(shaped) => layouter.relayout(&input, &fonts, shaped),
540 None => layouter.layout(&input, &fonts),
541 }
542 }
543
544 fn intrinsics(
546 &self,
547 text: &AnnotatedString,
548 style: &TextStyle,
549 units: TextUnits,
550 ) -> ParagraphIntrinsics {
551 let key = TextKey::of(
552 text,
553 style.measurement_hash(),
554 TextLayoutOptions::default(),
555 units,
556 );
557 if let Some(key) = key
558 && let Some(found) = self.intrinsics.borrow_mut().get(&key).copied()
559 {
560 return found;
561 }
562 let paragraph = ParagraphSpans::new(text, style, units);
563 let fonts = self.fonts.borrow();
564 let spans = paragraph.spans(&fonts);
565 let input = LayoutInput {
566 text: &text.text,
567 spans: &spans,
568 paragraph: &style.paragraph_style,
569 lengths: paragraph.lengths,
570 min_width: 0.0,
571 max_width: f32::INFINITY,
572 options: TextLayoutOptions::default(),
573 };
574 let found = self.layouter.borrow_mut().intrinsics(&input, &fonts);
575 if let Some(key) = key {
576 self.intrinsics.borrow_mut().push(key, found);
577 }
578 found
579 }
580
581 fn extent(&self, style: &TextStyle, units: TextUnits) -> Option<FontExtent> {
584 let span = &style.span_style;
585 let size = units
586 .density
587 .font_scale_curve()
588 .sp_to_dp(style.resolve_font_size(DEFAULT_FONT_SIZE_SP))
589 * units.factor;
590 let fonts = self.fonts.borrow();
591 let typeface = fonts.resolve(FontRequest {
592 family: span.font_family.as_ref().unwrap_or(&FontFamily::Default),
593 weight: span.font_weight.unwrap_or(FontWeight::NORMAL),
594 style: span.font_style.unwrap_or(FontStyle::Normal),
595 })?;
596 let scale = size / f32::from(typeface.units_per_em());
597 let metrics = typeface.line_metrics();
598 Some(FontExtent::new(
599 metrics.ascent * scale,
600 -metrics.descent * scale,
601 metrics.line_gap * scale,
602 ))
603 }
604
605 fn line_box(&self, style: &TextStyle, units: TextUnits) -> Option<LineBox> {
608 let extent = self.extent(style, units)?;
609 let empty = AnnotatedString::from("");
610 let paragraph = ParagraphSpans::new(&empty, style, units);
611 let asked = paragraph.lengths.line_height.unwrap_or(f32::NAN);
612 Some(paragraph_line_box(
613 &style.paragraph_style,
614 extent,
615 asked,
616 units.density.density(),
617 ))
618 }
619
620 pub(crate) fn begin_layout_pass(&self) {
622 self.pass_owed.set(false);
623 let headroom = crate::current_memory_headroom();
624 let idle_passes = match headroom {
626 MemoryHeadroom::Ample => IDLE_PASSES,
627 MemoryHeadroom::Tight => 1,
628 };
629 self.layouts.borrow_mut().begin_pass(idle_passes, headroom);
630 self.intrinsics.borrow_mut().begin_pass(idle_passes, drop);
631 self.layouter.borrow_mut().set_memory_headroom(headroom);
632 }
633
634 pub(crate) fn owe_pass(&self) {
637 self.pass_owed.set(true);
638 }
639
640 pub(crate) fn pay_owed_pass(&self) {
643 if self.pass_owed.get() {
644 self.begin_layout_pass();
645 }
646 }
647}
648
649fn default_fonts() -> FontFamilyResolver {
652 #[cfg(any(test, feature = "test-helpers"))]
653 {
654 super::test_fonts()
655 }
656 #[cfg(not(any(test, feature = "test-helpers")))]
657 {
658 FontFamilyResolver::new([])
659 }
660}
661
662fn scaled_drawing(
666 text: &Rc<AnnotatedString>,
667 style: &TextStyle,
668 factor: f32,
669) -> (Rc<AnnotatedString>, Arc<TextStyle>) {
670 let mut scaled_style = style.clone();
671 scaled_style.span_style.font_size =
672 TextUnit::Sp(style.resolve_font_size(DEFAULT_FONT_SIZE_SP) * factor);
673 scale_span_drawing(&mut scaled_style.span_style, factor);
674 let text = if text.span_styles.is_empty() {
675 Rc::clone(text)
676 } else {
677 let mut scaled = AnnotatedString::clone(text);
678 for span in &mut scaled.span_styles {
679 if let TextUnit::Sp(size) = span.item.font_size {
680 span.item.font_size = TextUnit::Sp(size * factor);
681 }
682 scale_span_drawing(&mut span.item, factor);
683 }
684 Rc::new(scaled)
685 };
686 (text, Arc::new(scaled_style))
687}
688
689fn scale_span_drawing(span: &mut SpanStyle, factor: f32) {
691 if let TextUnit::Sp(spacing) = span.letter_spacing {
692 span.letter_spacing = TextUnit::Sp(spacing * factor);
693 }
694 if let Some(shadow) = &mut span.shadow {
695 shadow.offset.x *= factor;
696 shadow.offset.y *= factor;
697 shadow.blur_radius *= factor;
698 }
699 if let Some(TextDrawStyle::Stroke { width }) = &mut span.draw_style {
700 *width *= factor;
701 }
702}
703
704fn host_units() -> TextUnits {
706 TextUnits {
707 density: Density::from_host(),
708 factor: 1.0,
709 }
710}
711
712fn resolved_units() -> TextUnits {
715 TextUnits {
716 density: Density::with_curve(Density::from_host().density(), FontScaleCurve::linear(1.0)),
717 factor: 1.0,
718 }
719}
720
721fn prepare_borrowed(
722 text: &AnnotatedString,
723 style: &TextStyle,
724 frame: TextFrame,
725 units: TextUnits,
726) -> Rc<PreparedTextLayout> {
727 crate::render_state::with_text_service(|service| {
728 service.prepare(
729 LayoutSource::Borrowed { text, style },
730 frame,
731 (units, CacheAdmission::First),
732 )
733 })
734}
735
736pub fn set_text_fonts(fonts: FontFamilyResolver) {
739 let Some(context) = crate::render_state::current_app_context() else {
740 panic!("set_text_fonts requires an active AppContext");
741 };
742 context.set_text_fonts(fonts);
743}
744
745pub fn measure_text(text: &AnnotatedString, style: &TextStyle) -> TextMetrics {
748 prepare_borrowed(
749 text,
750 style,
751 TextFrame::unbounded(TextLayoutOptions::default()),
752 host_units(),
753 )
754 .metrics()
755}
756
757pub(crate) fn prepare_resolved(text: &SharedText<'_>) -> Rc<PreparedTextLayout> {
763 crate::render_state::with_text_service(|service| {
764 service.prepare(
765 LayoutSource::Shared { text, shaped: None },
766 TextFrame::unbounded(TextLayoutOptions::default()),
767 (resolved_units(), CacheAdmission::Drawn),
768 )
769 })
770}
771
772pub fn measure_text_with_options(
775 text: &AnnotatedString,
776 style: &TextStyle,
777 options: TextLayoutOptions,
778 max_width: Option<f32>,
779) -> TextMetrics {
780 prepare_borrowed(
781 text,
782 style,
783 TextFrame::up_to(max_width, options),
784 host_units(),
785 )
786 .metrics()
787}
788
789pub fn prepare_text_layout(
791 text: &AnnotatedString,
792 style: &TextStyle,
793 options: TextLayoutOptions,
794 max_width: Option<f32>,
795) -> Rc<PreparedTextLayout> {
796 prepare_borrowed(
797 text,
798 style,
799 TextFrame::up_to(max_width, options),
800 host_units(),
801 )
802}
803
804pub(crate) fn prepare_shared(
807 text: &SharedText<'_>,
808 frame: TextFrame,
809 admission: CacheAdmission,
810 shaped: Option<&TextLayout>,
811) -> Rc<PreparedTextLayout> {
812 crate::render_state::with_text_service(|service| {
813 service.prepare(
814 LayoutSource::Shared { text, shaped },
815 frame,
816 (host_units(), admission),
817 )
818 })
819}
820
821pub fn get_offset_for_position(text: &AnnotatedString, style: &TextStyle, x: f32, y: f32) -> usize {
825 prepare_borrowed(
826 text,
827 style,
828 TextFrame::unbounded(TextLayoutOptions::default()),
829 host_units(),
830 )
831 .layout
832 .offset_for_position(x, y)
833}
834
835pub fn offset_for_position_wrapped(
840 text: &str,
841 style: &TextStyle,
842 wrap_width: Option<f32>,
843 x: f32,
844 y: f32,
845) -> usize {
846 prepare_borrowed(
847 &AnnotatedString::from(text),
848 style,
849 TextFrame::up_to(wrap_width, TextLayoutOptions::default()),
850 host_units(),
851 )
852 .layout
853 .offset_for_position(x, y)
854}
855
856pub fn get_cursor_x_for_offset(text: &AnnotatedString, style: &TextStyle, offset: usize) -> f32 {
859 prepare_borrowed(
860 text,
861 style,
862 TextFrame::unbounded(TextLayoutOptions::default()),
863 host_units(),
864 )
865 .layout
866 .horizontal_position(offset, CaretDirection::Primary)
867}
868
869pub fn layout_text(text: &AnnotatedString, style: &TextStyle) -> TextLayoutResult {
871 prepare_borrowed(
872 text,
873 style,
874 TextFrame::unbounded(TextLayoutOptions::default()),
875 host_units(),
876 )
877 .result()
878}
879
880pub fn wrapped_line_ranges(
883 text: &AnnotatedString,
884 style: &TextStyle,
885 options: TextLayoutOptions,
886 max_width: Option<f32>,
887) -> Vec<Range<usize>> {
888 let prepared = prepare_borrowed(
889 text,
890 style,
891 TextFrame::up_to(max_width, options),
892 host_units(),
893 );
894 let layout = &prepared.layout;
895 (0..layout.line_count())
896 .map(|line| {
897 let (start, end) = (layout.line_start(line), layout.line_end(line));
898 let line_text = text.text.get(start..end).unwrap_or_default();
899 let without_break = line_text.trim_end_matches(['\n', '\r', '\u{2028}', '\u{2029}']);
900 start..start + without_break.len()
901 })
902 .collect()
903}
904
905pub(crate) fn min_intrinsic_text_width(text: &AnnotatedString, style: &TextStyle) -> f32 {
908 crate::render_state::with_text_service(|service| service.intrinsics(text, style, host_units()))
909 .min_intrinsic_width
910}
911
912pub fn text_line_box(style: &TextStyle) -> Option<LineBox> {
915 crate::render_state::with_text_service(|service| service.line_box(style, host_units()))
916}
917
918pub(crate) fn resolved_line_box(style: &TextStyle) -> Option<LineBox> {
920 crate::render_state::current_app_context()?;
921 crate::render_state::with_text_service(|service| service.line_box(style, resolved_units()))
922}
923
924pub fn first_baseline(style: &TextStyle) -> Option<f32> {
927 text_line_box(style).map(LineBox::first_baseline)
928}
929
930pub fn glyph_line_box(style: &TextStyle, line_height: f32) -> (f32, f32) {
935 let units = host_units();
936 crate::render_state::with_text_service(|service| {
937 let extent = service.extent(style, units)?;
938 let line_box = service.line_box(style, units)?;
939 Some((
940 line_box.first_baseline() - extent.ascent,
941 extent.ascent + extent.descent,
942 ))
943 })
944 .map_or((0.0, line_height), |(top, height)| {
945 (top.clamp(0.0, line_height), height.min(line_height))
946 })
947}
948
949#[cfg(test)]
950#[path = "tests/measure_tests.rs"]
951mod tests;