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