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