gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1095HTML 1027Markdown 12TOML 84433 Definitions

⎇ b6e361a2a1582e94c8124db6401c52d519caaf11 src/algo.rs Rust · 1368 lines · 61.3 KB History Who touched what raw

Rename NodeId to Node and use newtype b6e361a2a · msiglreith ·

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