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