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