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