gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 498HTML 254C 19JavaScript 81349 Definiciones

⎇ b9a8938f53128ba82d1782ccb66ed8e2b19b664c src/algo.rs Rust · 1365 líneas · 57.0 KB Historia Quién tocó qué crudo

Remove unneeded scope b9a8938f5 · Emil Sjölander ·

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