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