Avoid performing layout when computing size and size is known from styles (#361)
1//! Computes the [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) layout algorithm on [`Taffy`](crate::Taffy) according to the [spec](https://www.w3.org/TR/css-flexbox-1/)
2use core::f32;
3
4use crate::compute::common::alignment::compute_alignment_offset;
5use crate::compute::{GenericAlgorithm, LayoutAlgorithm};
6use crate::geometry::{Point, Rect, Size};
7use crate::layout::{Layout, RunMode, SizeAndBaselines, SizingMode};
8use crate::math::MaybeMath;
9use crate::node::Node;
10use crate::prelude::{TaffyMaxContent, TaffyMinContent};
11use crate::resolve::{MaybeResolve, ResolveOrZero};
12use crate::style::{
13 AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display, FlexWrap, JustifyContent,
14 LengthPercentageAuto, Position,
15};
16use crate::style::{FlexDirection, Style};
17use crate::sys::f32_max;
18use crate::sys::Vec;
19use crate::tree::LayoutTree;
20
21#[cfg(feature = "debug")]
22use crate::debug::NODE_LOGGER;
23
24/// The public interface to Taffy's Flexbox algorithm implementation
25pub(crate) struct FlexboxAlgorithm;
26impl LayoutAlgorithm for FlexboxAlgorithm {
27 const NAME: &'static str = "FLEXBOX";
28
29 fn perform_layout(
30 tree: &mut impl LayoutTree,
31 node: Node,
32 known_dimensions: Size<Option<f32>>,
33 parent_size: Size<Option<f32>>,
34 available_space: Size<AvailableSpace>,
35 _sizing_mode: SizingMode,
36 ) -> SizeAndBaselines {
37 compute(tree, node, known_dimensions, parent_size, available_space, RunMode::PeformLayout)
38 }
39
40 fn measure_size(
41 tree: &mut impl LayoutTree,
42 node: Node,
43 known_dimensions: Size<Option<f32>>,
44 parent_size: Size<Option<f32>>,
45 available_space: Size<AvailableSpace>,
46 _sizing_mode: SizingMode,
47 ) -> Size<f32> {
48 compute(tree, node, known_dimensions, parent_size, available_space, RunMode::ComputeSize).size
49 }
50}
51
52/// The intermediate results of a flexbox calculation for a single item
53struct FlexItem {
54 /// The identifier for the associated [`Node`](crate::node::Node)
55 node: Node,
56
57 /// The base size of this item
58 size: Size<Option<f32>>,
59 /// The minimum allowable size of this item
60 min_size: Size<Option<f32>>,
61 /// The maximum allowable size of this item
62 max_size: Size<Option<f32>>,
63 /// The cross-alignment of this item
64 align_self: AlignSelf,
65
66 /// The flex shrink style of the item
67 flex_shrink: f32,
68 /// The flex grow style of the item
69 flex_grow: f32,
70
71 /// The minimum size of the item. This differs from min_size above because it also
72 /// takes into account content based automatic minimum sizes
73 resolved_minimum_size: Size<f32>,
74
75 /// The final offset of this item
76 inset: Rect<Option<f32>>,
77 /// The margin of this item
78 margin: Rect<f32>,
79 /// The padding of this item
80 padding: Rect<f32>,
81 /// The border of this item
82 border: Rect<f32>,
83
84 /// The default size of this item
85 flex_basis: f32,
86 /// The default size of this item, minus padding and border
87 inner_flex_basis: f32,
88 /// The amount by which this item has deviated from its target size
89 violation: f32,
90 /// Is the size of this item locked
91 frozen: bool,
92
93 /// Either the max- or min- content flex fraction
94 /// See https://www.w3.org/TR/css-flexbox-1/#intrinsic-main-sizes
95 content_flex_fraction: f32,
96
97 /// The proposed inner size of this item
98 hypothetical_inner_size: Size<f32>,
99 /// The proposed outer size of this item
100 hypothetical_outer_size: Size<f32>,
101 /// The size that this item wants to be
102 target_size: Size<f32>,
103 /// The size that this item wants to be, plus any padding and border
104 outer_target_size: Size<f32>,
105
106 /// The position of the bottom edge of this item
107 baseline: f32,
108
109 /// A temporary value for the main offset
110 ///
111 /// Offset is the relative position from the item's natural flow position based on
112 /// relative position values, alignment, and justification. Does not include margin/padding/border.
113 offset_main: f32,
114 /// A temporary value for the cross offset
115 ///
116 /// Offset is the relative position from the item's natural flow position based on
117 /// relative position values, alignment, and justification. Does not include margin/padding/border.
118 offset_cross: f32,
119}
120
121/// A line of [`FlexItem`] used for intermediate computation
122struct FlexLine<'a> {
123 /// The slice of items to iterate over during computation of this line
124 items: &'a mut [FlexItem],
125 /// The dimensions of the cross-axis
126 cross_size: f32,
127 /// The relative offset of the cross-axis
128 offset_cross: f32,
129}
130
131/// Values that can be cached during the flexbox algorithm
132struct AlgoConstants {
133 /// The direction of the current segment being laid out
134 dir: FlexDirection,
135 /// Is this segment a row
136 is_row: bool,
137 /// Is this segment a column
138 is_column: bool,
139 /// Is wrapping enabled (in either direction)
140 is_wrap: bool,
141 /// Is the wrap direction inverted
142 is_wrap_reverse: bool,
143
144 /// The margin of this section
145 margin: Rect<f32>,
146 /// The border of this section
147 border: Rect<f32>,
148 /// The padding of this section
149 padding_border: Rect<f32>,
150 /// The gap of this section
151 gap: Size<f32>,
152 /// The align_items property of this node
153 align_items: AlignItems,
154 /// The align_content property of this node
155 align_content: AlignContent,
156
157 /// The border-box size of the node being laid out (if known)
158 node_outer_size: Size<Option<f32>>,
159 /// The content-box size of the node being laid out (if known)
160 node_inner_size: Size<Option<f32>>,
161
162 /// The size of the virtual container containing the flex items.
163 container_size: Size<f32>,
164 /// The size of the internal container
165 inner_container_size: Size<f32>,
166}
167
168/// Computes the layout of [`LayoutTree`] according to the flexbox algorithm
169pub fn compute(
170 tree: &mut impl LayoutTree,
171 node: Node,
172 known_dimensions: Size<Option<f32>>,
173 parent_size: Size<Option<f32>>,
174 available_space: Size<AvailableSpace>,
175 run_mode: RunMode,
176) -> SizeAndBaselines {
177 let style = tree.style(node);
178 let has_min_max_sizes = style.min_size.width.is_defined()
179 || style.min_size.height.is_defined()
180 || style.max_size.width.is_defined()
181 || style.max_size.height.is_defined();
182
183 // Pull these out earlier to avoid borrowing issues
184 let aspect_ratio = style.aspect_ratio;
185 let min_size = style.min_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
186 let max_size = style.max_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
187 let clamped_style_size =
188 style.size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
189
190 // If both min and max in a given axis are set and max <= min then this determines the size in that axis
191 let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
192 (Some(min), Some(max)) if max <= min => Some(min),
193 _ => None,
194 });
195 let styled_based_known_dimensions = known_dimensions.or(min_max_definite_size).or(clamped_style_size);
196
197 // Short-circuit layout if the container's size is fully determined by the container's size and the run mode
198 // is ComputeSize (and thus the container's size is all that we're interested in)
199 if run_mode == RunMode::ComputeSize {
200 if let Size { width: Some(width), height: Some(height) } = styled_based_known_dimensions {
201 return Size { width, height }.into();
202 }
203 }
204
205 if styled_based_known_dimensions.both_axis_defined() || !has_min_max_sizes {
206 #[cfg(feature = "debug")]
207 NODE_LOGGER.log("FLEX: single-pass");
208 compute_preliminary(tree, node, styled_based_known_dimensions, parent_size, available_space, run_mode)
209 } else {
210 #[cfg(feature = "debug")]
211 NODE_LOGGER.log("FLEX: two-pass");
212 let first_pass = compute_preliminary(
213 tree,
214 node,
215 styled_based_known_dimensions,
216 parent_size,
217 available_space,
218 RunMode::ComputeSize,
219 )
220 .size;
221
222 let clamped_first_pass_size = first_pass.maybe_clamp(min_size, max_size);
223
224 compute_preliminary(
225 tree,
226 node,
227 styled_based_known_dimensions
228 .zip_map(clamped_first_pass_size, |known, first_pass| known.or_else(|| first_pass.into())),
229 parent_size,
230 available_space,
231 run_mode,
232 )
233 }
234}
235
236/// Compute a preliminary size for an item
237fn compute_preliminary(
238 tree: &mut impl LayoutTree,
239 node: Node,
240 known_dimensions: Size<Option<f32>>,
241 parent_size: Size<Option<f32>>,
242 available_space: Size<AvailableSpace>,
243 run_mode: RunMode,
244) -> SizeAndBaselines {
245 // Define some general constants we will need for the remainder of the algorithm.
246 let mut constants = compute_constants(tree.style(node), known_dimensions, parent_size);
247
248 // 9. Flex Layout Algorithm
249
250 // 9.1. Initial Setup
251
252 // 1. Generate anonymous flex items as described in §4 Flex Items.
253 #[cfg(feature = "debug")]
254 NODE_LOGGER.log("generate_anonymous_flex_items");
255 let mut flex_items = generate_anonymous_flex_items(tree, node, &constants);
256
257 // 9.2. Line Length Determination
258
259 // 2. Determine the available main and cross space for the flex items
260 #[cfg(feature = "debug")]
261 NODE_LOGGER.log("determine_available_space");
262 let available_space = determine_available_space(known_dimensions, available_space, &constants);
263
264 // 3. Determine the flex base size and hypothetical main size of each item.
265 #[cfg(feature = "debug")]
266 NODE_LOGGER.log("determine_flex_base_size");
267 determine_flex_base_size(tree, &constants, available_space, &mut flex_items);
268
269 #[cfg(feature = "debug")]
270 for item in flex_items.iter() {
271 NODE_LOGGER.labelled_log("item.flex_basis", item.flex_basis);
272 NODE_LOGGER.labelled_log("item.inner_flex_basis", item.inner_flex_basis);
273 NODE_LOGGER.labelled_debug_log("item.hypothetical_outer_size", item.hypothetical_outer_size);
274 NODE_LOGGER.labelled_debug_log("item.hypothetical_inner_size", item.hypothetical_inner_size);
275 NODE_LOGGER.labelled_debug_log("item.resolved_minimum_size", item.resolved_minimum_size);
276 }
277
278 // 4. Determine the main size of the flex container
279 // This has already been done as part of compute_constants. The inner size is exposed as constants.node_inner_size.
280
281 // 9.3. Main Size Determination
282
283 // 5. Collect flex items into flex lines.
284 #[cfg(feature = "debug")]
285 NODE_LOGGER.log("collect_flex_lines");
286 let mut flex_lines = collect_flex_lines(tree, node, &constants, available_space, &mut flex_items);
287
288 // If container size is undefined, determine the container's main size
289 // and then re-resolve gaps based on newly determined size
290 let original_gap = constants.gap;
291 if let Some(inner_main_size) = constants.node_inner_size.main(constants.dir) {
292 let outer_main_size = inner_main_size + constants.padding_border.main_axis_sum(constants.dir);
293 constants.inner_container_size.set_main(constants.dir, inner_main_size);
294 constants.container_size.set_main(constants.dir, outer_main_size);
295 } else {
296 // Sets constants.container_size and constants.outer_container_size
297 determine_container_main_size(tree, available_space.main(constants.dir), &mut flex_lines, &mut constants);
298 constants.node_inner_size.set_main(constants.dir, Some(constants.inner_container_size.main(constants.dir)));
299 constants.node_outer_size.set_main(constants.dir, Some(constants.container_size.main(constants.dir)));
300
301 // Re-resolve percentage gaps
302 let style = tree.style(node);
303 let inner_container_size = constants.inner_container_size.main(constants.dir);
304 let new_gap = style.gap.main(constants.dir).maybe_resolve(inner_container_size).unwrap_or(0.0);
305 constants.gap.set_main(constants.dir, new_gap);
306 }
307
308 // 6. Resolve the flexible lengths of all the flex items to find their used main size.
309 #[cfg(feature = "debug")]
310 NODE_LOGGER.log("resolve_flexible_lengths");
311 for line in &mut flex_lines {
312 resolve_flexible_lengths(tree, line, &constants, original_gap);
313 }
314
315 // 9.4. Cross Size Determination
316
317 // 7. Determine the hypothetical cross size of each item.
318 #[cfg(feature = "debug")]
319 NODE_LOGGER.log("determine_hypothetical_cross_size");
320 for line in &mut flex_lines {
321 determine_hypothetical_cross_size(tree, line, &constants, available_space);
322 }
323
324 // Calculate child baselines. This function is internally smart and only computes child baselines
325 // if they are necessary.
326 #[cfg(feature = "debug")]
327 NODE_LOGGER.log("calculate_children_base_lines");
328 calculate_children_base_lines(tree, known_dimensions, available_space, &mut flex_lines, &constants);
329
330 // 8. Calculate the cross size of each flex line.
331 #[cfg(feature = "debug")]
332 NODE_LOGGER.log("calculate_cross_size");
333 calculate_cross_size(tree, &mut flex_lines, known_dimensions, &constants);
334
335 // 9. Handle 'align-content: stretch'.
336 #[cfg(feature = "debug")]
337 NODE_LOGGER.log("handle_align_content_stretch");
338 handle_align_content_stretch(&mut flex_lines, known_dimensions, &constants);
339
340 // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
341 // note the cross size of the line they’re in as the item’s strut size, and restart
342 // layout from the beginning.
343 //
344 // In this second layout round, when collecting items into lines, treat the collapsed
345 // items as having zero main size. For the rest of the algorithm following that step,
346 // ignore the collapsed items entirely (as if they were display:none) except that after
347 // calculating the cross size of the lines, if any line’s cross size is less than the
348 // largest strut size among all the collapsed items in the line, set its cross size to
349 // that strut size.
350 //
351 // Skip this step in the second layout round.
352
353 // TODO implement once (if ever) we support visibility:collapse
354
355 // 11. Determine the used cross size of each flex item.
356 #[cfg(feature = "debug")]
357 NODE_LOGGER.log("determine_used_cross_size");
358 determine_used_cross_size(tree, &mut flex_lines, &constants);
359
360 // 9.5. Main-Axis Alignment
361
362 // 12. Distribute any remaining free space.
363 #[cfg(feature = "debug")]
364 NODE_LOGGER.log("distribute_remaining_free_space");
365 distribute_remaining_free_space(tree, &mut flex_lines, node, &constants);
366
367 // 9.6. Cross-Axis Alignment
368
369 // 13. Resolve cross-axis auto margins (also includes 14).
370 #[cfg(feature = "debug")]
371 NODE_LOGGER.log("resolve_cross_axis_auto_margins");
372 resolve_cross_axis_auto_margins(tree, &mut flex_lines, &constants);
373
374 // 15. Determine the flex container’s used cross size.
375 #[cfg(feature = "debug")]
376 NODE_LOGGER.log("determine_container_cross_size");
377 let total_line_cross_size = determine_container_cross_size(&mut flex_lines, known_dimensions, &mut constants);
378
379 // We have the container size.
380 // If our caller does not care about performing layout we are done now.
381 if run_mode == RunMode::ComputeSize {
382 return constants.container_size.into();
383 }
384
385 // 16. Align all flex lines per align-content.
386 #[cfg(feature = "debug")]
387 NODE_LOGGER.log("align_flex_lines_per_align_content");
388 align_flex_lines_per_align_content(tree, &mut flex_lines, node, &constants, total_line_cross_size);
389
390 // Do a final layout pass and gather the resulting layouts
391 #[cfg(feature = "debug")]
392 NODE_LOGGER.log("final_layout_pass");
393 final_layout_pass(tree, node, &mut flex_lines, &constants);
394
395 // Before returning we perform absolute layout on all absolutely positioned children
396 #[cfg(feature = "debug")]
397 NODE_LOGGER.log("perform_absolute_layout_on_absolute_children");
398 perform_absolute_layout_on_absolute_children(tree, node, &constants);
399
400 #[cfg(feature = "debug")]
401 NODE_LOGGER.log("hidden_layout");
402 let len = tree.child_count(node);
403 for order in 0..len {
404 let child = tree.child(node, order);
405 if tree.style(child).display == Display::None {
406 *tree.layout_mut(node) = Layout::with_order(order as u32);
407 GenericAlgorithm::measure_size(
408 tree,
409 child,
410 Size::NONE,
411 Size::NONE,
412 Size::MAX_CONTENT,
413 SizingMode::InherentSize,
414 );
415 }
416 }
417
418 // 8.5. Flex Container Baselines: calculate the flex container's first baseline
419 // See https://www.w3.org/TR/css-flexbox-1/#flex-baselines
420 let first_vertical_baseline = if flex_lines.is_empty() {
421 None
422 } else {
423 flex_lines[0]
424 .items
425 .iter()
426 .find(|item| constants.is_column || item.align_self == AlignSelf::Baseline)
427 .or_else(|| flex_lines[0].items.iter().next())
428 .map(|child| {
429 let offset_vertical = if constants.is_row { child.offset_cross } else { child.offset_main };
430 offset_vertical + child.baseline
431 })
432 };
433
434 SizeAndBaselines { size: constants.container_size, first_baselines: Point { x: None, y: first_vertical_baseline } }
435}
436
437/// Compute constants that can be reused during the flexbox algorithm.
438#[inline]
439fn compute_constants(
440 style: &Style,
441 known_dimensions: Size<Option<f32>>,
442 parent_size: Size<Option<f32>>,
443) -> AlgoConstants {
444 let dir = style.flex_direction;
445 let is_row = dir.is_row();
446 let is_column = dir.is_column();
447 let is_wrap = matches!(style.flex_wrap, FlexWrap::Wrap | FlexWrap::WrapReverse);
448 let is_wrap_reverse = style.flex_wrap == FlexWrap::WrapReverse;
449
450 let margin = style.margin.resolve_or_zero(parent_size.width);
451 let padding = style.padding.resolve_or_zero(parent_size.width);
452 let border = style.border.resolve_or_zero(parent_size.width);
453 let align_items = style.align_items.unwrap_or(crate::style::AlignItems::Stretch);
454 let align_content = style.align_content.unwrap_or(crate::style::AlignContent::Stretch);
455
456 let padding_border = padding + border;
457
458 let node_outer_size = known_dimensions;
459 let node_inner_size = Size {
460 width: node_outer_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
461 height: node_outer_size.height.maybe_sub(padding_border.vertical_axis_sum()),
462 };
463 let gap = style.gap.resolve_or_zero(node_inner_size.or(Size::zero()));
464
465 let container_size = Size::zero();
466 let inner_container_size = Size::zero();
467
468 AlgoConstants {
469 dir,
470 is_row,
471 is_column,
472 is_wrap,
473 is_wrap_reverse,
474 margin,
475 border,
476 gap,
477 padding_border,
478 align_items,
479 align_content,
480 node_outer_size,
481 node_inner_size,
482 container_size,
483 inner_container_size,
484 }
485}
486
487/// Generate anonymous flex items.
488///
489/// # [9.1. Initial Setup](https://www.w3.org/TR/css-flexbox-1/#box-manip)
490///
491/// - [**Generate anonymous flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-anon-box) as described in [§4 Flex Items](https://www.w3.org/TR/css-flexbox-1/#flex-items).
492#[inline]
493fn generate_anonymous_flex_items(tree: &impl LayoutTree, node: Node, constants: &AlgoConstants) -> Vec<FlexItem> {
494 tree.children(node)
495 .map(|child| (child, tree.style(*child)))
496 .filter(|(_, style)| style.position != Position::Absolute)
497 .filter(|(_, style)| style.display != Display::None)
498 .map(|(child, child_style)| {
499 let aspect_ratio = child_style.aspect_ratio;
500 FlexItem {
501 node: *child,
502 size: child_style.size.maybe_resolve(constants.node_inner_size).maybe_apply_aspect_ratio(aspect_ratio),
503 min_size: child_style
504 .min_size
505 .maybe_resolve(constants.node_inner_size)
506 .maybe_apply_aspect_ratio(aspect_ratio),
507 max_size: child_style
508 .max_size
509 .maybe_resolve(constants.node_inner_size)
510 .maybe_apply_aspect_ratio(aspect_ratio),
511
512 inset: child_style.inset.zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
513 margin: child_style.margin.resolve_or_zero(constants.node_inner_size.width),
514 padding: child_style.padding.resolve_or_zero(constants.node_inner_size.width),
515 border: child_style.border.resolve_or_zero(constants.node_inner_size.width),
516 align_self: child_style.align_self.unwrap_or(constants.align_items),
517 flex_grow: child_style.flex_grow,
518 flex_shrink: child_style.flex_shrink,
519 flex_basis: 0.0,
520 inner_flex_basis: 0.0,
521 violation: 0.0,
522 frozen: false,
523
524 resolved_minimum_size: Size::zero(),
525 hypothetical_inner_size: Size::zero(),
526 hypothetical_outer_size: Size::zero(),
527 target_size: Size::zero(),
528 outer_target_size: Size::zero(),
529 content_flex_fraction: 0.0,
530
531 baseline: 0.0,
532
533 offset_main: 0.0,
534 offset_cross: 0.0,
535 }
536 })
537 .collect()
538}
539
540/// Determine the available main and cross space for the flex items.
541///
542/// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
543///
544/// - [**Determine the available main and cross space for the flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-available).
545/// For each dimension, if that dimension of the flex container’s content box is a definite size, use that;
546/// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint;
547/// otherwise, subtract the flex container’s margin, border, and padding from the space available to the flex container in that dimension and use that value.
548/// **This might result in an infinite value**.
549#[inline]
550#[must_use]
551fn determine_available_space(
552 known_dimensions: Size<Option<f32>>,
553 outer_available_space: Size<AvailableSpace>,
554 constants: &AlgoConstants,
555) -> Size<AvailableSpace> {
556 // Note: min/max/preferred size styles have already been applied to known_dimensions in the `compute` function above
557 let width = match known_dimensions.width {
558 Some(node_width) => AvailableSpace::Definite(node_width - constants.padding_border.horizontal_axis_sum()),
559 None => outer_available_space
560 .width
561 .maybe_sub(constants.margin.horizontal_axis_sum())
562 .maybe_sub(constants.padding_border.horizontal_axis_sum()),
563 };
564
565 let height = match known_dimensions.height {
566 Some(node_height) => AvailableSpace::Definite(node_height - constants.padding_border.vertical_axis_sum()),
567 None => outer_available_space
568 .height
569 .maybe_sub(constants.margin.vertical_axis_sum())
570 .maybe_sub(constants.padding_border.vertical_axis_sum()),
571 };
572
573 Size { width, height }
574}
575
576/// Determine the flex base size and hypothetical main size of each item.
577///
578/// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
579///
580/// - [**Determine the flex base size and hypothetical main size of each item:**](https://www.w3.org/TR/css-flexbox-1/#algo-main-item)
581///
582/// - A. If the item has a definite used flex basis, that’s the flex base size.
583///
584/// - B. If the flex item has ...
585///
586/// - an intrinsic aspect ratio,
587/// - a used flex basis of content, and
588/// - a definite cross size,
589///
590/// then the flex base size is calculated from its inner cross size and the flex item’s intrinsic aspect ratio.
591///
592/// - C. If the used flex basis is content or depends on its available space, and the flex container is being sized under a min-content
593/// or max-content constraint (e.g. when performing automatic table layout \[CSS21\]), size the item under that constraint.
594/// The flex base size is the item’s resulting main size.
595///
596/// - E. Otherwise, size the item into the available space using its used flex basis in place of its main size, treating a value of content as max-content.
597/// If a cross size is needed to determine the main size (e.g. when the flex item’s main size is in its block axis) and the flex item’s cross size is auto and not definite,
598/// in this calculation use fit-content as the flex item’s cross size. The flex base size is the item’s resulting main size.
599///
600/// When determining the flex base size, the item’s min and max main sizes are ignored (no clamping occurs).
601/// Furthermore, the sizing calculations that floor the content box size at zero when applying box-sizing are also ignored.
602/// (For example, an item with a specified size of zero, positive padding, and box-sizing: border-box will have an outer flex base size of zero—and hence a negative inner flex base size.)
603#[inline]
604fn determine_flex_base_size(
605 tree: &mut impl LayoutTree,
606 constants: &AlgoConstants,
607 available_space: Size<AvailableSpace>,
608 flex_items: &mut Vec<FlexItem>,
609) {
610 for child in flex_items.iter_mut() {
611 let child_style = tree.style(child.node);
612
613 // A. If the item has a definite used flex basis, that’s the flex base size.
614
615 // B. If the flex item has an intrinsic aspect ratio,
616 // a used flex basis of content, and a definite cross size,
617 // then the flex base size is calculated from its inner
618 // cross size and the flex item’s intrinsic aspect ratio.
619
620 // Note: `child.size` has already been resolved against aspect_ratio in generate_anonymous_flex_items
621 // So B will just work here by using main_size without special handling for aspect_ratio
622
623 let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(constants.dir));
624 let main_size = child.size.main(constants.dir);
625 if let Some(flex_basis) = flex_basis.or(main_size) {
626 child.flex_basis = flex_basis;
627 continue;
628 };
629
630 // C. If the used flex basis is content or depends on its available space,
631 // and the flex container is being sized under a min-content or max-content
632 // constraint (e.g. when performing automatic table layout [CSS21]),
633 // size the item under that constraint. The flex base size is the item’s
634 // resulting main size.
635
636 // This is covered by the implementation of E below, which passes the available_space constraint
637 // through to the child size computation. It may need a separate implementation if/when D is implemented.
638
639 // D. Otherwise, if the used flex basis is content or depends on its
640 // available space, the available main size is infinite, and the flex item’s
641 // inline axis is parallel to the main axis, lay the item out using the rules
642 // for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
643 // is the item’s max-content main size.
644
645 // TODO if/when vertical writing modes are supported
646
647 // E. Otherwise, size the item into the available space using its used flex basis
648 // in place of its main size, treating a value of content as max-content.
649 // If a cross size is needed to determine the main size (e.g. when the
650 // flex item’s main size is in its block axis) and the flex item’s cross size
651 // is auto and not definite, in this calculation use fit-content as the
652 // flex item’s cross size. The flex base size is the item’s resulting main size.
653
654 let child_known_dimensions = {
655 let mut ckd = child.size;
656 if child.align_self == AlignSelf::Stretch && ckd.cross(constants.dir).is_none() {
657 ckd.set_cross(
658 constants.dir,
659 available_space
660 .cross(constants.dir)
661 .into_option()
662 .maybe_sub(constants.margin.cross_axis_sum(constants.dir)),
663 );
664 }
665 ckd
666 };
667
668 child.flex_basis = GenericAlgorithm::measure_size(
669 tree,
670 child.node,
671 child_known_dimensions,
672 constants.node_inner_size,
673 available_space,
674 SizingMode::ContentSize,
675 )
676 .main(constants.dir);
677 }
678
679 // The hypothetical main size is the item’s flex base size clamped according to its
680 // used min and max main sizes (and flooring the content box size at zero).
681
682 for child in flex_items {
683 child.inner_flex_basis =
684 child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
685
686 let hypothetical_inner_min_main = child.min_size.main(constants.dir);
687 child.hypothetical_inner_size.set_main(
688 constants.dir,
689 child.flex_basis.maybe_clamp(hypothetical_inner_min_main, child.max_size.main(constants.dir)),
690 );
691 child.hypothetical_outer_size.set_main(
692 constants.dir,
693 child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
694 );
695
696 let min_content_size = GenericAlgorithm::measure_size(
697 tree,
698 child.node,
699 Size::NONE,
700 constants.node_inner_size,
701 Size::MIN_CONTENT,
702 SizingMode::ContentSize,
703 );
704
705 // 4.5. Automatic Minimum Size of Flex Items
706 // https://www.w3.org/TR/css-flexbox-1/#min-size-auto
707 let clamped_min_content_size = min_content_size.maybe_min(child.size).maybe_min(child.max_size);
708 child.resolved_minimum_size = child.min_size.unwrap_or(clamped_min_content_size);
709 }
710}
711
712/// Collect flex items into flex lines.
713///
714/// # [9.3. Main Size Determination](https://www.w3.org/TR/css-flexbox-1/#main-sizing)
715///
716/// - [**Collect flex items into flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-break):
717///
718/// - If the flex container is single-line, collect all the flex items into a single flex line.
719///
720/// - Otherwise, starting from the first uncollected item, collect consecutive items one by one until the first time that the next collected item would not fit into the flex container’s inner main size
721/// (or until a forced break is encountered, see [§10 Fragmenting Flex Layout](https://www.w3.org/TR/css-flexbox-1/#pagination)).
722/// If the very first uncollected item wouldn't fit, collect just it into the line.
723///
724/// For this step, the size of a flex item is its outer hypothetical main size. (**Note: This can be negative**.)
725///
726/// Repeat until all flex items have been collected into flex lines.
727///
728/// **Note that the "collect as many" line will collect zero-sized flex items onto the end of the previous line even if the last non-zero item exactly "filled up" the line**.
729#[inline]
730fn collect_flex_lines<'a>(
731 tree: &impl LayoutTree,
732 node: Node,
733 constants: &AlgoConstants,
734 available_space: Size<AvailableSpace>,
735 flex_items: &'a mut Vec<FlexItem>,
736) -> Vec<FlexLine<'a>> {
737 if tree.style(node).flex_wrap == FlexWrap::NoWrap {
738 vec![FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 }]
739 } else {
740 match available_space.main(constants.dir) {
741 // If we're sizing under a max-content constraint then the flex items will never wrap
742 // (at least for now - future extensions to the CSS spec may add provisions for forced wrap points)
743 AvailableSpace::MaxContent => {
744 vec![FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 }]
745 }
746 // If flex-wrap is Wrap and we're sizing under a min-content constraint, then we take every possible wrapping opportunity
747 // and place each item in it's own line
748 AvailableSpace::MinContent => {
749 let mut lines = crate::sys::new_vec_with_capacity(flex_items.len());
750 let mut items = &mut flex_items[..];
751 while !items.is_empty() {
752 let (line_items, rest) = items.split_at_mut(1);
753 lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
754 items = rest;
755 }
756 lines
757 }
758 AvailableSpace::Definite(main_axis_available_space) => {
759 let mut lines = crate::sys::new_vec_with_capacity(1);
760 let mut flex_items = &mut flex_items[..];
761 let main_axis_gap = constants.gap.main(constants.dir);
762
763 while !flex_items.is_empty() {
764 // Find index of the first item in the next line
765 // (or the last item if all remaining items are in the current line)
766 let mut line_length = 0.0;
767 let index = flex_items
768 .iter()
769 .enumerate()
770 .find(|&(idx, child)| {
771 // Gaps only occur between items (not before the first one or after the last one)
772 // So first item in the line does not contribute a gap to the line length
773 let gap_contribution = if idx == 0 { 0.0 } else { main_axis_gap };
774 line_length += child.hypothetical_outer_size.main(constants.dir) + gap_contribution;
775 line_length > main_axis_available_space && idx != 0
776 })
777 .map(|(idx, _)| idx)
778 .unwrap_or(flex_items.len());
779
780 let (items, rest) = flex_items.split_at_mut(index);
781 lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
782 flex_items = rest;
783 }
784 lines
785 }
786 }
787 }
788}
789
790/// Determine the container's main size (if not already known)
791fn determine_container_main_size(
792 tree: &mut impl LayoutTree,
793 main_axis_available_space: AvailableSpace,
794 lines: &mut Vec<FlexLine<'_>>,
795 constants: &mut AlgoConstants,
796) {
797 let outer_main_size: f32 = constants.node_outer_size.main(constants.dir).unwrap_or_else(|| {
798 match main_axis_available_space {
799 AvailableSpace::Definite(main_axis_available_space) => {
800 let longest_line_length: f32 = lines
801 .iter()
802 .map(|line| {
803 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
804 let total_target_size = line
805 .items
806 .iter()
807 .map(|child| child.flex_basis + child.margin.main_axis_sum(constants.dir))
808 .sum::<f32>();
809 total_target_size + line_main_axis_gap
810 })
811 .max_by(|a, b| a.total_cmp(b))
812 .unwrap_or(0.0);
813 let size = longest_line_length + constants.padding_border.main_axis_sum(constants.dir);
814 if lines.len() > 1 {
815 f32_max(size, main_axis_available_space)
816 } else {
817 size
818 }
819 }
820 AvailableSpace::MinContent if constants.is_wrap => {
821 let longest_line_length: f32 = lines
822 .iter()
823 .map(|line| {
824 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
825 let total_target_size = line
826 .items
827 .iter()
828 .map(|child| child.flex_basis + child.margin.main_axis_sum(constants.dir))
829 .sum::<f32>();
830 total_target_size + line_main_axis_gap
831 })
832 .max_by(|a, b| a.total_cmp(b))
833 .unwrap_or(0.0);
834 longest_line_length + constants.padding_border.main_axis_sum(constants.dir)
835 }
836 AvailableSpace::MinContent | AvailableSpace::MaxContent => {
837 // Define a base main_size variable. This is mutated once for iteration over the outer
838 // loop over the flex lines as:
839 // "The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line."
840 let mut main_size = 0.0;
841
842 for line in lines.iter_mut() {
843 for item in line.items.iter_mut() {
844 let style_min = item.min_size.main(constants.dir);
845 let style_preferred = item.size.main(constants.dir);
846 let style_max = item.max_size.main(constants.dir);
847 let flex_basis_min = Some(item.flex_basis).filter(|_| item.flex_shrink == 0.0);
848 let flex_basis_max = Some(item.flex_basis).filter(|_| item.flex_grow == 0.0);
849 let min_main_size = style_min.maybe_max(flex_basis_min).or(flex_basis_min);
850 let max_main_size = style_max.maybe_min(flex_basis_max).or(flex_basis_max);
851
852 let content_contribution = match (min_main_size, style_preferred, max_main_size) {
853 // If the clamping values are such that max <= min, then we can avoid the expensive step of computing the content size
854 // as we know that the clamping values will override it anyway
855 (Some(min), Some(pref), Some(max)) if max <= min || max <= pref => {
856 pref.min(max).max(min) + item.margin.main_axis_sum(constants.dir)
857 }
858 (Some(min), _, Some(max)) if max <= min => min + item.margin.main_axis_sum(constants.dir),
859 (_, Some(pref), Some(max)) if max <= pref => max + item.margin.main_axis_sum(constants.dir),
860 // Else compute the min- or -max content size and apply the full formula for computing the
861 // min- or max- content contributuon
862 _ => {
863 // Either the min- or max- content size depending on which constraint we are sizing under.
864 let content_main_size = GenericAlgorithm::measure_size(
865 tree,
866 item.node,
867 Size::NONE,
868 constants.node_inner_size,
869 Size { width: main_axis_available_space, height: main_axis_available_space },
870 SizingMode::InherentSize,
871 )
872 .main(constants.dir)
873 + item.margin.main_axis_sum(constants.dir);
874
875 // This is somewhat bizarre in that it's asymetrical depending whether the flex container is a column or a row.
876 //
877 // I *think* this might relate to https://drafts.csswg.org/css-flexbox-1/#algo-main-container:
878 //
879 // "The automatic block size of a block-level flex container is its max-content size."
880 //
881 // Which could suggest that flex-basis defining a vertical size does not shrink because it is in the block axis, and the automatic size
882 // in the block axis is a MAX content size. Whereas a flex-basis defining a horizontal size does shrink because the automatic size in
883 // inline axis is MIN content size (although I don't have a reference for that).
884 //
885 // Ultimately, this was not found by reading the spec, but by trial and error fixing tests to align with Webkit/Firefox output.
886 // (see the `flex_basis_unconstraint_row` and `flex_basis_uncontraint_column` generated tests which demonstrate this)
887 if constants.is_row {
888 content_main_size.maybe_clamp(style_min, style_max)
889 } else {
890 content_main_size.max(item.flex_basis).maybe_clamp(style_min, style_max)
891 }
892 }
893 };
894 item.content_flex_fraction = {
895 let diff = content_contribution - item.flex_basis;
896 if diff > 0.0 {
897 diff / f32_max(1.0, item.flex_grow)
898 } else if diff < 0.0 {
899 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
900 // let scaled_shrink_factor - f32_max(1.0, item.flex_shrink * item.inner_flex_basis);
901 diff / scaled_shrink_factor
902 } else {
903 // We are assuming that diff is 0.0 here and that we haven't accidentally introduced a NaN
904 0.0
905 }
906 };
907 }
908
909 // TODO Spec says to scale everything by the line's max flex fraction. But neither Chrome nor firefox implement this
910 // so we don't either. But if we did want to, we'd need this computation here (and to use it below):
911 //
912 // Within each line, find the largest max-content flex fraction among all the flex items.
913 // let line_flex_fraction = line
914 // .items
915 // .iter()
916 // .map(|item| item.content_flex_fraction)
917 // .max_by(|a, b| a.total_cmp(b))
918 // .unwrap_or(0.0); // Unwrap case never gets hit because there is always at least one item a line
919
920 // Add each item’s flex base size to the product of:
921 // - its flex grow factor (or scaled flex shrink factor,if the chosen max-content flex fraction was negative)
922 // - the chosen max-content flex fraction
923 // then clamp that result by the max main size floored by the min main size.
924 //
925 // The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line.
926 let item_main_size_sum = line
927 .items
928 .iter_mut()
929 .map(|item| {
930 let flex_fraction = item.content_flex_fraction;
931 // let flex_fraction = line_flex_fraction;
932
933 let flex_contribution = if item.content_flex_fraction > 0.0 {
934 f32_max(1.0, item.flex_grow) * flex_fraction
935 } else if item.content_flex_fraction < 0.0 {
936 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
937 scaled_shrink_factor * flex_fraction
938 } else {
939 0.0
940 };
941 let size = item.flex_basis + flex_contribution;
942 item.outer_target_size.set_main(constants.dir, size);
943 item.target_size.set_main(constants.dir, size);
944 size
945 })
946 .sum::<f32>();
947
948 let gap_sum = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
949 main_size = f32_max(main_size, item_main_size_sum + gap_sum)
950 }
951
952 main_size + constants.padding_border.main_axis_sum(constants.dir)
953 }
954 }
955 });
956 // let outer_main_size = inner_main_size + constants.padding_border.main_axis_sum(constants.dir);
957 let inner_main_size = outer_main_size - constants.padding_border.main_axis_sum(constants.dir);
958 constants.container_size.set_main(constants.dir, outer_main_size);
959 constants.inner_container_size.set_main(constants.dir, inner_main_size);
960 constants.node_inner_size.set_main(constants.dir, Some(inner_main_size));
961}
962
963/// Resolve the flexible lengths of the items within a flex line.
964/// Sets the `main` component of each item's `target_size` and `outer_target_size`
965///
966/// # [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
967#[inline]
968fn resolve_flexible_lengths(
969 tree: &mut impl LayoutTree,
970 line: &mut FlexLine,
971 constants: &AlgoConstants,
972 original_gap: Size<f32>,
973) {
974 let total_original_main_axis_gap = sum_axis_gaps(original_gap.main(constants.dir), line.items.len());
975 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
976
977 // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
978 // items on the line. If the sum is less than the flex container’s inner main size,
979 // use the flex grow factor for the rest of this algorithm; otherwise, use the
980 // flex shrink factor.
981
982 let total_hypothetical_outer_main_size =
983 line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum::<f32>();
984 let used_flex_factor: f32 = total_original_main_axis_gap + total_hypothetical_outer_main_size;
985 let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
986 let shrinking = !growing;
987
988 // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
989 // - Any item that has a flex factor of zero
990 // - If using the flex grow factor: any item that has a flex base size
991 // greater than its hypothetical main size
992 // - If using the flex shrink factor: any item that has a flex base size
993 // smaller than its hypothetical main size
994
995 for child in line.items.iter_mut() {
996 let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
997 child.target_size.set_main(constants.dir, inner_target_size);
998
999 let child_style = tree.style(child.node);
1000 if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0)
1001 || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
1002 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
1003 {
1004 child.frozen = true;
1005 let outer_target_size = inner_target_size + child.margin.main_axis_sum(constants.dir);
1006 child.outer_target_size.set_main(constants.dir, outer_target_size);
1007 }
1008 }
1009
1010 // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
1011 // and subtract this from the flex container’s inner main size. For frozen items,
1012 // use their outer target main size; for other items, use their outer flex base size.
1013
1014 let used_space: f32 = total_main_axis_gap
1015 + line
1016 .items
1017 .iter()
1018 .map(|child| {
1019 child.margin.main_axis_sum(constants.dir)
1020 + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
1021 })
1022 .sum::<f32>();
1023
1024 let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
1025
1026 // 4. Loop
1027
1028 loop {
1029 // a. Check for flexible items. If all the flex items on the line are frozen,
1030 // free space has been distributed; exit this loop.
1031
1032 if line.items.iter().all(|child| child.frozen) {
1033 break;
1034 }
1035
1036 // b. Calculate the remaining free space as for initial free space, above.
1037 // If the sum of the unfrozen flex items’ flex factors is less than one,
1038 // multiply the initial free space by this sum. If the magnitude of this
1039 // value is less than the magnitude of the remaining free space, use this
1040 // as the remaining free space.
1041
1042 let used_space: f32 = total_main_axis_gap
1043 + line
1044 .items
1045 .iter()
1046 .map(|child| {
1047 child.margin.main_axis_sum(constants.dir)
1048 + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
1049 })
1050 .sum::<f32>();
1051
1052 let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
1053
1054 let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
1055 unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
1056 let style = tree.style(item.node);
1057 (flex_grow + style.flex_grow, flex_shrink + style.flex_shrink)
1058 });
1059
1060 let free_space = if growing && sum_flex_grow < 1.0 {
1061 (initial_free_space * sum_flex_grow - total_main_axis_gap)
1062 .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1063 } else if shrinking && sum_flex_shrink < 1.0 {
1064 (initial_free_space * sum_flex_shrink - total_main_axis_gap)
1065 .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1066 } else {
1067 (constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1068 .unwrap_or(used_flex_factor - used_space)
1069 };
1070
1071 // c. Distribute free space proportional to the flex factors.
1072 // - If the remaining free space is zero
1073 // Do Nothing
1074 // - If using the flex grow factor
1075 // Find the ratio of the item’s flex grow factor to the sum of the
1076 // flex grow factors of all unfrozen items on the line. Set the item’s
1077 // target main size to its flex base size plus a fraction of the remaining
1078 // free space proportional to the ratio.
1079 // - If using the flex shrink factor
1080 // For every unfrozen item on the line, multiply its flex shrink factor by
1081 // its inner flex base size, and note this as its scaled flex shrink factor.
1082 // Find the ratio of the item’s scaled flex shrink factor to the sum of the
1083 // scaled flex shrink factors of all unfrozen items on the line. Set the item’s
1084 // target main size to its flex base size minus a fraction of the absolute value
1085 // of the remaining free space proportional to the ratio. Note this may result
1086 // in a negative inner main size; it will be corrected in the next step.
1087 // - Otherwise
1088 // Do Nothing
1089
1090 if free_space.is_normal() {
1091 if growing && sum_flex_grow > 0.0 {
1092 for child in &mut unfrozen {
1093 child.target_size.set_main(
1094 constants.dir,
1095 child.flex_basis + free_space * (tree.style(child.node).flex_grow / sum_flex_grow),
1096 );
1097 }
1098 } else if shrinking && sum_flex_shrink > 0.0 {
1099 let sum_scaled_shrink_factor: f32 =
1100 unfrozen.iter().map(|child| child.inner_flex_basis * tree.style(child.node).flex_shrink).sum();
1101
1102 if sum_scaled_shrink_factor > 0.0 {
1103 for child in &mut unfrozen {
1104 let scaled_shrink_factor = child.inner_flex_basis * tree.style(child.node).flex_shrink;
1105 child.target_size.set_main(
1106 constants.dir,
1107 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
1108 )
1109 }
1110 }
1111 }
1112 }
1113
1114 // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
1115 // used min and max main sizes and floor its content-box size at zero. If the
1116 // item’s target main size was made smaller by this, it’s a max violation.
1117 // If the item’s target main size was made larger by this, it’s a min violation.
1118
1119 let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
1120 let resolved_min_main: Option<f32> = child.resolved_minimum_size.main(constants.dir).into();
1121 let max_main = child.max_size.main(constants.dir);
1122 let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
1123 child.violation = clamped - child.target_size.main(constants.dir);
1124 child.target_size.set_main(constants.dir, clamped);
1125 child.outer_target_size.set_main(
1126 constants.dir,
1127 child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
1128 );
1129
1130 acc + child.violation
1131 });
1132
1133 // e. Freeze over-flexed items. The total violation is the sum of the adjustments
1134 // from the previous step ∑(clamped size - unclamped size). If the total violation is:
1135 // - Zero
1136 // Freeze all items.
1137 // - Positive
1138 // Freeze all the items with min violations.
1139 // - Negative
1140 // Freeze all the items with max violations.
1141
1142 for child in &mut unfrozen {
1143 match total_violation {
1144 v if v > 0.0 => child.frozen = child.violation > 0.0,
1145 v if v < 0.0 => child.frozen = child.violation < 0.0,
1146 _ => child.frozen = true,
1147 }
1148 }
1149
1150 // f. Return to the start of this loop.
1151 }
1152}
1153
1154/// Determine the hypothetical cross size of each item.
1155///
1156/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1157///
1158/// - [**Determine the hypothetical cross size of each item**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-item)
1159/// by performing layout with the used main size and the available space, treating auto as fit-content.
1160#[inline]
1161fn determine_hypothetical_cross_size(
1162 tree: &mut impl LayoutTree,
1163 line: &mut FlexLine,
1164 constants: &AlgoConstants,
1165 available_space: Size<AvailableSpace>,
1166) {
1167 for child in line.items.iter_mut() {
1168 let child_cross = child
1169 .size
1170 .cross(constants.dir)
1171 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir));
1172
1173 child.hypothetical_inner_size.set_cross(
1174 constants.dir,
1175 GenericAlgorithm::measure_size(
1176 tree,
1177 child.node,
1178 Size {
1179 width: if constants.is_row { child.target_size.width.into() } else { child_cross },
1180 height: if constants.is_row { child_cross } else { child.target_size.height.into() },
1181 },
1182 constants.node_inner_size,
1183 Size {
1184 width: if constants.is_row {
1185 constants.container_size.main(constants.dir).into()
1186 } else {
1187 available_space.width
1188 },
1189 height: if constants.is_row {
1190 available_space.height
1191 } else {
1192 constants.container_size.main(constants.dir).into()
1193 },
1194 },
1195 SizingMode::ContentSize,
1196 )
1197 .cross(constants.dir)
1198 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir)),
1199 );
1200
1201 child.hypothetical_outer_size.set_cross(
1202 constants.dir,
1203 child.hypothetical_inner_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1204 );
1205 }
1206}
1207
1208/// Calculate the base lines of the children.
1209#[inline]
1210fn calculate_children_base_lines(
1211 tree: &mut impl LayoutTree,
1212 node_size: Size<Option<f32>>,
1213 available_space: Size<AvailableSpace>,
1214 flex_lines: &mut [FlexLine],
1215 constants: &AlgoConstants,
1216) {
1217 // Only compute baselines for flex rows because we only support baseline alignment in the cross axis
1218 // where that axis is also the inline axis
1219 // TODO: this may need revisiting if/when we support vertical writing modes
1220 if !constants.is_row {
1221 return;
1222 }
1223
1224 for line in flex_lines {
1225 // If a flex line has one or zero items participating in baseline alignment then baseline alignment is a no-op so we skip
1226 let line_baseline_child_count =
1227 line.items.iter().filter(|child| child.align_self == AlignSelf::Baseline).count();
1228 if line_baseline_child_count <= 1 {
1229 continue;
1230 }
1231
1232 for child in line.items.iter_mut() {
1233 // Only calculate baselines for children participating in baseline alignment
1234 if child.align_self != AlignSelf::Baseline {
1235 continue;
1236 }
1237
1238 let measured_size_and_baselines = GenericAlgorithm::perform_layout(
1239 tree,
1240 child.node,
1241 Size {
1242 width: if constants.is_row {
1243 child.target_size.width.into()
1244 } else {
1245 child.hypothetical_inner_size.width.into()
1246 },
1247 height: if constants.is_row {
1248 child.hypothetical_inner_size.height.into()
1249 } else {
1250 child.target_size.height.into()
1251 },
1252 },
1253 constants.node_inner_size,
1254 Size {
1255 width: if constants.is_row {
1256 constants.container_size.width.into()
1257 } else {
1258 available_space.width.maybe_set(node_size.width)
1259 },
1260 height: if constants.is_row {
1261 available_space.height.maybe_set(node_size.height)
1262 } else {
1263 constants.container_size.height.into()
1264 },
1265 },
1266 SizingMode::ContentSize,
1267 );
1268
1269 let baseline = measured_size_and_baselines.first_baselines.y;
1270 let height = measured_size_and_baselines.size.height;
1271
1272 child.baseline = baseline.unwrap_or(height) + child.margin.top;
1273 }
1274 }
1275}
1276
1277/// Calculate the cross size of each flex line.
1278///
1279/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1280///
1281/// - [**Calculate the cross size of each flex line**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-line).
1282///
1283/// If the flex container is single-line and has a definite cross size, the cross size of the flex line is the flex container’s inner cross size.
1284///
1285/// Otherwise, for each flex line:
1286///
1287/// 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose align-self is baseline, and whose cross-axis margins are both non-auto.
1288/// Find the largest of the distances between each item’s baseline and its hypothetical outer cross-start edge,
1289/// and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, and sum these two values.
1290///
1291/// 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size.
1292///
1293/// 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero.
1294///
1295/// If the flex container is single-line, then clamp the line’s cross-size to be within the container’s computed min and max cross sizes.
1296/// **Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically**.
1297#[inline]
1298fn calculate_cross_size(
1299 tree: &mut impl LayoutTree,
1300 flex_lines: &mut [FlexLine],
1301 node_size: Size<Option<f32>>,
1302 constants: &AlgoConstants,
1303) {
1304 // Note: AlignContent::SpaceEvenly and AlignContent::SpaceAround behave like AlignContent::Stretch when there is only
1305 // a single flex line in the container. See: https://www.w3.org/TR/css-flexbox-1/#align-content-property
1306 if flex_lines.len() == 1
1307 && node_size.cross(constants.dir).is_some()
1308 && matches!(
1309 constants.align_content,
1310 AlignContent::Stretch | AlignContent::SpaceEvenly | AlignContent::SpaceAround
1311 )
1312 {
1313 flex_lines[0].cross_size =
1314 (node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
1315 .unwrap_or(0.0);
1316 } else {
1317 for line in flex_lines.iter_mut() {
1318 // 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
1319 // align-self is baseline, and whose cross-axis margins are both non-auto. Find the
1320 // largest of the distances between each item’s baseline and its hypothetical outer
1321 // cross-start edge, and the largest of the distances between each item’s baseline
1322 // and its hypothetical outer cross-end edge, and sum these two values.
1323
1324 // 2. Among all the items not collected by the previous step, find the largest
1325 // outer hypothetical cross size.
1326
1327 // 3. The used cross-size of the flex line is the largest of the numbers found in the
1328 // previous two steps and zero.
1329
1330 let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1331 line.cross_size = line
1332 .items
1333 .iter()
1334 .map(|child| {
1335 let child_style = tree.style(child.node);
1336 if child.align_self == AlignSelf::Baseline
1337 && child_style.margin.cross_start(constants.dir) != LengthPercentageAuto::Auto
1338 && child_style.margin.cross_end(constants.dir) != LengthPercentageAuto::Auto
1339 {
1340 max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
1341 } else {
1342 child.hypothetical_outer_size.cross(constants.dir)
1343 }
1344 })
1345 .fold(0.0, |acc, x| acc.max(x));
1346 }
1347 }
1348}
1349
1350/// Handle 'align-content: stretch'.
1351///
1352/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1353///
1354/// - [**Handle 'align-content: stretch'**](https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch). If the flex container has a definite cross size, align-content is stretch,
1355/// and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size,
1356/// increase the cross size of each flex line by equal amounts such that the sum of their cross sizes exactly equals the flex container’s inner cross size.
1357#[inline]
1358fn handle_align_content_stretch(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1359 if constants.align_content == AlignContent::Stretch && node_size.cross(constants.dir).is_some() {
1360 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1361 let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
1362 let inner_cross =
1363 (node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
1364 .unwrap_or(0.0);
1365
1366 if total_cross < inner_cross {
1367 let remaining = inner_cross - total_cross;
1368 let addition = remaining / flex_lines.len() as f32;
1369 flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
1370 }
1371 }
1372}
1373
1374/// Determine the used cross size of each flex item.
1375///
1376/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1377///
1378/// - [**Determine the used cross size of each flex item**](https://www.w3.org/TR/css-flexbox-1/#algo-stretch). If a flex item has align-self: stretch, its computed cross size property is auto,
1379/// and neither of its cross-axis margins are auto, the used outer cross size is the used cross size of its flex line, clamped according to the item’s used min and max cross sizes.
1380/// Otherwise, the used cross size is the item’s hypothetical cross size.
1381///
1382/// If the flex item has align-self: stretch, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved.
1383///
1384/// **Note that this step does not affect the main size of the flex item, even if it has an intrinsic aspect ratio**.
1385#[inline]
1386fn determine_used_cross_size(tree: &mut impl LayoutTree, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1387 for line in flex_lines {
1388 let line_cross_size = line.cross_size;
1389
1390 for child in line.items.iter_mut() {
1391 let child_style = tree.style(child.node);
1392 child.target_size.set_cross(
1393 constants.dir,
1394 if child.align_self == AlignSelf::Stretch
1395 && child_style.margin.cross_start(constants.dir) != LengthPercentageAuto::Auto
1396 && child_style.margin.cross_end(constants.dir) != LengthPercentageAuto::Auto
1397 && child_style.size.cross(constants.dir) == Dimension::Auto
1398 {
1399 // For some reason this particular usage of max_width is an exception to the rule that max_width's transfer
1400 // using the aspect_ratio (if set). Both Chrome and Firefox agree on this. And reading the spec, it seems like
1401 // a reasonable interpretation. Although it seems to me that the spec *should* apply aspect_ratio here.
1402 let max_size_ignoring_aspect_ratio = child_style.max_size.maybe_resolve(constants.node_inner_size);
1403
1404 (line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
1405 child.min_size.cross(constants.dir),
1406 max_size_ignoring_aspect_ratio.cross(constants.dir),
1407 )
1408 } else {
1409 child.hypothetical_inner_size.cross(constants.dir)
1410 },
1411 );
1412
1413 child.outer_target_size.set_cross(
1414 constants.dir,
1415 child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1416 );
1417 }
1418 }
1419}
1420
1421/// Distribute any remaining free space.
1422///
1423/// # [9.5. Main-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#main-alignment)
1424///
1425/// - [**Distribute any remaining free space**](https://www.w3.org/TR/css-flexbox-1/#algo-main-align). For each flex line:
1426///
1427/// 1. If the remaining free space is positive and at least one main-axis margin on this line is `auto`, distribute the free space equally among these margins.
1428/// Otherwise, set all `auto` margins to zero.
1429///
1430/// 2. Align the items along the main-axis per `justify-content`.
1431#[inline]
1432fn distribute_remaining_free_space(
1433 tree: &mut impl LayoutTree,
1434 flex_lines: &mut [FlexLine],
1435 node: Node,
1436 constants: &AlgoConstants,
1437) {
1438 for line in flex_lines {
1439 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1440 let used_space: f32 = total_main_axis_gap
1441 + line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
1442 let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1443 let mut num_auto_margins = 0;
1444
1445 for child in line.items.iter_mut() {
1446 let child_style = tree.style(child.node);
1447 if child_style.margin.main_start(constants.dir) == LengthPercentageAuto::Auto {
1448 num_auto_margins += 1;
1449 }
1450 if child_style.margin.main_end(constants.dir) == LengthPercentageAuto::Auto {
1451 num_auto_margins += 1;
1452 }
1453 }
1454
1455 if free_space > 0.0 && num_auto_margins > 0 {
1456 let margin = free_space / num_auto_margins as f32;
1457
1458 for child in line.items.iter_mut() {
1459 let child_style = tree.style(child.node);
1460 if child_style.margin.main_start(constants.dir) == LengthPercentageAuto::Auto {
1461 if constants.is_row {
1462 child.margin.left = margin;
1463 } else {
1464 child.margin.top = margin;
1465 }
1466 }
1467 if child_style.margin.main_end(constants.dir) == LengthPercentageAuto::Auto {
1468 if constants.is_row {
1469 child.margin.right = margin;
1470 } else {
1471 child.margin.bottom = margin;
1472 }
1473 }
1474 }
1475 } else {
1476 let num_items = line.items.len();
1477 let layout_reverse = constants.dir.is_reverse();
1478 let gap = constants.gap.main(constants.dir);
1479 let justify_content_mode: JustifyContent =
1480 tree.style(node).justify_content.unwrap_or(JustifyContent::FlexStart);
1481
1482 let justify_item = |(i, child): (usize, &mut FlexItem)| {
1483 child.offset_main =
1484 compute_alignment_offset(free_space, num_items, gap, justify_content_mode, layout_reverse, i == 0);
1485 };
1486
1487 if layout_reverse {
1488 line.items.iter_mut().rev().enumerate().for_each(justify_item);
1489 } else {
1490 line.items.iter_mut().enumerate().for_each(justify_item);
1491 }
1492 }
1493 }
1494}
1495
1496/// Resolve cross-axis `auto` margins.
1497///
1498/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1499///
1500/// - [**Resolve cross-axis `auto` margins**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-margins).
1501/// If a flex item has auto cross-axis margins:
1502///
1503/// - If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line,
1504/// distribute the difference in those sizes equally to the auto margins.
1505///
1506/// - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
1507/// Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
1508#[inline]
1509fn resolve_cross_axis_auto_margins(tree: &mut impl LayoutTree, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1510 for line in flex_lines {
1511 let line_cross_size = line.cross_size;
1512 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1513
1514 for child in line.items.iter_mut() {
1515 let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1516 let child_style = tree.style(child.node);
1517
1518 if child_style.margin.cross_start(constants.dir) == LengthPercentageAuto::Auto
1519 && child_style.margin.cross_end(constants.dir) == LengthPercentageAuto::Auto
1520 {
1521 if constants.is_row {
1522 child.margin.top = free_space / 2.0;
1523 child.margin.bottom = free_space / 2.0;
1524 } else {
1525 child.margin.left = free_space / 2.0;
1526 child.margin.right = free_space / 2.0;
1527 }
1528 } else if child_style.margin.cross_start(constants.dir) == LengthPercentageAuto::Auto {
1529 if constants.is_row {
1530 child.margin.top = free_space;
1531 } else {
1532 child.margin.left = free_space;
1533 }
1534 } else if child_style.margin.cross_end(constants.dir) == LengthPercentageAuto::Auto {
1535 if constants.is_row {
1536 child.margin.bottom = free_space;
1537 } else {
1538 child.margin.right = free_space;
1539 }
1540 } else {
1541 // 14. Align all flex items along the cross-axis.
1542 child.offset_cross = align_flex_items_along_cross_axis(child, free_space, max_baseline, constants);
1543 }
1544 }
1545 }
1546}
1547
1548/// Align all flex items along the cross-axis.
1549///
1550/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1551///
1552/// - [**Align all flex items along the cross-axis**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-align) per `align-self`,
1553/// if neither of the item's cross-axis margins are `auto`.
1554#[inline]
1555fn align_flex_items_along_cross_axis(
1556 child: &mut FlexItem,
1557 free_space: f32,
1558 max_baseline: f32,
1559 constants: &AlgoConstants,
1560) -> f32 {
1561 match child.align_self {
1562 AlignSelf::Start => 0.0,
1563 AlignSelf::FlexStart => {
1564 if constants.is_wrap_reverse {
1565 free_space
1566 } else {
1567 0.0
1568 }
1569 }
1570 AlignSelf::End => free_space,
1571 AlignSelf::FlexEnd => {
1572 if constants.is_wrap_reverse {
1573 0.0
1574 } else {
1575 free_space
1576 }
1577 }
1578 AlignSelf::Center => free_space / 2.0,
1579 AlignSelf::Baseline => {
1580 if constants.is_row {
1581 max_baseline - child.baseline
1582 } else {
1583 // Until we support vertical writing modes, baseline alignment only makes sense if
1584 // the constants.direction is row, so we treat it as flex-start alignment in columns.
1585 if constants.is_wrap_reverse {
1586 free_space
1587 } else {
1588 0.0
1589 }
1590 }
1591 }
1592 AlignSelf::Stretch => {
1593 if constants.is_wrap_reverse {
1594 free_space
1595 } else {
1596 0.0
1597 }
1598 }
1599 }
1600}
1601
1602/// Determine the flex container’s used cross size.
1603///
1604/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1605///
1606/// - [**Determine the flex container’s used cross size**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-container):
1607///
1608/// - If the cross size property is a definite size, use that, clamped by the used min and max cross sizes of the flex container.
1609///
1610/// - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used min and max cross sizes of the flex container.
1611#[inline]
1612#[must_use]
1613fn determine_container_cross_size(
1614 flex_lines: &mut [FlexLine],
1615 node_size: Size<Option<f32>>,
1616 constants: &mut AlgoConstants,
1617) -> f32 {
1618 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1619 let total_line_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>();
1620
1621 constants.container_size.set_cross(
1622 constants.dir,
1623 node_size.cross(constants.dir).unwrap_or(
1624 total_line_cross_size + total_cross_axis_gap + constants.padding_border.cross_axis_sum(constants.dir),
1625 ),
1626 );
1627
1628 constants.inner_container_size.set_cross(
1629 constants.dir,
1630 constants.container_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir),
1631 );
1632
1633 total_line_cross_size
1634}
1635
1636/// Align all flex lines per `align-content`.
1637///
1638/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1639///
1640/// - [**Align all flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-align) per `align-content`.
1641#[inline]
1642fn align_flex_lines_per_align_content(
1643 tree: &impl LayoutTree,
1644 flex_lines: &mut [FlexLine],
1645 node: Node,
1646 constants: &AlgoConstants,
1647 total_cross_size: f32,
1648) {
1649 let num_lines = flex_lines.len();
1650 let gap = constants.gap.cross(constants.dir);
1651 let align_content_mode = tree.style(node).align_content.unwrap_or(AlignContent::Stretch);
1652 let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
1653 let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;
1654
1655 let align_line = |(i, line): (usize, &mut FlexLine)| {
1656 line.offset_cross =
1657 compute_alignment_offset(free_space, num_lines, gap, align_content_mode, constants.is_wrap_reverse, i == 0);
1658 };
1659
1660 if constants.is_wrap_reverse {
1661 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1662 } else {
1663 flex_lines.iter_mut().enumerate().for_each(align_line);
1664 }
1665}
1666
1667/// Calculates the layout for a flex-item
1668#[allow(clippy::too_many_arguments)]
1669fn calculate_flex_item(
1670 tree: &mut impl LayoutTree,
1671 node: Node,
1672 item: &mut FlexItem,
1673 total_offset_main: &mut f32,
1674 total_offset_cross: f32,
1675 line_offset_cross: f32,
1676 container_size: Size<f32>,
1677 node_inner_size: Size<Option<f32>>,
1678 direction: FlexDirection,
1679) {
1680 let preliminary_size_and_baselines = GenericAlgorithm::perform_layout(
1681 tree,
1682 item.node,
1683 item.target_size.map(|s| s.into()),
1684 node_inner_size,
1685 container_size.map(|s| s.into()),
1686 SizingMode::ContentSize,
1687 );
1688 let preliminary_size = preliminary_size_and_baselines.size;
1689
1690 let offset_main = *total_offset_main
1691 + item.offset_main
1692 + item.margin.main_start(direction)
1693 + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1694
1695 let offset_cross = total_offset_cross
1696 + item.offset_cross
1697 + line_offset_cross
1698 + item.margin.cross_start(direction)
1699 + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1700
1701 if direction.is_row() {
1702 let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction);
1703 let inner_baseline = preliminary_size_and_baselines.first_baselines.y.unwrap_or(preliminary_size.height);
1704 item.baseline = baseline_offset_cross + inner_baseline;
1705 } else {
1706 let baseline_offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction);
1707 let inner_baseline = preliminary_size_and_baselines.first_baselines.y.unwrap_or(preliminary_size.height);
1708 item.baseline = baseline_offset_main + inner_baseline;
1709 }
1710
1711 let order = tree.children(node).position(|n| *n == item.node).unwrap() as u32;
1712
1713 *tree.layout_mut(item.node) = Layout {
1714 order,
1715 size: preliminary_size_and_baselines.size,
1716 location: Point {
1717 x: if direction.is_row() { offset_main } else { offset_cross },
1718 y: if direction.is_column() { offset_main } else { offset_cross },
1719 },
1720 };
1721
1722 *total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + preliminary_size.main(direction);
1723}
1724
1725/// Calculates the layout line
1726#[allow(clippy::too_many_arguments)]
1727fn calculate_layout_line(
1728 tree: &mut impl LayoutTree,
1729 node: Node,
1730 line: &mut FlexLine,
1731 total_offset_cross: &mut f32,
1732 container_size: Size<f32>,
1733 node_inner_size: Size<Option<f32>>,
1734 padding_border: Rect<f32>,
1735 direction: FlexDirection,
1736) {
1737 let mut total_offset_main = padding_border.main_start(direction);
1738 let line_offset_cross = line.offset_cross;
1739
1740 if direction.is_reverse() {
1741 for item in line.items.iter_mut().rev() {
1742 calculate_flex_item(
1743 tree,
1744 node,
1745 item,
1746 &mut total_offset_main,
1747 *total_offset_cross,
1748 line_offset_cross,
1749 container_size,
1750 node_inner_size,
1751 direction,
1752 );
1753 }
1754 } else {
1755 for item in line.items.iter_mut() {
1756 calculate_flex_item(
1757 tree,
1758 node,
1759 item,
1760 &mut total_offset_main,
1761 *total_offset_cross,
1762 line_offset_cross,
1763 container_size,
1764 node_inner_size,
1765 direction,
1766 );
1767 }
1768 }
1769
1770 *total_offset_cross += line_offset_cross + line.cross_size;
1771}
1772
1773/// Do a final layout pass and collect the resulting layouts.
1774#[inline]
1775fn final_layout_pass(tree: &mut impl LayoutTree, node: Node, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1776 let mut total_offset_cross = constants.padding_border.cross_start(constants.dir);
1777
1778 if constants.is_wrap_reverse {
1779 for line in flex_lines.iter_mut().rev() {
1780 calculate_layout_line(
1781 tree,
1782 node,
1783 line,
1784 &mut total_offset_cross,
1785 constants.container_size,
1786 constants.node_inner_size,
1787 constants.padding_border,
1788 constants.dir,
1789 );
1790 }
1791 } else {
1792 for line in flex_lines.iter_mut() {
1793 calculate_layout_line(
1794 tree,
1795 node,
1796 line,
1797 &mut total_offset_cross,
1798 constants.container_size,
1799 constants.node_inner_size,
1800 constants.padding_border,
1801 constants.dir,
1802 );
1803 }
1804 }
1805}
1806
1807/// Perform absolute layout on all absolutely positioned children.
1808#[inline]
1809fn perform_absolute_layout_on_absolute_children(tree: &mut impl LayoutTree, node: Node, constants: &AlgoConstants) {
1810 let container_width = constants.container_size.width;
1811 let container_height = constants.container_size.height;
1812
1813 for order in 0..tree.child_count(node) {
1814 let child = tree.child(node, order);
1815 let child_style = tree.style(child);
1816
1817 // Skip items that are display:none or are not position:absolute
1818 if child_style.display == Display::None || child_style.position != Position::Absolute {
1819 continue;
1820 }
1821
1822 let aspect_ratio = child_style.aspect_ratio;
1823 let margin = child_style.margin.map(|margin| margin.resolve_to_option(container_width));
1824 let align_self = child_style.align_self.unwrap_or(constants.align_items);
1825
1826 // Resolve inset
1827 let left = child_style.inset.left.maybe_resolve(container_width);
1828 let right = child_style.inset.right.maybe_resolve(container_width);
1829 let top = child_style.inset.top.maybe_resolve(container_height);
1830 let bottom = child_style.inset.bottom.maybe_resolve(container_height);
1831
1832 // Compute known dimensions from min/max/inherent size styles
1833 let style_size =
1834 child_style.size.maybe_resolve(constants.container_size).maybe_apply_aspect_ratio(aspect_ratio);
1835 let min_size =
1836 child_style.min_size.maybe_resolve(constants.container_size).maybe_apply_aspect_ratio(aspect_ratio);
1837 let max_size =
1838 child_style.max_size.maybe_resolve(constants.container_size).maybe_apply_aspect_ratio(aspect_ratio);
1839 let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
1840
1841 // Fill in width from left/right and reapply aspect ratio if:
1842 // - Width is not already known
1843 // - Item has both left and right inset properties set
1844 if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
1845 let new_width_raw = container_width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
1846 known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
1847 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
1848 }
1849
1850 // Fill in height from top/bottom and reapply aspect ratio if:
1851 // - Height is not already known
1852 // - Item has both top and bottom inset properties set
1853 if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
1854 let new_height_raw = container_height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
1855 known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
1856 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
1857 }
1858
1859 let measured_size_and_baselines = GenericAlgorithm::perform_layout(
1860 tree,
1861 child,
1862 known_dimensions,
1863 constants.node_inner_size,
1864 Size {
1865 width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
1866 height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
1867 },
1868 SizingMode::ContentSize,
1869 );
1870 let measured_size = measured_size_and_baselines.size;
1871 let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
1872
1873 let non_auto_margin = margin.map(|m| m.unwrap_or(0.0));
1874
1875 let free_space = Size {
1876 width: constants.container_size.width - final_size.width - non_auto_margin.horizontal_axis_sum(),
1877 height: constants.container_size.height - final_size.height - non_auto_margin.vertical_axis_sum(),
1878 }
1879 .f32_max(Size::ZERO);
1880
1881 // Expand auto margins to fill available space
1882 let resolved_margin = {
1883 let auto_margin_size = Size {
1884 width: {
1885 let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
1886 if auto_margin_count > 0 {
1887 free_space.width / auto_margin_count as f32
1888 } else {
1889 0.0
1890 }
1891 },
1892 height: {
1893 let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
1894 if auto_margin_count > 0 {
1895 free_space.height / auto_margin_count as f32
1896 } else {
1897 0.0
1898 }
1899 },
1900 };
1901
1902 Rect {
1903 left: margin.left.unwrap_or(auto_margin_size.width),
1904 right: margin.right.unwrap_or(auto_margin_size.width),
1905 top: margin.top.unwrap_or(auto_margin_size.height),
1906 bottom: margin.bottom.unwrap_or(auto_margin_size.height),
1907 }
1908 };
1909
1910 // Determine flex-relative insets
1911 let (start_main, end_main) = if constants.is_row { (left, right) } else { (top, bottom) };
1912 let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (left, right) };
1913
1914 // Apply main-axis alignment
1915 // let free_main_space = free_space.main(constants.dir) - resolved_margin.main_axis_sum(constants.dir);
1916 let offset_main = if let Some(start) = start_main {
1917 start + constants.border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
1918 } else if let Some(end) = end_main {
1919 constants.container_size.main(constants.dir)
1920 - constants.border.main_end(constants.dir)
1921 - final_size.main(constants.dir)
1922 - end
1923 - resolved_margin.main_end(constants.dir)
1924 } else {
1925 // Stretch is an invalid value for justify_content in the flexbox algorithm, so we
1926 // treat it as if it wasn't set (and thus we default to FlexStart behaviour)
1927 match (tree.style(node).justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
1928 (JustifyContent::SpaceBetween, _)
1929 | (JustifyContent::Start, _)
1930 | (JustifyContent::Stretch, false)
1931 | (JustifyContent::FlexStart, false)
1932 | (JustifyContent::FlexEnd, true) => {
1933 constants.padding_border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
1934 }
1935 (JustifyContent::End, _)
1936 | (JustifyContent::FlexEnd, false)
1937 | (JustifyContent::FlexStart, true)
1938 | (JustifyContent::Stretch, true) => {
1939 constants.container_size.main(constants.dir)
1940 - constants.padding_border.main_end(constants.dir)
1941 - final_size.main(constants.dir)
1942 - resolved_margin.main_end(constants.dir)
1943 }
1944 (JustifyContent::SpaceEvenly, _) | (JustifyContent::SpaceAround, _) | (JustifyContent::Center, _) => {
1945 (constants.container_size.main(constants.dir) + constants.padding_border.main_start(constants.dir)
1946 - constants.padding_border.main_end(constants.dir)
1947 - final_size.main(constants.dir)
1948 + resolved_margin.main_start(constants.dir)
1949 - resolved_margin.main_end(constants.dir))
1950 / 2.0
1951 }
1952 }
1953 };
1954
1955 // Apply cross-axis alignment
1956 // let free_cross_space = free_space.cross(constants.dir) - resolved_margin.cross_axis_sum(constants.dir);
1957 let offset_cross = if let Some(start) = start_cross {
1958 start + constants.border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
1959 } else if let Some(end) = end_cross {
1960 constants.container_size.cross(constants.dir)
1961 - constants.border.cross_end(constants.dir)
1962 - final_size.cross(constants.dir)
1963 - end
1964 - resolved_margin.cross_end(constants.dir)
1965 } else {
1966 match (align_self, constants.is_wrap_reverse) {
1967 // Stretch alignment does not apply to absolutely positioned items
1968 // See "Example 3" at https://www.w3.org/TR/css-flexbox-1/#abspos-items
1969 // Note: Stretch should be FlexStart not Start when we support both
1970 (AlignSelf::Start, _)
1971 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, false)
1972 | (AlignSelf::FlexEnd, true) => {
1973 constants.padding_border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
1974 }
1975 (AlignSelf::End, _)
1976 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, true)
1977 | (AlignSelf::FlexEnd, false) => {
1978 constants.container_size.cross(constants.dir)
1979 - constants.padding_border.cross_end(constants.dir)
1980 - final_size.cross(constants.dir)
1981 - resolved_margin.cross_end(constants.dir)
1982 }
1983 (AlignSelf::Center, _) => {
1984 (constants.container_size.cross(constants.dir)
1985 + constants.padding_border.cross_start(constants.dir)
1986 - constants.padding_border.cross_end(constants.dir)
1987 - final_size.cross(constants.dir)
1988 + resolved_margin.cross_start(constants.dir)
1989 - resolved_margin.cross_end(constants.dir))
1990 / 2.0
1991 }
1992 }
1993 };
1994
1995 *tree.layout_mut(child) = Layout {
1996 order: order as u32,
1997 size: final_size,
1998 location: Point {
1999 x: if constants.is_row { offset_main } else { offset_cross },
2000 y: if constants.is_column { offset_main } else { offset_cross },
2001 },
2002 };
2003 }
2004}
2005
2006/// Computes the total space taken up by gaps in an axis given:
2007/// - The size of each gap
2008/// - The number of items (children or flex-lines) between which there are gaps
2009#[inline(always)]
2010fn sum_axis_gaps(gap: f32, num_items: usize) -> f32 {
2011 // Gaps only exist between items, so...
2012 if num_items <= 1 {
2013 // ...if there are less than 2 items then there are no gaps
2014 0.0
2015 } else {
2016 // ...otherwise there are (num_items - 1) gaps
2017 gap * (num_items - 1) as f32
2018 }
2019}
2020
2021#[cfg(test)]
2022mod tests {
2023 #![allow(clippy::redundant_clone)]
2024
2025 use crate::{
2026 geometry::Size,
2027 math::MaybeMath,
2028 resolve::ResolveOrZero,
2029 style::{FlexWrap, Style},
2030 Taffy,
2031 };
2032
2033 // Make sure we get correct constants
2034 #[test]
2035 fn correct_constants() {
2036 let mut tree = Taffy::with_capacity(16);
2037
2038 let style = Style::default();
2039 let node_id = tree.new_leaf(style.clone()).unwrap();
2040
2041 let node_size = Size::NONE;
2042 let parent_size = Size::NONE;
2043
2044 let constants = super::compute_constants(tree.style(node_id).unwrap(), node_size, parent_size);
2045
2046 assert!(constants.dir == style.flex_direction);
2047 assert!(constants.is_row == style.flex_direction.is_row());
2048 assert!(constants.is_column == style.flex_direction.is_column());
2049 assert!(constants.is_wrap_reverse == (style.flex_wrap == FlexWrap::WrapReverse));
2050
2051 let margin = style.margin.resolve_or_zero(parent_size);
2052 assert_eq!(constants.margin, margin);
2053
2054 let border = style.border.resolve_or_zero(parent_size);
2055 let padding = style.padding.resolve_or_zero(parent_size);
2056 let padding_border = padding + border;
2057 assert_eq!(constants.border, border);
2058 assert_eq!(constants.padding_border, padding_border);
2059
2060 let inner_size = Size {
2061 width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
2062 height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
2063 };
2064 assert_eq!(constants.node_inner_size, inner_size);
2065
2066 assert_eq!(constants.container_size, Size::zero());
2067 assert_eq!(constants.inner_container_size, Size::zero());
2068 }
2069}