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