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