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: TextStyle, options: TextLayoutOptions) -> Self {
76 let style_hash = style.render_hash();
77 Self {
78 source: RefCell::new(TextLayoutSource {
79 text,
80 style: Arc::new(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(&self, text: &Rc<AnnotatedString>, style: &TextStyle, options: TextLayoutOptions) {
95 let mut source = self.source.borrow_mut();
96 let same_style = *source.style == *style;
97 if source.text == *text && same_style && source.options == options {
98 return;
99 }
100 if !same_style {
101 source.style = Arc::new(style.clone());
102 source.style_hash = style.render_hash();
103 }
104 source.text = Rc::clone(text);
105 source.options = options;
106 source.shares_layouts = false;
107 drop(source);
108 self.cache.borrow_mut().clear();
109 }
110
111 fn annotated_text(&self) -> Ref<'_, Rc<AnnotatedString>> {
112 Ref::map(self.source.borrow(), |source| &source.text)
113 }
114
115 fn style(&self) -> Ref<'_, TextStyle> {
116 Ref::map(self.source.borrow(), |source| &*source.style)
117 }
118
119 fn options(&self) -> TextLayoutOptions {
120 self.source.borrow().options
121 }
122
123 fn node_id(&self) -> Option<cranpose_core::NodeId> {
124 self.node_id.get()
125 }
126
127 fn set_node_id(&self, node_id: Option<cranpose_core::NodeId>) {
128 if self.node_id.replace(node_id) != node_id {
129 self.cache.borrow_mut().clear();
130 }
131 }
132
133 fn with_prepared<R>(
134 &self,
135 max_width: Option<f32>,
136 read: impl FnOnce(&Rc<crate::text::PreparedTextLayout>) -> R,
137 ) -> R {
138 let normalized_max_width = max_width.filter(|width| width.is_finite() && *width > 0.0);
139 let (text_generation, font_scale_fingerprint) = crate::render_state::text_layout_stamp();
140
141 {
142 let mut cache = self.cache.borrow_mut();
143 if let Some(index) = cache.iter().position(|entry| {
144 entry.widths.hold(normalized_max_width)
145 && entry.text_generation == text_generation
146 && entry.font_scale_fingerprint == font_scale_fingerprint
147 }) {
148 cache[..=index].rotate_right(1);
149 return read(&cache[0].layout);
150 }
151 }
152
153 let source = self.source.borrow();
154 let prepare = if source.shares_layouts {
155 crate::text::prepare_text_layout_for_node
156 } else {
157 crate::text::measure::prepare_unshared_text_layout_for_node
158 };
159 let prepared = prepare(
160 self.node_id(),
161 &source.text,
162 &source.style,
163 source.options,
164 normalized_max_width,
165 );
166 if Arc::ptr_eq(&prepared.visual_style, &source.style) {
167 let _ = prepared.visual_style_hash.set(source.style_hash);
168 }
169 let widths = crate::text::measure::PreparedWidths::of(
170 source.text.as_ref(),
171 source.options,
172 normalized_max_width,
173 &prepared,
174 );
175 drop(source);
176
177 let mut cache = self.cache.borrow_mut();
178 cache.insert(
179 0,
180 TextPreparedLayoutCacheEntry {
181 widths,
182 text_generation,
183 font_scale_fingerprint,
184 layout: prepared,
185 },
186 );
187 cache.truncate(PREPARED_LAYOUT_CACHE_CAPACITY);
188 read(&cache[0].layout)
189 }
190
191 fn cached_widths(
194 &self,
195 max_width: Option<f32>,
196 ) -> Option<(Size, crate::text::measure::PreparedWidths)> {
197 let normalized_max_width = max_width.filter(|width| width.is_finite() && *width > 0.0);
198 let (text_generation, font_scale_fingerprint) = crate::render_state::text_layout_stamp();
199 self.cache
200 .borrow()
201 .iter()
202 .find(|entry| {
203 entry.widths.hold(normalized_max_width)
204 && entry.text_generation == text_generation
205 && entry.font_scale_fingerprint == font_scale_fingerprint
206 })
207 .map(|entry| {
208 (
209 Size::new(entry.layout.metrics.width, entry.layout.metrics.height),
210 entry.widths,
211 )
212 })
213 }
214
215 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
216 self.with_prepared(max_width, |prepared| Size {
217 width: prepared.metrics.width,
218 height: prepared.metrics.height,
219 })
220 }
221
222 fn measure_layout(&self, max_width: Option<f32>) -> (Size, cranpose_ui_layout::AlignmentLines) {
223 self.measured_max_width.set(Some(max_width));
224 self.with_prepared(max_width, |prepared| {
225 (
226 Size::new(prepared.metrics.width, prepared.metrics.height),
227 prepared.alignment_lines,
228 )
229 })
230 }
231
232 fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
233 self.measured_max_width
234 .get()
235 .map(|max_width| self.with_prepared(max_width, Rc::clone))
236 }
237}
238
239impl TextPreparedLayoutHandle {
240 fn new(owner: Rc<TextPreparedLayoutOwner>) -> Self {
241 Self { owner }
242 }
243
244 pub(crate) fn annotated_text(&self) -> Ref<'_, Rc<AnnotatedString>> {
245 self.owner.annotated_text()
246 }
247
248 pub(crate) fn style(&self) -> Ref<'_, TextStyle> {
249 self.owner.style()
250 }
251
252 pub(crate) fn options(&self) -> TextLayoutOptions {
253 self.owner.options()
254 }
255
256 pub(crate) fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
257 self.owner.measured_layout()
258 }
259}
260
261impl TextModifierNode {
262 pub fn new(
264 text: Rc<AnnotatedString>,
265 style: TextStyle,
266 options: TextLayoutOptions,
267 density: Density,
268 ) -> Self {
269 Self {
270 layout: Rc::new(TextPreparedLayoutOwner::new(text, style, options)),
271 density,
272 state: NodeState::new(),
273 }
274 }
275
276 fn pixel_size(&self, size: Size) -> Size {
280 Size {
281 width: self.density.ceil(size.width),
282 height: self.density.ceil(size.height),
283 }
284 }
285
286 pub fn text(&self) -> Ref<'_, str> {
287 Ref::map(self.layout.annotated_text(), |text| text.text.as_str())
288 }
289
290 pub fn annotated_text(&self) -> Rc<AnnotatedString> {
291 Rc::clone(&self.layout.annotated_text())
292 }
293
294 pub fn style(&self) -> Ref<'_, TextStyle> {
295 self.layout.style()
296 }
297
298 pub fn options(&self) -> TextLayoutOptions {
299 self.layout.options()
300 }
301
302 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
303 self.layout.measure_text_content(max_width)
304 }
305
306 pub(crate) fn prepared_layout_handle(&self) -> TextPreparedLayoutHandle {
307 TextPreparedLayoutHandle::new(self.layout.clone())
308 }
309}
310
311impl DelegatableNode for TextModifierNode {
312 fn node_state(&self) -> &NodeState {
313 &self.state
314 }
315}
316
317impl ModifierNode for TextModifierNode {
318 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
319 self.layout.set_node_id(context.node_id());
320 context.invalidate(InvalidationKind::Layout);
321 context.invalidate(InvalidationKind::Draw);
322 context.invalidate(InvalidationKind::Semantics);
323 }
324
325 fn on_detach(&mut self) {
326 self.layout.set_node_id(None);
327 }
328
329 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
330 Some(self)
331 }
332
333 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
334 Some(self)
335 }
336
337 fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
338 Some(self)
339 }
340
341 fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
342 Some(self)
343 }
344
345 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
346 Some(self)
347 }
348
349 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
350 Some(self)
351 }
352}
353
354impl LayoutModifierNode for TextModifierNode {
355 fn measure_hold(
358 &self,
359 _density: f32,
360 constraints: Constraints,
361 size: Size,
362 _wrapped: cranpose_ui_layout::WrappedHold,
363 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
364 let max_width = constraints
365 .max_width
366 .is_finite()
367 .then_some(constraints.max_width);
368 let (text_size, widths) = self.layout.cached_widths(max_width)?;
369 let text_size = self.pixel_size(text_size);
370 if size != text_size {
371 return None;
372 }
373 Some(cranpose_ui_layout::ConstraintsHold {
374 width: cranpose_ui_layout::AxisHold {
375 min: cranpose_ui_layout::BoundRange::up_to(text_size.width),
376 max: cranpose_ui_layout::BoundRange::from(text_size.width)
377 .intersect(widths.max_width_range())?,
378 },
379 height: cranpose_ui_layout::AxisHold::sized(text_size.height),
380 })
381 }
382
383 fn measure(
384 &self,
385 _context: &mut dyn ModifierNodeContext,
386 _measurable: &dyn Measurable,
387 constraints: Constraints,
388 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
389 let max_width = constraints
390 .max_width
391 .is_finite()
392 .then_some(constraints.max_width);
393 let (text_size, alignment_lines) = self.layout.measure_layout(max_width);
394 let text_size = self.pixel_size(text_size);
395
396 let width = text_size
397 .width
398 .clamp(constraints.min_width, constraints.max_width);
399 let height = text_size
400 .height
401 .clamp(constraints.min_height, constraints.max_height);
402
403 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size { width, height })
404 .with_alignment_lines(alignment_lines)
405 }
406
407 fn min_intrinsic_width(
408 &self,
409 _measurable: &dyn Measurable,
410 _height: f32,
411 _density: f32,
412 ) -> f32 {
413 self.pixel_size(self.measure_text_content(None)).width
414 }
415
416 fn max_intrinsic_width(
417 &self,
418 _measurable: &dyn Measurable,
419 _height: f32,
420 _density: f32,
421 ) -> f32 {
422 self.pixel_size(self.measure_text_content(None)).width
423 }
424
425 fn min_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
426 self.pixel_size(
427 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
428 )
429 .height
430 }
431
432 fn max_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
433 self.pixel_size(
434 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
435 )
436 .height
437 }
438}
439
440impl DrawModifierNode for TextModifierNode {}
441
442impl SemanticsNode for TextModifierNode {
443 fn merge_semantics(&self, config: &mut SemanticsConfiguration) {
444 config
445 .content_description
446 .get_or_insert_with(|| self.text().to_string());
447 }
448
449 fn reach(&self) -> cranpose_foundation::SemanticsReach {
450 cranpose_foundation::SemanticsReach::default()
451 }
452}
453
454#[derive(Debug, Clone, PartialEq)]
463pub struct TextModifierElement {
464 text: Rc<AnnotatedString>,
465 style: TextStyle,
466 options: TextLayoutOptions,
467 density: Density,
468}
469
470impl TextModifierElement {
471 pub fn new(
474 text: Rc<AnnotatedString>,
475 style: TextStyle,
476 options: TextLayoutOptions,
477 density: Density,
478 ) -> Self {
479 Self {
480 text,
481 style,
482 options: options.normalized(),
483 density,
484 }
485 }
486}
487
488impl Hash for TextModifierElement {
489 fn hash<H: Hasher>(&self, state: &mut H) {
490 self.text.render_hash().hash(state);
491 self.options.hash(state);
492 self.density.density().to_bits().hash(state);
493 }
494}
495
496impl ModifierNodeElement for TextModifierElement {
497 type Node = TextModifierNode;
498
499 fn create(&self) -> Self::Node {
500 TextModifierNode::new(
501 self.text.clone(),
502 self.style.clone(),
503 self.options,
504 self.density,
505 )
506 }
507
508 fn update(&self, node: &mut Self::Node) {
509 node.density = self.density;
510 node.layout.update(&self.text, &self.style, self.options);
511 }
512
513 fn capabilities(&self) -> NodeCapabilities {
514 NodeCapabilities::LAYOUT | NodeCapabilities::DRAW | NodeCapabilities::SEMANTICS
515 }
516}
517
518#[cfg(test)]
519#[path = "tests/text_modifier_node_tests.rs"]
520mod tests;