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