1use std::{
2 cell::{Cell, Ref, RefCell},
3 hash::{Hash, Hasher},
4 rc::Rc,
5 sync::Arc,
6};
7
8use cranpose_foundation::{
9 Constraints, DelegatableNode, DrawModifierNode, InvalidationKind, LayoutModifierNode,
10 Measurable, ModifierNode, ModifierNodeContext, ModifierNodeElement, NodeCapabilities,
11 NodeState, SemanticsConfiguration, SemanticsNode, Size,
12};
13use smallvec::SmallVec;
14
15use crate::{
16 density::Density,
17 text::{AnnotatedString, TextLayoutOptions, TextStyle},
18};
19
20#[derive(Debug)]
30pub struct TextModifierNode {
31 layout: Rc<TextPreparedLayoutOwner>,
32 density: Density,
33 state: NodeState,
34}
35
36const PREPARED_LAYOUT_CACHE_CAPACITY: usize = 4;
37
38#[derive(Clone, Debug)]
39struct TextPreparedLayoutCacheEntry {
40 widths: crate::text::measure::PreparedWidths,
41 text_generation: u64,
42 font_scale_fingerprint: u32,
43 layout: Rc<crate::text::PreparedTextLayout>,
44}
45
46#[derive(Debug)]
50struct TextPreparedLayoutOwner {
51 source: RefCell<TextLayoutSource>,
52 node_id: Cell<Option<cranpose_core::NodeId>>,
53 measured_max_width: Cell<Option<Option<f32>>>,
54 cache: RefCell<SmallVec<[TextPreparedLayoutCacheEntry; 1]>>,
55}
56
57#[derive(Debug)]
59struct TextLayoutSource {
60 text: Rc<AnnotatedString>,
61 style: Arc<TextStyle>,
62 style_hash: u64,
63 options: TextLayoutOptions,
64 shares_layouts: bool,
67}
68
69#[derive(Clone, Debug)]
70pub(crate) struct TextPreparedLayoutHandle {
71 owner: Rc<TextPreparedLayoutOwner>,
72}
73
74impl TextPreparedLayoutOwner {
75 fn new(text: Rc<AnnotatedString>, style: Arc<TextStyle>, options: TextLayoutOptions) -> Self {
76 let style_hash = style.render_hash();
77 Self {
78 source: RefCell::new(TextLayoutSource {
79 text,
80 style,
81 style_hash,
82 options: options.normalized(),
83 shares_layouts: true,
84 }),
85 node_id: Cell::new(None),
86 measured_max_width: Cell::new(None),
87 cache: RefCell::new(SmallVec::new()),
88 }
89 }
90
91 fn update(
97 &self,
98 text: &Rc<AnnotatedString>,
99 style: &Arc<TextStyle>,
100 options: TextLayoutOptions,
101 ) {
102 let mut source = self.source.borrow_mut();
103 let shared_style = Arc::ptr_eq(&source.style, style);
104 let same_style = shared_style || *source.style == **style;
105 if source.text == *text && same_style && source.options == options {
106 return;
107 }
108 if !same_style {
109 source.style_hash = style.render_hash();
110 }
111 if !shared_style {
112 source.style = Arc::clone(style);
113 }
114 source.text = Rc::clone(text);
115 source.options = options;
116 source.shares_layouts = false;
117 drop(source);
118 self.cache.borrow_mut().clear();
119 }
120
121 fn annotated_text(&self) -> Ref<'_, Rc<AnnotatedString>> {
122 Ref::map(self.source.borrow(), |source| &source.text)
123 }
124
125 fn style(&self) -> Ref<'_, TextStyle> {
126 Ref::map(self.source.borrow(), |source| &*source.style)
127 }
128
129 fn options(&self) -> TextLayoutOptions {
130 self.source.borrow().options
131 }
132
133 fn node_id(&self) -> Option<cranpose_core::NodeId> {
134 self.node_id.get()
135 }
136
137 fn set_node_id(&self, node_id: Option<cranpose_core::NodeId>) {
138 if self.node_id.replace(node_id) != node_id {
139 self.cache.borrow_mut().clear();
140 }
141 }
142
143 fn with_prepared<R>(
144 &self,
145 max_width: Option<f32>,
146 read: impl FnOnce(&Rc<crate::text::PreparedTextLayout>) -> R,
147 ) -> R {
148 let normalized_max_width = max_width.filter(|width| width.is_finite() && *width > 0.0);
149 let (text_generation, font_scale_fingerprint) = crate::render_state::text_layout_stamp();
150
151 {
152 let mut cache = self.cache.borrow_mut();
153 if let Some(index) = cache.iter().position(|entry| {
154 entry.widths.hold(normalized_max_width)
155 && entry.text_generation == text_generation
156 && entry.font_scale_fingerprint == font_scale_fingerprint
157 }) {
158 cache[..=index].rotate_right(1);
159 return read(&cache[0].layout);
160 }
161 }
162
163 let source = self.source.borrow();
164 let prepare = if source.shares_layouts {
165 crate::text::prepare_text_layout_for_node
166 } else {
167 crate::text::measure::prepare_unshared_text_layout_for_node
168 };
169 let prepared = prepare(
170 self.node_id(),
171 &source.text,
172 &source.style,
173 source.options,
174 normalized_max_width,
175 );
176 if Arc::ptr_eq(&prepared.visual_style, &source.style) {
177 let _ = prepared.visual_style_hash.set(source.style_hash);
178 }
179 let widths = crate::text::measure::PreparedWidths::of(
180 source.text.as_ref(),
181 source.options,
182 normalized_max_width,
183 &prepared,
184 );
185 drop(source);
186
187 let mut cache = self.cache.borrow_mut();
188 cache.insert(
189 0,
190 TextPreparedLayoutCacheEntry {
191 widths,
192 text_generation,
193 font_scale_fingerprint,
194 layout: prepared,
195 },
196 );
197 cache.truncate(PREPARED_LAYOUT_CACHE_CAPACITY);
198 read(&cache[0].layout)
199 }
200
201 fn cached_widths(
204 &self,
205 max_width: Option<f32>,
206 ) -> Option<(Size, crate::text::measure::PreparedWidths)> {
207 let normalized_max_width = max_width.filter(|width| width.is_finite() && *width > 0.0);
208 let (text_generation, font_scale_fingerprint) = crate::render_state::text_layout_stamp();
209 self.cache
210 .borrow()
211 .iter()
212 .find(|entry| {
213 entry.widths.hold(normalized_max_width)
214 && entry.text_generation == text_generation
215 && entry.font_scale_fingerprint == font_scale_fingerprint
216 })
217 .map(|entry| {
218 (
219 Size::new(entry.layout.metrics.width, entry.layout.metrics.height),
220 entry.widths,
221 )
222 })
223 }
224
225 fn min_intrinsic_width(&self) -> f32 {
227 let source = self.source.borrow();
228 crate::text::measure::min_intrinsic_text_width(&source.text, &source.style)
229 }
230
231 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
232 self.with_prepared(max_width, |prepared| Size {
233 width: prepared.metrics.width,
234 height: prepared.metrics.height,
235 })
236 }
237
238 fn measure_layout(&self, max_width: Option<f32>) -> (Size, cranpose_ui_layout::AlignmentLines) {
239 self.measured_max_width.set(Some(max_width));
240 self.with_prepared(max_width, |prepared| {
241 (
242 Size::new(prepared.metrics.width, prepared.metrics.height),
243 prepared.alignment_lines,
244 )
245 })
246 }
247
248 fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
249 self.measured_max_width
250 .get()
251 .map(|max_width| self.with_prepared(max_width, Rc::clone))
252 }
253}
254
255impl TextPreparedLayoutHandle {
256 fn new(owner: Rc<TextPreparedLayoutOwner>) -> Self {
257 Self { owner }
258 }
259
260 pub(crate) fn annotated_text(&self) -> Ref<'_, Rc<AnnotatedString>> {
261 self.owner.annotated_text()
262 }
263
264 pub(crate) fn style(&self) -> Ref<'_, TextStyle> {
265 self.owner.style()
266 }
267
268 pub(crate) fn options(&self) -> TextLayoutOptions {
269 self.owner.options()
270 }
271
272 pub(crate) fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
273 self.owner.measured_layout()
274 }
275}
276
277impl TextModifierNode {
278 pub fn new(
280 text: Rc<AnnotatedString>,
281 style: impl Into<Arc<TextStyle>>,
282 options: TextLayoutOptions,
283 density: Density,
284 ) -> Self {
285 Self {
286 layout: Rc::new(TextPreparedLayoutOwner::new(text, style.into(), options)),
287 density,
288 state: NodeState::new(),
289 }
290 }
291
292 fn pixel_size(&self, size: Size) -> Size {
296 Size {
297 width: self.density.ceil(size.width),
298 height: self.density.ceil(size.height),
299 }
300 }
301
302 pub fn text(&self) -> Ref<'_, str> {
304 Ref::map(self.layout.annotated_text(), |text| text.text.as_str())
305 }
306
307 pub fn annotated_text(&self) -> Rc<AnnotatedString> {
309 Rc::clone(&self.layout.annotated_text())
310 }
311
312 pub fn style(&self) -> Ref<'_, TextStyle> {
314 self.layout.style()
315 }
316
317 pub fn options(&self) -> TextLayoutOptions {
319 self.layout.options()
320 }
321
322 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
323 self.layout.measure_text_content(max_width)
324 }
325
326 pub(crate) fn prepared_layout_handle(&self) -> TextPreparedLayoutHandle {
327 TextPreparedLayoutHandle::new(self.layout.clone())
328 }
329}
330
331impl DelegatableNode for TextModifierNode {
332 fn node_state(&self) -> &NodeState {
333 &self.state
334 }
335}
336
337impl ModifierNode for TextModifierNode {
338 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
339 self.layout.set_node_id(context.node_id());
340 context.invalidate(InvalidationKind::Layout);
341 context.invalidate(InvalidationKind::Draw);
342 context.invalidate(InvalidationKind::Semantics);
343 }
344
345 fn on_detach(&mut self) {
346 self.layout.set_node_id(None);
347 }
348
349 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
350 Some(self)
351 }
352
353 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
354 Some(self)
355 }
356
357 fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
358 Some(self)
359 }
360
361 fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
362 Some(self)
363 }
364
365 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
366 Some(self)
367 }
368
369 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
370 Some(self)
371 }
372}
373
374impl LayoutModifierNode for TextModifierNode {
375 fn measure_hold(
378 &self,
379 _density: f32,
380 constraints: Constraints,
381 size: Size,
382 _wrapped: cranpose_ui_layout::WrappedHold,
383 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
384 let max_width = constraints
385 .max_width
386 .is_finite()
387 .then_some(constraints.max_width);
388 let (text_size, widths) = self.layout.cached_widths(max_width)?;
389 let text_size = self.pixel_size(text_size);
390 if size != text_size {
391 return None;
392 }
393 Some(cranpose_ui_layout::ConstraintsHold {
394 width: cranpose_ui_layout::AxisHold {
395 min: cranpose_ui_layout::BoundRange::up_to(text_size.width),
396 max: cranpose_ui_layout::BoundRange::from(text_size.width)
397 .intersect(widths.max_width_range())?,
398 },
399 height: cranpose_ui_layout::AxisHold::sized(text_size.height),
400 })
401 }
402
403 fn measure(
404 &self,
405 _context: &mut dyn ModifierNodeContext,
406 _measurable: &dyn Measurable,
407 constraints: Constraints,
408 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
409 let max_width = constraints
410 .max_width
411 .is_finite()
412 .then_some(constraints.max_width);
413 let (text_size, alignment_lines) = self.layout.measure_layout(max_width);
414 let text_size = self.pixel_size(text_size);
415
416 let width = text_size
417 .width
418 .clamp(constraints.min_width, constraints.max_width);
419 let height = text_size
420 .height
421 .clamp(constraints.min_height, constraints.max_height);
422
423 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size { width, height })
424 .with_alignment_lines(alignment_lines)
425 }
426
427 fn min_intrinsic_width(
428 &self,
429 _measurable: &dyn Measurable,
430 _height: f32,
431 _density: f32,
432 ) -> f32 {
433 self.density.ceil(self.layout.min_intrinsic_width())
434 }
435
436 fn max_intrinsic_width(
437 &self,
438 _measurable: &dyn Measurable,
439 _height: f32,
440 _density: f32,
441 ) -> f32 {
442 self.pixel_size(self.measure_text_content(None)).width
443 }
444
445 fn min_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
446 self.pixel_size(
447 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
448 )
449 .height
450 }
451
452 fn max_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
453 self.pixel_size(
454 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
455 )
456 .height
457 }
458}
459
460impl DrawModifierNode for TextModifierNode {}
461
462impl SemanticsNode for TextModifierNode {
463 fn merge_semantics(&self, config: &mut SemanticsConfiguration) {
464 config
465 .content_description
466 .get_or_insert_with(|| self.text().to_string());
467 }
468
469 fn reach(&self) -> cranpose_foundation::SemanticsReach {
470 cranpose_foundation::SemanticsReach::default()
471 }
472}
473
474#[derive(Debug, Clone)]
483pub struct TextModifierElement {
484 text: Rc<AnnotatedString>,
485 style: Arc<TextStyle>,
488 options: TextLayoutOptions,
489 density: Density,
490}
491
492impl TextModifierElement {
493 pub fn new(
496 text: Rc<AnnotatedString>,
497 style: TextStyle,
498 options: TextLayoutOptions,
499 density: Density,
500 ) -> Self {
501 Self::with_shared_style(text, shared_text_style(style), options, density)
502 }
503
504 pub(crate) fn with_shared_style(
506 text: Rc<AnnotatedString>,
507 style: Arc<TextStyle>,
508 options: TextLayoutOptions,
509 density: Density,
510 ) -> Self {
511 Self {
512 text,
513 style,
514 options: options.normalized(),
515 density,
516 }
517 }
518}
519
520impl PartialEq for TextModifierElement {
521 fn eq(&self, other: &Self) -> bool {
522 self.text == other.text
523 && (Arc::ptr_eq(&self.style, &other.style) || self.style == other.style)
524 && self.options == other.options
525 && self.density == other.density
526 }
527}
528
529const SHARED_TEXT_STYLES: usize = 16;
531
532pub(crate) fn shared_text_style(style: TextStyle) -> Arc<TextStyle> {
538 crate::thread_state::with(|state| {
539 let mut recent = state.recent_text_styles.borrow_mut();
540 match recent.iter().position(|kept| **kept == style) {
541 Some(0) => Arc::clone(&recent[0]),
542 Some(index) => {
545 recent.swap(index, index - 1);
546 Arc::clone(&recent[index - 1])
547 }
548 None => {
549 let shared = Arc::new(style);
550 recent.truncate(SHARED_TEXT_STYLES - 1);
551 recent.insert(0, Arc::clone(&shared));
552 shared
553 }
554 }
555 })
556}
557
558impl Hash for TextModifierElement {
559 fn hash<H: Hasher>(&self, state: &mut H) {
560 self.text.render_hash().hash(state);
561 self.options.hash(state);
562 self.density.density().to_bits().hash(state);
563 }
564}
565
566impl ModifierNodeElement for TextModifierElement {
567 type Node = TextModifierNode;
568
569 fn create(&self) -> Self::Node {
570 TextModifierNode::new(
571 self.text.clone(),
572 Arc::clone(&self.style),
573 self.options,
574 self.density,
575 )
576 }
577
578 fn update(&self, node: &mut Self::Node) {
579 node.density = self.density;
580 node.layout.update(&self.text, &self.style, self.options);
581 }
582
583 fn capabilities(&self) -> NodeCapabilities {
584 NodeCapabilities::LAYOUT | NodeCapabilities::DRAW | NodeCapabilities::SEMANTICS
585 }
586}
587
588#[cfg(test)]
589#[path = "tests/text_modifier_node_tests.rs"]
590mod tests;