1use super::{LAYOUT_WRAP_SLACK, NodeRects, ResolvedLayout, wrap};
5use crate::PictureSize;
6use alpha2_eval::{EvaluatedLayout, EvaluatedNode, NodeContent, NodeLayout, Sides};
7use alpha2_model::{
8 AlignContent, AlignItems, Display, FlexDirection, FlexWrap, GridAutoFlow, GridEdge,
9 GridPlacement, JustifyContent, LayoutLength, Overflow, Position, RepeatCount, TrackBreadth,
10 TrackComponent, TrackList, TrackSize,
11};
12use alpha2_text::ShapedText;
13use std::sync::Arc;
14use taffy::{
15 AvailableSpace, Dimension, GridTemplateComponent, GridTemplateRepetition, LengthPercentage,
16 LengthPercentageAuto, MaxTrackSizingFunction, MinTrackSizingFunction, NodeId, Point, Rect,
17 RepetitionCount, Size, Style, TaffyTree, TrackSizingFunction,
18};
19
20#[derive(Copy, Clone)]
22enum NodeContext {
23 Text(usize),
25 Other,
27}
28
29#[allow(clippy::cast_precision_loss)]
32#[must_use]
33pub fn resolve(
34 layout: &EvaluatedLayout,
35 size: PictureSize,
36 shaped: &[Option<Arc<ShapedText>>],
37) -> ResolvedLayout {
38 let (width, height) = (size.width() as f32, size.height() as f32);
39 let unit = width.min(height);
40 let mut tree = TaffyTree::<NodeContext>::new();
41 let mut ids = Vec::new();
42 let mut texts = 0;
43 let root = build(
44 &mut tree,
45 &layout.root,
46 unit,
47 Some(Size { width, height }),
48 &mut ids,
49 &mut texts,
50 );
51 let laid_out = tree
53 .compute_layout_with_measure(
54 root,
55 Size {
56 width: AvailableSpace::Definite(width),
57 height: AvailableSpace::Definite(height),
58 },
59 |inputs, _, context, style| match context {
60 Some(NodeContext::Text(index)) => {
61 let text = shaped.get(*index).and_then(Option::as_deref);
62 taffy::compute_leaf_layout(
63 inputs,
64 style,
65 |_, _| 0.0,
66 |known, available| {
67 let Some(text) = text else {
68 return Size::ZERO;
69 };
70 let (min, max) = (text.min_content_width(), text.max_content_width());
71 let width = known.width.unwrap_or(match available.width {
72 AvailableSpace::MinContent => min,
73 AvailableSpace::MaxContent => max,
74 AvailableSpace::Definite(space) => space.min(max).max(min),
75 });
76 let height = known
77 .height
78 .unwrap_or_else(|| text.lines_at(wrap(width)).height);
79 Size { width, height }
80 },
81 )
82 }
83 _ => taffy::compute_leaf_layout(
84 inputs,
85 style,
86 |_, _| 0.0,
87 |known, _| Size {
88 width: known.width.unwrap_or(0.0),
89 height: known.height.unwrap_or(0.0),
90 },
91 ),
92 },
93 )
94 .is_ok();
95 let mut nodes = Vec::with_capacity(ids.len());
96 if laid_out {
97 collect(
98 &tree,
99 &layout.root,
100 &ids,
101 [0.0, 0.0],
102 &mut 0,
103 shaped,
104 &mut 0,
105 &mut nodes,
106 );
107 } else {
108 nodes.resize(ids.len(), NodeRects::default());
109 }
110 ResolvedLayout { nodes }
111}
112
113fn build(
116 tree: &mut TaffyTree<NodeContext>,
117 node: &EvaluatedNode,
118 unit: f32,
119 root_size: Option<Size<f32>>,
120 ids: &mut Vec<NodeId>,
121 texts: &mut usize,
122) -> NodeId {
123 let style = taffy_style(&node.layout, unit, root_size);
124 let slot = ids.len();
125 ids.push(NodeId::from(0_u64));
126 let id = match &node.content {
127 NodeContent::Children(children) => {
128 let children = children
129 .iter()
130 .map(|child| build(tree, child, unit, None, ids, texts))
131 .collect::<Vec<_>>();
132 tree.new_with_children(style, &children)
133 }
134 NodeContent::Text(_) => {
135 let index = *texts;
136 *texts += 1;
137 tree.new_leaf_with_context(style, NodeContext::Text(index))
138 }
139 NodeContent::Empty | NodeContent::Shape(_) => {
140 tree.new_leaf_with_context(style, NodeContext::Other)
141 }
142 }
143 .expect("taffy adds nodes without failing");
144 ids[slot] = id;
145 id
146}
147
148fn length(value: f32, unit: f32) -> LengthPercentage {
149 LengthPercentage::length(value * unit)
150}
151
152fn length_auto(value: Option<LayoutLength>, unit: f32) -> LengthPercentageAuto {
155 match value {
156 None => LengthPercentageAuto::auto(),
157 Some(LayoutLength::Fraction(value) | LayoutLength::Px(value)) => {
158 LengthPercentageAuto::length(value * unit)
159 }
160 Some(LayoutLength::Percent(value)) => LengthPercentageAuto::percent(value / 100.0),
161 }
162}
163
164fn dimension(value: Option<LayoutLength>, unit: f32) -> Dimension {
165 match value {
166 None => Dimension::auto(),
167 Some(LayoutLength::Fraction(value) | LayoutLength::Px(value)) => {
168 Dimension::length(value * unit)
169 }
170 Some(LayoutLength::Percent(value)) => Dimension::percent(value / 100.0),
171 }
172}
173
174fn min_breadth(breadth: TrackBreadth, unit: f32) -> MinTrackSizingFunction {
175 match breadth {
176 TrackBreadth::Length(LayoutLength::Fraction(value) | LayoutLength::Px(value)) => {
177 MinTrackSizingFunction::length(value * unit)
178 }
179 TrackBreadth::Length(LayoutLength::Percent(value)) => {
180 MinTrackSizingFunction::percent(value / 100.0)
181 }
182 TrackBreadth::Auto | TrackBreadth::Fr(_) => MinTrackSizingFunction::auto(),
184 TrackBreadth::MinContent => MinTrackSizingFunction::min_content(),
185 TrackBreadth::MaxContent => MinTrackSizingFunction::max_content(),
186 }
187}
188
189fn max_breadth(breadth: TrackBreadth, unit: f32) -> MaxTrackSizingFunction {
190 match breadth {
191 TrackBreadth::Length(LayoutLength::Fraction(value) | LayoutLength::Px(value)) => {
192 MaxTrackSizingFunction::length(value * unit)
193 }
194 TrackBreadth::Length(LayoutLength::Percent(value)) => {
195 MaxTrackSizingFunction::percent(value / 100.0)
196 }
197 TrackBreadth::Fr(share) => MaxTrackSizingFunction::fr(share),
198 TrackBreadth::Auto => MaxTrackSizingFunction::auto(),
199 TrackBreadth::MinContent => MaxTrackSizingFunction::min_content(),
200 TrackBreadth::MaxContent => MaxTrackSizingFunction::max_content(),
201 }
202}
203
204fn track(size: TrackSize, unit: f32) -> TrackSizingFunction {
207 let (min, max) = match size {
208 TrackSize::Breadth(TrackBreadth::Fr(share)) => (
209 MinTrackSizingFunction::auto(),
210 MaxTrackSizingFunction::fr(share),
211 ),
212 TrackSize::Breadth(breadth) => (min_breadth(breadth, unit), max_breadth(breadth, unit)),
213 TrackSize::MinMax(min, max) => (min_breadth(min, unit), max_breadth(max, unit)),
214 TrackSize::FitContent(limit) => (
215 MinTrackSizingFunction::auto(),
216 match limit {
217 LayoutLength::Fraction(value) | LayoutLength::Px(value) => {
218 MaxTrackSizingFunction::fit_content_px(value * unit)
219 }
220 LayoutLength::Percent(value) => {
221 MaxTrackSizingFunction::fit_content_percent(value / 100.0)
222 }
223 },
224 ),
225 };
226 TrackSizingFunction { min, max }
227}
228
229fn template<S: taffy::CheapCloneStr>(
232 tracks: Option<&TrackList>,
233 unit: f32,
234) -> Vec<GridTemplateComponent<S>> {
235 tracks.map_or_else(Vec::new, |tracks| {
236 tracks
237 .0
238 .iter()
239 .map(|component| match component {
240 TrackComponent::Single(size) => GridTemplateComponent::Single(track(*size, unit)),
241 TrackComponent::Repeat(count, sizes) => {
242 GridTemplateComponent::Repeat(GridTemplateRepetition {
243 count: match count {
244 RepeatCount::Count(count) => RepetitionCount::Count(*count),
245 RepeatCount::AutoFill => RepetitionCount::AutoFill,
246 RepeatCount::AutoFit => RepetitionCount::AutoFit,
247 },
248 tracks: sizes.iter().map(|size| track(*size, unit)).collect(),
249 line_names: Vec::new(),
250 })
251 }
252 })
253 .collect()
254 })
255}
256
257fn implicit(tracks: Option<&TrackList>, unit: f32) -> Vec<TrackSizingFunction> {
259 tracks.map_or_else(Vec::new, |tracks| {
260 tracks.sizes().map(|size| track(size, unit)).collect()
261 })
262}
263
264fn placement<S: taffy::CheapCloneStr>(
265 placement: GridPlacement,
266) -> taffy::Line<taffy::GridPlacement<S>> {
267 let edge = |edge: GridEdge| match edge {
268 GridEdge::Auto => taffy::GridPlacement::Auto,
269 GridEdge::Line(line) => taffy::style_helpers::line(line),
270 GridEdge::Span(count) => taffy::GridPlacement::Span(count),
271 };
272 taffy::Line {
273 start: edge(placement.start),
274 end: edge(placement.end),
275 }
276}
277
278const fn align(value: AlignItems) -> taffy::AlignItems {
279 match value {
280 AlignItems::Start => taffy::AlignItems::START,
281 AlignItems::Center => taffy::AlignItems::CENTER,
282 AlignItems::End => taffy::AlignItems::END,
283 AlignItems::Stretch => taffy::AlignItems::STRETCH,
284 }
285}
286
287const fn align_content(value: AlignContent) -> taffy::AlignContent {
288 match value {
289 AlignContent::Start => taffy::AlignContent::START,
290 AlignContent::Center => taffy::AlignContent::CENTER,
291 AlignContent::End => taffy::AlignContent::END,
292 AlignContent::SpaceBetween => taffy::AlignContent::SPACE_BETWEEN,
293 AlignContent::SpaceAround => taffy::AlignContent::SPACE_AROUND,
294 AlignContent::SpaceEvenly => taffy::AlignContent::SPACE_EVENLY,
295 AlignContent::Stretch => taffy::AlignContent::STRETCH,
296 }
297}
298
299fn taffy_style(node: &NodeLayout, unit: f32, root_size: Option<Size<f32>>) -> Style {
302 let insets = |insets: Sides| Rect {
303 left: length(insets.left, unit),
304 right: length(insets.right, unit),
305 top: length(insets.top, unit),
306 bottom: length(insets.bottom, unit),
307 };
308 let margin = Rect {
309 left: LengthPercentageAuto::length(node.margin.left * unit),
310 right: LengthPercentageAuto::length(node.margin.right * unit),
311 top: LengthPercentageAuto::length(node.margin.top * unit),
312 bottom: LengthPercentageAuto::length(node.margin.bottom * unit),
313 };
314 let border = length(node.border_width, unit);
315 let [left, top, right, bottom] = node.inset;
316 let overflow = match node.overflow {
317 Overflow::Visible => taffy::Overflow::Visible,
318 Overflow::Hidden => taffy::Overflow::Hidden,
319 };
320 Style {
321 display: match node.display {
322 Display::Flex => taffy::Display::Flex,
323 Display::Grid => taffy::Display::Grid,
324 },
325 box_sizing: taffy::BoxSizing::BorderBox,
326 direction: taffy::Direction::Ltr,
327 flex_direction: match node.flex_direction {
328 FlexDirection::Row => taffy::FlexDirection::Row,
329 FlexDirection::Column => taffy::FlexDirection::Column,
330 FlexDirection::RowReverse => taffy::FlexDirection::RowReverse,
331 FlexDirection::ColumnReverse => taffy::FlexDirection::ColumnReverse,
332 },
333 flex_wrap: match node.flex_wrap {
334 FlexWrap::NoWrap => taffy::FlexWrap::NoWrap,
335 FlexWrap::Wrap => taffy::FlexWrap::Wrap,
336 },
337 gap: Size {
338 width: length(node.column_gap, unit),
339 height: length(node.row_gap, unit),
340 },
341 justify_content: match node.justify_content {
342 JustifyContent::Start => taffy::JustifyContent::START,
343 JustifyContent::Center => taffy::JustifyContent::CENTER,
344 JustifyContent::End => taffy::JustifyContent::END,
345 JustifyContent::SpaceBetween => taffy::JustifyContent::SPACE_BETWEEN,
346 JustifyContent::SpaceAround => taffy::JustifyContent::SPACE_AROUND,
347 JustifyContent::SpaceEvenly => taffy::JustifyContent::SPACE_EVENLY,
348 },
349 align_items: align(node.align_items),
350 align_self: node.align_self.map(align),
351 align_content: align_content(node.align_content),
352 padding: insets(node.padding),
353 margin,
354 border: Rect {
355 left: border,
356 right: border,
357 top: border,
358 bottom: border,
359 },
360 size: root_size.map_or_else(
361 || Size {
362 width: dimension(node.width, unit),
363 height: dimension(node.height, unit),
364 },
365 |size| Size {
366 width: Dimension::length(size.width),
367 height: Dimension::length(size.height),
368 },
369 ),
370 min_size: Size {
371 width: length_auto(node.min_width, unit),
372 height: length_auto(node.min_height, unit),
373 },
374 max_size: Size {
375 width: length_auto(node.max_width, unit),
376 height: length_auto(node.max_height, unit),
377 },
378 aspect_ratio: node.aspect_ratio,
379 flex_grow: node.flex_grow,
380 flex_shrink: node.flex_shrink,
381 flex_basis: dimension(node.flex_basis, unit),
382 position: match node.position {
384 Position::Relative => taffy::Position::Relative,
385 Position::Absolute => taffy::Position::Absolute,
386 },
387 inset: Rect {
388 left: length_auto(left, unit),
389 top: length_auto(top, unit),
390 right: length_auto(right, unit),
391 bottom: length_auto(bottom, unit),
392 },
393 overflow: Point {
394 x: overflow,
395 y: overflow,
396 },
397 scrollbar_width: 0.0,
398 ..grid_style(node, unit)
399 }
400}
401
402fn grid_style(node: &NodeLayout, unit: f32) -> Style {
405 Style {
406 justify_items: align(node.justify_items),
407 justify_self: node.justify_self.map(align),
408 grid_template_columns: template(node.grid_template_columns.as_ref(), unit),
409 grid_template_rows: template(node.grid_template_rows.as_ref(), unit),
410 grid_auto_columns: implicit(node.grid_auto_columns.as_ref(), unit),
411 grid_auto_rows: implicit(node.grid_auto_rows.as_ref(), unit),
412 grid_auto_flow: match node.grid_auto_flow {
413 GridAutoFlow::Row => taffy::GridAutoFlow::Row,
414 GridAutoFlow::Column => taffy::GridAutoFlow::Column,
415 GridAutoFlow::RowDense => taffy::GridAutoFlow::RowDense,
416 GridAutoFlow::ColumnDense => taffy::GridAutoFlow::ColumnDense,
417 },
418 grid_column: placement(node.grid_column),
419 grid_row: placement(node.grid_row),
420 ..Style::default()
421 }
422}
423
424#[allow(clippy::too_many_arguments)]
427fn collect(
428 tree: &TaffyTree<NodeContext>,
429 node: &EvaluatedNode,
430 ids: &[NodeId],
431 origin: [f32; 2],
432 index: &mut usize,
433 shaped: &[Option<Arc<ShapedText>>],
434 texts: &mut usize,
435 out: &mut Vec<NodeRects>,
436) {
437 let id = ids[*index];
438 *index += 1;
439 let rounded = tree.layout(id).expect("laid out");
440 let unrounded = tree.unrounded_layout(id);
441 let left = origin[0] + rounded.location.x;
442 let top = origin[1] + rounded.location.y;
443 let border = [
444 left,
445 top,
446 left + rounded.size.width,
447 top + rounded.size.height,
448 ];
449 let padding = [
450 border[0] + rounded.border.left,
451 border[1] + rounded.border.top,
452 border[2] - rounded.border.right,
453 border[3] - rounded.border.bottom,
454 ];
455 let content = [
456 padding[0] + rounded.padding.left,
457 padding[1] + rounded.padding.top,
458 padding[2] - rounded.padding.right,
459 padding[3] - rounded.padding.bottom,
460 ];
461 let text_width = (unrounded.size.width
462 - unrounded.border.left
463 - unrounded.border.right
464 - unrounded.padding.left
465 - unrounded.padding.right)
466 .max(0.0);
467 let text_height = (unrounded.size.height
471 - unrounded.border.top
472 - unrounded.border.bottom
473 - unrounded.padding.top
474 - unrounded.padding.bottom)
475 .max(0.0);
476 let slot = out.len();
477 out.push(NodeRects {
478 border,
479 padding,
480 content,
481 text_width,
482 overflow: false,
483 line_count: None,
484 baselines: None,
485 });
486 let mut overflow = false;
487 match &node.content {
488 NodeContent::Text(_) => {
489 let text = shaped.get(*texts).and_then(Option::as_deref);
490 *texts += 1;
491 let lines = text.map(|text| {
492 let lines = text.lines_at(wrap(text_width));
493 overflow = lines.height > text_height + LAYOUT_WRAP_SLACK
494 || text.min_content_width() > wrap(text_width);
495 lines
496 });
497 out[slot].line_count = Some(lines.map_or(0, |lines| lines.line_count));
498 out[slot].baselines = lines.map(|lines| {
499 [
500 content[1] + lines.first_baseline,
501 content[1] + lines.last_baseline,
502 ]
503 });
504 }
505 NodeContent::Children(children) => {
506 for child in children {
507 let at = out.len();
508 collect(tree, child, ids, [left, top], index, shaped, texts, out);
509 if child.layout.position == Position::Relative {
510 let [l, t, r, b] = out[at].border;
511 overflow |= l < padding[0] - 0.5
512 || t < padding[1] - 0.5
513 || r > padding[2] + 0.5
514 || b > padding[3] + 0.5;
515 }
516 }
517 }
518 NodeContent::Empty | NodeContent::Shape(_) => {}
519 }
520 out[slot].overflow = overflow;
521}