Fix divide by zero in main size determination
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 // A. If the item has a definite used flex basis, that’s the flex base size.
618
619 // B. If the flex item has an intrinsic aspect ratio,
620 // a used flex basis of content, and a definite cross size,
621 // then the flex base size is calculated from its inner
622 // cross size and the flex item’s intrinsic aspect ratio.
623
624 // Note: `child.size` has already been resolved against aspect_ratio in generate_anonymous_flex_items
625 // So B will just work here by using main_size without special handling for aspect_ratio
626
627 let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(constants.dir));
628 let main_size = child.size.main(constants.dir);
629 if let Some(flex_basis) = flex_basis.or(main_size) {
630 child.flex_basis = flex_basis;
631 continue;
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 child.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 // The hypothetical main size is the item’s flex base size clamped according to its
684 // used min and max main sizes (and flooring the content box size at zero).
685
686 for child in flex_items {
687 child.inner_flex_basis =
688 child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
689
690 let hypothetical_inner_min_main = child.min_size.main(constants.dir);
691 child.hypothetical_inner_size.set_main(
692 constants.dir,
693 child.flex_basis.maybe_clamp(hypothetical_inner_min_main, child.max_size.main(constants.dir)),
694 );
695 child.hypothetical_outer_size.set_main(
696 constants.dir,
697 child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
698 );
699
700 let min_content_size = GenericAlgorithm::measure_size(
701 tree,
702 child.node,
703 Size::NONE,
704 constants.node_inner_size,
705 Size::MIN_CONTENT,
706 SizingMode::ContentSize,
707 );
708
709 // 4.5. Automatic Minimum Size of Flex Items
710 // https://www.w3.org/TR/css-flexbox-1/#min-size-auto
711 let clamped_min_content_size = min_content_size.maybe_min(child.size).maybe_min(child.max_size);
712 child.resolved_minimum_size = child.min_size.unwrap_or(clamped_min_content_size);
713 }
714}
715
716/// Collect flex items into flex lines.
717///
718/// # [9.3. Main Size Determination](https://www.w3.org/TR/css-flexbox-1/#main-sizing)
719///
720/// - [**Collect flex items into flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-break):
721///
722/// - If the flex container is single-line, collect all the flex items into a single flex line.
723///
724/// - 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
725/// (or until a forced break is encountered, see [§10 Fragmenting Flex Layout](https://www.w3.org/TR/css-flexbox-1/#pagination)).
726/// If the very first uncollected item wouldn't fit, collect just it into the line.
727///
728/// For this step, the size of a flex item is its outer hypothetical main size. (**Note: This can be negative**.)
729///
730/// Repeat until all flex items have been collected into flex lines.
731///
732/// **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**.
733#[inline]
734fn collect_flex_lines<'a>(
735 constants: &AlgoConstants,
736 available_space: Size<AvailableSpace>,
737 flex_items: &'a mut Vec<FlexItem>,
738) -> Vec<FlexLine<'a>> {
739 if !constants.is_wrap {
740 let mut lines = new_vec_with_capacity(1);
741 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
742 lines
743 } else {
744 match available_space.main(constants.dir) {
745 // If we're sizing under a max-content constraint then the flex items will never wrap
746 // (at least for now - future extensions to the CSS spec may add provisions for forced wrap points)
747 AvailableSpace::MaxContent => {
748 let mut lines = new_vec_with_capacity(1);
749 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
750 lines
751 }
752 // If flex-wrap is Wrap and we're sizing under a min-content constraint, then we take every possible wrapping opportunity
753 // and place each item in it's own line
754 AvailableSpace::MinContent => {
755 let mut lines = new_vec_with_capacity(flex_items.len());
756 let mut items = &mut flex_items[..];
757 while !items.is_empty() {
758 let (line_items, rest) = items.split_at_mut(1);
759 lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
760 items = rest;
761 }
762 lines
763 }
764 AvailableSpace::Definite(main_axis_available_space) => {
765 let mut lines = new_vec_with_capacity(1);
766 let mut flex_items = &mut flex_items[..];
767 let main_axis_gap = constants.gap.main(constants.dir);
768
769 while !flex_items.is_empty() {
770 // Find index of the first item in the next line
771 // (or the last item if all remaining items are in the current line)
772 let mut line_length = 0.0;
773 let index = flex_items
774 .iter()
775 .enumerate()
776 .find(|&(idx, child)| {
777 // Gaps only occur between items (not before the first one or after the last one)
778 // So first item in the line does not contribute a gap to the line length
779 let gap_contribution = if idx == 0 { 0.0 } else { main_axis_gap };
780 line_length += child.hypothetical_outer_size.main(constants.dir) + gap_contribution;
781 line_length > main_axis_available_space && idx != 0
782 })
783 .map(|(idx, _)| idx)
784 .unwrap_or(flex_items.len());
785
786 let (items, rest) = flex_items.split_at_mut(index);
787 lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
788 flex_items = rest;
789 }
790 lines
791 }
792 }
793 }
794}
795
796/// Determine the container's main size (if not already known)
797fn determine_container_main_size(
798 tree: &mut impl LayoutTree,
799 main_axis_available_space: AvailableSpace,
800 lines: &mut Vec<FlexLine<'_>>,
801 constants: &mut AlgoConstants,
802) {
803 let outer_main_size: f32 = constants.node_outer_size.main(constants.dir).unwrap_or_else(|| {
804 match main_axis_available_space {
805 AvailableSpace::Definite(main_axis_available_space) => {
806 let longest_line_length: f32 = lines
807 .iter()
808 .map(|line| {
809 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
810 let total_target_size = line
811 .items
812 .iter()
813 .map(|child| child.flex_basis + child.margin.main_axis_sum(constants.dir))
814 .sum::<f32>();
815 total_target_size + line_main_axis_gap
816 })
817 .max_by(|a, b| a.total_cmp(b))
818 .unwrap_or(0.0);
819 let size = longest_line_length + constants.padding_border.main_axis_sum(constants.dir);
820 if lines.len() > 1 {
821 f32_max(size, main_axis_available_space)
822 } else {
823 size
824 }
825 }
826 AvailableSpace::MinContent if constants.is_wrap => {
827 let longest_line_length: f32 = lines
828 .iter()
829 .map(|line| {
830 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
831 let total_target_size = line
832 .items
833 .iter()
834 .map(|child| child.flex_basis + child.margin.main_axis_sum(constants.dir))
835 .sum::<f32>();
836 total_target_size + line_main_axis_gap
837 })
838 .max_by(|a, b| a.total_cmp(b))
839 .unwrap_or(0.0);
840 longest_line_length + constants.padding_border.main_axis_sum(constants.dir)
841 }
842 AvailableSpace::MinContent | AvailableSpace::MaxContent => {
843 // Define a base main_size variable. This is mutated once for iteration over the outer
844 // loop over the flex lines as:
845 // "The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line."
846 let mut main_size = 0.0;
847
848 for line in lines.iter_mut() {
849 for item in line.items.iter_mut() {
850 let style_min = item.min_size.main(constants.dir);
851 let style_preferred = item.size.main(constants.dir);
852 let style_max = item.max_size.main(constants.dir);
853 let flex_basis_min = Some(item.flex_basis).filter(|_| item.flex_shrink == 0.0);
854 let flex_basis_max = Some(item.flex_basis).filter(|_| item.flex_grow == 0.0);
855 let min_main_size = style_min.maybe_max(flex_basis_min).or(flex_basis_min);
856 let max_main_size = style_max.maybe_min(flex_basis_max).or(flex_basis_max);
857
858 let content_contribution = match (min_main_size, style_preferred, max_main_size) {
859 // If the clamping values are such that max <= min, then we can avoid the expensive step of computing the content size
860 // as we know that the clamping values will override it anyway
861 (Some(min), Some(pref), Some(max)) if max <= min || max <= pref => {
862 pref.min(max).max(min) + item.margin.main_axis_sum(constants.dir)
863 }
864 (Some(min), _, Some(max)) if max <= min => min + item.margin.main_axis_sum(constants.dir),
865 (_, Some(pref), Some(max)) if max <= pref => max + item.margin.main_axis_sum(constants.dir),
866 // Else compute the min- or -max content size and apply the full formula for computing the
867 // min- or max- content contributuon
868 _ => {
869 // Either the min- or max- content size depending on which constraint we are sizing under.
870 let content_main_size = GenericAlgorithm::measure_size(
871 tree,
872 item.node,
873 Size::NONE,
874 constants.node_inner_size,
875 Size { width: main_axis_available_space, height: main_axis_available_space },
876 SizingMode::InherentSize,
877 )
878 .main(constants.dir)
879 + item.margin.main_axis_sum(constants.dir);
880
881 // This is somewhat bizarre in that it's asymetrical depending whether the flex container is a column or a row.
882 //
883 // I *think* this might relate to https://drafts.csswg.org/css-flexbox-1/#algo-main-container:
884 //
885 // "The automatic block size of a block-level flex container is its max-content size."
886 //
887 // Which could suggest that flex-basis defining a vertical size does not shrink because it is in the block axis, and the automatic size
888 // in the block axis is a MAX content size. Whereas a flex-basis defining a horizontal size does shrink because the automatic size in
889 // inline axis is MIN content size (although I don't have a reference for that).
890 //
891 // Ultimately, this was not found by reading the spec, but by trial and error fixing tests to align with Webkit/Firefox output.
892 // (see the `flex_basis_unconstraint_row` and `flex_basis_uncontraint_column` generated tests which demonstrate this)
893 if constants.is_row {
894 content_main_size.maybe_clamp(style_min, style_max)
895 } else {
896 content_main_size.max(item.flex_basis).maybe_clamp(style_min, style_max)
897 }
898 }
899 };
900 item.content_flex_fraction = {
901 let diff = content_contribution - item.flex_basis;
902 if diff > 0.0 {
903 diff / f32_max(1.0, item.flex_grow)
904 } else if diff < 0.0 {
905 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink * item.inner_flex_basis);
906 diff / scaled_shrink_factor
907 } else {
908 // We are assuming that diff is 0.0 here and that we haven't accidentally introduced a NaN
909 0.0
910 }
911 };
912 }
913
914 // TODO Spec says to scale everything by the line's max flex fraction. But neither Chrome nor firefox implement this
915 // so we don't either. But if we did want to, we'd need this computation here (and to use it below):
916 //
917 // Within each line, find the largest max-content flex fraction among all the flex items.
918 // let line_flex_fraction = line
919 // .items
920 // .iter()
921 // .map(|item| item.content_flex_fraction)
922 // .max_by(|a, b| a.total_cmp(b))
923 // .unwrap_or(0.0); // Unwrap case never gets hit because there is always at least one item a line
924
925 // Add each item’s flex base size to the product of:
926 // - its flex grow factor (or scaled flex shrink factor,if the chosen max-content flex fraction was negative)
927 // - the chosen max-content flex fraction
928 // then clamp that result by the max main size floored by the min main size.
929 //
930 // The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line.
931 let item_main_size_sum = line
932 .items
933 .iter_mut()
934 .map(|item| {
935 let flex_fraction = item.content_flex_fraction;
936 // let flex_fraction = line_flex_fraction;
937
938 let flex_contribution = if item.content_flex_fraction > 0.0 {
939 f32_max(1.0, item.flex_grow) * flex_fraction
940 } else if item.content_flex_fraction < 0.0 {
941 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
942 scaled_shrink_factor * flex_fraction
943 } else {
944 0.0
945 };
946 let size = item.flex_basis + flex_contribution;
947 item.outer_target_size.set_main(constants.dir, size);
948 item.target_size.set_main(constants.dir, size);
949 size
950 })
951 .sum::<f32>();
952
953 let gap_sum = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
954 main_size = f32_max(main_size, item_main_size_sum + gap_sum)
955 }
956
957 main_size + constants.padding_border.main_axis_sum(constants.dir)
958 }
959 }
960 });
961 // let outer_main_size = inner_main_size + constants.padding_border.main_axis_sum(constants.dir);
962 let inner_main_size = outer_main_size - constants.padding_border.main_axis_sum(constants.dir);
963 constants.container_size.set_main(constants.dir, outer_main_size);
964 constants.inner_container_size.set_main(constants.dir, inner_main_size);
965 constants.node_inner_size.set_main(constants.dir, Some(inner_main_size));
966}
967
968/// Resolve the flexible lengths of the items within a flex line.
969/// Sets the `main` component of each item's `target_size` and `outer_target_size`
970///
971/// # [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
972#[inline]
973fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants, original_gap: Size<f32>) {
974 let total_original_main_axis_gap = sum_axis_gaps(original_gap.main(constants.dir), line.items.len());
975 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
976
977 // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
978 // items on the line. If the sum is less than the flex container’s inner main size,
979 // use the flex grow factor for the rest of this algorithm; otherwise, use the
980 // flex shrink factor.
981
982 let total_hypothetical_outer_main_size =
983 line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum::<f32>();
984 let used_flex_factor: f32 = total_original_main_axis_gap + total_hypothetical_outer_main_size;
985 let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
986 let shrinking = !growing;
987
988 // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
989 // - Any item that has a flex factor of zero
990 // - If using the flex grow factor: any item that has a flex base size
991 // greater than its hypothetical main size
992 // - If using the flex shrink factor: any item that has a flex base size
993 // smaller than its hypothetical main size
994
995 for child in line.items.iter_mut() {
996 let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
997 child.target_size.set_main(constants.dir, inner_target_size);
998
999 if (child.flex_grow == 0.0 && child.flex_shrink == 0.0)
1000 || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
1001 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
1002 {
1003 child.frozen = true;
1004 let outer_target_size = inner_target_size + child.margin.main_axis_sum(constants.dir);
1005 child.outer_target_size.set_main(constants.dir, outer_target_size);
1006 }
1007 }
1008
1009 // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
1010 // and subtract this from the flex container’s inner main size. For frozen items,
1011 // use their outer target main size; for other items, use their outer flex base size.
1012
1013 let used_space: f32 = total_main_axis_gap
1014 + line
1015 .items
1016 .iter()
1017 .map(|child| {
1018 child.margin.main_axis_sum(constants.dir)
1019 + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
1020 })
1021 .sum::<f32>();
1022
1023 let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
1024
1025 // 4. Loop
1026
1027 loop {
1028 // a. Check for flexible items. If all the flex items on the line are frozen,
1029 // free space has been distributed; exit this loop.
1030
1031 if line.items.iter().all(|child| child.frozen) {
1032 break;
1033 }
1034
1035 // b. Calculate the remaining free space as for initial free space, above.
1036 // If the sum of the unfrozen flex items’ flex factors is less than one,
1037 // multiply the initial free space by this sum. If the magnitude of this
1038 // value is less than the magnitude of the remaining free space, use this
1039 // as the remaining free space.
1040
1041 let used_space: f32 = total_main_axis_gap
1042 + line
1043 .items
1044 .iter()
1045 .map(|child| {
1046 child.margin.main_axis_sum(constants.dir)
1047 + if child.frozen { child.outer_target_size.main(constants.dir) } else { child.flex_basis }
1048 })
1049 .sum::<f32>();
1050
1051 let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
1052
1053 let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
1054 unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
1055 (flex_grow + item.flex_grow, flex_shrink + item.flex_shrink)
1056 });
1057
1058 let free_space = if growing && sum_flex_grow < 1.0 {
1059 (initial_free_space * sum_flex_grow - total_main_axis_gap)
1060 .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1061 } else if shrinking && sum_flex_shrink < 1.0 {
1062 (initial_free_space * sum_flex_shrink - total_main_axis_gap)
1063 .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1064 } else {
1065 (constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1066 .unwrap_or(used_flex_factor - used_space)
1067 };
1068
1069 // c. Distribute free space proportional to the flex factors.
1070 // - If the remaining free space is zero
1071 // Do Nothing
1072 // - If using the flex grow factor
1073 // Find the ratio of the item’s flex grow factor to the sum of the
1074 // flex grow factors of all unfrozen items on the line. Set the item’s
1075 // target main size to its flex base size plus a fraction of the remaining
1076 // free space proportional to the ratio.
1077 // - If using the flex shrink factor
1078 // For every unfrozen item on the line, multiply its flex shrink factor by
1079 // its inner flex base size, and note this as its scaled flex shrink factor.
1080 // Find the ratio of the item’s scaled flex shrink factor to the sum of the
1081 // scaled flex shrink factors of all unfrozen items on the line. Set the item’s
1082 // target main size to its flex base size minus a fraction of the absolute value
1083 // of the remaining free space proportional to the ratio. Note this may result
1084 // in a negative inner main size; it will be corrected in the next step.
1085 // - Otherwise
1086 // Do Nothing
1087
1088 if free_space.is_normal() {
1089 if growing && sum_flex_grow > 0.0 {
1090 for child in &mut unfrozen {
1091 child
1092 .target_size
1093 .set_main(constants.dir, child.flex_basis + free_space * (child.flex_grow / sum_flex_grow));
1094 }
1095 } else if shrinking && sum_flex_shrink > 0.0 {
1096 let sum_scaled_shrink_factor: f32 =
1097 unfrozen.iter().map(|child| child.inner_flex_basis * child.flex_shrink).sum();
1098
1099 if sum_scaled_shrink_factor > 0.0 {
1100 for child in &mut unfrozen {
1101 let scaled_shrink_factor = child.inner_flex_basis * child.flex_shrink;
1102 child.target_size.set_main(
1103 constants.dir,
1104 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
1105 )
1106 }
1107 }
1108 }
1109 }
1110
1111 // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
1112 // used min and max main sizes and floor its content-box size at zero. If the
1113 // item’s target main size was made smaller by this, it’s a max violation.
1114 // If the item’s target main size was made larger by this, it’s a min violation.
1115
1116 let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
1117 let resolved_min_main: Option<f32> = child.resolved_minimum_size.main(constants.dir).into();
1118 let max_main = child.max_size.main(constants.dir);
1119 let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
1120 child.violation = clamped - child.target_size.main(constants.dir);
1121 child.target_size.set_main(constants.dir, clamped);
1122 child.outer_target_size.set_main(
1123 constants.dir,
1124 child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
1125 );
1126
1127 acc + child.violation
1128 });
1129
1130 // e. Freeze over-flexed items. The total violation is the sum of the adjustments
1131 // from the previous step ∑(clamped size - unclamped size). If the total violation is:
1132 // - Zero
1133 // Freeze all items.
1134 // - Positive
1135 // Freeze all the items with min violations.
1136 // - Negative
1137 // Freeze all the items with max violations.
1138
1139 for child in &mut unfrozen {
1140 match total_violation {
1141 v if v > 0.0 => child.frozen = child.violation > 0.0,
1142 v if v < 0.0 => child.frozen = child.violation < 0.0,
1143 _ => child.frozen = true,
1144 }
1145 }
1146
1147 // f. Return to the start of this loop.
1148 }
1149}
1150
1151/// Determine the hypothetical cross size of each item.
1152///
1153/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1154///
1155/// - [**Determine the hypothetical cross size of each item**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-item)
1156/// by performing layout with the used main size and the available space, treating auto as fit-content.
1157#[inline]
1158fn determine_hypothetical_cross_size(
1159 tree: &mut impl LayoutTree,
1160 line: &mut FlexLine,
1161 constants: &AlgoConstants,
1162 available_space: Size<AvailableSpace>,
1163) {
1164 for child in line.items.iter_mut() {
1165 let child_cross = child
1166 .size
1167 .cross(constants.dir)
1168 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir));
1169
1170 child.hypothetical_inner_size.set_cross(
1171 constants.dir,
1172 GenericAlgorithm::measure_size(
1173 tree,
1174 child.node,
1175 Size {
1176 width: if constants.is_row { child.target_size.width.into() } else { child_cross },
1177 height: if constants.is_row { child_cross } else { child.target_size.height.into() },
1178 },
1179 constants.node_inner_size,
1180 Size {
1181 width: if constants.is_row {
1182 constants.container_size.main(constants.dir).into()
1183 } else {
1184 available_space.width
1185 },
1186 height: if constants.is_row {
1187 available_space.height
1188 } else {
1189 constants.container_size.main(constants.dir).into()
1190 },
1191 },
1192 SizingMode::ContentSize,
1193 )
1194 .cross(constants.dir)
1195 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir)),
1196 );
1197
1198 child.hypothetical_outer_size.set_cross(
1199 constants.dir,
1200 child.hypothetical_inner_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1201 );
1202 }
1203}
1204
1205/// Calculate the base lines of the children.
1206#[inline]
1207fn calculate_children_base_lines(
1208 tree: &mut impl LayoutTree,
1209 node_size: Size<Option<f32>>,
1210 available_space: Size<AvailableSpace>,
1211 flex_lines: &mut [FlexLine],
1212 constants: &AlgoConstants,
1213) {
1214 // Only compute baselines for flex rows because we only support baseline alignment in the cross axis
1215 // where that axis is also the inline axis
1216 // TODO: this may need revisiting if/when we support vertical writing modes
1217 if !constants.is_row {
1218 return;
1219 }
1220
1221 for line in flex_lines {
1222 // If a flex line has one or zero items participating in baseline alignment then baseline alignment is a no-op so we skip
1223 let line_baseline_child_count =
1224 line.items.iter().filter(|child| child.align_self == AlignSelf::Baseline).count();
1225 if line_baseline_child_count <= 1 {
1226 continue;
1227 }
1228
1229 for child in line.items.iter_mut() {
1230 // Only calculate baselines for children participating in baseline alignment
1231 if child.align_self != AlignSelf::Baseline {
1232 continue;
1233 }
1234
1235 let measured_size_and_baselines = GenericAlgorithm::perform_layout(
1236 tree,
1237 child.node,
1238 Size {
1239 width: if constants.is_row {
1240 child.target_size.width.into()
1241 } else {
1242 child.hypothetical_inner_size.width.into()
1243 },
1244 height: if constants.is_row {
1245 child.hypothetical_inner_size.height.into()
1246 } else {
1247 child.target_size.height.into()
1248 },
1249 },
1250 constants.node_inner_size,
1251 Size {
1252 width: if constants.is_row {
1253 constants.container_size.width.into()
1254 } else {
1255 available_space.width.maybe_set(node_size.width)
1256 },
1257 height: if constants.is_row {
1258 available_space.height.maybe_set(node_size.height)
1259 } else {
1260 constants.container_size.height.into()
1261 },
1262 },
1263 SizingMode::ContentSize,
1264 );
1265
1266 let baseline = measured_size_and_baselines.first_baselines.y;
1267 let height = measured_size_and_baselines.size.height;
1268
1269 child.baseline = baseline.unwrap_or(height) + child.margin.top;
1270 }
1271 }
1272}
1273
1274/// Calculate the cross size of each flex line.
1275///
1276/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1277///
1278/// - [**Calculate the cross size of each flex line**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-line).
1279///
1280/// 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.
1281///
1282/// Otherwise, for each flex line:
1283///
1284/// 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.
1285/// Find the largest of the distances between each item’s baseline and its hypothetical outer cross-start edge,
1286/// and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, and sum these two values.
1287///
1288/// 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size.
1289///
1290/// 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero.
1291///
1292/// 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.
1293/// **Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically**.
1294#[inline]
1295fn calculate_cross_size(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1296 // Note: AlignContent::SpaceEvenly and AlignContent::SpaceAround behave like AlignContent::Stretch when there is only
1297 // a single flex line in the container. See: https://www.w3.org/TR/css-flexbox-1/#align-content-property
1298 // Also: align_content is ignored entirely (and thus behaves like Stretch) when `flex_wrap` is set to `nowrap`.
1299 if flex_lines.len() == 1
1300 && node_size.cross(constants.dir).is_some()
1301 && (!constants.is_wrap
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 if child.align_self == AlignSelf::Baseline
1330 && !child.margin_is_auto.cross_start(constants.dir)
1331 && !child.margin_is_auto.cross_end(constants.dir)
1332 {
1333 max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
1334 } else {
1335 child.hypothetical_outer_size.cross(constants.dir)
1336 }
1337 })
1338 .fold(0.0, |acc, x| acc.max(x));
1339 }
1340 }
1341}
1342
1343/// Handle 'align-content: stretch'.
1344///
1345/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1346///
1347/// - [**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,
1348/// and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size,
1349/// 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.
1350#[inline]
1351fn handle_align_content_stretch(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1352 if constants.align_content == AlignContent::Stretch && node_size.cross(constants.dir).is_some() {
1353 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1354 let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
1355 let inner_cross =
1356 (node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
1357 .unwrap_or(0.0);
1358
1359 if total_cross < inner_cross {
1360 let remaining = inner_cross - total_cross;
1361 let addition = remaining / flex_lines.len() as f32;
1362 flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
1363 }
1364 }
1365}
1366
1367/// Determine the used cross size of each flex item.
1368///
1369/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1370///
1371/// - [**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,
1372/// 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.
1373/// Otherwise, the used cross size is the item’s hypothetical cross size.
1374///
1375/// 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.
1376///
1377/// **Note that this step does not affect the main size of the flex item, even if it has an intrinsic aspect ratio**.
1378#[inline]
1379fn determine_used_cross_size(tree: &mut impl LayoutTree, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1380 for line in flex_lines {
1381 let line_cross_size = line.cross_size;
1382
1383 for child in line.items.iter_mut() {
1384 let child_style = tree.style(child.node);
1385 child.target_size.set_cross(
1386 constants.dir,
1387 if child.align_self == AlignSelf::Stretch
1388 && !child.margin_is_auto.cross_start(constants.dir)
1389 && !child.margin_is_auto.cross_end(constants.dir)
1390 && child_style.size.cross(constants.dir) == Dimension::Auto
1391 {
1392 // For some reason this particular usage of max_width is an exception to the rule that max_width's transfer
1393 // using the aspect_ratio (if set). Both Chrome and Firefox agree on this. And reading the spec, it seems like
1394 // a reasonable interpretation. Although it seems to me that the spec *should* apply aspect_ratio here.
1395 let max_size_ignoring_aspect_ratio = child_style.max_size.maybe_resolve(constants.node_inner_size);
1396
1397 (line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
1398 child.min_size.cross(constants.dir),
1399 max_size_ignoring_aspect_ratio.cross(constants.dir),
1400 )
1401 } else {
1402 child.hypothetical_inner_size.cross(constants.dir)
1403 },
1404 );
1405
1406 child.outer_target_size.set_cross(
1407 constants.dir,
1408 child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1409 );
1410 }
1411 }
1412}
1413
1414/// Distribute any remaining free space.
1415///
1416/// # [9.5. Main-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#main-alignment)
1417///
1418/// - [**Distribute any remaining free space**](https://www.w3.org/TR/css-flexbox-1/#algo-main-align). For each flex line:
1419///
1420/// 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.
1421/// Otherwise, set all `auto` margins to zero.
1422///
1423/// 2. Align the items along the main-axis per `justify-content`.
1424#[inline]
1425fn distribute_remaining_free_space(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1426 for line in flex_lines {
1427 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1428 let used_space: f32 = total_main_axis_gap
1429 + line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
1430 let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1431 let mut num_auto_margins = 0;
1432
1433 for child in line.items.iter_mut() {
1434 if child.margin_is_auto.main_start(constants.dir) {
1435 num_auto_margins += 1;
1436 }
1437 if child.margin_is_auto.main_end(constants.dir) {
1438 num_auto_margins += 1;
1439 }
1440 }
1441
1442 if free_space > 0.0 && num_auto_margins > 0 {
1443 let margin = free_space / num_auto_margins as f32;
1444
1445 for child in line.items.iter_mut() {
1446 if child.margin_is_auto.main_start(constants.dir) {
1447 if constants.is_row {
1448 child.margin.left = margin;
1449 } else {
1450 child.margin.top = margin;
1451 }
1452 }
1453 if child.margin_is_auto.main_end(constants.dir) {
1454 if constants.is_row {
1455 child.margin.right = margin;
1456 } else {
1457 child.margin.bottom = margin;
1458 }
1459 }
1460 }
1461 } else {
1462 let num_items = line.items.len();
1463 let layout_reverse = constants.dir.is_reverse();
1464 let gap = constants.gap.main(constants.dir);
1465 let justify_content_mode = constants.justify_content.unwrap_or(JustifyContent::FlexStart);
1466
1467 let justify_item = |(i, child): (usize, &mut FlexItem)| {
1468 child.offset_main =
1469 compute_alignment_offset(free_space, num_items, gap, justify_content_mode, layout_reverse, i == 0);
1470 };
1471
1472 if layout_reverse {
1473 line.items.iter_mut().rev().enumerate().for_each(justify_item);
1474 } else {
1475 line.items.iter_mut().enumerate().for_each(justify_item);
1476 }
1477 }
1478 }
1479}
1480
1481/// Resolve cross-axis `auto` margins.
1482///
1483/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1484///
1485/// - [**Resolve cross-axis `auto` margins**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-margins).
1486/// If a flex item has auto cross-axis margins:
1487///
1488/// - If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line,
1489/// distribute the difference in those sizes equally to the auto margins.
1490///
1491/// - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
1492/// Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
1493#[inline]
1494fn resolve_cross_axis_auto_margins(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1495 for line in flex_lines {
1496 let line_cross_size = line.cross_size;
1497 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1498
1499 for child in line.items.iter_mut() {
1500 let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1501
1502 if child.margin_is_auto.cross_start(constants.dir) && child.margin_is_auto.cross_end(constants.dir) {
1503 if constants.is_row {
1504 child.margin.top = free_space / 2.0;
1505 child.margin.bottom = free_space / 2.0;
1506 } else {
1507 child.margin.left = free_space / 2.0;
1508 child.margin.right = free_space / 2.0;
1509 }
1510 } else if child.margin_is_auto.cross_start(constants.dir) {
1511 if constants.is_row {
1512 child.margin.top = free_space;
1513 } else {
1514 child.margin.left = free_space;
1515 }
1516 } else if child.margin_is_auto.cross_end(constants.dir) {
1517 if constants.is_row {
1518 child.margin.bottom = free_space;
1519 } else {
1520 child.margin.right = free_space;
1521 }
1522 } else {
1523 // 14. Align all flex items along the cross-axis.
1524 child.offset_cross = align_flex_items_along_cross_axis(child, free_space, max_baseline, constants);
1525 }
1526 }
1527 }
1528}
1529
1530/// Align all flex items along the cross-axis.
1531///
1532/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1533///
1534/// - [**Align all flex items along the cross-axis**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-align) per `align-self`,
1535/// if neither of the item's cross-axis margins are `auto`.
1536#[inline]
1537fn align_flex_items_along_cross_axis(
1538 child: &mut FlexItem,
1539 free_space: f32,
1540 max_baseline: f32,
1541 constants: &AlgoConstants,
1542) -> f32 {
1543 match child.align_self {
1544 AlignSelf::Start => 0.0,
1545 AlignSelf::FlexStart => {
1546 if constants.is_wrap_reverse {
1547 free_space
1548 } else {
1549 0.0
1550 }
1551 }
1552 AlignSelf::End => free_space,
1553 AlignSelf::FlexEnd => {
1554 if constants.is_wrap_reverse {
1555 0.0
1556 } else {
1557 free_space
1558 }
1559 }
1560 AlignSelf::Center => free_space / 2.0,
1561 AlignSelf::Baseline => {
1562 if constants.is_row {
1563 max_baseline - child.baseline
1564 } else {
1565 // Until we support vertical writing modes, baseline alignment only makes sense if
1566 // the constants.direction is row, so we treat it as flex-start alignment in columns.
1567 if constants.is_wrap_reverse {
1568 free_space
1569 } else {
1570 0.0
1571 }
1572 }
1573 }
1574 AlignSelf::Stretch => {
1575 if constants.is_wrap_reverse {
1576 free_space
1577 } else {
1578 0.0
1579 }
1580 }
1581 }
1582}
1583
1584/// Determine the flex container’s used cross size.
1585///
1586/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1587///
1588/// - [**Determine the flex container’s used cross size**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-container):
1589///
1590/// - If the cross size property is a definite size, use that, clamped by the used min and max cross sizes of the flex container.
1591///
1592/// - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used min and max cross sizes of the flex container.
1593#[inline]
1594#[must_use]
1595fn determine_container_cross_size(
1596 flex_lines: &mut [FlexLine],
1597 node_size: Size<Option<f32>>,
1598 constants: &mut AlgoConstants,
1599) -> f32 {
1600 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1601 let total_line_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>();
1602
1603 constants.container_size.set_cross(
1604 constants.dir,
1605 node_size.cross(constants.dir).unwrap_or(
1606 total_line_cross_size + total_cross_axis_gap + constants.padding_border.cross_axis_sum(constants.dir),
1607 ),
1608 );
1609
1610 constants.inner_container_size.set_cross(
1611 constants.dir,
1612 constants.container_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir),
1613 );
1614
1615 total_line_cross_size
1616}
1617
1618/// Align all flex lines per `align-content`.
1619///
1620/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1621///
1622/// - [**Align all flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-align) per `align-content`.
1623#[inline]
1624fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &AlgoConstants, total_cross_size: f32) {
1625 let num_lines = flex_lines.len();
1626 let gap = constants.gap.cross(constants.dir);
1627 let align_content_mode = constants.align_content;
1628 let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
1629 let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;
1630
1631 let align_line = |(i, line): (usize, &mut FlexLine)| {
1632 line.offset_cross =
1633 compute_alignment_offset(free_space, num_lines, gap, align_content_mode, constants.is_wrap_reverse, i == 0);
1634 };
1635
1636 if constants.is_wrap_reverse {
1637 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1638 } else {
1639 flex_lines.iter_mut().enumerate().for_each(align_line);
1640 }
1641}
1642
1643/// Calculates the layout for a flex-item
1644#[allow(clippy::too_many_arguments)]
1645fn calculate_flex_item(
1646 tree: &mut impl LayoutTree,
1647 node: Node,
1648 item: &mut FlexItem,
1649 total_offset_main: &mut f32,
1650 total_offset_cross: f32,
1651 line_offset_cross: f32,
1652 container_size: Size<f32>,
1653 node_inner_size: Size<Option<f32>>,
1654 direction: FlexDirection,
1655) {
1656 let preliminary_size_and_baselines = GenericAlgorithm::perform_layout(
1657 tree,
1658 item.node,
1659 item.target_size.map(|s| s.into()),
1660 node_inner_size,
1661 container_size.map(|s| s.into()),
1662 SizingMode::ContentSize,
1663 );
1664 let preliminary_size = preliminary_size_and_baselines.size;
1665
1666 let offset_main = *total_offset_main
1667 + item.offset_main
1668 + item.margin.main_start(direction)
1669 + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1670
1671 let offset_cross = total_offset_cross
1672 + item.offset_cross
1673 + line_offset_cross
1674 + item.margin.cross_start(direction)
1675 + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1676
1677 if direction.is_row() {
1678 let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction);
1679 let inner_baseline = preliminary_size_and_baselines.first_baselines.y.unwrap_or(preliminary_size.height);
1680 item.baseline = baseline_offset_cross + inner_baseline;
1681 } else {
1682 let baseline_offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction);
1683 let inner_baseline = preliminary_size_and_baselines.first_baselines.y.unwrap_or(preliminary_size.height);
1684 item.baseline = baseline_offset_main + inner_baseline;
1685 }
1686
1687 let order = tree.children(node).position(|n| *n == item.node).unwrap() as u32;
1688
1689 *tree.layout_mut(item.node) = Layout {
1690 order,
1691 size: preliminary_size_and_baselines.size,
1692 location: Point {
1693 x: if direction.is_row() { offset_main } else { offset_cross },
1694 y: if direction.is_column() { offset_main } else { offset_cross },
1695 },
1696 };
1697
1698 *total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + preliminary_size.main(direction);
1699}
1700
1701/// Calculates the layout line
1702#[allow(clippy::too_many_arguments)]
1703fn calculate_layout_line(
1704 tree: &mut impl LayoutTree,
1705 node: Node,
1706 line: &mut FlexLine,
1707 total_offset_cross: &mut f32,
1708 container_size: Size<f32>,
1709 node_inner_size: Size<Option<f32>>,
1710 padding_border: Rect<f32>,
1711 direction: FlexDirection,
1712) {
1713 let mut total_offset_main = padding_border.main_start(direction);
1714 let line_offset_cross = line.offset_cross;
1715
1716 if direction.is_reverse() {
1717 for item in line.items.iter_mut().rev() {
1718 calculate_flex_item(
1719 tree,
1720 node,
1721 item,
1722 &mut total_offset_main,
1723 *total_offset_cross,
1724 line_offset_cross,
1725 container_size,
1726 node_inner_size,
1727 direction,
1728 );
1729 }
1730 } else {
1731 for item in line.items.iter_mut() {
1732 calculate_flex_item(
1733 tree,
1734 node,
1735 item,
1736 &mut total_offset_main,
1737 *total_offset_cross,
1738 line_offset_cross,
1739 container_size,
1740 node_inner_size,
1741 direction,
1742 );
1743 }
1744 }
1745
1746 *total_offset_cross += line_offset_cross + line.cross_size;
1747}
1748
1749/// Do a final layout pass and collect the resulting layouts.
1750#[inline]
1751fn final_layout_pass(tree: &mut impl LayoutTree, node: Node, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1752 let mut total_offset_cross = constants.padding_border.cross_start(constants.dir);
1753
1754 if constants.is_wrap_reverse {
1755 for line in flex_lines.iter_mut().rev() {
1756 calculate_layout_line(
1757 tree,
1758 node,
1759 line,
1760 &mut total_offset_cross,
1761 constants.container_size,
1762 constants.node_inner_size,
1763 constants.padding_border,
1764 constants.dir,
1765 );
1766 }
1767 } else {
1768 for line in flex_lines.iter_mut() {
1769 calculate_layout_line(
1770 tree,
1771 node,
1772 line,
1773 &mut total_offset_cross,
1774 constants.container_size,
1775 constants.node_inner_size,
1776 constants.padding_border,
1777 constants.dir,
1778 );
1779 }
1780 }
1781}
1782
1783/// Perform absolute layout on all absolutely positioned children.
1784#[inline]
1785fn perform_absolute_layout_on_absolute_children(tree: &mut impl LayoutTree, node: Node, constants: &AlgoConstants) {
1786 let container_width = constants.container_size.width;
1787 let container_height = constants.container_size.height;
1788
1789 for order in 0..tree.child_count(node) {
1790 let child = tree.child(node, order);
1791 let child_style = tree.style(child);
1792
1793 // Skip items that are display:none or are not position:absolute
1794 if child_style.display == Display::None || child_style.position != Position::Absolute {
1795 continue;
1796 }
1797
1798 let aspect_ratio = child_style.aspect_ratio;
1799 let margin = child_style.margin.map(|margin| margin.resolve_to_option(container_width));
1800 let align_self = child_style.align_self.unwrap_or(constants.align_items);
1801
1802 // Resolve inset
1803 let left = child_style.inset.left.maybe_resolve(container_width);
1804 let right = child_style.inset.right.maybe_resolve(container_width);
1805 let top = child_style.inset.top.maybe_resolve(container_height);
1806 let bottom = child_style.inset.bottom.maybe_resolve(container_height);
1807
1808 // Compute known dimensions from min/max/inherent size styles
1809 let style_size =
1810 child_style.size.maybe_resolve(constants.container_size).maybe_apply_aspect_ratio(aspect_ratio);
1811 let min_size =
1812 child_style.min_size.maybe_resolve(constants.container_size).maybe_apply_aspect_ratio(aspect_ratio);
1813 let max_size =
1814 child_style.max_size.maybe_resolve(constants.container_size).maybe_apply_aspect_ratio(aspect_ratio);
1815 let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
1816
1817 // Fill in width from left/right and reapply aspect ratio if:
1818 // - Width is not already known
1819 // - Item has both left and right inset properties set
1820 if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
1821 let new_width_raw = container_width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
1822 known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
1823 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
1824 }
1825
1826 // Fill in height from top/bottom and reapply aspect ratio if:
1827 // - Height is not already known
1828 // - Item has both top and bottom inset properties set
1829 if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
1830 let new_height_raw = container_height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
1831 known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
1832 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
1833 }
1834
1835 let measured_size_and_baselines = GenericAlgorithm::perform_layout(
1836 tree,
1837 child,
1838 known_dimensions,
1839 constants.node_inner_size,
1840 Size {
1841 width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
1842 height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
1843 },
1844 SizingMode::ContentSize,
1845 );
1846 let measured_size = measured_size_and_baselines.size;
1847 let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
1848
1849 let non_auto_margin = margin.map(|m| m.unwrap_or(0.0));
1850
1851 let free_space = Size {
1852 width: constants.container_size.width - final_size.width - non_auto_margin.horizontal_axis_sum(),
1853 height: constants.container_size.height - final_size.height - non_auto_margin.vertical_axis_sum(),
1854 }
1855 .f32_max(Size::ZERO);
1856
1857 // Expand auto margins to fill available space
1858 let resolved_margin = {
1859 let auto_margin_size = Size {
1860 width: {
1861 let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
1862 if auto_margin_count > 0 {
1863 free_space.width / auto_margin_count as f32
1864 } else {
1865 0.0
1866 }
1867 },
1868 height: {
1869 let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
1870 if auto_margin_count > 0 {
1871 free_space.height / auto_margin_count as f32
1872 } else {
1873 0.0
1874 }
1875 },
1876 };
1877
1878 Rect {
1879 left: margin.left.unwrap_or(auto_margin_size.width),
1880 right: margin.right.unwrap_or(auto_margin_size.width),
1881 top: margin.top.unwrap_or(auto_margin_size.height),
1882 bottom: margin.bottom.unwrap_or(auto_margin_size.height),
1883 }
1884 };
1885
1886 // Determine flex-relative insets
1887 let (start_main, end_main) = if constants.is_row { (left, right) } else { (top, bottom) };
1888 let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (left, right) };
1889
1890 // Apply main-axis alignment
1891 // let free_main_space = free_space.main(constants.dir) - resolved_margin.main_axis_sum(constants.dir);
1892 let offset_main = if let Some(start) = start_main {
1893 start + constants.border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
1894 } else if let Some(end) = end_main {
1895 constants.container_size.main(constants.dir)
1896 - constants.border.main_end(constants.dir)
1897 - final_size.main(constants.dir)
1898 - end
1899 - resolved_margin.main_end(constants.dir)
1900 } else {
1901 // Stretch is an invalid value for justify_content in the flexbox algorithm, so we
1902 // treat it as if it wasn't set (and thus we default to FlexStart behaviour)
1903 match (constants.justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
1904 (JustifyContent::SpaceBetween, _)
1905 | (JustifyContent::Start, _)
1906 | (JustifyContent::Stretch, false)
1907 | (JustifyContent::FlexStart, false)
1908 | (JustifyContent::FlexEnd, true) => {
1909 constants.padding_border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
1910 }
1911 (JustifyContent::End, _)
1912 | (JustifyContent::FlexEnd, false)
1913 | (JustifyContent::FlexStart, true)
1914 | (JustifyContent::Stretch, true) => {
1915 constants.container_size.main(constants.dir)
1916 - constants.padding_border.main_end(constants.dir)
1917 - final_size.main(constants.dir)
1918 - resolved_margin.main_end(constants.dir)
1919 }
1920 (JustifyContent::SpaceEvenly, _) | (JustifyContent::SpaceAround, _) | (JustifyContent::Center, _) => {
1921 (constants.container_size.main(constants.dir) + constants.padding_border.main_start(constants.dir)
1922 - constants.padding_border.main_end(constants.dir)
1923 - final_size.main(constants.dir)
1924 + resolved_margin.main_start(constants.dir)
1925 - resolved_margin.main_end(constants.dir))
1926 / 2.0
1927 }
1928 }
1929 };
1930
1931 // Apply cross-axis alignment
1932 // let free_cross_space = free_space.cross(constants.dir) - resolved_margin.cross_axis_sum(constants.dir);
1933 let offset_cross = if let Some(start) = start_cross {
1934 start + constants.border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
1935 } else if let Some(end) = end_cross {
1936 constants.container_size.cross(constants.dir)
1937 - constants.border.cross_end(constants.dir)
1938 - final_size.cross(constants.dir)
1939 - end
1940 - resolved_margin.cross_end(constants.dir)
1941 } else {
1942 match (align_self, constants.is_wrap_reverse) {
1943 // Stretch alignment does not apply to absolutely positioned items
1944 // See "Example 3" at https://www.w3.org/TR/css-flexbox-1/#abspos-items
1945 // Note: Stretch should be FlexStart not Start when we support both
1946 (AlignSelf::Start, _)
1947 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, false)
1948 | (AlignSelf::FlexEnd, true) => {
1949 constants.padding_border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
1950 }
1951 (AlignSelf::End, _)
1952 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, true)
1953 | (AlignSelf::FlexEnd, false) => {
1954 constants.container_size.cross(constants.dir)
1955 - constants.padding_border.cross_end(constants.dir)
1956 - final_size.cross(constants.dir)
1957 - resolved_margin.cross_end(constants.dir)
1958 }
1959 (AlignSelf::Center, _) => {
1960 (constants.container_size.cross(constants.dir)
1961 + constants.padding_border.cross_start(constants.dir)
1962 - constants.padding_border.cross_end(constants.dir)
1963 - final_size.cross(constants.dir)
1964 + resolved_margin.cross_start(constants.dir)
1965 - resolved_margin.cross_end(constants.dir))
1966 / 2.0
1967 }
1968 }
1969 };
1970
1971 *tree.layout_mut(child) = Layout {
1972 order: order as u32,
1973 size: final_size,
1974 location: Point {
1975 x: if constants.is_row { offset_main } else { offset_cross },
1976 y: if constants.is_column { offset_main } else { offset_cross },
1977 },
1978 };
1979 }
1980}
1981
1982/// Computes the total space taken up by gaps in an axis given:
1983/// - The size of each gap
1984/// - The number of items (children or flex-lines) between which there are gaps
1985#[inline(always)]
1986fn sum_axis_gaps(gap: f32, num_items: usize) -> f32 {
1987 // Gaps only exist between items, so...
1988 if num_items <= 1 {
1989 // ...if there are less than 2 items then there are no gaps
1990 0.0
1991 } else {
1992 // ...otherwise there are (num_items - 1) gaps
1993 gap * (num_items - 1) as f32
1994 }
1995}
1996
1997#[cfg(test)]
1998mod tests {
1999 #![allow(clippy::redundant_clone)]
2000
2001 use crate::{
2002 geometry::Size,
2003 math::MaybeMath,
2004 resolve::ResolveOrZero,
2005 style::{FlexWrap, Style},
2006 Taffy,
2007 };
2008
2009 // Make sure we get correct constants
2010 #[test]
2011 fn correct_constants() {
2012 let mut tree = Taffy::with_capacity(16);
2013
2014 let style = Style::default();
2015 let node_id = tree.new_leaf(style.clone()).unwrap();
2016
2017 let node_size = Size::NONE;
2018 let parent_size = Size::NONE;
2019
2020 let constants = super::compute_constants(tree.style(node_id).unwrap(), node_size, parent_size);
2021
2022 assert!(constants.dir == style.flex_direction);
2023 assert!(constants.is_row == style.flex_direction.is_row());
2024 assert!(constants.is_column == style.flex_direction.is_column());
2025 assert!(constants.is_wrap_reverse == (style.flex_wrap == FlexWrap::WrapReverse));
2026
2027 let margin = style.margin.resolve_or_zero(parent_size);
2028 assert_eq!(constants.margin, margin);
2029
2030 let border = style.border.resolve_or_zero(parent_size);
2031 let padding = style.padding.resolve_or_zero(parent_size);
2032 let padding_border = padding + border;
2033 assert_eq!(constants.border, border);
2034 assert_eq!(constants.padding_border, padding_border);
2035
2036 let inner_size = Size {
2037 width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
2038 height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
2039 };
2040 assert_eq!(constants.node_inner_size, inner_size);
2041
2042 assert_eq!(constants.container_size, Size::zero());
2043 assert_eq!(constants.inner_container_size, Size::zero());
2044 }
2045}