gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 492HTML 245JavaScript 8CSS 61137 Definitions

⎇ 7f5be2a5c43dd1b7be4291e0d0ddbda22a636c42 src/algo.rs Rust · 1365 lines · 61.5 KB History Who touched what raw

index: move forest into separate module 7f5be2a5c · msiglreith ·

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