1use std::{
2 cell::{Cell, Ref, RefCell},
3 hash::{Hash, Hasher},
4 rc::Rc,
5 sync::Arc,
6};
7
8use cranpose_core::PackedU64;
9use cranpose_foundation::{
10 Constraints, DelegatableNode, DrawModifierNode, InvalidationKind, LayoutModifierNode,
11 Measurable, ModifierNode, ModifierNodeContext, ModifierNodeElement, NodeCapabilities,
12 NodeState, SemanticsConfiguration, SemanticsNode, Size,
13};
14use smallvec::SmallVec;
15
16use crate::{
17 density::Density,
18 text::{
19 AnnotatedString, TextLayoutOptions, TextStyle, layout_cache::CacheAdmission,
20 recent_values::RecentValues,
21 },
22};
23
24#[derive(Debug)]
34pub struct TextModifierNode {
35 layout: Rc<TextPreparedLayoutOwner>,
36 density: Density,
37 state: NodeState,
38}
39
40const PREPARED_LAYOUT_CACHE_CAPACITY: usize = 4;
41
42#[derive(Clone, Debug)]
43struct TextPreparedLayoutCacheEntry {
44 min_width: f32,
45 max_widths: cranpose_ui_layout::BoundRange,
48 text_generation: PackedU64,
49 font_scale_fingerprint: u32,
50 layout: Rc<crate::text::PreparedTextLayout>,
51}
52
53impl TextPreparedLayoutCacheEntry {
54 fn new(
55 min_width: f32,
56 (text_generation, font_scale_fingerprint): (u64, u32),
57 layout: Rc<crate::text::PreparedTextLayout>,
58 ) -> Self {
59 Self {
60 min_width,
61 max_widths: layout.max_widths_kept(min_width),
62 text_generation: PackedU64::new(text_generation),
63 font_scale_fingerprint,
64 layout,
65 }
66 }
67
68 fn keeps(&self, (min_width, max_width): (f32, f32), stamp: (u64, u32)) -> bool {
71 self.min_width.to_bits() == min_width.to_bits()
72 && (self.text_generation.get(), self.font_scale_fingerprint) == stamp
73 && self.max_widths.contains(max_width)
74 }
75}
76
77#[derive(Debug)]
81struct TextPreparedLayoutOwner {
82 source: RefCell<TextLayoutSource>,
83 measured_widths: Cell<Option<(f32, f32)>>,
84 cache: RefCell<SmallVec<[TextPreparedLayoutCacheEntry; 1]>>,
85 changed: Cell<bool>,
89}
90
91#[derive(Debug)]
93struct TextLayoutSource {
94 text: Rc<AnnotatedString>,
95 style: Arc<TextStyle>,
96 style_hash: PackedU64,
97 options: TextLayoutOptions,
98 admission: CacheAdmission,
102}
103
104impl TextLayoutSource {
105 fn frame(&self, (min_width, max_width): (f32, f32)) -> crate::text::measure::TextFrame {
107 crate::text::measure::TextFrame {
108 min_width,
109 max_width,
110 options: self.options,
111 }
112 }
113}
114
115#[derive(Clone, Debug)]
116pub(crate) struct TextPreparedLayoutHandle {
117 owner: Rc<TextPreparedLayoutOwner>,
118}
119
120impl TextPreparedLayoutOwner {
121 fn new(text: Rc<AnnotatedString>, style: Arc<TextStyle>, options: TextLayoutOptions) -> Self {
122 let style_hash = PackedU64::new(style.render_hash());
123 Self {
124 source: RefCell::new(TextLayoutSource {
125 text,
126 style,
127 style_hash,
128 options: options.normalized(),
129 admission: CacheAdmission::First,
130 }),
131 measured_widths: Cell::new(None),
132 changed: Cell::new(false),
133 cache: RefCell::new(SmallVec::new()),
134 }
135 }
136
137 fn update(
143 &self,
144 text: &Rc<AnnotatedString>,
145 style: &Arc<TextStyle>,
146 options: TextLayoutOptions,
147 ) {
148 let mut source = self.source.borrow_mut();
149 let shared_style = Arc::ptr_eq(&source.style, style);
150 let same_style = shared_style || *source.style == **style;
151 if source.text == *text && same_style && source.options == options {
152 return;
153 }
154 if !same_style {
155 source.style_hash = PackedU64::new(style.render_hash());
156 }
157 if !shared_style {
158 source.style = Arc::clone(style);
159 }
160 source.text = Rc::clone(text);
161 source.options = options;
162 source.admission = CacheAdmission::Changed;
163 drop(source);
164 self.cache.borrow_mut().clear();
165 self.changed.set(true);
166 }
167
168 fn annotated_text(&self) -> Ref<'_, Rc<AnnotatedString>> {
169 Ref::map(self.source.borrow(), |source| &source.text)
170 }
171
172 fn style(&self) -> Ref<'_, TextStyle> {
173 Ref::map(self.source.borrow(), |source| &*source.style)
174 }
175
176 fn options(&self) -> TextLayoutOptions {
177 self.source.borrow().options
178 }
179
180 fn prepared(&self, widths: (f32, f32)) -> Ref<'_, Rc<crate::text::PreparedTextLayout>> {
185 let stamp = crate::render_state::text_layout_stamp();
186 {
187 let cache = self.cache.borrow();
188 if cache
189 .first()
190 .is_some_and(|latest| latest.keeps(widths, stamp))
191 {
192 return Ref::map(cache, |cache| &cache[0].layout);
193 }
194 }
195 if !self.bring_kept(widths, stamp) {
196 self.prepare(widths, stamp);
197 }
198 Ref::map(self.cache.borrow(), |cache| &cache[0].layout)
199 }
200
201 fn bring_kept(&self, widths: (f32, f32), stamp: (u64, u32)) -> bool {
206 let mut cache = self.cache.borrow_mut();
207 let Some(latest) = cache.first_mut() else {
208 return false;
209 };
210 if (latest.text_generation.get(), latest.font_scale_fingerprint) == stamp
212 && let Some(prepared) = Rc::get_mut(&mut latest.layout)
213 && prepared.try_realign(widths, prepared.visual_style.paragraph_style.text_align)
214 {
215 latest.min_width = widths.0;
216 latest.max_widths = prepared.max_widths_kept(widths.0);
217 return true;
218 }
219 let Some(index) = cache.iter().position(|entry| entry.keeps(widths, stamp)) else {
220 return false;
221 };
222 cache[..=index].rotate_right(1);
223 true
224 }
225
226 #[inline(never)]
229 fn prepare(&self, widths: (f32, f32), stamp: (u64, u32)) {
230 let source = self.source.borrow();
231 let frame = source.frame(widths);
232 let align = source.style.paragraph_style.text_align;
233 let spare = {
236 let mut cache = self.cache.borrow_mut();
237 (cache.len() >= PREPARED_LAYOUT_CACHE_CAPACITY)
238 .then(|| cache.pop())
239 .flatten()
240 .map(|entry| entry.layout)
241 };
242 let prepared = {
243 let cache = self.cache.borrow();
248 let latest = cache
249 .first()
250 .filter(|entry| {
251 (entry.text_generation.get(), entry.font_scale_fingerprint) == stamp
252 })
253 .map(|entry| &entry.layout);
254 match latest.filter(|latest| latest.layout.holds(widths.0, widths.1)) {
255 Some(latest) => latest.at_widths(widths, align, spare),
256 None => {
257 let text = crate::text::measure::SharedText {
258 text: &source.text,
259 style: &source.style,
260 style_hash: source.style_hash.get(),
261 };
262 let shaped = latest.map(|latest| &latest.layout);
263 crate::text::measure::prepare_shared(&text, frame, source.admission, shaped)
264 }
265 }
266 };
267 if Arc::ptr_eq(&prepared.visual_style, &source.style) {
268 let _ = prepared.visual_style_hash.set(source.style_hash);
269 }
270 drop(source);
271
272 let mut cache = self.cache.borrow_mut();
273 cache.insert(
274 0,
275 TextPreparedLayoutCacheEntry::new(widths.0, stamp, prepared),
276 );
277 cache.truncate(PREPARED_LAYOUT_CACHE_CAPACITY);
278 }
279
280 fn cached_widths(&self, widths: (f32, f32)) -> Option<(Size, cranpose_ui_layout::BoundRange)> {
283 let stamp = crate::render_state::text_layout_stamp();
284 let cache = self.cache.borrow();
285 let entry = cache.iter().find(|entry| entry.keeps(widths, stamp))?;
286 Some((entry.layout.size(), entry.max_widths))
287 }
288
289 fn min_intrinsic_width(&self) -> f32 {
291 let source = self.source.borrow();
292 crate::text::measure::min_intrinsic_text_width(&source.text, &source.style)
293 }
294
295 fn measure_text_content(&self, widths: (f32, f32)) -> Size {
296 self.prepared(widths).size()
297 }
298
299 fn measure_layout(&self, widths: (f32, f32)) -> (Size, cranpose_ui_layout::AlignmentLines) {
300 self.measured_widths.set(Some(widths));
301 let prepared = self.prepared(widths);
302 (prepared.size(), prepared.alignment_lines())
303 }
304
305 fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
306 self.measured_widths
307 .get()
308 .map(|widths| Rc::clone(&self.prepared(widths)))
309 }
310}
311
312impl TextPreparedLayoutHandle {
313 fn new(owner: Rc<TextPreparedLayoutOwner>) -> Self {
314 Self { owner }
315 }
316
317 pub(crate) fn annotated_text(&self) -> Ref<'_, Rc<AnnotatedString>> {
318 self.owner.annotated_text()
319 }
320
321 pub(crate) fn style(&self) -> Ref<'_, TextStyle> {
322 self.owner.style()
323 }
324
325 pub(crate) fn options(&self) -> TextLayoutOptions {
326 self.owner.options()
327 }
328
329 pub(crate) fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
330 self.owner.measured_layout()
331 }
332}
333
334impl TextModifierNode {
335 pub fn new(
337 text: Rc<AnnotatedString>,
338 style: impl Into<Arc<TextStyle>>,
339 options: TextLayoutOptions,
340 density: Density,
341 ) -> Self {
342 Self {
343 layout: Rc::new(TextPreparedLayoutOwner::new(text, style.into(), options)),
344 density,
345 state: NodeState::new(),
346 }
347 }
348
349 fn pixel_size(&self, size: Size) -> Size {
353 Size {
354 width: self.density.ceil(size.width),
355 height: self.density.ceil(size.height),
356 }
357 }
358
359 pub fn text(&self) -> Ref<'_, str> {
361 Ref::map(self.layout.annotated_text(), |text| text.text.as_str())
362 }
363
364 pub fn annotated_text(&self) -> Rc<AnnotatedString> {
366 Rc::clone(&self.layout.annotated_text())
367 }
368
369 pub fn style(&self) -> Ref<'_, TextStyle> {
371 self.layout.style()
372 }
373
374 pub fn options(&self) -> TextLayoutOptions {
376 self.layout.options()
377 }
378
379 fn measure_text_content(&self, widths: (f32, f32)) -> Size {
380 self.layout.measure_text_content(widths)
381 }
382
383 pub(crate) fn prepared_layout_handle(&self) -> TextPreparedLayoutHandle {
384 TextPreparedLayoutHandle::new(Rc::clone(&self.layout))
385 }
386}
387
388impl DelegatableNode for TextModifierNode {
389 fn node_state(&self) -> &NodeState {
390 &self.state
391 }
392}
393
394impl ModifierNode for TextModifierNode {
395 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
396 context.invalidate(InvalidationKind::Layout);
397 context.invalidate(InvalidationKind::Draw);
398 context.invalidate(InvalidationKind::Semantics);
399 }
400
401 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
402 Some(self)
403 }
404
405 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
406 Some(self)
407 }
408
409 fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
410 Some(self)
411 }
412
413 fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
414 Some(self)
415 }
416
417 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
418 Some(self)
419 }
420
421 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
422 Some(self)
423 }
424}
425
426impl LayoutModifierNode for TextModifierNode {
427 fn measure_hold(
430 &self,
431 _density: f32,
432 constraints: Constraints,
433 size: Size,
434 _wrapped: cranpose_ui_layout::WrappedHold,
435 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
436 if self.layout.changed.take() {
439 return None;
440 }
441 let (text_size, max_widths) = self.layout.cached_widths(text_widths(constraints))?;
442 let text_size = self.pixel_size(text_size);
443 if size != text_size {
444 return None;
445 }
446 Some(cranpose_ui_layout::ConstraintsHold {
447 width: cranpose_ui_layout::AxisHold {
448 min: cranpose_ui_layout::BoundRange::exactly(constraints.min_width),
449 max: cranpose_ui_layout::BoundRange::from(text_size.width).intersect(max_widths)?,
450 },
451 height: cranpose_ui_layout::AxisHold::sized(text_size.height),
452 })
453 }
454
455 fn measure(
456 &self,
457 _context: &mut dyn ModifierNodeContext,
458 _measurable: &dyn Measurable,
459 constraints: Constraints,
460 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
461 let (text_size, alignment_lines) = self.layout.measure_layout(text_widths(constraints));
462 let text_size = self.pixel_size(text_size);
463
464 let width = text_size
465 .width
466 .clamp(constraints.min_width, constraints.max_width);
467 let height = text_size
468 .height
469 .clamp(constraints.min_height, constraints.max_height);
470
471 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size { width, height })
472 .with_alignment_lines(alignment_lines)
473 }
474
475 fn min_intrinsic_width(
476 &self,
477 _measurable: &dyn Measurable,
478 _height: f32,
479 _density: f32,
480 ) -> f32 {
481 self.density.ceil(self.layout.min_intrinsic_width())
482 }
483
484 fn max_intrinsic_width(
485 &self,
486 _measurable: &dyn Measurable,
487 _height: f32,
488 _density: f32,
489 ) -> f32 {
490 self.pixel_size(self.measure_text_content((0.0, f32::INFINITY)))
491 .width
492 }
493
494 fn min_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
495 self.pixel_size(self.measure_text_content((0.0, intrinsic_width(width))))
496 .height
497 }
498
499 fn max_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
500 self.pixel_size(self.measure_text_content((0.0, intrinsic_width(width))))
501 .height
502 }
503}
504
505fn text_widths(constraints: Constraints) -> (f32, f32) {
508 (constraints.min_width.max(0.0), constraints.max_width)
509}
510
511fn intrinsic_width(width: f32) -> f32 {
514 if width.is_finite() && width > 0.0 {
515 width
516 } else {
517 f32::INFINITY
518 }
519}
520
521impl DrawModifierNode for TextModifierNode {}
522
523impl SemanticsNode for TextModifierNode {
524 fn merge_semantics(&self, config: &mut SemanticsConfiguration) {
525 config
526 .content_description
527 .get_or_insert_with(|| self.text().to_string());
528 }
529
530 fn reach(&self) -> cranpose_foundation::SemanticsReach {
531 cranpose_foundation::SemanticsReach::default()
532 }
533}
534
535#[derive(Debug, Clone)]
544pub struct TextModifierElement {
545 text: Rc<AnnotatedString>,
546 style: Arc<TextStyle>,
549 options: TextLayoutOptions,
550 density: Density,
551}
552
553impl TextModifierElement {
554 pub fn new(
557 text: Rc<AnnotatedString>,
558 style: TextStyle,
559 options: TextLayoutOptions,
560 density: Density,
561 ) -> Self {
562 Self::with_shared_style(text, shared_text_style(style), options, density)
563 }
564
565 pub(crate) fn with_shared_style(
567 text: Rc<AnnotatedString>,
568 style: Arc<TextStyle>,
569 options: TextLayoutOptions,
570 density: Density,
571 ) -> Self {
572 Self {
573 text,
574 style,
575 options: options.normalized(),
576 density,
577 }
578 }
579}
580
581impl PartialEq for TextModifierElement {
582 fn eq(&self, other: &Self) -> bool {
583 self.text == other.text
584 && (Arc::ptr_eq(&self.style, &other.style) || self.style == other.style)
585 && self.options == other.options
586 && self.density == other.density
587 }
588}
589
590const SHARED_TEXT_STYLES: usize = 16;
592
593pub(crate) type RecentTextStyles = RecentValues<Arc<TextStyle>, SHARED_TEXT_STYLES>;
595
596pub(crate) fn shared_text_style(style: TextStyle) -> Arc<TextStyle> {
602 crate::thread_state::with(|state| {
603 let mut recent = state.recent_text_styles.borrow_mut();
604 if let Some(kept) = recent.find(|kept| **kept == style) {
605 return Arc::clone(kept);
606 }
607 Arc::clone(recent.keep(Arc::new(style)))
608 })
609}
610
611impl Hash for TextModifierElement {
612 fn hash<H: Hasher>(&self, state: &mut H) {
613 self.text.render_hash().hash(state);
614 self.options.hash(state);
615 self.density.density().to_bits().hash(state);
616 }
617}
618
619impl ModifierNodeElement for TextModifierElement {
620 type Node = TextModifierNode;
621
622 fn create(&self) -> Self::Node {
623 TextModifierNode::new(
624 Rc::clone(&self.text),
625 Arc::clone(&self.style),
626 self.options,
627 self.density,
628 )
629 }
630
631 fn update(&self, node: &mut Self::Node) {
632 node.density = self.density;
633 node.layout.update(&self.text, &self.style, self.options);
634 }
635
636 fn capabilities(&self) -> NodeCapabilities {
637 NodeCapabilities::LAYOUT | NodeCapabilities::DRAW | NodeCapabilities::SEMANTICS
638 }
639}
640
641#[cfg(test)]
642#[path = "tests/text_modifier_node_tests.rs"]
643mod tests;