gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1090HTML 1022Markdown 12TOML 84418 Definitions

⎇ 80d832d1390b9d070ab601ffa7615ef4ef9e6220 src/algo.rs Rust · 1363 lines · 61.5 KB History Who touched what raw

Enable compilation of crate for no_std again. 80d832d13 · David Flemström ·

1#[cfg(not(feature = "std"))]
2use alloc::{boxed::Box, vec, vec::Vec};
3
4use core::any::Any;
5use core::f32;
6
7use crate::forest::{Forest, NodeData};
8use crate::id::NodeId;
9use crate::result;
10use crate::style::*;
11
12use crate::number::Number::*;
13use crate::number::*;
14
15use crate::geometry::{Point, Rect, Size};
16
17#[derive(Debug, Clone)]
18pub struct ComputeResult {
19    pub size: Size<f32>,
20}
21
22struct FlexItem {
23    node: NodeId,
24
25    size: Size<Number>,
26    min_size: Size<Number>,
27    max_size: Size<Number>,
28
29    position: Rect<Number>,
30    margin: Rect<f32>,
31    padding: Rect<f32>,
32    border: Rect<f32>,
33
34    flex_basis: f32,
35    inner_flex_basis: f32,
36    violation: f32,
37    frozen: bool,
38
39    hypothetical_inner_size: Size<f32>,
40    hypothetical_outer_size: Size<f32>,
41    target_size: Size<f32>,
42    outer_target_size: Size<f32>,
43
44    baseline: f32,
45
46    // temporary values for holding offset in the main / cross direction.
47    // offset is the relative position from the item's natural flow position based on
48    // relative position values, alignment, and justification. Does not include margin/padding/border.
49    offset_main: f32,
50    offset_cross: f32,
51}
52
53struct FlexLine {
54    items: Vec<FlexItem>,
55    cross_size: f32,
56    offset_cross: f32,
57}
58
59impl Forest {
60    pub(crate) fn compute(&mut self, root: NodeId, size: Size<Number>) -> Result<(), Box<dyn Any>> {
61        let style = self.nodes[root].style;
62        let has_root_min_max = style.min_size.width.is_defined()
63            || style.min_size.height.is_defined()
64            || style.max_size.width.is_defined()
65            || style.max_size.height.is_defined();
66
67        let result = if has_root_min_max {
68            let first_pass = self.compute_internal(
69                root,
70                Size { width: style.size.width.resolve(size.width), height: style.size.height.resolve(size.height) },
71                size,
72                false,
73            )?;
74
75            self.compute_internal(
76                root,
77                Size {
78                    width: first_pass
79                        .size
80                        .width
81                        .maybe_max(style.min_size.width.resolve(size.width))
82                        .maybe_min(style.max_size.width.resolve(size.width))
83                        .to_number(),
84                    height: first_pass
85                        .size
86                        .height
87                        .maybe_max(style.min_size.height.resolve(size.height))
88                        .maybe_min(style.max_size.height.resolve(size.height))
89                        .to_number(),
90                },
91                size,
92                true,
93            )?
94        } else {
95            self.compute_internal(
96                root,
97                Size { width: style.size.width.resolve(size.width), height: style.size.height.resolve(size.height) },
98                size,
99                true,
100            )?
101        };
102
103        self.nodes[root].layout = result::Layout {
104            order: 0,
105            size: Size { width: result.size.width, height: result.size.height },
106            location: Point { x: 0.0, y: 0.0 },
107        };
108
109        Self::round_layout(&mut self.nodes, &self.children, root, 0.0, 0.0);
110        Ok(())
111    }
112
113    fn round_layout(nodes: &mut Vec<NodeData>, children: &Vec<Vec<NodeId>>, root: NodeId, abs_x: f32, abs_y: f32) {
114        let layout = &mut nodes[root].layout;
115        let abs_x = abs_x + layout.location.x;
116        let abs_y = abs_y + layout.location.y;
117
118        layout.location.x = layout.location.x.round();
119        layout.location.y = layout.location.y.round();
120        layout.size.width = (abs_x + layout.size.width).round() - abs_x.round();
121        layout.size.height = (abs_y + layout.size.height).round() - abs_y.round();
122        for child in &children[root] {
123            Self::round_layout(nodes, children, *child, abs_x, abs_y);
124        }
125    }
126
127    fn compute_internal(
128        &mut self,
129        node: NodeId,
130        node_size: Size<Number>,
131        parent_size: Size<Number>,
132        perform_layout: bool,
133    ) -> Result<ComputeResult, Box<dyn Any>> {
134        self.nodes[node].is_dirty = false;
135
136        // First we check if we have a result for the given input
137        if let Some(ref cache) = self.nodes[node].layout_cache {
138            if cache.perform_layout || !perform_layout {
139                let width_compatible = if let Number::Defined(width) = node_size.width {
140                    (width - cache.result.size.width).abs() < f32::EPSILON
141                } else {
142                    cache.node_size.width.is_undefined()
143                };
144
145                let height_compatible = if let Number::Defined(height) = node_size.height {
146                    (height - cache.result.size.height).abs() < f32::EPSILON
147                } else {
148                    cache.node_size.height.is_undefined()
149                };
150
151                if width_compatible && height_compatible {
152                    return Ok(cache.result.clone());
153                }
154
155                if cache.node_size == node_size && cache.parent_size == parent_size {
156                    return Ok(cache.result.clone());
157                }
158            }
159        }
160
161        // Define some general constants we will need for the remainder
162        // of the algorithm.
163
164        let dir = self.nodes[node].style.flex_direction;
165        let is_row = dir.is_row();
166        let is_column = dir.is_column();
167        let is_wrap_reverse = self.nodes[node].style.flex_wrap == FlexWrap::WrapReverse;
168
169        let margin = self.nodes[node].style.margin.map(|n| n.resolve(parent_size.width).or_else(0.0));
170        let padding = self.nodes[node].style.padding.map(|n| n.resolve(parent_size.width).or_else(0.0));
171        let border = self.nodes[node].style.border.map(|n| n.resolve(parent_size.width).or_else(0.0));
172
173        let padding_border = Rect {
174            start: padding.start + border.start,
175            end: padding.end + border.end,
176            top: padding.top + border.top,
177            bottom: padding.bottom + border.bottom,
178        };
179
180        let node_inner_size = Size {
181            width: node_size.width - padding_border.horizontal(),
182            height: node_size.height - padding_border.vertical(),
183        };
184
185        let mut container_size = Size { width: 0.0, height: 0.0 };
186        let mut inner_container_size = Size { width: 0.0, height: 0.0 };
187
188        // If this is a leaf node we can skip a lot of this function in some cases
189        if self.children[node].is_empty() {
190            if node_size.width.is_defined() && node_size.height.is_defined() {
191                return Ok(ComputeResult { size: node_size.map(|s| s.or_else(0.0)) });
192            }
193
194            if let Some(ref measure) = self.nodes[node].measure {
195                let result = ComputeResult { size: measure(node_size)? };
196                self.nodes[node].layout_cache =
197                    Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() });
198                return Ok(result);
199            }
200
201            return Ok(ComputeResult {
202                size: Size {
203                    width: node_size.width.or_else(0.0) + padding_border.horizontal(),
204                    height: node_size.height.or_else(0.0) + padding_border.vertical(),
205                },
206            });
207        }
208
209        // 9.2. Line Length Determination
210
211        // 1. Generate anonymous flex items as described in §4 Flex Items.
212
213        // 2. Determine the available main and cross space for the flex items.
214        //    For each dimension, if that dimension of the flex container’s content box
215        //    is a definite size, use that; if that dimension of the flex container is
216        //    being sized under a min or max-content constraint, the available space in
217        //    that dimension is that constraint; otherwise, subtract the flex container’s
218        //    margin, border, and padding from the space available to the flex container
219        //    in that dimension and use that value. This might result in an infinite value.
220
221        let available_space = Size {
222            width: node_size.width.or_else(parent_size.width - margin.horizontal()) - padding_border.horizontal(),
223            height: node_size.height.or_else(parent_size.height - margin.vertical()) - padding_border.vertical(),
224        };
225
226        let mut flex_items: Vec<FlexItem> = self.children[node]
227            .iter()
228            .map(|child| (child, &self.nodes[*child].style))
229            .filter(|(_, style)| style.position_type != PositionType::Absolute)
230            .filter(|(_, style)| style.display != Display::None)
231            .map(|(child, child_style)| FlexItem {
232                node: *child,
233
234                size: Size {
235                    width: child_style.size.width.resolve(node_inner_size.width),
236                    height: child_style.size.height.resolve(node_inner_size.height),
237                },
238
239                min_size: Size {
240                    width: child_style.min_size.width.resolve(node_inner_size.width),
241                    height: child_style.min_size.height.resolve(node_inner_size.height),
242                },
243
244                max_size: Size {
245                    width: child_style.max_size.width.resolve(node_inner_size.width),
246                    height: child_style.max_size.height.resolve(node_inner_size.height),
247                },
248
249                position: child_style.position.map(|p| p.resolve(node_inner_size.width)),
250                margin: child_style.margin.map(|m| m.resolve(node_inner_size.width).or_else(0.0)),
251                padding: child_style.padding.map(|p| p.resolve(node_inner_size.width).or_else(0.0)),
252                border: child_style.border.map(|b| b.resolve(node_inner_size.width).or_else(0.0)),
253
254                flex_basis: 0.0,
255                inner_flex_basis: 0.0,
256                violation: 0.0,
257                frozen: false,
258
259                hypothetical_inner_size: Size { width: 0.0, height: 0.0 },
260                hypothetical_outer_size: Size { width: 0.0, height: 0.0 },
261                target_size: Size { width: 0.0, height: 0.0 },
262                outer_target_size: Size { width: 0.0, height: 0.0 },
263
264                baseline: 0.0,
265
266                offset_main: 0.0,
267                offset_cross: 0.0,
268            })
269            .collect();
270
271        let has_baseline_child = flex_items.iter().fold(false, |result, child| {
272            result || self.nodes[child.node].style.align_self(&self.nodes[node].style) == AlignSelf::Baseline
273        });
274
275        // TODO - this does not follow spec. See commented out code below
276        // 3. Determine the flex base size and hypothetical main size of each item:
277        flex_items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
278            let child_style = self.nodes[child.node].style;
279
280            // A. If the item has a definite used flex basis, that’s the flex base size.
281
282            let flex_basis = child_style.flex_basis.resolve(node_inner_size.main(dir));
283            if flex_basis.is_defined() {
284                child.flex_basis = flex_basis.or_else(0.0);
285                return Ok(());
286            };
287
288            // B. If the flex item has an intrinsic aspect ratio,
289            //    a used flex basis of content, and a definite cross size,
290            //    then the flex base size is calculated from its inner
291            //    cross size and the flex item’s intrinsic aspect ratio.
292
293            if let Defined(ratio) = child_style.aspect_ratio {
294                if let Defined(cross) = node_size.cross(dir) {
295                    if child_style.flex_basis == Dimension::Auto {
296                        child.flex_basis = cross * ratio;
297                        return Ok(());
298                    }
299                }
300            }
301
302            // C. If the used flex basis is content or depends on its available space,
303            //    and the flex container is being sized under a min-content or max-content
304            //    constraint (e.g. when performing automatic table layout [CSS21]),
305            //    size the item under that constraint. The flex base size is the item’s
306            //    resulting main size.
307
308            // TODO - Probably need to cover this case in future
309
310            // D. Otherwise, if the used flex basis is content or depends on its
311            //    available space, the available main size is infinite, and the flex item’s
312            //    inline axis is parallel to the main axis, lay the item out using the rules
313            //    for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
314            //    is the item’s max-content main size.
315
316            // TODO - Probably need to cover this case in future
317
318            // E. Otherwise, size the item into the available space using its used flex basis
319            //    in place of its main size, treating a value of content as max-content.
320            //    If a cross size is needed to determine the main size (e.g. when the
321            //    flex item’s main size is in its block axis) and the flex item’s cross size
322            //    is auto and not definite, in this calculation use fit-content as the
323            //    flex item’s cross size. The flex base size is the item’s resulting main size.
324
325            let width: Number = if !child.size.width.is_defined()
326                && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
327                && is_column
328            {
329                available_space.width
330            } else {
331                child.size.width
332            };
333
334            let height: Number = if !child.size.height.is_defined()
335                && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
336                && is_row
337            {
338                available_space.height
339            } else {
340                child.size.height
341            };
342
343            child.flex_basis = self
344                .compute_internal(
345                    child.node,
346                    Size {
347                        width: width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
348                        height: height.maybe_max(child.min_size.height).maybe_min(child.max_size.height),
349                    },
350                    available_space,
351                    false,
352                )?
353                .size
354                .main(dir)
355                .maybe_max(child.min_size.main(dir))
356                .maybe_min(child.max_size.main(dir));
357
358            Ok(())
359        })?;
360
361        // The hypothetical main size is the item’s flex base size clamped according to its
362        // used min and max main sizes (and flooring the content box size at zero).
363
364        flex_items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
365            child.inner_flex_basis = child.flex_basis - child.padding.main(dir) - child.border.main(dir);
366
367            // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
368            // The following logic was developed not from the spec but by trail and error looking into how
369            // webkit handled various scenarios. Can probably be solved better by passing in
370            // min-content max-content constraints from the top
371            let min_main = self
372                .compute_internal(child.node, Size { width: Undefined, height: Undefined }, available_space, false)?
373                .size
374                .main(dir)
375                .maybe_max(child.min_size.main(dir))
376                .maybe_min(child.size.main(dir))
377                .to_number();
378
379            child
380                .hypothetical_inner_size
381                .set_main(dir, child.flex_basis.maybe_max(min_main).maybe_min(child.max_size.main(dir)));
382
383            child
384                .hypothetical_outer_size
385                .set_main(dir, child.hypothetical_inner_size.main(dir) + child.margin.main(dir));
386
387            Ok(())
388        })?;
389
390        // 9.3. Main Size Determination
391
392        // 5. Collect flex items into flex lines:
393        //    - If the flex container is single-line, collect all the flex items into
394        //      a single flex line.
395        //    - Otherwise, starting from the first uncollected item, collect consecutive
396        //      items one by one until the first time that the next collected item would
397        //      not fit into the flex container’s inner main size (or until a forced break
398        //      is encountered, see §10 Fragmenting Flex Layout). If the very first
399        //      uncollected item wouldn’t fit, collect just it into the line.
400        //
401        //      For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.)
402        //      Repeat until all flex items have been collected into flex lines
403        //
404        //      Note that the "collect as many" line will collect zero-sized flex items onto
405        //      the end of the previous line even if the last non-zero item exactly "filled up" the line.
406
407        let mut flex_lines = {
408            let mut lines: Vec<FlexLine> = vec![];
409            let mut line_length = 0.0;
410
411            if self.nodes[node].style.flex_wrap == FlexWrap::NoWrap {
412                lines.push(FlexLine { items: flex_items, cross_size: 0.0, offset_cross: 0.0 });
413            } else {
414                let mut line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 };
415
416                for child in flex_items {
417                    line_length += child.hypothetical_outer_size.main(dir);
418
419                    if let Defined(main) = available_space.main(dir) {
420                        if line_length > main && !line.items.is_empty() {
421                            line_length = child.hypothetical_outer_size.main(dir);
422                            lines.push(line);
423                            line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 };
424                        }
425                    }
426
427                    line.items.push(child);
428                }
429
430                lines.push(line);
431            }
432
433            lines
434        };
435
436        // 6. Resolve the flexible lengths of all the flex items to find their used main size.
437        //    See §9.7 Resolving Flexible Lengths.
438        //
439        // 9.7. Resolving Flexible Lengths
440
441        flex_lines.iter_mut().try_for_each(|line| -> Result<(), Box<dyn Any>> {
442            // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
443            //    items on the line. If the sum is less than the flex container’s inner main size,
444            //    use the flex grow factor for the rest of this algorithm; otherwise, use the
445            //    flex shrink factor.
446
447            let used_flex_factor: f32 = line.items.iter().map(|child| child.hypothetical_outer_size.main(dir)).sum();
448            let growing = used_flex_factor < node_inner_size.main(dir).or_else(0.0);
449            let shrinking = !growing;
450
451            // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
452            //    - Any item that has a flex factor of zero
453            //    - If using the flex grow factor: any item that has a flex base size
454            //      greater than its hypothetical main size
455            //    - If using the flex shrink factor: any item that has a flex base size
456            //      smaller than its hypothetical main size
457
458            line.items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
459                // TODO - This is not found by reading the spec. Maybe this can be done in some other place
460                // instead. This was found by trail and error fixing tests to align with webkit output.
461                if node_inner_size.main(dir).is_undefined() && is_row {
462                    child.target_size.set_main(
463                        dir,
464                        self.compute_internal(
465                            child.node,
466                            Size {
467                                width: child.size.width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
468                                height: child
469                                    .size
470                                    .height
471                                    .maybe_max(child.min_size.height)
472                                    .maybe_min(child.max_size.height),
473                            },
474                            available_space,
475                            false,
476                        )?
477                        .size
478                        .main(dir)
479                        .maybe_max(child.min_size.main(dir))
480                        .maybe_min(child.max_size.main(dir)),
481                    );
482                } else {
483                    child.target_size.set_main(dir, child.hypothetical_inner_size.main(dir));
484                }
485
486                // TODO this should really only be set inside the if-statement below but
487                // that causes the target_main_size to never be set for some items
488
489                child.outer_target_size.set_main(dir, child.target_size.main(dir) + child.margin.main(dir));
490
491                let child_style = &self.nodes[child.node].style;
492                if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0)
493                    || (growing && child.flex_basis > child.hypothetical_inner_size.main(dir))
494                    || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(dir))
495                {
496                    child.frozen = true;
497                }
498
499                Ok(())
500            })?;
501
502            // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
503            //    and subtract this from the flex container’s inner main size. For frozen items,
504            //    use their outer target main size; for other items, use their outer flex base size.
505
506            let used_space: f32 = line
507                .items
508                .iter()
509                .map(|child| {
510                    child.margin.main(dir) + if child.frozen { child.target_size.main(dir) } else { child.flex_basis }
511                })
512                .sum();
513
514            let initial_free_space = (node_inner_size.main(dir) - used_space).or_else(0.0);
515
516            // 4. Loop
517
518            loop {
519                // a. Check for flexible items. If all the flex items on the line are frozen,
520                //    free space has been distributed; exit this loop.
521
522                let mut frozen: Vec<&mut FlexItem> = vec![];
523                let mut unfrozen: Vec<&mut FlexItem> = vec![];
524
525                line.items.iter_mut().for_each(|child| {
526                    if child.frozen {
527                        frozen.push(child);
528                    } else {
529                        unfrozen.push(child);
530                    }
531                });
532
533                if unfrozen.is_empty() {
534                    break;
535                }
536
537                // b. Calculate the remaining free space as for initial free space, above.
538                //    If the sum of the unfrozen flex items’ flex factors is less than one,
539                //    multiply the initial free space by this sum. If the magnitude of this
540                //    value is less than the magnitude of the remaining free space, use this
541                //    as the remaining free space.
542
543                let used_space: f32 = Iterator::chain(frozen.iter(), unfrozen.iter())
544                    .map(|child| {
545                        child.margin.main(dir)
546                            + if child.frozen { child.target_size.main(dir) } else { child.flex_basis }
547                    })
548                    .sum();
549
550                let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
551                    unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
552                        let style = &self.nodes[item.node].style;
553                        (flex_grow + style.flex_grow, flex_shrink + style.flex_shrink)
554                    });
555
556                let free_space = if growing && sum_flex_grow < 1.0 {
557                    (initial_free_space * sum_flex_grow).maybe_min(node_inner_size.main(dir) - used_space)
558                } else if shrinking && sum_flex_shrink < 1.0 {
559                    (initial_free_space * sum_flex_shrink).maybe_max(node_inner_size.main(dir) - used_space)
560                } else {
561                    (node_inner_size.main(dir) - used_space).or_else(0.0)
562                };
563
564                // c. Distribute free space proportional to the flex factors.
565                //    - If the remaining free space is zero
566                //        Do Nothing
567                //    - If using the flex grow factor
568                //        Find the ratio of the item’s flex grow factor to the sum of the
569                //        flex grow factors of all unfrozen items on the line. Set the item’s
570                //        target main size to its flex base size plus a fraction of the remaining
571                //        free space proportional to the ratio.
572                //    - If using the flex shrink factor
573                //        For every unfrozen item on the line, multiply its flex shrink factor by
574                //        its inner flex base size, and note this as its scaled flex shrink factor.
575                //        Find the ratio of the item’s scaled flex shrink factor to the sum of the
576                //        scaled flex shrink factors of all unfrozen items on the line. Set the item’s
577                //        target main size to its flex base size minus a fraction of the absolute value
578                //        of the remaining free space proportional to the ratio. Note this may result
579                //        in a negative inner main size; it will be corrected in the next step.
580                //    - Otherwise
581                //        Do Nothing
582
583                if free_space.is_normal() {
584                    if growing && sum_flex_grow > 0.0 {
585                        unfrozen.iter_mut().for_each(|child| {
586                            child.target_size.set_main(
587                                dir,
588                                child.flex_basis
589                                    + free_space * (self.nodes[child.node].style.flex_grow / sum_flex_grow),
590                            );
591                        });
592                    } else if shrinking && sum_flex_shrink > 0.0 {
593                        let sum_scaled_shrink_factor: f32 = unfrozen
594                            .iter()
595                            .map(|child| child.inner_flex_basis * self.nodes[child.node].style.flex_shrink)
596                            .sum();
597
598                        if sum_scaled_shrink_factor > 0.0 {
599                            unfrozen.iter_mut().for_each(|child| {
600                                let scaled_shrink_factor =
601                                    child.inner_flex_basis * self.nodes[child.node].style.flex_shrink;
602                                child.target_size.set_main(
603                                    dir,
604                                    child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
605                                )
606                            });
607                        }
608                    }
609                }
610
611                // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
612                //    used min and max main sizes and floor its content-box size at zero. If the
613                //    item’s target main size was made smaller by this, it’s a max violation.
614                //    If the item’s target main size was made larger by this, it’s a min violation.
615
616                let total_violation = unfrozen.iter_mut().try_fold(0.0, |acc, child| -> Result<f32, Box<dyn Any>> {
617                    // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
618                    // The following logic was developed not from the spec but by trail and error looking into how
619                    // webkit handled various scenarios. Can probably be solved better by passing in
620                    // min-content max-content constraints from the top. Need to figure out correct thing to do here as
621                    // just piling on more conditionals.
622                    let min_main = if is_row && self.nodes[child.node].measure.is_none() {
623                        self.compute_internal(
624                            child.node,
625                            Size { width: Undefined, height: Undefined },
626                            available_space,
627                            false,
628                        )?
629                        .size
630                        .width
631                        .maybe_min(child.size.width)
632                        .maybe_max(child.min_size.width)
633                        .to_number()
634                    } else {
635                        child.min_size.main(dir)
636                    };
637
638                    let max_main = child.max_size.main(dir);
639                    let clamped = child.target_size.main(dir).maybe_min(max_main).maybe_max(min_main).max(0.0);
640                    child.violation = clamped - child.target_size.main(dir);
641                    child.target_size.set_main(dir, clamped);
642                    child.outer_target_size.set_main(dir, child.target_size.main(dir) + child.margin.main(dir));
643
644                    Ok(acc + child.violation)
645                })?;
646
647                // e. Freeze over-flexed items. The total violation is the sum of the adjustments
648                //    from the previous step ∑(clamped size - unclamped size). If the total violation is:
649                //    - Zero
650                //        Freeze all items.
651                //    - Positive
652                //        Freeze all the items with min violations.
653                //    - Negative
654                //        Freeze all the items with max violations.
655
656                unfrozen.iter_mut().for_each(|child| match total_violation {
657                    v if v > 0.0 => child.frozen = child.violation > 0.0,
658                    v if v < 0.0 => child.frozen = child.violation < 0.0,
659                    _ => child.frozen = true,
660                })
661
662                // f. Return to the start of this loop.
663            }
664
665            Ok(())
666        })?;
667
668        // Not part of the spec from what i can see but seems correct
669        container_size.set_main(
670            dir,
671            node_size.main(dir).or_else({
672                let longest_line = flex_lines.iter().fold(f32::MIN, |acc, line| {
673                    let length: f32 = line.items.iter().map(|item| item.outer_target_size.main(dir)).sum();
674                    acc.max(length)
675                });
676
677                let size = longest_line + padding_border.main(dir);
678                match available_space.main(dir) {
679                    Defined(val) if flex_lines.len() > 1 && size < val => val,
680                    _ => size,
681                }
682            }),
683        );
684
685        inner_container_size.set_main(dir, container_size.main(dir) - padding_border.main(dir));
686
687        // 9.4. Cross Size Determination
688
689        // 7. Determine the hypothetical cross size of each item by performing layout with the
690        //    used main size and the available space, treating auto as fit-content.
691
692        flex_lines.iter_mut().try_for_each(|line| {
693            line.items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
694                let child_cross =
695                    child.size.cross(dir).maybe_max(child.min_size.cross(dir)).maybe_min(child.max_size.cross(dir));
696
697                child.hypothetical_inner_size.set_cross(
698                    dir,
699                    self.compute_internal(
700                        child.node,
701                        Size {
702                            width: if is_row { child.target_size.width.to_number() } else { child_cross },
703                            height: if is_row { child_cross } else { child.target_size.height.to_number() },
704                        },
705                        Size {
706                            width: if is_row { container_size.main(dir).to_number() } else { available_space.width },
707                            height: if is_row { available_space.height } else { container_size.main(dir).to_number() },
708                        },
709                        false,
710                    )?
711                    .size
712                    .cross(dir)
713                    .maybe_max(child.min_size.cross(dir))
714                    .maybe_min(child.max_size.cross(dir)),
715                );
716
717                child
718                    .hypothetical_outer_size
719                    .set_cross(dir, child.hypothetical_inner_size.cross(dir) + child.margin.cross(dir));
720
721                Ok(())
722            })
723        })?;
724
725        // TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below
726        // TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy
727
728        fn calc_baseline(db: &Forest, node: NodeId, layout: &result::Layout) -> f32 {
729            if db.children[node].is_empty() {
730                layout.size.height
731            } else {
732                let child = db.children[node][0];
733                calc_baseline(db, child, &db.nodes[child].layout)
734            }
735        };
736
737        if has_baseline_child {
738            flex_lines.iter_mut().try_for_each(|line| {
739                line.items.iter_mut().try_for_each(|child| -> Result<(), Box<dyn Any>> {
740                    let result = self.compute_internal(
741                        child.node,
742                        Size {
743                            width: if is_row {
744                                child.target_size.width.to_number()
745                            } else {
746                                child.hypothetical_inner_size.width.to_number()
747                            },
748                            height: if is_row {
749                                child.hypothetical_inner_size.height.to_number()
750                            } else {
751                                child.target_size.height.to_number()
752                            },
753                        },
754                        Size {
755                            width: if is_row { container_size.width.to_number() } else { node_size.width },
756                            height: if is_row { node_size.height } else { container_size.height.to_number() },
757                        },
758                        true,
759                    )?;
760
761                    child.baseline = calc_baseline(
762                        self,
763                        child.node,
764                        &result::Layout {
765                            order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32,
766                            size: result.size,
767                            location: Point { x: 0.0, y: 0.0 },
768                        },
769                    );
770
771                    Ok(())
772                })
773            })?;
774        }
775
776        // 8. Calculate the cross size of each flex line.
777        //    If the flex container is single-line and has a definite cross size, the cross size
778        //    of the flex line is the flex container’s inner cross size. Otherwise, for each flex line:
779        //
780        //    If the flex container is single-line, then clamp the line’s cross-size to be within
781        //    the container’s computed min and max cross sizes. Note that if CSS 2.1’s definition
782        //    of min/max-width/height applied more generally, this behavior would fall out automatically.
783
784        if flex_lines.len() == 1 && node_size.cross(dir).is_defined() {
785            flex_lines[0].cross_size = (node_size.cross(dir) - padding_border.cross(dir)).or_else(0.0);
786        } else {
787            flex_lines.iter_mut().for_each(|line| {
788                //    1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
789                //       align-self is baseline, and whose cross-axis margins are both non-auto. Find the
790                //       largest of the distances between each item’s baseline and its hypothetical outer
791                //       cross-start edge, and the largest of the distances between each item’s baseline
792                //       and its hypothetical outer cross-end edge, and sum these two values.
793
794                //    2. Among all the items not collected by the previous step, find the largest
795                //       outer hypothetical cross size.
796
797                //    3. The used cross-size of the flex line is the largest of the numbers found in the
798                //       previous two steps and zero.
799
800                let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
801                line.cross_size = line
802                    .items
803                    .iter()
804                    .map(|child| {
805                        let child_style = &self.nodes[child.node].style;
806                        if child_style.align_self(&self.nodes[node].style) == AlignSelf::Baseline
807                            && child_style.cross_margin_start(dir) != Dimension::Auto
808                            && child_style.cross_margin_end(dir) != Dimension::Auto
809                            && child_style.cross_size(dir) == Dimension::Auto
810                        {
811                            max_baseline - child.baseline + child.hypothetical_outer_size.cross(dir)
812                        } else {
813                            child.hypothetical_outer_size.cross(dir)
814                        }
815                    })
816                    .fold(0.0, |acc, x| acc.max(x));
817            });
818        }
819
820        // 9. Handle 'align-content: stretch'. If the flex container has a definite cross size,
821        //    align-content is stretch, and the sum of the flex lines' cross sizes is less than
822        //    the flex container’s inner cross size, increase the cross size of each flex line
823        //    by equal amounts such that the sum of their cross sizes exactly equals the
824        //    flex container’s inner cross size.
825
826        if self.nodes[node].style.align_content == AlignContent::Stretch && node_size.cross(dir).is_defined() {
827            let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
828            let inner_cross = (node_size.cross(dir) - padding_border.cross(dir)).or_else(0.0);
829
830            if total_cross < inner_cross {
831                let remaining = inner_cross - total_cross;
832                let addition = remaining / flex_lines.len() as f32;
833                flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
834            }
835        }
836
837        // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
838        //     note the cross size of the line they’re in as the item’s strut size, and restart
839        //     layout from the beginning.
840        //
841        //     In this second layout round, when collecting items into lines, treat the collapsed
842        //     items as having zero main size. For the rest of the algorithm following that step,
843        //     ignore the collapsed items entirely (as if they were display:none) except that after
844        //     calculating the cross size of the lines, if any line’s cross size is less than the
845        //     largest strut size among all the collapsed items in the line, set its cross size to
846        //     that strut size.
847        //
848        //     Skip this step in the second layout round.
849
850        // TODO implement once (if ever) we support visibility:collapse
851
852        // 11. Determine the used cross size of each flex item. If a flex item has align-self: stretch,
853        //     its computed cross size property is auto, and neither of its cross-axis margins are auto,
854        //     the used outer cross size is the used cross size of its flex line, clamped according to
855        //     the item’s used min and max cross sizes. Otherwise, the used cross size is the item’s
856        //     hypothetical cross size.
857        //
858        //     If the flex item has align-self: stretch, redo layout for its contents, treating this
859        //     used size as its definite cross size so that percentage-sized children can be resolved.
860        //
861        //     Note that this step does not affect the main size of the flex item, even if it has an
862        //     intrinsic aspect ratio.
863
864        flex_lines.iter_mut().for_each(|line| {
865            let line_cross_size = line.cross_size;
866
867            line.items.iter_mut().for_each(|child| {
868                let child_style = &self.nodes[child.node].style;
869                child.target_size.set_cross(
870                    dir,
871                    if child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
872                        && child_style.cross_margin_start(dir) != Dimension::Auto
873                        && child_style.cross_margin_end(dir) != Dimension::Auto
874                        && child_style.cross_size(dir) == Dimension::Auto
875                    {
876                        (line_cross_size - child.margin.cross(dir))
877                            .maybe_max(child.min_size.cross(dir))
878                            .maybe_min(child.max_size.cross(dir))
879                    } else {
880                        child.hypothetical_inner_size.cross(dir)
881                    },
882                );
883
884                child.outer_target_size.set_cross(dir, child.target_size.cross(dir) + child.margin.cross(dir));
885            });
886        });
887
888        // 9.5. Main-Axis Alignment
889
890        // 12. Distribute any remaining free space. For each flex line:
891        //     1. If the remaining free space is positive and at least one main-axis margin on this
892        //        line is auto, distribute the free space equally among these margins. Otherwise,
893        //        set all auto margins to zero.
894        //     2. Align the items along the main-axis per justify-content.
895
896        flex_lines.iter_mut().for_each(|line| {
897            let used_space: f32 = line.items.iter().map(|child| child.outer_target_size.main(dir)).sum();
898            let free_space = inner_container_size.main(dir) - used_space;
899            let mut num_auto_margins = 0;
900
901            line.items.iter_mut().for_each(|child| {
902                let child_style = &self.nodes[child.node].style;
903                if child_style.main_margin_start(dir) == Dimension::Auto {
904                    num_auto_margins += 1;
905                }
906                if child_style.main_margin_end(dir) == Dimension::Auto {
907                    num_auto_margins += 1;
908                }
909            });
910
911            if free_space > 0.0 && num_auto_margins > 0 {
912                let margin = free_space / num_auto_margins as f32;
913
914                line.items.iter_mut().for_each(|child| {
915                    let child_style = &self.nodes[child.node].style;
916                    if child_style.main_margin_start(dir) == Dimension::Auto {
917                        if is_row {
918                            child.margin.start = margin;
919                        } else {
920                            child.margin.top = margin;
921                        }
922                    }
923                    if child_style.main_margin_end(dir) == Dimension::Auto {
924                        if is_row {
925                            child.margin.end = margin;
926                        } else {
927                            child.margin.bottom = margin;
928                        }
929                    }
930                });
931            } else {
932                let num_items = line.items.len();
933                let layout_reverse = dir.is_reverse();
934
935                let justify_item = |(i, child): (usize, &mut FlexItem)| {
936                    let is_first = i == 0;
937
938                    child.offset_main = match self.nodes[node].style.justify_content {
939                        JustifyContent::FlexStart => {
940                            if layout_reverse && is_first {
941                                free_space
942                            } else {
943                                0.0
944                            }
945                        }
946                        JustifyContent::Center => {
947                            if is_first {
948                                free_space / 2.0
949                            } else {
950                                0.0
951                            }
952                        }
953                        JustifyContent::FlexEnd => {
954                            if is_first && !layout_reverse {
955                                free_space
956                            } else {
957                                0.0
958                            }
959                        }
960                        JustifyContent::SpaceBetween => {
961                            if is_first {
962                                0.0
963                            } else {
964                                free_space / (num_items - 1) as f32
965                            }
966                        }
967                        JustifyContent::SpaceAround => {
968                            if is_first {
969                                (free_space / num_items as f32) / 2.0
970                            } else {
971                                free_space / num_items as f32
972                            }
973                        }
974                        JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32,
975                    };
976                };
977
978                if layout_reverse {
979                    line.items.iter_mut().rev().enumerate().for_each(justify_item);
980                } else {
981                    line.items.iter_mut().enumerate().for_each(justify_item);
982                }
983            }
984        });
985
986        // 9.6. Cross-Axis Alignment
987
988        // 13. Resolve cross-axis auto margins. If a flex item has auto cross-axis margins:
989        //     - If its outer cross size (treating those auto margins as zero) is less than the
990        //       cross size of its flex line, distribute the difference in those sizes equally
991        //       to the auto margins.
992        //     - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis)
993        //       is auto, set it to zero. Set the opposite margin so that the outer cross size of the
994        //       item equals the cross size of its flex line.
995
996        flex_lines.iter_mut().for_each(|line| {
997            let line_cross_size = line.cross_size;
998            let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
999
1000            line.items.iter_mut().for_each(|child| {
1001                let free_space = line_cross_size - child.outer_target_size.cross(dir);
1002                let child_style = &self.nodes[child.node].style;
1003
1004                if child_style.cross_margin_start(dir) == Dimension::Auto
1005                    && child_style.cross_margin_end(dir) == Dimension::Auto
1006                {
1007                    if is_row {
1008                        child.margin.top = free_space / 2.0;
1009                        child.margin.bottom = free_space / 2.0;
1010                    } else {
1011                        child.margin.start = free_space / 2.0;
1012                        child.margin.end = free_space / 2.0;
1013                    }
1014                } else if child_style.cross_margin_start(dir) == Dimension::Auto {
1015                    if is_row {
1016                        child.margin.top = free_space;
1017                    } else {
1018                        child.margin.start = free_space;
1019                    }
1020                } else if child_style.cross_margin_end(dir) == Dimension::Auto {
1021                    if is_row {
1022                        child.margin.bottom = free_space;
1023                    } else {
1024                        child.margin.end = free_space;
1025                    }
1026                } else {
1027                    // 14. Align all flex items along the cross-axis per align-self, if neither of the item’s
1028                    //     cross-axis margins are auto.
1029
1030                    child.offset_cross = match child_style.align_self(&self.nodes[node].style) {
1031                        AlignSelf::Auto => 0.0, // Should never happen
1032                        AlignSelf::FlexStart => {
1033                            if is_wrap_reverse {
1034                                free_space
1035                            } else {
1036                                0.0
1037                            }
1038                        }
1039                        AlignSelf::FlexEnd => {
1040                            if is_wrap_reverse {
1041                                0.0
1042                            } else {
1043                                free_space
1044                            }
1045                        }
1046                        AlignSelf::Center => free_space / 2.0,
1047                        AlignSelf::Baseline => {
1048                            if is_row {
1049                                max_baseline - child.baseline
1050                            } else {
1051                                // baseline alignment only makes sense if the direction is row
1052                                // we treat it as flex-start alignment in columns.
1053                                if is_wrap_reverse {
1054                                    free_space
1055                                } else {
1056                                    0.0
1057                                }
1058                            }
1059                        }
1060                        AlignSelf::Stretch => {
1061                            if is_wrap_reverse {
1062                                free_space
1063                            } else {
1064                                0.0
1065                            }
1066                        }
1067                    };
1068                }
1069            });
1070        });
1071
1072        // 15. Determine the flex container’s used cross size:
1073        //     - If the cross size property is a definite size, use that, clamped by the used
1074        //       min and max cross sizes of the flex container.
1075        //     - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used
1076        //       min and max cross sizes of the flex container.
1077
1078        let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
1079        container_size.set_cross(dir, node_size.cross(dir).or_else(total_cross_size + padding_border.cross(dir)));
1080        inner_container_size.set_cross(dir, container_size.cross(dir) - padding_border.cross(dir));
1081
1082        // We have the container size. If our caller does not care about performing
1083        // layout we are done now.
1084        if !perform_layout {
1085            let result = ComputeResult { size: container_size };
1086            self.nodes[node].layout_cache =
1087                Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() });
1088            return Ok(result);
1089        }
1090
1091        // 16. Align all flex lines per align-content.
1092
1093        let free_space = inner_container_size.cross(dir) - total_cross_size;
1094        let num_lines = flex_lines.len();
1095
1096        let align_line = |(i, line): (usize, &mut FlexLine)| {
1097            let is_first = i == 0;
1098
1099            line.offset_cross = match self.nodes[node].style.align_content {
1100                AlignContent::FlexStart => {
1101                    if is_first && is_wrap_reverse {
1102                        free_space
1103                    } else {
1104                        0.0
1105                    }
1106                }
1107                AlignContent::FlexEnd => {
1108                    if is_first && !is_wrap_reverse {
1109                        free_space
1110                    } else {
1111                        0.0
1112                    }
1113                }
1114                AlignContent::Center => {
1115                    if is_first {
1116                        free_space / 2.0
1117                    } else {
1118                        0.0
1119                    }
1120                }
1121                AlignContent::Stretch => 0.0,
1122                AlignContent::SpaceBetween => {
1123                    if is_first {
1124                        0.0
1125                    } else {
1126                        free_space / (num_lines - 1) as f32
1127                    }
1128                }
1129                AlignContent::SpaceAround => {
1130                    if is_first {
1131                        (free_space / num_lines as f32) / 2.0
1132                    } else {
1133                        free_space / num_lines as f32
1134                    }
1135                }
1136            };
1137        };
1138
1139        if is_wrap_reverse {
1140            flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1141        } else {
1142            flex_lines.iter_mut().enumerate().for_each(align_line);
1143        }
1144
1145        // Do a final layout pass and gather the resulting layouts
1146        {
1147            let mut lines: Vec<Vec<result::Layout>> = vec![];
1148            let mut total_offset_cross = padding_border.cross_start(dir);
1149
1150            let layout_line = |line: &mut FlexLine| -> Result<(), Box<dyn Any>> {
1151                let mut children: Vec<result::Layout> = vec![];
1152                let mut total_offset_main = padding_border.main_start(dir);
1153                let line_offset_cross = line.offset_cross;
1154
1155                let layout_item = |child: &mut FlexItem| -> Result<(), Box<dyn Any>> {
1156                    let result = self.compute_internal(
1157                        child.node,
1158                        child.target_size.map(|s| s.to_number()),
1159                        container_size.map(|s| s.to_number()),
1160                        true,
1161                    )?;
1162
1163                    let offset_main = total_offset_main
1164                        + child.offset_main
1165                        + child.margin.main_start(dir)
1166                        + (child.position.main_start(dir).or_else(0.0) - child.position.main_end(dir).or_else(0.0));
1167
1168                    let offset_cross = total_offset_cross
1169                        + child.offset_cross
1170                        + line_offset_cross
1171                        + child.margin.cross_start(dir)
1172                        + (child.position.cross_start(dir).or_else(0.0) - child.position.cross_end(dir).or_else(0.0));
1173
1174                    self.nodes[child.node].layout = result::Layout {
1175                        order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32,
1176                        size: result.size,
1177                        location: Point {
1178                            x: if is_row { offset_main } else { offset_cross },
1179                            y: if is_column { offset_main } else { offset_cross },
1180                        },
1181                    };
1182
1183                    total_offset_main += child.offset_main + child.margin.main(dir) + result.size.main(dir);
1184
1185                    Ok(())
1186                };
1187
1188                if dir.is_reverse() {
1189                    line.items.iter_mut().rev().try_for_each(layout_item)?;
1190                } else {
1191                    line.items.iter_mut().try_for_each(layout_item)?;
1192                }
1193
1194                total_offset_cross += line_offset_cross + line.cross_size;
1195
1196                if dir.is_reverse() {
1197                    children.reverse();
1198                }
1199
1200                lines.push(children);
1201
1202                Ok(())
1203            };
1204
1205            if is_wrap_reverse {
1206                flex_lines.iter_mut().rev().try_for_each(layout_line)?;
1207            } else {
1208                flex_lines.iter_mut().try_for_each(layout_line)?;
1209            }
1210        }
1211
1212        // Before returning we perform absolute layout on all absolutely positioned children
1213        {
1214            // TODO: remove number of Vec<_> generated
1215            let candidates = self.children[node]
1216                .iter()
1217                .cloned()
1218                .enumerate()
1219                .filter(|(_, child)| self.nodes[*child].style.position_type == PositionType::Absolute)
1220                .collect::<Vec<_>>();
1221
1222            for (order, child) in candidates {
1223                let container_width = container_size.width.to_number();
1224                let container_height = container_size.height.to_number();
1225
1226                let child_style = self.nodes[child].style;
1227
1228                let start = child_style.position.start.resolve(container_width)
1229                    + child_style.margin.start.resolve(container_width);
1230                let end =
1231                    child_style.position.end.resolve(container_width) + child_style.margin.end.resolve(container_width);
1232                let top = child_style.position.top.resolve(container_height)
1233                    + child_style.margin.top.resolve(container_height);
1234                let bottom = child_style.position.bottom.resolve(container_height)
1235                    + child_style.margin.bottom.resolve(container_height);
1236
1237                let (start_main, end_main) = if is_row { (start, end) } else { (top, bottom) };
1238                let (start_cross, end_cross) = if is_row { (top, bottom) } else { (start, end) };
1239
1240                let width = child_style
1241                    .size
1242                    .width
1243                    .resolve(container_width)
1244                    .maybe_max(child_style.min_size.width.resolve(container_width))
1245                    .maybe_min(child_style.max_size.width.resolve(container_width))
1246                    .or_else(if start.is_defined() && end.is_defined() {
1247                        container_width - start - end
1248                    } else {
1249                        Undefined
1250                    });
1251
1252                let height = child_style
1253                    .size
1254                    .height
1255                    .resolve(container_height)
1256                    .maybe_max(child_style.min_size.height.resolve(container_height))
1257                    .maybe_min(child_style.max_size.height.resolve(container_height))
1258                    .or_else(if top.is_defined() && bottom.is_defined() {
1259                        container_height - top - bottom
1260                    } else {
1261                        Undefined
1262                    });
1263
1264                let result = self.compute_internal(
1265                    child,
1266                    Size { width, height },
1267                    Size { width: container_width, height: container_height },
1268                    true,
1269                )?;
1270
1271                let free_main_space = container_size.main(dir)
1272                    - result
1273                        .size
1274                        .main(dir)
1275                        .maybe_max(child_style.min_main_size(dir).resolve(node_inner_size.main(dir)))
1276                        .maybe_min(child_style.max_main_size(dir).resolve(node_inner_size.main(dir)));
1277
1278                let free_cross_space = container_size.cross(dir)
1279                    - result
1280                        .size
1281                        .cross(dir)
1282                        .maybe_max(child_style.min_cross_size(dir).resolve(node_inner_size.cross(dir)))
1283                        .maybe_min(child_style.max_cross_size(dir).resolve(node_inner_size.cross(dir)));
1284
1285                let offset_main = if start_main.is_defined() {
1286                    start_main.or_else(0.0) + border.main_start(dir)
1287                } else if end_main.is_defined() {
1288                    free_main_space - end_main.or_else(0.0) - border.main_end(dir)
1289                } else {
1290                    match self.nodes[node].style.justify_content {
1291                        JustifyContent::SpaceBetween | JustifyContent::FlexStart => padding_border.main_start(dir),
1292                        JustifyContent::FlexEnd => free_main_space - padding_border.main_end(dir),
1293                        JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => {
1294                            free_main_space / 2.0
1295                        }
1296                    }
1297                };
1298
1299                let offset_cross = if start_cross.is_defined() {
1300                    start_cross.or_else(0.0) + border.cross_start(dir)
1301                } else if end_cross.is_defined() {
1302                    free_cross_space - end_cross.or_else(0.0) - border.cross_end(dir)
1303                } else {
1304                    match child_style.align_self(&self.nodes[node].style) {
1305                        AlignSelf::Auto => 0.0, // Should never happen
1306                        AlignSelf::FlexStart => {
1307                            if is_wrap_reverse {
1308                                free_cross_space - padding_border.cross_end(dir)
1309                            } else {
1310                                padding_border.cross_start(dir)
1311                            }
1312                        }
1313                        AlignSelf::FlexEnd => {
1314                            if is_wrap_reverse {
1315                                padding_border.cross_start(dir)
1316                            } else {
1317                                free_cross_space - padding_border.cross_end(dir)
1318                            }
1319                        }
1320                        AlignSelf::Center => free_cross_space / 2.0,
1321                        AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support
1322                        AlignSelf::Stretch => {
1323                            if is_wrap_reverse {
1324                                free_cross_space - padding_border.cross_end(dir)
1325                            } else {
1326                                padding_border.cross_start(dir)
1327                            }
1328                        }
1329                    }
1330                };
1331
1332                self.nodes[child].layout = result::Layout {
1333                    order: order as u32,
1334                    size: result.size,
1335                    location: Point {
1336                        x: if is_row { offset_main } else { offset_cross },
1337                        y: if is_column { offset_main } else { offset_cross },
1338                    },
1339                };
1340            }
1341        }
1342
1343        fn hidden_layout(nodes: &mut Vec<NodeData>, children: &Vec<Vec<NodeId>>, node: NodeId, order: u32) {
1344            nodes[node].layout =
1345                result::Layout { order, size: Size { width: 0.0, height: 0.0 }, location: Point { x: 0.0, y: 0.0 } };
1346
1347            for (order, child) in children[node].iter().enumerate() {
1348                hidden_layout(nodes, children, *child, order as _);
1349            }
1350        }
1351
1352        for (order, child) in self.children[node].iter().enumerate() {
1353            if self.nodes[*child].style.display == Display::None {
1354                hidden_layout(&mut self.nodes, &self.children, *child, order as _);
1355            }
1356        }
1357
1358        let result = ComputeResult { size: container_size };
1359        self.nodes[node].layout_cache =
1360            Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() });
1361        Ok(result)
1362    }
1363}