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