gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 230Rust 8TOML 3CSS 1315 Definitions

⎇ 26e9badc92cbae94bf95aab608191de42f1f7341 src/algo.rs Rust · 1414 lines · 62.4 KB History Who touched what raw

Fix typo 26e9badc9 · Emil Sjölander ·

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