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