1pub use parley;
6pub use parley::fontique;
7
8use crate::{
9 Color,
10 graphics::FontRequest,
11 item_rendering::PlainOrStyledText,
12 items::TextStrokeStyle,
13 lengths::{
14 LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx,
15 PointLengths, RectLengths, ScaleFactor, SizeLengths,
16 },
17 renderer::RendererSealed,
18 textlayout::{TextHorizontalAlignment, TextOverflow, TextVerticalAlignment, TextWrap},
19 window::WindowAdapter,
20};
21use alloc::rc::Rc;
22use alloc::vec::Vec;
23use core::ops::Range;
24use core::pin::Pin;
25use euclid::num::Zero;
26use i_slint_common::sharedfontique;
27use skrifa::MetadataProvider as _;
28use std::cell::RefCell;
29use std::collections::HashSet;
30use std::sync::Arc;
31
32#[derive(derive_more::Deref, derive_more::DerefMut)]
33pub struct FontContext {
34 #[deref]
35 #[deref_mut]
36 pub inner: parley::FontContext,
37 registered_static_fonts: HashSet<(usize, usize)>,
40}
41
42impl FontContext {
43 pub fn new(inner: parley::FontContext) -> Self {
44 Self { inner, registered_static_fonts: HashSet::default() }
45 }
46
47 pub fn register_static_font(&mut self, data: &'static [u8]) {
48 let key = (data.as_ptr() as usize, data.len());
49 if self.registered_static_fonts.insert(key) {
50 self.inner.collection.register_fonts(fontique::Blob::new(Arc::new(data)), None);
51 }
52 }
53
54 pub fn clear_registered_static_fonts(&mut self) {
55 self.registered_static_fonts.clear();
56 }
57}
58
59pub type PhysicalLength = euclid::Length<f32, PhysicalPx>;
60pub type PhysicalRect = euclid::Rect<f32, PhysicalPx>;
61type PhysicalSize = euclid::Size2D<f32, PhysicalPx>;
62type PhysicalPoint = euclid::Point2D<f32, PhysicalPx>;
63
64pub use super::DEFAULT_FONT_SIZE;
65
66mod cache;
67mod draw;
68mod layout;
69mod selection;
70mod shaping;
71#[cfg(test)]
72mod tests;
73
74pub use cache::TextLayoutCache;
75pub use draw::{GlyphRenderer, RectangleBorder};
76
77use cache::cached_paragraphs;
78use layout::{Layout, LayoutOptions, layout};
79use selection::{SelectionRendering, SelectionSpans};
80use shaping::{
81 Brush, LayoutWithoutLineBreaksBuilder, create_text_paragraphs, shape_paragraphs,
82 shaping_builder,
83};
84
85fn with_text_layout<R>(
98 cache: Option<&TextLayoutCache>,
99 item_rc: Option<&crate::item_tree::ItemRc>,
100 text: Pin<&dyn crate::item_rendering::RenderString>,
101 layout_builder: &LayoutWithoutLineBreaksBuilder,
102 options: LayoutOptions,
103 window: &crate::api::Window,
104 f: impl FnOnce(&Layout) -> R,
105) -> Option<R> {
106 let ctx = crate::window::WindowInner::from_pub(window).try_context()?;
107 let mut font_ctx = ctx.font_context().borrow_mut();
108
109 let text_wrap = layout_builder.text_wrap;
110 let scale_factor = layout_builder.scale_factor;
111 let mut guard =
112 cached_paragraphs(cache, item_rc, text_wrap, window, &mut font_ctx, &|font_context| {
113 shape_paragraphs(text, item_rc, text_wrap, scale_factor, font_context)
114 });
115
116 let line_breaking = guard.take_line_breaking();
117 let layout =
118 layout(layout_builder, &mut font_ctx, guard.take(), scale_factor, options, line_breaking);
119 drop(font_ctx);
120
121 if layout.broke_lines {
122 #[cfg(feature = "testing")]
123 if let Some(cache) = cache {
124 cache.count_layout_miss();
125 }
126 }
127
128 let result = f(&layout);
129 let (paragraphs, line_breaking) = layout.dismantle();
130 guard.restore(paragraphs, line_breaking);
131 Some(result)
132}
133
134pub fn draw_text(
135 item_renderer: &mut impl GlyphRenderer,
136 text: Pin<&dyn crate::item_rendering::RenderText>,
137 item_rc: Option<&crate::item_tree::ItemRc>,
138 size: LogicalSize,
139 cache: Option<&TextLayoutCache>,
140) {
141 let max_width = size.width_length();
142 let max_height = size.height_length();
143
144 if max_width.get() <= 0. || max_height.get() <= 0. {
145 return;
146 }
147
148 let Some(platform_fill_brush) = item_renderer.platform_text_fill_brush(text.color(), size)
149 else {
150 return;
152 };
153
154 let scale_factor = item_renderer.scale_factor();
155
156 let (stroke_brush, stroke_width, stroke_style) = text.stroke();
157 let platform_stroke_brush = if !stroke_brush.is_transparent() {
158 let stroke_width = if stroke_width.get() != 0.0 {
159 (stroke_width * scale_factor).get()
160 } else {
161 1.0
163 };
164 let stroke_width = match stroke_style {
165 TextStrokeStyle::Outside => stroke_width * 2.0,
166 TextStrokeStyle::Center => stroke_width,
167 };
168 item_renderer.platform_text_stroke_brush(stroke_brush, stroke_width, size)
169 } else {
170 None
171 };
172
173 let layout_builder = shaping_builder(text, item_rc, text.wrap(), scale_factor);
174
175 let window_adapter = item_renderer.window().window_adapter();
176
177 let (horizontal_align, vertical_align) = text.alignment();
178 let text_overflow = text.overflow();
179 let text_color = text.color().color();
180
181 let _ = with_text_layout(
182 cache,
183 item_rc,
184 text,
185 &layout_builder,
186 LayoutOptions {
187 horizontal_align,
188 vertical_align,
189 max_height: Some(max_height),
190 max_width: Some(max_width),
191 max_lines: text.line_limit(),
192 text_overflow,
193 },
194 window_adapter.window(),
195 |layout| {
196 let clip_overflowing_first_line =
202 text_overflow == TextOverflow::Elide && layout.first_line_exceeds_height();
203
204 let render = if text_overflow == TextOverflow::Clip || clip_overflowing_first_line {
205 item_renderer.save_state();
206
207 item_renderer.combine_clip(
208 LogicalRect::new(LogicalPoint::default(), size),
209 LogicalBorderRadius::zero(),
210 )
211 } else {
212 true
213 };
214
215 if render {
216 layout.draw(
217 item_renderer,
218 platform_fill_brush,
219 platform_stroke_brush,
220 text_color,
221 None,
224 );
225 }
226
227 if text_overflow == TextOverflow::Clip || clip_overflowing_first_line {
228 item_renderer.restore_state();
229 }
230 },
231 );
232}
233
234#[cfg(feature = "std")]
235pub fn link_under_cursor(
236 scale_factor: ScaleFactor,
237 text: Pin<&dyn crate::item_rendering::RenderText>,
238 item_rc: &crate::item_tree::ItemRc,
239 size: LogicalSize,
240 cursor: PhysicalPoint,
241 window: &crate::api::Window,
242 cache: Option<&TextLayoutCache>,
243) -> Option<std::string::String> {
244 let layout_builder = shaping_builder(text, Some(item_rc), text.wrap(), scale_factor);
245
246 let (horizontal_align, vertical_align) = text.alignment();
247
248 with_text_layout(
249 cache,
250 Some(item_rc),
251 text,
252 &layout_builder,
253 LayoutOptions {
254 horizontal_align,
255 vertical_align,
256 max_height: Some(size.height_length()),
257 max_width: Some(size.width_length()),
258 max_lines: text.line_limit(),
259 text_overflow: text.overflow(),
260 },
261 window,
262 |layout| link_in_layout(layout, cursor),
263 )
264 .flatten()
265}
266
267fn link_in_layout(layout: &Layout, cursor: PhysicalPoint) -> Option<std::string::String> {
268 layout.paragraph_by_y(cursor.y_length()).and_then(|paragraph| {
269 let paragraph_y: f64 = paragraph.y.cast::<f64>().get();
270
271 paragraph
272 .links
273 .iter()
274 .find(|(range, _)| {
275 let start = parley::editing::Cursor::from_byte_index(
276 ¶graph.layout,
277 range.start,
278 Default::default(),
279 );
280 let end = parley::editing::Cursor::from_byte_index(
281 ¶graph.layout,
282 range.end,
283 Default::default(),
284 );
285 let mut clicked = false;
286 let link_range = parley::Selection::new(start, end);
287 link_range.geometry_with(¶graph.layout, |mut bounding_box, _line| {
288 bounding_box.y0 += paragraph_y;
289 bounding_box.y1 += paragraph_y;
290 clicked = bounding_box.union(parley::BoundingBox::new(
291 cursor.x.into(),
292 cursor.y.into(),
293 cursor.x.into(),
294 cursor.y.into(),
295 )) == bounding_box;
296 });
297 clicked
298 })
299 .map(|(_, link)| link.clone())
300 })
301}
302
303pub fn draw_text_input(
304 item_renderer: &mut impl GlyphRenderer,
305 text_input: Pin<&crate::items::TextInput>,
306 item_rc: &crate::item_tree::ItemRc,
307 size: LogicalSize,
308 cache: &TextLayoutCache,
309) {
310 let width = size.width_length();
311 let height = size.height_length();
312 if width.get() <= 0. || height.get() <= 0. {
313 return;
314 }
315
316 let visual_representation = text_input.visual_representation();
317
318 let text_color = visual_representation.text_color.color();
319 let Some(platform_fill_brush) =
320 item_renderer.platform_text_fill_brush(visual_representation.text_color.clone(), size)
321 else {
322 return;
323 };
324
325 let selection_range = if !visual_representation.preedit_range.is_empty() {
326 visual_representation.preedit_range.start..visual_representation.preedit_range.end
327 } else {
328 visual_representation.selection_range.start..visual_representation.selection_range.end
329 };
330
331 let scale_factor = item_renderer.scale_factor();
332
333 let layout_builder =
334 shaping_builder(text_input, Some(item_rc), text_input.wrap(), scale_factor);
335
336 let window_adapter = item_renderer.window().window_adapter();
337
338 let _ = with_text_layout(
346 Some(cache),
347 Some(item_rc),
348 text_input,
349 &layout_builder,
350 LayoutOptions::new_from_textinput(text_input, Some(width), Some(height)),
351 window_adapter.window(),
352 |layout| {
353 item_renderer.save_state();
354
355 let render = item_renderer.combine_clip(
356 LogicalRect::new(LogicalPoint::default(), size),
357 LogicalBorderRadius::zero(),
358 );
359
360 if render {
361 let selection_spans = if selection_range.is_empty() {
367 SelectionSpans::default()
368 } else {
369 layout.selection_geometry(selection_range, &draw::visible_band(item_renderer))
370 };
371 for background in selection_spans.backgrounds() {
374 item_renderer.fill_rectangle_with_color(
375 background,
376 text_input.selection_background_color(),
377 );
378 }
379
380 let selection = (!selection_spans.is_empty())
384 .then(|| {
385 item_renderer
386 .platform_brush_for_color(&text_input.selection_foreground_color())
387 .map(|foreground| SelectionRendering {
388 spans: &selection_spans,
389 foreground,
390 })
391 })
392 .flatten();
393
394 layout.draw(
395 item_renderer,
396 platform_fill_brush,
397 None,
398 text_color,
399 selection.as_ref(),
400 );
401
402 if let Some(cursor_pos) = visual_representation.cursor_position {
403 let cursor_rect = layout.cursor_rect_for_byte_offset(
404 cursor_pos,
405 visual_representation.cursor_affinity,
406 text_input.text_cursor_width() * scale_factor,
407 );
408 item_renderer
409 .fill_rectangle_with_color(cursor_rect, visual_representation.cursor_color);
410 }
411 }
412
413 item_renderer.restore_state();
414 },
415 );
416}
417
418pub fn text_size(
423 renderer: &(impl RendererSealed + ?Sized),
424 text_item: Pin<&dyn crate::item_rendering::RenderString>,
425 item_rc: &crate::item_tree::ItemRc,
426 max_width: Option<LogicalLength>,
427 text_wrap: TextWrap,
428 cache: Option<&TextLayoutCache>,
429) -> Option<LogicalSize> {
430 text_size_impl(
431 renderer.scale_factor(),
432 renderer.window_adapter(),
433 text_item,
434 item_rc,
435 max_width,
436 text_wrap,
437 cache,
438 )
439}
440
441fn text_size_impl(
442 scale_factor: Option<ScaleFactor>,
443 window_adapter: Option<Rc<dyn WindowAdapter>>,
444 text_item: Pin<&dyn crate::item_rendering::RenderString>,
445 item_rc: &crate::item_tree::ItemRc,
446 max_width: Option<LogicalLength>,
447 text_wrap: TextWrap,
448 cache: Option<&TextLayoutCache>,
449) -> Option<LogicalSize> {
450 let scale_factor = scale_factor?;
451
452 let _ = text_item.font_request(item_rc);
457 let _ = text_item.stroke();
458 let _ = text_item.link_color();
459 let _ = text_item.text();
460
461 let window_adapter = window_adapter?;
462
463 let layout_builder = shaping_builder(text_item, Some(item_rc), text_wrap, scale_factor);
465
466 with_text_layout(
467 cache,
468 Some(item_rc),
469 text_item,
470 &layout_builder,
471 LayoutOptions {
472 max_width,
473 max_height: None,
474 max_lines: text_item.line_limit(),
475 horizontal_align: TextHorizontalAlignment::Left,
476 vertical_align: TextVerticalAlignment::Top,
477 text_overflow: TextOverflow::Clip,
478 },
479 window_adapter.window(),
480 |layout| PhysicalSize::from_lengths(layout.max_width, layout.height) / scale_factor,
481 )
482}
483
484pub fn text_content_widths(
486 renderer: &(impl RendererSealed + ?Sized),
487 text_item: Pin<&dyn crate::item_rendering::RenderString>,
488 item_rc: &crate::item_tree::ItemRc,
489 cache: Option<&TextLayoutCache>,
490) -> Option<crate::renderer::ContentWidths> {
491 text_content_widths_impl(
492 renderer.scale_factor(),
493 renderer.window_adapter(),
494 renderer.slint_context(),
495 text_item,
496 item_rc,
497 cache,
498 )
499}
500
501fn text_content_widths_impl(
502 scale_factor: Option<ScaleFactor>,
503 window_adapter: Option<Rc<dyn WindowAdapter>>,
504 ctx: Option<crate::SlintContext>,
505 text_item: Pin<&dyn crate::item_rendering::RenderString>,
506 item_rc: &crate::item_tree::ItemRc,
507 cache: Option<&TextLayoutCache>,
508) -> Option<crate::renderer::ContentWidths> {
509 let scale_factor = scale_factor?;
510
511 let _ = text_item.font_request(item_rc);
515 let _ = text_item.text();
516
517 let ctx = ctx?;
518 let mut font_ctx = ctx.font_context().borrow_mut();
519
520 let compute = |font_ctx: &mut parley::FontContext| {
523 let layout_builder =
524 shaping::content_widths_builder(text_item.font_request(item_rc), scale_factor);
525
526 let paragraphs_without_linebreaks =
527 create_text_paragraphs(&layout_builder, font_ctx, text_item.text(), Color::default());
528
529 let (min, max) = paragraphs_without_linebreaks
534 .iter()
535 .take(text_item.line_limit().unwrap_or(usize::MAX))
536 .fold((0., 0.), |(min, max), p| {
537 let w = p.layout.calculate_content_widths();
538 (f32::max(min, w.min), f32::max(max, w.max))
539 });
540 crate::renderer::ContentWidths {
541 min: PhysicalLength::new(min) / scale_factor,
542 max: PhysicalLength::new(max) / scale_factor,
543 }
544 };
545
546 let Some((cache, window_adapter)) = cache.zip(window_adapter) else {
549 return Some(compute(&mut font_ctx));
550 };
551 cache.clear_if_scale_factor_changed(window_adapter.window());
552 Some(cache.content_widths(item_rc, || compute(&mut font_ctx)))
553}
554
555pub fn char_size(
556 font_ctx: &mut parley::FontContext,
557 text_item: Pin<&dyn crate::item_rendering::HasFont>,
558 item_rc: &crate::item_tree::ItemRc,
559 ch: char,
560) -> Option<LogicalSize> {
561 let font_request = text_item.font_request(item_rc);
562 let font = font_request.query_fontique(&mut font_ctx.collection, &mut font_ctx.source_cache)?;
563
564 let char_map = font.charmap()?;
565
566 let face = skrifa::FontRef::from_index(font.blob.data(), font.index).unwrap();
567
568 let glyph_index = char_map.map(ch)?;
569
570 let pixel_size = font_request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE);
571
572 let location = face.axes().location(font.synthesis.variation_settings());
573
574 let glyph_metrics = skrifa::metrics::GlyphMetrics::new(
575 &face,
576 skrifa::instance::Size::new(pixel_size.get()),
577 &location,
578 );
579
580 let advance_width = LogicalLength::new(glyph_metrics.advance_width(glyph_index.into())?);
581
582 let font_metrics = skrifa::metrics::Metrics::new(
583 &face,
584 skrifa::instance::Size::new(pixel_size.get()),
585 &location,
586 );
587 let natural_line_height = font_metrics.ascent - font_metrics.descent;
588 let line_height = font_request
589 .line_height_for_natural_height(natural_line_height)
590 .unwrap_or(natural_line_height);
591
592 Some(LogicalSize::from_lengths(advance_width, LogicalLength::new(line_height)))
593}
594
595pub fn text_line_height(
597 font_ctx: &mut parley::FontContext,
598 font_request: &FontRequest,
599) -> Option<LogicalLength> {
600 let pixel_size = font_request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE);
601 shaping::line_height_ratio(font_ctx, font_request).map(|ratio| pixel_size * ratio)
602}
603
604pub fn font_metrics(
605 font_ctx: &mut parley::FontContext,
606 font_request: FontRequest,
607) -> crate::items::FontMetrics {
608 let logical_pixel_size = font_request.pixel_size.unwrap_or(DEFAULT_FONT_SIZE).get();
609
610 let Some(font) =
611 font_request.query_fontique(&mut font_ctx.collection, &mut font_ctx.source_cache)
612 else {
613 return crate::items::FontMetrics::default();
614 };
615
616 let face = skrifa::FontRef::from_index(font.blob.data(), font.index).unwrap();
617 let location = face.axes().location(font.synthesis.variation_settings());
618 let metrics = face.metrics(skrifa::instance::Size::unscaled(), &location);
619
620 let units_per_em = metrics.units_per_em as f32;
621
622 crate::items::FontMetrics {
623 ascent: metrics.ascent * logical_pixel_size / units_per_em,
624 descent: metrics.descent * logical_pixel_size / units_per_em,
625 x_height: metrics.x_height.unwrap_or_default() * logical_pixel_size / units_per_em,
626 cap_height: metrics.cap_height.unwrap_or_default() * logical_pixel_size / units_per_em,
627 }
628}
629
630pub fn text_input_byte_offset_for_position(
631 renderer: &(impl RendererSealed + ?Sized),
632 text_input: Pin<&crate::items::TextInput>,
633 item_rc: &crate::item_tree::ItemRc,
634 pos: LogicalPoint,
635 cache: Option<&TextLayoutCache>,
636) -> (usize, crate::items::TextCursorAffinity) {
637 text_input_byte_offset_for_position_impl(
638 renderer.scale_factor(),
639 renderer.window_adapter(),
640 text_input,
641 item_rc,
642 pos,
643 cache,
644 )
645}
646
647fn text_input_byte_offset_for_position_impl(
648 scale_factor: Option<ScaleFactor>,
649 window_adapter: Option<Rc<dyn WindowAdapter>>,
650 text_input: Pin<&crate::items::TextInput>,
651 item_rc: &crate::item_tree::ItemRc,
652 pos: LogicalPoint,
653 cache: Option<&TextLayoutCache>,
654) -> (usize, crate::items::TextCursorAffinity) {
655 let no_hit = (0, crate::items::TextCursorAffinity::NextCharacter);
656 let Some(scale_factor) = scale_factor else {
657 return no_hit;
658 };
659 let pos: PhysicalPoint = pos * scale_factor;
660
661 let width = text_input.width();
662 let height = text_input.height();
663 if width.get() <= 0. || height.get() <= 0. || pos.y < 0. {
664 return no_hit;
665 }
666
667 let layout_builder =
668 shaping_builder(text_input, Some(item_rc), text_input.wrap(), scale_factor);
669 let visual_representation = text_input.visual_representation();
670
671 let Some(window_adapter) = window_adapter else {
672 return no_hit;
673 };
674
675 let (byte_offset, affinity) = with_text_layout(
676 cache,
677 Some(item_rc),
678 text_input,
679 &layout_builder,
680 LayoutOptions::new_from_textinput(text_input, Some(width), Some(height)),
681 window_adapter.window(),
682 |layout| layout.byte_offset_from_point(pos),
683 )
684 .unwrap_or(no_hit);
685 (visual_representation.map_byte_offset_from_visual_text_to_actual_text(byte_offset), affinity)
686}
687
688pub fn text_input_cursor_rect_for_byte_offset(
689 renderer: &(impl RendererSealed + ?Sized),
690 text_input: Pin<&crate::items::TextInput>,
691 item_rc: &crate::item_tree::ItemRc,
692 byte_offset: usize,
693 affinity: crate::items::TextCursorAffinity,
694 cache: Option<&TextLayoutCache>,
695) -> LogicalRect {
696 text_input_cursor_rect_for_byte_offset_impl(
697 renderer.scale_factor(),
698 renderer.window_adapter(),
699 text_input,
700 item_rc,
701 byte_offset,
702 affinity,
703 cache,
704 )
705}
706
707fn text_input_cursor_rect_for_byte_offset_impl(
708 scale_factor: Option<ScaleFactor>,
709 window_adapter: Option<Rc<dyn WindowAdapter>>,
710 text_input: Pin<&crate::items::TextInput>,
711 item_rc: &crate::item_tree::ItemRc,
712 byte_offset: usize,
713 affinity: crate::items::TextCursorAffinity,
714 cache: Option<&TextLayoutCache>,
715) -> LogicalRect {
716 let Some(scale_factor) = scale_factor else {
717 return LogicalRect::default();
718 };
719
720 let layout_builder =
721 shaping_builder(text_input, Some(item_rc), text_input.wrap(), scale_factor);
722
723 let width = text_input.width();
724 let height = text_input.height();
725 if width.get() <= 0. || height.get() <= 0. {
726 return LogicalRect::new(
727 LogicalPoint::default(),
728 LogicalSize::from_lengths(LogicalLength::new(1.0), layout_builder.pixel_size),
729 );
730 }
731
732 let visual_representation = text_input.visual_representation();
733 let cursor_width = text_input.text_cursor_width() * scale_factor;
734
735 let Some(window_adapter) = window_adapter else {
736 return LogicalRect::default();
737 };
738
739 let byte_offset = visual_representation.map_byte_offset_from_actual_to_visual_text(byte_offset);
740
741 with_text_layout(
742 cache,
743 Some(item_rc),
744 text_input,
745 &layout_builder,
746 LayoutOptions::new_from_textinput(text_input, Some(width), Some(height)),
747 window_adapter.window(),
748 |layout| {
749 layout.cursor_rect_for_byte_offset(byte_offset, affinity, cursor_width) / scale_factor
750 },
751 )
752 .unwrap_or_default()
753}
754
755#[allow(dead_code)]
757pub struct TextInputLayout<'a> {
758 layout: &'a Layout,
759 text: &'a str,
761}
762
763#[allow(dead_code)]
764impl<'a> TextInputLayout<'a> {
765 pub(crate) fn paragraphs(&self) -> impl Iterator<Item = TextInputParagraph<'a>> {
768 let (text, y_offset) = (self.text, self.layout.y_offset);
769 self.layout.paragraphs.iter().map(move |para| TextInputParagraph {
770 range: para.range.clone(),
771 text: &text[para.range.clone()],
772 layout: ¶.layout,
773 y: y_offset + para.y,
774 })
775 }
776}
777
778#[allow(dead_code)]
780pub(crate) struct TextInputParagraph<'a> {
781 range: Range<usize>,
783 text: &'a str,
785 layout: &'a parley::Layout<Brush>,
787 y: PhysicalLength,
789}
790
791pub fn with_text_input_layout<R>(
799 renderer: &(impl RendererSealed + ?Sized),
800 text_input: Pin<&crate::items::TextInput>,
801 item_rc: &crate::item_tree::ItemRc,
802 size: LogicalSize,
803 f: impl FnOnce(TextInputLayout<'_>) -> R,
804) -> Option<R> {
805 if !renderer.text_input_has_parley_layout(text_input, item_rc) {
806 return None;
807 }
808 with_text_input_layout_impl(
809 renderer.scale_factor(),
810 renderer.window_adapter(),
811 renderer.text_layout_cache(),
812 text_input,
813 item_rc,
814 size,
815 f,
816 )
817}
818
819fn with_text_input_layout_impl<R>(
820 scale_factor: Option<ScaleFactor>,
821 window_adapter: Option<Rc<dyn WindowAdapter>>,
822 cache: Option<&TextLayoutCache>,
823 text_input: Pin<&crate::items::TextInput>,
824 item_rc: &crate::item_tree::ItemRc,
825 size: LogicalSize,
826 f: impl FnOnce(TextInputLayout<'_>) -> R,
827) -> Option<R> {
828 let scale_factor = scale_factor?;
829 let window_adapter = window_adapter?;
830
831 let width = size.width_length();
832 let height = size.height_length();
833 if width.get() <= 0. || height.get() <= 0. {
834 return None;
835 }
836
837 let layout_builder =
838 shaping_builder(text_input, Some(item_rc), text_input.wrap(), scale_factor);
839
840 let PlainOrStyledText::Plain(text) = crate::item_rendering::RenderString::text(text_input)
842 else {
843 return None;
844 };
845
846 with_text_layout(
847 cache,
848 Some(item_rc),
849 text_input,
850 &layout_builder,
851 LayoutOptions::new_from_textinput(text_input, Some(width), Some(height)),
852 window_adapter.window(),
853 |layout| f(TextInputLayout { layout, text: &text }),
854 )
855}
856
857#[cfg(feature = "accessibility-text")]
858mod accessibility;
859#[cfg(feature = "accessibility-text")]
860pub use accessibility::CachedTextInputAccessibilityState;