Replace per-node measure functions with a single measure function + per-node context (#490)
1use taffy::prelude::*;
2
3const LOREM_IPSUM : &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
4
5struct FontMetrics {
6 char_width: f32,
7 char_height: f32,
8}
9enum WritingMode {
10 Horizontal,
11 Vertical,
12}
13struct TextContext {
14 text_content: String,
15 writing_mode: WritingMode,
16}
17struct ImageContext {
18 width: f32,
19 height: f32,
20}
21enum NodeContext {
22 Text(TextContext),
23 Image(ImageContext),
24}
25
26fn main() -> Result<(), taffy::TaffyError> {
27 let mut taffy: Taffy<NodeContext> = Taffy::new();
28
29 let font_metrics = FontMetrics { char_width: 10.0, char_height: 10.0 };
30
31 let text_node = taffy.new_leaf_with_context(
32 Style::default(),
33 NodeContext::Text(TextContext { text_content: LOREM_IPSUM.into(), writing_mode: WritingMode::Horizontal }),
34 )?;
35
36 let image_node = taffy
37 .new_leaf_with_context(Style::default(), NodeContext::Image(ImageContext { width: 400.0, height: 300.0 }))?;
38
39 let root = taffy.new_with_children(
40 Style {
41 display: Display::Flex,
42 flex_direction: FlexDirection::Column,
43 size: Size { width: length(200.0), height: auto() },
44 ..Default::default()
45 },
46 &[text_node, image_node],
47 )?;
48
49 // Compute layout and print result
50 taffy.compute_layout_with_measure(
51 root,
52 Size::MAX_CONTENT,
53 // Note: this closure is a FnMut closure and can be used to borrow external context for the duration of layout
54 // For example, you may wish to borrow a global font registry and pass it into your text measuring function
55 |known_dimensions, available_space, _node_id, node_context| {
56 measure_function(known_dimensions, available_space, node_context, &font_metrics)
57 },
58 )?;
59 taffy.print_tree(root);
60
61 Ok(())
62}
63
64fn measure_function(
65 known_dimensions: taffy::geometry::Size<Option<f32>>,
66 available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
67 node_context: Option<&mut NodeContext>,
68 font_metrics: &FontMetrics,
69) -> Size<f32> {
70 if let Size { width: Some(width), height: Some(height) } = known_dimensions {
71 return Size { width, height };
72 }
73
74 match node_context {
75 None => Size::ZERO,
76 Some(NodeContext::Text(text_context)) => {
77 text_measure_function(known_dimensions, available_space, &*text_context, font_metrics)
78 }
79 Some(NodeContext::Image(image_context)) => image_measure_function(known_dimensions, image_context),
80 }
81}
82
83fn image_measure_function(
84 known_dimensions: taffy::geometry::Size<Option<f32>>,
85 image_context: &ImageContext,
86) -> taffy::geometry::Size<f32> {
87 match (known_dimensions.width, known_dimensions.height) {
88 (Some(width), Some(height)) => Size { width, height },
89 (Some(width), None) => Size { width, height: (width / image_context.width) * image_context.height },
90 (None, Some(height)) => Size { width: (height / image_context.height) * image_context.width, height },
91 (None, None) => Size { width: image_context.width, height: image_context.height },
92 }
93}
94
95fn text_measure_function(
96 known_dimensions: taffy::geometry::Size<Option<f32>>,
97 available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
98 text_context: &TextContext,
99 font_metrics: &FontMetrics,
100) -> taffy::geometry::Size<f32> {
101 use taffy::geometry::AbsoluteAxis;
102 use taffy::prelude::*;
103
104 let inline_axis = match text_context.writing_mode {
105 WritingMode::Horizontal => AbsoluteAxis::Horizontal,
106 WritingMode::Vertical => AbsoluteAxis::Vertical,
107 };
108 let block_axis = inline_axis.other_axis();
109 let words: Vec<&str> = text_context.text_content.split_whitespace().collect();
110
111 if words.is_empty() {
112 return Size::ZERO;
113 }
114
115 let min_line_length: usize = words.iter().map(|line| line.len()).max().unwrap_or(0);
116 let max_line_length: usize = words.iter().map(|line| line.len()).sum();
117 let inline_size =
118 known_dimensions.get_abs(inline_axis).unwrap_or_else(|| match available_space.get_abs(inline_axis) {
119 AvailableSpace::MinContent => min_line_length as f32 * font_metrics.char_width,
120 AvailableSpace::MaxContent => max_line_length as f32 * font_metrics.char_width,
121 AvailableSpace::Definite(inline_size) => inline_size
122 .min(max_line_length as f32 * font_metrics.char_width)
123 .max(min_line_length as f32 * font_metrics.char_width),
124 });
125 let block_size = known_dimensions.get_abs(block_axis).unwrap_or_else(|| {
126 let inline_line_length = (inline_size / font_metrics.char_width).floor() as usize;
127 let mut line_count = 1;
128 let mut current_line_length = 0;
129 for word in &words {
130 if current_line_length + word.len() > inline_line_length {
131 if current_line_length > 0 {
132 line_count += 1
133 };
134 current_line_length = word.len();
135 } else {
136 current_line_length += word.len();
137 };
138 }
139 (line_count as f32) * font_metrics.char_height
140 });
141
142 match text_context.writing_mode {
143 WritingMode::Horizontal => Size { width: inline_size, height: block_size },
144 WritingMode::Vertical => Size { width: block_size, height: inline_size },
145 }
146}