tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 695HTML 648Markdown 12TOML 62320 Sutichaykuna

⎇ d4ef0a22219e0d80049a556f56479dd35ffea071 src/algo.rs Rust · 1372 siq'ikuna · 61.6 KB Ñawpa willay Pi imata llamkarqan ch'uya

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