tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Sutichaykuna

⎇ d01eeab2439f64bfcece56dd160b0e6efeca42b8 src/flexbox.rs Rust · 1715 siq'ikuna · 76.0 KB Ñawpa willay Pi imata llamkarqan ch'uya

Minor docs fix in `flexbox.rs` (#151) d01eeab24 · Andreas Weibye ·

1//! Computes the [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) layout algorithm on a [`Forest`](crate::forest::Forest) 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::flexbox::Number::{Defined, Undefined};
7use crate::forest::{Forest, NodeData};
8use crate::geometry::{Point, Rect, Size};
9use crate::layout::{Cache, Layout};
10use crate::node::{MeasureFunc, NodeId};
11use crate::number::{MinMax, OrElse};
12use crate::prelude::Number;
13use crate::style::{AlignContent, AlignSelf, Dimension, Display, FlexWrap, JustifyContent, PositionType};
14use crate::style::{FlexDirection, FlexboxLayout};
15use crate::sys::{abs, round, ChildrenVec, Vec};
16
17/// The intermediate results of a flexbox calculation for a single item
18struct FlexItem {
19    /// The identifier for the associated [`Node`](crate::node::Node)
20    node: NodeId,
21
22    /// The base size of this item
23    size: Size<Number>,
24    /// The minimum allowable size of this item
25    min_size: Size<Number>,
26    /// The maximum allowable size of this item
27    max_size: Size<Number>,
28
29    /// The final offset of this item
30    position: Rect<Number>,
31    /// The margin of this item
32    margin: Rect<f32>,
33    /// The padding of this item
34    padding: Rect<f32>,
35    /// The border of this item
36    border: Rect<f32>,
37
38    /// The default size of this item
39    flex_basis: f32,
40    /// The default size of this item, minus padding and border
41    inner_flex_basis: f32,
42    /// The amount by which this item has deviated from its target size
43    violation: f32,
44    /// Is the size of this item locked
45    frozen: bool,
46
47    /// The proposed inner size of this item
48    hypothetical_inner_size: Size<f32>,
49    /// The proposed outer size of this item
50    hypothetical_outer_size: Size<f32>,
51    /// The size that this item wants to be
52    target_size: Size<f32>,
53    /// The size that this item wants to be, plus any padding and border
54    outer_target_size: Size<f32>,
55
56    /// The position of the bottom edge of this item
57    baseline: f32,
58
59    /// A temporary value for the main offset
60    ///
61    /// Offset is the relative position from the item's natural flow position based on
62    /// relative position values, alignment, and justification. Does not include margin/padding/border.
63    offset_main: f32,
64    /// A temporary value for the cross offset
65    ///
66    /// Offset is the relative position from the item's natural flow position based on
67    /// relative position values, alignment, and justification. Does not include margin/padding/border.
68    offset_cross: f32,
69}
70
71/// A line of [`FlexItem`] used for intermediate computation
72struct FlexLine<'a> {
73    /// The slice of items to iterate over during computation of this line
74    items: &'a mut [FlexItem],
75    /// The dimensions of the cross-axis
76    cross_size: f32,
77    /// The relative offset of the cross-axis
78    offset_cross: f32,
79}
80
81/// Values that can be cached during the flexbox algorithm
82struct AlgoConstants {
83    /// The direction of the current segment being layed out
84    dir: FlexDirection,
85    /// Is this segment a row
86    is_row: bool,
87    /// Is this segment a column
88    is_column: bool,
89    /// Is the wrap direction inverted
90    is_wrap_reverse: bool,
91
92    /// The margin of this section
93    margin: Rect<f32>,
94    /// The border of this section
95    border: Rect<f32>,
96    /// The padding of this section
97    padding_border: Rect<f32>,
98
99    /// The size of the internal node
100    node_inner_size: Size<Number>,
101    /// The size of the surrounding container
102    container_size: Size<f32>,
103    /// The size of the internal container
104    inner_container_size: Size<f32>,
105}
106
107impl Forest {
108    /// Computes the layout of this [`Forest`] according to the flexbox algorithm
109    pub(crate) fn compute(&mut self, root: NodeId, size: Size<Number>) {
110        let style = self.nodes[root].style;
111        let has_root_min_max = style.min_size.width.is_defined()
112            || style.min_size.height.is_defined()
113            || style.max_size.width.is_defined()
114            || style.max_size.height.is_defined();
115
116        let preliminary_size = if has_root_min_max {
117            let first_pass = self.compute_preliminary(root, style.size.resolve(size), size, false, true);
118
119            self.compute_preliminary(
120                root,
121                Size {
122                    width: first_pass
123                        .width
124                        .maybe_max(style.min_size.width.resolve(size.width))
125                        .maybe_min(style.max_size.width.resolve(size.width))
126                        .into(),
127                    height: first_pass
128                        .height
129                        .maybe_max(style.min_size.height.resolve(size.height))
130                        .maybe_min(style.max_size.height.resolve(size.height))
131                        .into(),
132                },
133                size,
134                true,
135                true,
136            )
137        } else {
138            self.compute_preliminary(root, style.size.resolve(size), size, true, true)
139        };
140
141        self.nodes[root].layout = Layout { order: 0, size: preliminary_size, location: Point::zero() };
142
143        Self::round_layout(&mut self.nodes, &self.children, root, 0.0, 0.0);
144    }
145
146    /// Rounds the calculated [`NodeData`] according to the spec
147    fn round_layout(nodes: &mut [NodeData], children: &[ChildrenVec<NodeId>], root: NodeId, abs_x: f32, abs_y: f32) {
148        let layout = &mut nodes[root].layout;
149        let abs_x = abs_x + layout.location.x;
150        let abs_y = abs_y + layout.location.y;
151
152        layout.location.x = round(layout.location.x);
153        layout.location.y = round(layout.location.y);
154
155        layout.size.width = round(layout.size.width);
156        layout.size.height = round(layout.size.height);
157
158        for child in &children[root] {
159            Self::round_layout(nodes, children, *child, abs_x, abs_y);
160        }
161    }
162
163    /// Saves intermediate results to a [`Cache`]
164    fn cache(&mut self, node: NodeId, main_size: bool) -> &mut Option<Cache> {
165        if main_size {
166            &mut self.nodes[node].main_size_layout_cache
167        } else {
168            &mut self.nodes[node].other_layout_cache
169        }
170    }
171
172    /// Try to get the computation result from the cache.
173    #[inline]
174    fn compute_from_cache(
175        &mut self,
176        node: NodeId,
177        node_size: Size<Number>,
178        parent_size: Size<Number>,
179        perform_layout: bool,
180        main_size: bool,
181    ) -> Option<Size<f32>> {
182        if let Some(ref cache) = self.cache(node, main_size) {
183            if cache.perform_layout || !perform_layout {
184                let width_compatible = if let Number::Defined(width) = node_size.width {
185                    abs(width - cache.size.width) < f32::EPSILON
186                } else {
187                    cache.node_size.width.is_undefined()
188                };
189
190                let height_compatible = if let Number::Defined(height) = node_size.height {
191                    abs(height - cache.size.height) < f32::EPSILON
192                } else {
193                    cache.node_size.height.is_undefined()
194                };
195
196                if width_compatible && height_compatible {
197                    return Some(cache.size);
198                }
199
200                if cache.node_size == node_size && cache.parent_size == parent_size {
201                    return Some(cache.size);
202                }
203            }
204        }
205
206        None
207    }
208
209    /// Compute constants that can be reused during the flexbox algorithm.
210    #[inline]
211    fn compute_constants(&self, node: NodeId, node_size: Size<Number>, parent_size: Size<Number>) -> AlgoConstants {
212        let dir = self.nodes[node].style.flex_direction;
213        let is_row = dir.is_row();
214        let is_column = dir.is_column();
215        let is_wrap_reverse = self.nodes[node].style.flex_wrap == FlexWrap::WrapReverse;
216
217        let margin = self.nodes[node].style.margin.map(|n| n.resolve(parent_size.width).or_else(0.0));
218        let padding = self.nodes[node].style.padding.map(|n| n.resolve(parent_size.width).or_else(0.0));
219        let border = self.nodes[node].style.border.map(|n| n.resolve(parent_size.width).or_else(0.0));
220
221        let padding_border = Rect {
222            start: padding.start + border.start,
223            end: padding.end + border.end,
224            top: padding.top + border.top,
225            bottom: padding.bottom + border.bottom,
226        };
227
228        let node_inner_size = Size {
229            width: node_size.width - padding_border.horizontal_axis_sum(),
230            height: node_size.height - padding_border.vertical_axis_sum(),
231        };
232
233        let container_size = Size::zero();
234        let inner_container_size = Size::zero();
235
236        AlgoConstants {
237            dir,
238            is_row,
239            is_column,
240            is_wrap_reverse,
241            margin,
242            border,
243            padding_border,
244            node_inner_size,
245            container_size,
246            inner_container_size,
247        }
248    }
249
250    /// Generate anonymous flex items.
251    ///
252    /// # [9.1. Initial Setup](https://www.w3.org/TR/css-flexbox-1/#box-manip)
253    ///
254    /// - [**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).
255    #[inline]
256    fn generate_anonymous_flex_items(&self, node: NodeId, constants: &AlgoConstants) -> Vec<FlexItem> {
257        self.children[node]
258            .iter()
259            .map(|child| (child, &self.nodes[*child].style))
260            .filter(|(_, style)| style.position_type != PositionType::Absolute)
261            .filter(|(_, style)| style.display != Display::None)
262            .map(|(child, child_style)| FlexItem {
263                node: *child,
264                size: child_style.size.resolve(constants.node_inner_size),
265                min_size: child_style.min_size.resolve(constants.node_inner_size),
266                max_size: child_style.max_size.resolve(constants.node_inner_size),
267
268                position: child_style.position.zip_size(constants.node_inner_size, |p, s| p.resolve(s)),
269                margin: child_style.margin.map(|m| m.resolve(constants.node_inner_size.width).or_else(0.0)),
270                padding: child_style.padding.map(|p| p.resolve(constants.node_inner_size.width).or_else(0.0)),
271                border: child_style.border.map(|b| b.resolve(constants.node_inner_size.width).or_else(0.0)),
272
273                flex_basis: 0.0,
274                inner_flex_basis: 0.0,
275                violation: 0.0,
276                frozen: false,
277
278                hypothetical_inner_size: Size::zero(),
279                hypothetical_outer_size: Size::zero(),
280                target_size: Size::zero(),
281                outer_target_size: Size::zero(),
282
283                baseline: 0.0,
284
285                offset_main: 0.0,
286                offset_cross: 0.0,
287            })
288            .collect()
289    }
290
291    /// Determine the available main and cross space for the flex items.
292    ///
293    /// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
294    ///
295    /// - [**Determine the available main and cross space for the flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-available).
296    /// For each dimension, if that dimension of the flex container’s content box is a definite size, use that;
297    /// 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;
298    /// 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.
299    /// **This might result in an infinite value**.
300    #[inline]
301    #[must_use]
302    fn determine_available_space(
303        node_size: Size<Number>,
304        parent_size: Size<Number>,
305        constants: &AlgoConstants,
306    ) -> Size<Number> {
307        Size {
308            width: node_size.width.or_else(parent_size.width - constants.margin.horizontal_axis_sum())
309                - constants.padding_border.horizontal_axis_sum(),
310            height: node_size.height.or_else(parent_size.height - constants.margin.vertical_axis_sum())
311                - constants.padding_border.vertical_axis_sum(),
312        }
313    }
314
315    /// Determine the flex base size and hypothetical main size of each item.
316    ///
317    /// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
318    ///
319    /// - [**Determine the flex base size and hypothetical main size of each item:**](https://www.w3.org/TR/css-flexbox-1/#algo-main-item)
320    ///
321    ///     - A. If the item has a definite used flex basis, that’s the flex base size.
322    ///
323    ///     - B. If the flex item has ...
324    ///
325    ///         - an intrinsic aspect ratio,
326    ///         - a used flex basis of content, and
327    ///         - a definite cross size,
328    ///
329    ///     then the flex base size is calculated from its inner cross size and the flex item’s intrinsic aspect ratio.
330    ///
331    ///     - 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
332    ///         or max-content constraint (e.g. when performing automatic table layout \[CSS21\]), size the item under that constraint.
333    ///         The flex base size is the item’s resulting main size.
334    ///
335    ///     - 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.
336    ///         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,
337    ///         in this calculation use fit-content as the flex item’s cross size. The flex base size is the item’s resulting main size.
338    ///
339    ///     When determining the flex base size, the item’s min and max main sizes are ignored (no clamping occurs).
340    ///     Furthermore, the sizing calculations that floor the content box size at zero when applying box-sizing are also ignored.
341    ///     (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.)
342    #[inline]
343    fn determine_flex_base_size(
344        &mut self,
345        node: NodeId,
346        node_size: Size<Number>,
347        constants: &AlgoConstants,
348        available_space: Size<Number>,
349        flex_items: &mut Vec<FlexItem>,
350    ) {
351        // TODO - this does not follow spec. See the TODOs below
352        for child in flex_items.iter_mut() {
353            let child_style = self.nodes[child.node].style;
354
355            // A. If the item has a definite used flex basis, that’s the flex base size.
356
357            let flex_basis = child_style.flex_basis.resolve(constants.node_inner_size.main(constants.dir));
358            if flex_basis.is_defined() {
359                child.flex_basis = flex_basis.or_else(0.0);
360                continue;
361            };
362
363            // B. If the flex item has an intrinsic aspect ratio,
364            //    a used flex basis of content, and a definite cross size,
365            //    then the flex base size is calculated from its inner
366            //    cross size and the flex item’s intrinsic aspect ratio.
367
368            if let Defined(ratio) = child_style.aspect_ratio {
369                if let Defined(cross) = node_size.cross(constants.dir) {
370                    if child_style.flex_basis == Dimension::Auto {
371                        child.flex_basis = cross * ratio;
372                        continue;
373                    }
374                }
375            }
376
377            // C. If the used flex basis is content or depends on its available space,
378            //    and the flex container is being sized under a min-content or max-content
379            //    constraint (e.g. when performing automatic table layout [CSS21]),
380            //    size the item under that constraint. The flex base size is the item’s
381            //    resulting main size.
382
383            // TODO - Probably need to cover this case in future
384
385            // D. Otherwise, if the used flex basis is content or depends on its
386            //    available space, the available main size is infinite, and the flex item’s
387            //    inline axis is parallel to the main axis, lay the item out using the rules
388            //    for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
389            //    is the item’s max-content main size.
390
391            // TODO - Probably need to cover this case in future
392
393            // E. Otherwise, size the item into the available space using its used flex basis
394            //    in place of its main size, treating a value of content as max-content.
395            //    If a cross size is needed to determine the main size (e.g. when the
396            //    flex item’s main size is in its block axis) and the flex item’s cross size
397            //    is auto and not definite, in this calculation use fit-content as the
398            //    flex item’s cross size. The flex base size is the item’s resulting main size.
399
400            let width: Number = if !child.size.width.is_defined()
401                && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
402                && constants.is_column
403            {
404                available_space.width
405            } else {
406                child.size.width
407            };
408
409            let height: Number = if !child.size.height.is_defined()
410                && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
411                && constants.is_row
412            {
413                available_space.height
414            } else {
415                child.size.height
416            };
417
418            child.flex_basis = self
419                .compute_preliminary(
420                    child.node,
421                    Size {
422                        width: width.maybe_min(child.max_size.width),
423                        height: height.maybe_min(child.max_size.height),
424                    },
425                    available_space,
426                    false,
427                    true,
428                )
429                .main(constants.dir)
430                .maybe_min(child.max_size.main(constants.dir));
431        }
432
433        // The hypothetical main size is the item’s flex base size clamped according to its
434        // used min and max main sizes (and flooring the content box size at zero).
435
436        for child in flex_items {
437            child.inner_flex_basis = child.flex_basis
438                - child.padding.main_axis_sum(constants.dir)
439                - child.border.main_axis_sum(constants.dir);
440
441            // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
442            // The following logic was developed not from the spec but by trail and error looking into how
443            // webkit handled various scenarios. Can probably be solved better by passing in
444            // min-content max-content constraints from the top
445            let min_main = self
446                .compute_preliminary(child.node, Size::undefined(), available_space, false, false)
447                .main(constants.dir)
448                .maybe_max(child.min_size.main(constants.dir))
449                .maybe_min(child.size.main(constants.dir))
450                .into();
451
452            child.hypothetical_inner_size.set_main(
453                constants.dir,
454                child.flex_basis.maybe_max(min_main).maybe_min(child.max_size.main(constants.dir)),
455            );
456
457            child.hypothetical_outer_size.set_main(
458                constants.dir,
459                child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
460            );
461        }
462    }
463
464    /// Collect flex items into flex lines.
465    ///
466    /// # [9.3. Main Size Determination](https://www.w3.org/TR/css-flexbox-1/#main-sizing)
467    ///
468    /// - [**Collect flex items into flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-break):
469    ///
470    ///     - If the flex container is single-line, collect all the flex items into a single flex line.
471    ///
472    ///     - 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
473    ///         (or until a forced break is encountered, see [§10 Fragmenting Flex Layout](https://www.w3.org/TR/css-flexbox-1/#pagination)).
474    ///         If the very first uncollected item wouldn’t fit, collect just it into the line.
475    ///         
476    ///         For this step, the size of a flex item is its outer hypothetical main size. (**Note: This can be negative**.)
477    ///
478    ///         Repeat until all flex items have been collected into flex lines.
479    ///         
480    ///         **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**.
481    #[inline]
482    fn collect_flex_lines<'a>(
483        &self,
484        node: NodeId,
485        constants: &AlgoConstants,
486        available_space: Size<Number>,
487        flex_items: &'a mut Vec<FlexItem>,
488    ) -> Vec<FlexLine<'a>> {
489        let mut lines = crate::sys::new_vec_with_capacity(1);
490
491        if self.nodes[node].style.flex_wrap == FlexWrap::NoWrap {
492            lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
493        } else {
494            let mut flex_items = &mut flex_items[..];
495
496            while !flex_items.is_empty() {
497                let mut line_length = 0.0;
498                let index = flex_items
499                    .iter()
500                    .enumerate()
501                    .find(|&(idx, child)| {
502                        line_length += child.hypothetical_outer_size.main(constants.dir);
503                        if let Defined(main) = available_space.main(constants.dir) {
504                            line_length > main && idx != 0
505                        } else {
506                            false
507                        }
508                    })
509                    .map(|(idx, _)| idx)
510                    .unwrap_or(flex_items.len());
511
512                let (items, rest) = flex_items.split_at_mut(index);
513                lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
514                flex_items = rest;
515            }
516        }
517
518        lines
519    }
520
521    /// Resolve the flexible lengths of the items within a flex line.
522    ///
523    /// # [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
524    #[inline]
525    fn resolve_flexible_lengths(
526        &mut self,
527        line: &mut FlexLine,
528        constants: &AlgoConstants,
529        available_space: Size<Number>,
530    ) {
531        // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
532        //    items on the line. If the sum is less than the flex container’s inner main size,
533        //    use the flex grow factor for the rest of this algorithm; otherwise, use the
534        //    flex shrink factor.
535
536        let used_flex_factor: f32 =
537            line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum();
538        let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).or_else(0.0);
539        let shrinking = !growing;
540
541        // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
542        //    - Any item that has a flex factor of zero
543        //    - If using the flex grow factor: any item that has a flex base size
544        //      greater than its hypothetical main size
545        //    - If using the flex shrink factor: any item that has a flex base size
546        //      smaller than its hypothetical main size
547
548        for child in line.items.iter_mut() {
549            // TODO - This is not found by reading the spec. Maybe this can be done in some other place
550            // instead. This was found by trail and error fixing tests to align with webkit output.
551            if constants.node_inner_size.main(constants.dir).is_undefined() && constants.is_row {
552                child.target_size.set_main(
553                    constants.dir,
554                    self.compute_preliminary(
555                        child.node,
556                        Size {
557                            width: child.size.width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
558                            height: child.size.height.maybe_max(child.min_size.height).maybe_min(child.max_size.height),
559                        },
560                        available_space,
561                        false,
562                        false,
563                    )
564                    .main(constants.dir)
565                    .maybe_max(child.min_size.main(constants.dir))
566                    .maybe_min(child.max_size.main(constants.dir)),
567                );
568            } else {
569                child.target_size.set_main(constants.dir, child.hypothetical_inner_size.main(constants.dir));
570            }
571
572            // TODO this should really only be set inside the if-statement below but
573            // that causes the target_main_size to never be set for some items
574
575            child.outer_target_size.set_main(
576                constants.dir,
577                child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
578            );
579
580            let child_style = &self.nodes[child.node].style;
581            if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0)
582                || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
583                || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
584            {
585                child.frozen = true;
586            }
587        }
588
589        // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
590        //    and subtract this from the flex container’s inner main size. For frozen items,
591        //    use their outer target main size; for other items, use their outer flex base size.
592
593        let used_space: f32 = line
594            .items
595            .iter()
596            .map(|child| {
597                child.margin.main_axis_sum(constants.dir)
598                    + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
599            })
600            .sum();
601
602        let initial_free_space = (constants.node_inner_size.main(constants.dir) - used_space).or_else(0.0);
603
604        // 4. Loop
605
606        loop {
607            // a. Check for flexible items. If all the flex items on the line are frozen,
608            //    free space has been distributed; exit this loop.
609
610            if line.items.iter().all(|child| child.frozen) {
611                break;
612            }
613
614            // b. Calculate the remaining free space as for initial free space, above.
615            //    If the sum of the unfrozen flex items’ flex factors is less than one,
616            //    multiply the initial free space by this sum. If the magnitude of this
617            //    value is less than the magnitude of the remaining free space, use this
618            //    as the remaining free space.
619
620            let used_space: f32 = line
621                .items
622                .iter()
623                .map(|child| {
624                    child.margin.main_axis_sum(constants.dir)
625                        + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
626                })
627                .sum();
628
629            let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
630
631            let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
632                unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
633                    let style = &self.nodes[item.node].style;
634                    (flex_grow + style.flex_grow, flex_shrink + style.flex_shrink)
635                });
636
637            let free_space = if growing && sum_flex_grow < 1.0 {
638                (initial_free_space * sum_flex_grow)
639                    .maybe_min(constants.node_inner_size.main(constants.dir) - used_space)
640            } else if shrinking && sum_flex_shrink < 1.0 {
641                (initial_free_space * sum_flex_shrink)
642                    .maybe_max(constants.node_inner_size.main(constants.dir) - used_space)
643            } else {
644                (constants.node_inner_size.main(constants.dir) - used_space).or_else(0.0)
645            };
646
647            // c. Distribute free space proportional to the flex factors.
648            //    - If the remaining free space is zero
649            //        Do Nothing
650            //    - If using the flex grow factor
651            //        Find the ratio of the item’s flex grow factor to the sum of the
652            //        flex grow factors of all unfrozen items on the line. Set the item’s
653            //        target main size to its flex base size plus a fraction of the remaining
654            //        free space proportional to the ratio.
655            //    - If using the flex shrink factor
656            //        For every unfrozen item on the line, multiply its flex shrink factor by
657            //        its inner flex base size, and note this as its scaled flex shrink factor.
658            //        Find the ratio of the item’s scaled flex shrink factor to the sum of the
659            //        scaled flex shrink factors of all unfrozen items on the line. Set the item’s
660            //        target main size to its flex base size minus a fraction of the absolute value
661            //        of the remaining free space proportional to the ratio. Note this may result
662            //        in a negative inner main size; it will be corrected in the next step.
663            //    - Otherwise
664            //        Do Nothing
665
666            if free_space.is_normal() {
667                if growing && sum_flex_grow > 0.0 {
668                    for child in &mut unfrozen {
669                        child.target_size.set_main(
670                            constants.dir,
671                            child.flex_basis + free_space * (self.nodes[child.node].style.flex_grow / sum_flex_grow),
672                        );
673                    }
674                } else if shrinking && sum_flex_shrink > 0.0 {
675                    let sum_scaled_shrink_factor: f32 = unfrozen
676                        .iter()
677                        .map(|child| child.inner_flex_basis * self.nodes[child.node].style.flex_shrink)
678                        .sum();
679
680                    if sum_scaled_shrink_factor > 0.0 {
681                        for child in &mut unfrozen {
682                            let scaled_shrink_factor =
683                                child.inner_flex_basis * self.nodes[child.node].style.flex_shrink;
684                            child.target_size.set_main(
685                                constants.dir,
686                                child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
687                            )
688                        }
689                    }
690                }
691            }
692
693            // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
694            //    used min and max main sizes and floor its content-box size at zero. If the
695            //    item’s target main size was made smaller by this, it’s a max violation.
696            //    If the item’s target main size was made larger by this, it’s a min violation.
697
698            let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
699                // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
700                // The following logic was developed not from the spec but by trail and error looking into how
701                // webkit handled various scenarios. Can probably be solved better by passing in
702                // min-content max-content constraints from the top. Need to figure out correct thing to do here as
703                // just piling on more conditionals.
704                let min_main = if constants.is_row && self.nodes[child.node].measure.is_none() {
705                    self.compute_preliminary(child.node, Size::undefined(), available_space, false, false)
706                        .width
707                        .maybe_min(child.size.width)
708                        .maybe_max(child.min_size.width)
709                        .into()
710                } else {
711                    child.min_size.main(constants.dir)
712                };
713
714                let max_main = child.max_size.main(constants.dir);
715                let clamped = child.target_size.main(constants.dir).maybe_min(max_main).maybe_max(min_main).max(0.0);
716                child.violation = clamped - child.target_size.main(constants.dir);
717                child.target_size.set_main(constants.dir, clamped);
718                child.outer_target_size.set_main(
719                    constants.dir,
720                    child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
721                );
722
723                acc + child.violation
724            });
725
726            // e. Freeze over-flexed items. The total violation is the sum of the adjustments
727            //    from the previous step ∑(clamped size - unclamped size). If the total violation is:
728            //    - Zero
729            //        Freeze all items.
730            //    - Positive
731            //        Freeze all the items with min violations.
732            //    - Negative
733            //        Freeze all the items with max violations.
734
735            for child in &mut unfrozen {
736                match total_violation {
737                    v if v > 0.0 => child.frozen = child.violation > 0.0,
738                    v if v < 0.0 => child.frozen = child.violation < 0.0,
739                    _ => child.frozen = true,
740                }
741            }
742
743            // f. Return to the start of this loop.
744        }
745    }
746
747    /// Determine the hypothetical cross size of each item.
748    ///
749    /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
750    ///
751    /// - [**Determine the hypothetical cross size of each item**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-item)
752    ///     by performing layout with the used main size and the available space, treating auto as fit-content.
753    #[inline]
754    fn determine_hypothetical_cross_size(
755        &mut self,
756        line: &mut FlexLine,
757        constants: &AlgoConstants,
758        available_space: Size<Number>,
759    ) {
760        for child in line.items.iter_mut() {
761            let child_cross = child
762                .size
763                .cross(constants.dir)
764                .maybe_max(child.min_size.cross(constants.dir))
765                .maybe_min(child.max_size.cross(constants.dir));
766
767            child.hypothetical_inner_size.set_cross(
768                constants.dir,
769                self.compute_preliminary(
770                    child.node,
771                    Size {
772                        width: if constants.is_row { child.target_size.width.into() } else { child_cross },
773                        height: if constants.is_row { child_cross } else { child.target_size.height.into() },
774                    },
775                    Size {
776                        width: if constants.is_row {
777                            constants.container_size.main(constants.dir).into()
778                        } else {
779                            available_space.width
780                        },
781                        height: if constants.is_row {
782                            available_space.height
783                        } else {
784                            constants.container_size.main(constants.dir).into()
785                        },
786                    },
787                    false,
788                    false,
789                )
790                .cross(constants.dir)
791                .maybe_max(child.min_size.cross(constants.dir))
792                .maybe_min(child.max_size.cross(constants.dir)),
793            );
794
795            child.hypothetical_outer_size.set_cross(
796                constants.dir,
797                child.hypothetical_inner_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
798            );
799        }
800    }
801
802    /// Calculate the base lines of the children.
803    #[inline]
804    fn calculate_children_base_lines(
805        &mut self,
806        node: NodeId,
807        node_size: Size<Number>,
808        flex_lines: &mut [FlexLine],
809        constants: &AlgoConstants,
810    ) {
811        /// Recursively caluculates the baseline for children
812        fn calc_baseline(db: &Forest, node: NodeId, layout: &Layout) -> f32 {
813            if db.children[node].is_empty() {
814                layout.size.height
815            } else {
816                let child = db.children[node][0];
817                calc_baseline(db, child, &db.nodes[child].layout)
818            }
819        }
820
821        for line in flex_lines {
822            for child in line.items.iter_mut() {
823                let preliminary_size = self.compute_preliminary(
824                    child.node,
825                    Size {
826                        width: if constants.is_row {
827                            child.target_size.width.into()
828                        } else {
829                            child.hypothetical_inner_size.width.into()
830                        },
831                        height: if constants.is_row {
832                            child.hypothetical_inner_size.height.into()
833                        } else {
834                            child.target_size.height.into()
835                        },
836                    },
837                    Size {
838                        width: if constants.is_row { constants.container_size.width.into() } else { node_size.width },
839                        height: if constants.is_row {
840                            node_size.height
841                        } else {
842                            constants.container_size.height.into()
843                        },
844                    },
845                    true,
846                    false,
847                );
848
849                child.baseline = calc_baseline(
850                    self,
851                    child.node,
852                    &Layout {
853                        order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32,
854                        size: preliminary_size,
855                        location: Point::zero(),
856                    },
857                );
858            }
859        }
860    }
861
862    /// Calculate the cross size of each flex line.
863    ///
864    /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
865    ///
866    /// - [**Calculate the cross size of each flex line**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-line).
867    ///
868    ///     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.
869    ///         
870    ///     Otherwise, for each flex line:
871    ///
872    ///     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.
873    ///         Find the largest of the distances between each item’s baseline and its hypothetical outer cross-start edge,
874    ///         and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, and sum these two values.
875    ///
876    ///     2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size.
877    ///
878    ///     3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero.
879    ///         
880    ///         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.
881    ///         **Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically**.
882    #[inline]
883    fn calculate_cross_size(
884        &mut self,
885        flex_lines: &mut [FlexLine],
886        node: NodeId,
887        node_size: Size<Number>,
888        constants: &AlgoConstants,
889    ) {
890        if flex_lines.len() == 1 && node_size.cross(constants.dir).is_defined() {
891            flex_lines[0].cross_size =
892                (node_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir)).or_else(0.0);
893        } else {
894            for line in flex_lines.iter_mut() {
895                //    1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
896                //       align-self is baseline, and whose cross-axis margins are both non-auto. Find the
897                //       largest of the distances between each item’s baseline and its hypothetical outer
898                //       cross-start edge, and the largest of the distances between each item’s baseline
899                //       and its hypothetical outer cross-end edge, and sum these two values.
900
901                //    2. Among all the items not collected by the previous step, find the largest
902                //       outer hypothetical cross size.
903
904                //    3. The used cross-size of the flex line is the largest of the numbers found in the
905                //       previous two steps and zero.
906
907                let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
908                line.cross_size = line
909                    .items
910                    .iter()
911                    .map(|child| {
912                        let child_style = &self.nodes[child.node].style;
913                        if child_style.align_self(&self.nodes[node].style) == AlignSelf::Baseline
914                            && child_style.cross_margin_start(constants.dir) != Dimension::Auto
915                            && child_style.cross_margin_end(constants.dir) != Dimension::Auto
916                            && child_style.cross_size(constants.dir) == Dimension::Auto
917                        {
918                            max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
919                        } else {
920                            child.hypothetical_outer_size.cross(constants.dir)
921                        }
922                    })
923                    .fold(0.0, |acc, x| acc.max(x));
924            }
925        }
926    }
927
928    /// Handle 'align-content: stretch'.
929    ///
930    /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
931    ///
932    /// - [**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,
933    ///     and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size,
934    ///     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.
935    #[inline]
936    fn handle_align_content_stretch(
937        &mut self,
938        flex_lines: &mut [FlexLine],
939        node: NodeId,
940        node_size: Size<Number>,
941        constants: &AlgoConstants,
942    ) {
943        if self.nodes[node].style.align_content == AlignContent::Stretch && node_size.cross(constants.dir).is_defined()
944        {
945            let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
946            let inner_cross =
947                (node_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir)).or_else(0.0);
948
949            if total_cross < inner_cross {
950                let remaining = inner_cross - total_cross;
951                let addition = remaining / flex_lines.len() as f32;
952                flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
953            }
954        }
955    }
956
957    /// Determine the used cross size of each flex item.
958    ///
959    /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
960    ///
961    /// - [**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,
962    ///     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.
963    ///     Otherwise, the used cross size is the item’s hypothetical cross size.
964    ///
965    ///     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.
966    ///
967    ///     **Note that this step does not affect the main size of the flex item, even if it has an intrinsic aspect ratio**.
968    #[inline]
969    fn determine_used_cross_size(&mut self, flex_lines: &mut [FlexLine], node: NodeId, constants: &AlgoConstants) {
970        for line in flex_lines {
971            let line_cross_size = line.cross_size;
972
973            for child in line.items.iter_mut() {
974                let child_style = &self.nodes[child.node].style;
975                child.target_size.set_cross(
976                    constants.dir,
977                    if child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
978                        && child_style.cross_margin_start(constants.dir) != Dimension::Auto
979                        && child_style.cross_margin_end(constants.dir) != Dimension::Auto
980                        && child_style.cross_size(constants.dir) == Dimension::Auto
981                    {
982                        (line_cross_size - child.margin.cross_axis_sum(constants.dir))
983                            .maybe_max(child.min_size.cross(constants.dir))
984                            .maybe_min(child.max_size.cross(constants.dir))
985                    } else {
986                        child.hypothetical_inner_size.cross(constants.dir)
987                    },
988                );
989
990                child.outer_target_size.set_cross(
991                    constants.dir,
992                    child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
993                );
994            }
995        }
996    }
997
998    /// Distribute any remaining free space.
999    ///
1000    /// # [9.5. Main-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#main-alignment)
1001    ///
1002    /// - [**Distribute any remaining free space**](https://www.w3.org/TR/css-flexbox-1/#algo-main-align). For each flex line:
1003    ///
1004    ///     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.
1005    ///         Otherwise, set all `auto` margins to zero.
1006    ///
1007    ///     2. Align the items along the main-axis per `justify-content`.
1008    #[inline]
1009    fn distribute_remaining_free_space(
1010        &mut self,
1011        flex_lines: &mut [FlexLine],
1012        node: NodeId,
1013        constants: &AlgoConstants,
1014    ) {
1015        for line in flex_lines {
1016            let used_space: f32 = line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum();
1017            let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1018            let mut num_auto_margins = 0;
1019
1020            for child in line.items.iter_mut() {
1021                let child_style = &self.nodes[child.node].style;
1022                if child_style.main_margin_start(constants.dir) == Dimension::Auto {
1023                    num_auto_margins += 1;
1024                }
1025                if child_style.main_margin_end(constants.dir) == Dimension::Auto {
1026                    num_auto_margins += 1;
1027                }
1028            }
1029
1030            if free_space > 0.0 && num_auto_margins > 0 {
1031                let margin = free_space / num_auto_margins as f32;
1032
1033                for child in line.items.iter_mut() {
1034                    let child_style = &self.nodes[child.node].style;
1035                    if child_style.main_margin_start(constants.dir) == Dimension::Auto {
1036                        if constants.is_row {
1037                            child.margin.start = margin;
1038                        } else {
1039                            child.margin.top = margin;
1040                        }
1041                    }
1042                    if child_style.main_margin_end(constants.dir) == Dimension::Auto {
1043                        if constants.is_row {
1044                            child.margin.end = margin;
1045                        } else {
1046                            child.margin.bottom = margin;
1047                        }
1048                    }
1049                }
1050            } else {
1051                let num_items = line.items.len();
1052                let layout_reverse = constants.dir.is_reverse();
1053
1054                let justify_item = |(i, child): (usize, &mut FlexItem)| {
1055                    let is_first = i == 0;
1056
1057                    child.offset_main = match self.nodes[node].style.justify_content {
1058                        JustifyContent::FlexStart => {
1059                            if layout_reverse && is_first {
1060                                free_space
1061                            } else {
1062                                0.0
1063                            }
1064                        }
1065                        JustifyContent::Center => {
1066                            if is_first {
1067                                free_space / 2.0
1068                            } else {
1069                                0.0
1070                            }
1071                        }
1072                        JustifyContent::FlexEnd => {
1073                            if is_first && !layout_reverse {
1074                                free_space
1075                            } else {
1076                                0.0
1077                            }
1078                        }
1079                        JustifyContent::SpaceBetween => {
1080                            if is_first {
1081                                0.0
1082                            } else {
1083                                free_space / (num_items - 1) as f32
1084                            }
1085                        }
1086                        JustifyContent::SpaceAround => {
1087                            if is_first {
1088                                (free_space / num_items as f32) / 2.0
1089                            } else {
1090                                free_space / num_items as f32
1091                            }
1092                        }
1093                        JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32,
1094                    };
1095                };
1096
1097                if layout_reverse {
1098                    line.items.iter_mut().rev().enumerate().for_each(justify_item);
1099                } else {
1100                    line.items.iter_mut().enumerate().for_each(justify_item);
1101                }
1102            }
1103        }
1104    }
1105
1106    /// Resolve cross-axis `auto` margins.
1107    ///
1108    /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1109    ///
1110    /// - [**Resolve cross-axis `auto` margins**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-margins).
1111    ///     If a flex item has auto cross-axis margins:
1112    ///     
1113    ///     - If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line,
1114    ///         distribute the difference in those sizes equally to the auto margins.
1115    ///     
1116    ///     - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
1117    ///         Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
1118    #[inline]
1119    fn resolve_cross_axis_auto_margins(
1120        &mut self,
1121        flex_lines: &mut [FlexLine],
1122        node: NodeId,
1123        constants: &AlgoConstants,
1124    ) {
1125        for line in flex_lines {
1126            let line_cross_size = line.cross_size;
1127            let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1128
1129            for child in line.items.iter_mut() {
1130                let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1131                let child_style = &self.nodes[child.node].style;
1132
1133                if child_style.cross_margin_start(constants.dir) == Dimension::Auto
1134                    && child_style.cross_margin_end(constants.dir) == Dimension::Auto
1135                {
1136                    if constants.is_row {
1137                        child.margin.top = free_space / 2.0;
1138                        child.margin.bottom = free_space / 2.0;
1139                    } else {
1140                        child.margin.start = free_space / 2.0;
1141                        child.margin.end = free_space / 2.0;
1142                    }
1143                } else if child_style.cross_margin_start(constants.dir) == Dimension::Auto {
1144                    if constants.is_row {
1145                        child.margin.top = free_space;
1146                    } else {
1147                        child.margin.start = free_space;
1148                    }
1149                } else if child_style.cross_margin_end(constants.dir) == Dimension::Auto {
1150                    if constants.is_row {
1151                        child.margin.bottom = free_space;
1152                    } else {
1153                        child.margin.end = free_space;
1154                    }
1155                } else {
1156                    // 14. Align all flex items along the cross-axis.
1157                    child.offset_cross = self.align_flex_items_along_cross_axis(
1158                        node,
1159                        child,
1160                        child_style,
1161                        free_space,
1162                        max_baseline,
1163                        constants,
1164                    );
1165                }
1166            }
1167        }
1168    }
1169
1170    /// Align all flex items along the cross-axis.
1171    ///
1172    /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1173    ///
1174    /// - [**Align all flex items along the cross-axis**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-align) per `align-self`,
1175    ///     if neither of the item's cross-axis margins are `auto`.
1176    #[inline]
1177    fn align_flex_items_along_cross_axis(
1178        &self,
1179        node: NodeId,
1180        child: &mut FlexItem,
1181        child_style: &FlexboxLayout,
1182        free_space: f32,
1183        max_baseline: f32,
1184        constants: &AlgoConstants,
1185    ) -> f32 {
1186        match child_style.align_self(&self.nodes[node].style) {
1187            AlignSelf::Auto => 0.0, // Should never happen
1188            AlignSelf::FlexStart => {
1189                if constants.is_wrap_reverse {
1190                    free_space
1191                } else {
1192                    0.0
1193                }
1194            }
1195            AlignSelf::FlexEnd => {
1196                if constants.is_wrap_reverse {
1197                    0.0
1198                } else {
1199                    free_space
1200                }
1201            }
1202            AlignSelf::Center => free_space / 2.0,
1203            AlignSelf::Baseline => {
1204                if constants.is_row {
1205                    max_baseline - child.baseline
1206                } else {
1207                    // baseline alignment only makes sense if the constants.direction is row
1208                    // we treat it as flex-start alignment in columns.
1209                    if constants.is_wrap_reverse {
1210                        free_space
1211                    } else {
1212                        0.0
1213                    }
1214                }
1215            }
1216            AlignSelf::Stretch => {
1217                if constants.is_wrap_reverse {
1218                    free_space
1219                } else {
1220                    0.0
1221                }
1222            }
1223        }
1224    }
1225
1226    /// Determine the flex container’s used cross size.
1227    ///
1228    /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1229    ///
1230    /// - [**Determine the flex container’s used cross size**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-container):
1231    ///     
1232    ///     - If the cross size property is a definite size, use that, clamped by the used min and max cross sizes of the flex container.
1233    ///
1234    ///     - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used min and max cross sizes of the flex container.
1235    #[inline]
1236    #[must_use]
1237    fn determine_container_cross_size(
1238        flex_lines: &mut [FlexLine],
1239        node_size: Size<Number>,
1240        constants: &mut AlgoConstants,
1241    ) -> f32 {
1242        let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
1243
1244        constants.container_size.set_cross(
1245            constants.dir,
1246            node_size
1247                .cross(constants.dir)
1248                .or_else(total_cross_size + constants.padding_border.cross_axis_sum(constants.dir)),
1249        );
1250
1251        constants.inner_container_size.set_cross(
1252            constants.dir,
1253            constants.container_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir),
1254        );
1255
1256        total_cross_size
1257    }
1258
1259    /// Align all flex lines per `align-content`.
1260    ///
1261    /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1262    ///
1263    /// - [**Align all flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-align) per `align-content`.
1264    #[inline]
1265    fn align_flex_lines_per_align_content(
1266        &self,
1267        flex_lines: &mut [FlexLine],
1268        node: NodeId,
1269        constants: &AlgoConstants,
1270        total_cross_size: f32,
1271    ) {
1272        let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size;
1273        let num_lines = flex_lines.len();
1274
1275        let align_line = |(i, line): (usize, &mut FlexLine)| {
1276            let is_first = i == 0;
1277
1278            line.offset_cross = match self.nodes[node].style.align_content {
1279                AlignContent::FlexStart => {
1280                    if is_first && constants.is_wrap_reverse {
1281                        free_space
1282                    } else {
1283                        0.0
1284                    }
1285                }
1286                AlignContent::FlexEnd => {
1287                    if is_first && !constants.is_wrap_reverse {
1288                        free_space
1289                    } else {
1290                        0.0
1291                    }
1292                }
1293                AlignContent::Center => {
1294                    if is_first {
1295                        free_space / 2.0
1296                    } else {
1297                        0.0
1298                    }
1299                }
1300                AlignContent::Stretch => 0.0,
1301                AlignContent::SpaceBetween => {
1302                    if is_first {
1303                        0.0
1304                    } else {
1305                        free_space / (num_lines - 1) as f32
1306                    }
1307                }
1308                AlignContent::SpaceAround => {
1309                    if is_first {
1310                        (free_space / num_lines as f32) / 2.0
1311                    } else {
1312                        free_space / num_lines as f32
1313                    }
1314                }
1315            };
1316        };
1317
1318        if constants.is_wrap_reverse {
1319            flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1320        } else {
1321            flex_lines.iter_mut().enumerate().for_each(align_line);
1322        }
1323    }
1324
1325    /// Do a final layout pass and collect the resulting layouts.
1326    #[inline]
1327    fn final_layout_pass(&mut self, node: NodeId, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1328        let mut total_offset_cross = constants.padding_border.cross_start(constants.dir);
1329
1330        let layout_line = |line: &mut FlexLine| {
1331            let mut total_offset_main = constants.padding_border.main_start(constants.dir);
1332            let line_offset_cross = line.offset_cross;
1333
1334            let layout_item = |child: &mut FlexItem| {
1335                let preliminary_size = self.compute_preliminary(
1336                    child.node,
1337                    child.target_size.map(|s| s.into()),
1338                    constants.container_size.map(|s| s.into()),
1339                    true,
1340                    false,
1341                );
1342
1343                let offset_main = total_offset_main
1344                    + child.offset_main
1345                    + child.margin.main_start(constants.dir)
1346                    + (child.position.main_start(constants.dir).or_else(0.0)
1347                        - child.position.main_end(constants.dir).or_else(0.0));
1348
1349                let offset_cross = total_offset_cross
1350                    + child.offset_cross
1351                    + line_offset_cross
1352                    + child.margin.cross_start(constants.dir)
1353                    + (child.position.cross_start(constants.dir).or_else(0.0)
1354                        - child.position.cross_end(constants.dir).or_else(0.0));
1355
1356                self.nodes[child.node].layout = Layout {
1357                    order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32,
1358                    size: preliminary_size,
1359                    location: Point {
1360                        x: if constants.is_row { offset_main } else { offset_cross },
1361                        y: if constants.is_column { offset_main } else { offset_cross },
1362                    },
1363                };
1364
1365                total_offset_main += child.offset_main
1366                    + child.margin.main_axis_sum(constants.dir)
1367                    + preliminary_size.main(constants.dir);
1368            };
1369
1370            if constants.dir.is_reverse() {
1371                line.items.iter_mut().rev().for_each(layout_item);
1372            } else {
1373                line.items.iter_mut().for_each(layout_item);
1374            }
1375
1376            total_offset_cross += line_offset_cross + line.cross_size;
1377        };
1378
1379        if constants.is_wrap_reverse {
1380            flex_lines.iter_mut().rev().for_each(layout_line);
1381        } else {
1382            flex_lines.iter_mut().for_each(layout_line);
1383        }
1384    }
1385
1386    /// Perform absolute layout on all absolutely positioned children.
1387    #[inline]
1388    fn perform_absolute_layout_on_absolute_children(&mut self, node: NodeId, constants: &AlgoConstants) {
1389        // TODO: remove number of Vec<_> generated
1390        let candidates = self.children[node]
1391            .iter()
1392            .cloned()
1393            .enumerate()
1394            .filter(|(_, child)| self.nodes[*child].style.position_type == PositionType::Absolute)
1395            .collect::<Vec<_>>();
1396
1397        for (order, child) in candidates {
1398            let container_width = constants.container_size.width.into();
1399            let container_height = constants.container_size.height.into();
1400
1401            let child_style = self.nodes[child].style;
1402
1403            let start =
1404                child_style.position.start.resolve(container_width) + child_style.margin.start.resolve(container_width);
1405            let end =
1406                child_style.position.end.resolve(container_width) + child_style.margin.end.resolve(container_width);
1407            let top =
1408                child_style.position.top.resolve(container_height) + child_style.margin.top.resolve(container_height);
1409            let bottom = child_style.position.bottom.resolve(container_height)
1410                + child_style.margin.bottom.resolve(container_height);
1411
1412            let (start_main, end_main) = if constants.is_row { (start, end) } else { (top, bottom) };
1413            let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (start, end) };
1414
1415            let width = child_style
1416                .size
1417                .width
1418                .resolve(container_width)
1419                .maybe_max(child_style.min_size.width.resolve(container_width))
1420                .maybe_min(child_style.max_size.width.resolve(container_width))
1421                .or_else(if start.is_defined() && end.is_defined() {
1422                    container_width - start - end
1423                } else {
1424                    Undefined
1425                });
1426
1427            let height = child_style
1428                .size
1429                .height
1430                .resolve(container_height)
1431                .maybe_max(child_style.min_size.height.resolve(container_height))
1432                .maybe_min(child_style.max_size.height.resolve(container_height))
1433                .or_else(if top.is_defined() && bottom.is_defined() {
1434                    container_height - top - bottom
1435                } else {
1436                    Undefined
1437                });
1438
1439            let preliminary_size = self.compute_preliminary(
1440                child,
1441                Size { width, height },
1442                Size { width: container_width, height: container_height },
1443                true,
1444                false,
1445            );
1446
1447            let free_main_space = constants.container_size.main(constants.dir)
1448                - preliminary_size
1449                    .main(constants.dir)
1450                    .maybe_max(
1451                        child_style.min_main_size(constants.dir).resolve(constants.node_inner_size.main(constants.dir)),
1452                    )
1453                    .maybe_min(
1454                        child_style.max_main_size(constants.dir).resolve(constants.node_inner_size.main(constants.dir)),
1455                    );
1456
1457            let free_cross_space = constants.container_size.cross(constants.dir)
1458                - preliminary_size
1459                    .cross(constants.dir)
1460                    .maybe_max(
1461                        child_style
1462                            .min_cross_size(constants.dir)
1463                            .resolve(constants.node_inner_size.cross(constants.dir)),
1464                    )
1465                    .maybe_min(
1466                        child_style
1467                            .max_cross_size(constants.dir)
1468                            .resolve(constants.node_inner_size.cross(constants.dir)),
1469                    );
1470
1471            let offset_main = if start_main.is_defined() {
1472                start_main.or_else(0.0) + constants.border.main_start(constants.dir)
1473            } else if end_main.is_defined() {
1474                free_main_space - end_main.or_else(0.0) - constants.border.main_end(constants.dir)
1475            } else {
1476                match self.nodes[node].style.justify_content {
1477                    JustifyContent::SpaceBetween | JustifyContent::FlexStart => {
1478                        constants.padding_border.main_start(constants.dir)
1479                    }
1480                    JustifyContent::FlexEnd => free_main_space - constants.padding_border.main_end(constants.dir),
1481                    JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => {
1482                        free_main_space / 2.0
1483                    }
1484                }
1485            };
1486
1487            let offset_cross = if start_cross.is_defined() {
1488                start_cross.or_else(0.0) + constants.border.cross_start(constants.dir)
1489            } else if end_cross.is_defined() {
1490                free_cross_space - end_cross.or_else(0.0) - constants.border.cross_end(constants.dir)
1491            } else {
1492                match child_style.align_self(&self.nodes[node].style) {
1493                    AlignSelf::Auto => 0.0, // Should never happen
1494                    AlignSelf::FlexStart => {
1495                        if constants.is_wrap_reverse {
1496                            free_cross_space - constants.padding_border.cross_end(constants.dir)
1497                        } else {
1498                            constants.padding_border.cross_start(constants.dir)
1499                        }
1500                    }
1501                    AlignSelf::FlexEnd => {
1502                        if constants.is_wrap_reverse {
1503                            constants.padding_border.cross_start(constants.dir)
1504                        } else {
1505                            free_cross_space - constants.padding_border.cross_end(constants.dir)
1506                        }
1507                    }
1508                    AlignSelf::Center => free_cross_space / 2.0,
1509                    AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support
1510                    AlignSelf::Stretch => {
1511                        if constants.is_wrap_reverse {
1512                            free_cross_space - constants.padding_border.cross_end(constants.dir)
1513                        } else {
1514                            constants.padding_border.cross_start(constants.dir)
1515                        }
1516                    }
1517                }
1518            };
1519
1520            self.nodes[child].layout = Layout {
1521                order: order as u32,
1522                size: preliminary_size,
1523                location: Point {
1524                    x: if constants.is_row { offset_main } else { offset_cross },
1525                    y: if constants.is_column { offset_main } else { offset_cross },
1526                },
1527            };
1528        }
1529    }
1530
1531    /// Compute a preliminary size for an item
1532    fn compute_preliminary(
1533        &mut self,
1534        node: NodeId,
1535        node_size: Size<Number>,
1536        parent_size: Size<Number>,
1537        perform_layout: bool,
1538        main_size: bool,
1539    ) -> Size<f32> {
1540        self.nodes[node].is_dirty = false;
1541
1542        // First we check if we have a result for the given input
1543        if let Some(cached_size) = self.compute_from_cache(node, node_size, parent_size, perform_layout, main_size) {
1544            return cached_size;
1545        }
1546
1547        // Define some general constants we will need for the remainder of the algorithm.
1548        let mut constants = self.compute_constants(node, node_size, parent_size);
1549
1550        // If this is a leaf node we can skip a lot of this function in some cases
1551        if self.children[node].is_empty() {
1552            if node_size.width.is_defined() && node_size.height.is_defined() {
1553                return node_size.map(|s| s.or_else(0.0));
1554            }
1555
1556            if let Some(ref measure) = self.nodes[node].measure {
1557                let converted_size = match measure {
1558                    MeasureFunc::Raw(measure) => measure(node_size),
1559                    #[cfg(any(feature = "std", feature = "alloc"))]
1560                    MeasureFunc::Boxed(measure) => measure(node_size),
1561                };
1562                *self.cache(node, main_size) =
1563                    Some(Cache { node_size, parent_size, perform_layout, size: converted_size });
1564                return converted_size;
1565            }
1566
1567            return Size {
1568                width: node_size.width.or_else(0.0) + constants.padding_border.horizontal_axis_sum(),
1569                height: node_size.height.or_else(0.0) + constants.padding_border.vertical_axis_sum(),
1570            };
1571        }
1572
1573        // 9. Flex Layout Algorithm
1574
1575        // 9.1. Initial Setup
1576
1577        // 1. Generate anonymous flex items as described in §4 Flex Items.
1578        let mut flex_items = self.generate_anonymous_flex_items(node, &constants);
1579
1580        // 9.2. Line Length Determination
1581
1582        // 2. Determine the available main and cross space for the flex items.
1583        let available_space = Self::determine_available_space(node_size, parent_size, &constants);
1584
1585        let has_baseline_child = flex_items
1586            .iter()
1587            .any(|child| self.nodes[child.node].style.align_self(&self.nodes[node].style) == AlignSelf::Baseline);
1588
1589        // 3. Determine the flex base size and hypothetical main size of each item.
1590        self.determine_flex_base_size(node, node_size, &constants, available_space, &mut flex_items);
1591
1592        // TODO: Add step 4 according to spec: https://www.w3.org/TR/css-flexbox-1/#algo-main-container
1593        // 9.3. Main Size Determination
1594
1595        // 5. Collect flex items into flex lines.
1596        let mut flex_lines = self.collect_flex_lines(node, &constants, available_space, &mut flex_items);
1597
1598        // 6. Resolve the flexible lengths of all the flex items to find their used main size.
1599        for line in &mut flex_lines {
1600            self.resolve_flexible_lengths(line, &constants, available_space);
1601        }
1602
1603        // TODO: Cleanup and make according to spec
1604        // Not part of the spec from what i can see but seems correct
1605        constants.container_size.set_main(
1606            constants.dir,
1607            node_size.main(constants.dir).or_else({
1608                let longest_line = flex_lines.iter().fold(f32::MIN, |acc, line| {
1609                    let length: f32 = line.items.iter().map(|item| item.outer_target_size.main(constants.dir)).sum();
1610                    acc.max(length)
1611                });
1612
1613                let size = longest_line + constants.padding_border.main_axis_sum(constants.dir);
1614                match available_space.main(constants.dir) {
1615                    Defined(val) if flex_lines.len() > 1 && size < val => val,
1616                    _ => size,
1617                }
1618            }),
1619        );
1620
1621        constants.inner_container_size.set_main(
1622            constants.dir,
1623            constants.container_size.main(constants.dir) - constants.padding_border.main_axis_sum(constants.dir),
1624        );
1625
1626        // 9.4. Cross Size Determination
1627
1628        // 7. Determine the hypothetical cross size of each item.
1629        for line in &mut flex_lines {
1630            self.determine_hypothetical_cross_size(line, &constants, available_space);
1631        }
1632
1633        // TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below
1634        // TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy
1635        if has_baseline_child {
1636            self.calculate_children_base_lines(node, node_size, &mut flex_lines, &constants);
1637        }
1638
1639        // 8. Calculate the cross size of each flex line.
1640        self.calculate_cross_size(&mut flex_lines, node, node_size, &constants);
1641
1642        // 9. Handle 'align-content: stretch'.
1643        self.handle_align_content_stretch(&mut flex_lines, node, node_size, &constants);
1644
1645        // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
1646        //     note the cross size of the line they’re in as the item’s strut size, and restart
1647        //     layout from the beginning.
1648        //
1649        //     In this second layout round, when collecting items into lines, treat the collapsed
1650        //     items as having zero main size. For the rest of the algorithm following that step,
1651        //     ignore the collapsed items entirely (as if they were display:none) except that after
1652        //     calculating the cross size of the lines, if any line’s cross size is less than the
1653        //     largest strut size among all the collapsed items in the line, set its cross size to
1654        //     that strut size.
1655        //
1656        //     Skip this step in the second layout round.
1657
1658        // TODO implement once (if ever) we support visibility:collapse
1659
1660        // 11. Determine the used cross size of each flex item.
1661        self.determine_used_cross_size(&mut flex_lines, node, &constants);
1662
1663        // 9.5. Main-Axis Alignment
1664
1665        // 12. Distribute any remaining free space.
1666        self.distribute_remaining_free_space(&mut flex_lines, node, &constants);
1667
1668        // 9.6. Cross-Axis Alignment
1669
1670        // 13. Resolve cross-axis auto margins (also includes 14).
1671        self.resolve_cross_axis_auto_margins(&mut flex_lines, node, &constants);
1672
1673        // 15. Determine the flex container’s used cross size.
1674        let total_cross_size = Self::determine_container_cross_size(&mut flex_lines, node_size, &mut constants);
1675
1676        // We have the container size.
1677        // If our caller does not care about performing layout we are done now.
1678        if !perform_layout {
1679            let container_size = constants.container_size;
1680            *self.cache(node, main_size) = Some(Cache { node_size, parent_size, perform_layout, size: container_size });
1681            return container_size;
1682        }
1683
1684        // 16. Align all flex lines per align-content.
1685        self.align_flex_lines_per_align_content(&mut flex_lines, node, &constants, total_cross_size);
1686
1687        // Do a final layout pass and gather the resulting layouts
1688        self.final_layout_pass(node, &mut flex_lines, &constants);
1689
1690        // Before returning we perform absolute layout on all absolutely positioned children
1691        self.perform_absolute_layout_on_absolute_children(node, &constants);
1692
1693        /// Lay out all hidden nodes recursively
1694        ///
1695        /// Each hidden node has zero size and is placed at the origin
1696        fn hidden_layout(nodes: &mut [NodeData], children: &[ChildrenVec<NodeId>], node: NodeId, order: u32) {
1697            nodes[node].layout = Layout { order, size: Size::zero(), location: Point::zero() };
1698
1699            for (order, child) in children[node].iter().enumerate() {
1700                hidden_layout(nodes, children, *child, order as _);
1701            }
1702        }
1703
1704        for (order, child) in self.children[node].iter().enumerate() {
1705            if self.nodes[*child].style.display == Display::None {
1706                hidden_layout(&mut self.nodes, &self.children, *child, order as _);
1707            }
1708        }
1709
1710        let container_size = constants.container_size;
1711        *self.cache(node, main_size) = Some(Cache { node_size, parent_size, perform_layout, size: container_size });
1712
1713        container_size
1714    }
1715}