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