gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 492HTML 238TOML 3CSS 11131 Sutichaykuna

⎇ 488ad901eea235196f3ab828843f7afff0449f65 src/algo.rs Rust · 1348 siq'ikuna · 57.6 KB Ñawpa willay Pi imata llamkarqan ch'uya

code cleanup 488ad901e · Emil Sjölander ·

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