tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81414 Definitions

⎇ bee63df8595766051e26fd3ad837bd882ecce216 src/algo.rs Rust · 1074 lines · 45.9 KB History Who touched what raw

fix how main and cross are caluclated for absolute items bee63df85 · Emil Sjölander ·

1use std::f32;
2
3use layout;
4use style;
5
6#[derive(Debug, Copy, Clone)]
7struct FlexSize {
8    width: f32,
9    height: f32,
10}
11
12impl FlexSize {
13    fn main(&self, direction: style::FlexDirection) -> f32 {
14        match direction {
15            style::FlexDirection::Row | style::FlexDirection::RowReverse => self.width,
16            style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.height,
17        }
18    }
19
20    fn cross(&self, direction: style::FlexDirection) -> f32 {
21        match direction {
22            style::FlexDirection::Row | style::FlexDirection::RowReverse => self.height,
23            style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.width,
24        }
25    }
26}
27
28#[derive(Debug, Copy, Clone)]
29struct SizeConstraint {
30    min: f32,
31    max: f32,
32}
33
34impl SizeConstraint {
35    fn exactly(size: f32) -> SizeConstraint {
36        SizeConstraint { min: size, max: size }
37    }
38
39    fn undefined() -> SizeConstraint {
40        SizeConstraint { min: f32::NAN, max: f32::NAN }
41    }
42
43    fn at_least(size: f32) -> SizeConstraint {
44        SizeConstraint { min: size, max: f32::NAN }
45    }
46
47    fn at_most(size: f32) -> SizeConstraint {
48        SizeConstraint { min: f32::NAN, max: size }
49    }
50
51    fn between(min: f32, max: f32) -> SizeConstraint {
52        SizeConstraint { min, max }
53    }
54}
55
56struct FlexItem<'a> {
57    node: &'a style::Node,
58
59    // temporary values for main size determination
60    flex_basis: f32,
61    inner_flex_basis: f32,
62    hypothetical_inner_main_size: f32,
63    hypothetical_outer_main_size: f32,
64    target_main_size: f32,
65    violation: f32,
66    frozen: bool,
67
68    // temporary values for cross size determination
69    hypothetical_inner_cross_size: f32,
70    hypothetical_outer_cross_size: f32,
71    target_cross_size: f32,
72
73    main_margin_start: f32,
74    main_margin_end: f32,
75    cross_margin_start: f32,
76    cross_margin_end: f32,
77
78    // temporary values for holding offset in the main / cross direction.
79    // offset is the relative position from the item's natural flow position based on
80    // relative position values, alignment, and justification. Does not unclude margin/padding/border.
81    offset_main: f32,
82    offset_cross: f32,
83}
84
85struct ComputeResult {
86    size: FlexSize,
87    children: Vec<layout::Node>,
88}
89
90struct FlexLine<'a> {
91    items: Vec<FlexItem<'a>>,
92    cross_size: f32,
93    offset_cross: f32,
94}
95
96pub fn compute(root: &style::Node) -> layout::Node {
97    let result = compute_internal(root, SizeConstraint::undefined(), SizeConstraint::undefined());
98
99    round_layout(
100        &layout::Node {
101            width: result.size.width,
102            height: result.size.height,
103            x: 0.0,
104            y: 0.0,
105            children: result.children,
106        },
107        0.0,
108        0.0,
109    )
110}
111
112fn round_layout(layout: &layout::Node, abs_x: f32, abs_y: f32) -> layout::Node {
113    let abs_x = abs_x + layout.x;
114    let abs_y = abs_y + layout.y;
115
116    let rounded_x = layout.x.round();
117    let rounded_y = layout.y.round();
118
119    let rounded_width = (abs_x + layout.width).round() - abs_x.round();
120    let rounded_height = (abs_y + layout.height).round() - abs_y.round();
121
122    let rounded_children = layout.children.iter().map(|child| round_layout(child, abs_x, abs_y)).collect();
123
124    layout::Node {
125        width: rounded_width,
126        height: rounded_height,
127        x: rounded_x,
128        y: rounded_y,
129        children: rounded_children,
130    }
131}
132
133fn compute_internal(
134    node: &style::Node,
135    width_constraint: SizeConstraint,
136    height_constraint: SizeConstraint,
137) -> ComputeResult {
138    // Define some general constants we will need for the remainder
139    // of the algorithm.
140
141    let (main_constraint, cross_constraint) = if node.flex_direction.is_row() {
142        (width_constraint, height_constraint)
143    } else {
144        (height_constraint, width_constraint)
145    };
146
147    let (intrinsic_main_size, intrinsic_cross_size) = (
148        node.main_size(node.flex_direction).resolve(main_constraint.max, f32::NAN),
149        node.cross_size(node.flex_direction).resolve(cross_constraint.max, f32::NAN),
150    );
151
152    let (margin_main_start, margin_cross_start) = (
153        node.main_margin_start(node.flex_direction).resolve(main_constraint.max, 0.0),
154        node.cross_margin_start(node.flex_direction).resolve(main_constraint.max, 0.0),
155    );
156
157    let (margin_main_end, margin_cross_end) = (
158        node.main_margin_end(node.flex_direction).resolve(main_constraint.max, 0.0),
159        node.cross_margin_end(node.flex_direction).resolve(main_constraint.max, 0.0),
160    );
161
162    let (margin_main, margin_cross) = (margin_main_start + margin_main_end, margin_cross_start + margin_cross_end);
163
164    let (padding_main_start, padding_cross_start) = (
165        node.main_padding_start(node.flex_direction).resolve(main_constraint.max, 0.0),
166        node.cross_padding_start(node.flex_direction).resolve(main_constraint.max, 0.0),
167    );
168
169    let (padding_main_end, padding_cross_end) = (
170        node.main_padding_end(node.flex_direction).resolve(main_constraint.max, 0.0),
171        node.cross_padding_end(node.flex_direction).resolve(main_constraint.max, 0.0),
172    );
173
174    let (padding_main, padding_cross) =
175        (padding_main_start + padding_main_end, padding_cross_start + padding_cross_end);
176
177    let (border_main_start, border_cross_start) = (
178        node.main_border_start(node.flex_direction).resolve(main_constraint.max, 0.0),
179        node.cross_border_start(node.flex_direction).resolve(main_constraint.max, 0.0),
180    );
181
182    let (border_main_end, border_cross_end) = (
183        node.main_border_end(node.flex_direction).resolve(main_constraint.max, 0.0),
184        node.cross_border_end(node.flex_direction).resolve(main_constraint.max, 0.0),
185    );
186
187    let (border_main, border_cross) = (border_main_start + border_main_end, border_cross_start + border_cross_end);
188
189    let (inner_intrinsic_main_size, inner_intrinsic_cross_size) =
190        (intrinsic_main_size - padding_main - border_main, intrinsic_cross_size - padding_cross - border_cross);
191
192    let wrap_reverse = node.flexWrap == style::FlexWrap::WrapReverse;
193
194    // 9.2. Line Length Determination
195
196    // 1. Generate anonymous flex items as described in §4 Flex Items.
197
198    let mut flex_items: Vec<FlexItem> = node
199        .children
200        .iter()
201        .filter(|child| child.position != style::Position::Absolute)
202        .map(|child| FlexItem {
203            node: child,
204
205            flex_basis: 0.0,
206            inner_flex_basis: 0.0,
207            hypothetical_inner_main_size: 0.0,
208            hypothetical_outer_main_size: 0.0,
209            target_main_size: 0.0,
210            violation: 0.0,
211            frozen: false,
212
213            hypothetical_inner_cross_size: 0.0,
214            hypothetical_outer_cross_size: 0.0,
215            target_cross_size: 0.0,
216
217            main_margin_start: 0.0,
218            main_margin_end: 0.0,
219            cross_margin_start: 0.0,
220            cross_margin_end: 0.0,
221
222            offset_main: 0.0,
223            offset_cross: 0.0,
224        }).collect();
225
226    // 2. Determine the available main and cross space for the flex items.
227    //    For each dimension, if that dimension of the flex container’s content box
228    //    is a definite size, use that; if that dimension of the flex container is
229    //    being sized under a min or max-content constraint, the available space in
230    //    that dimension is that constraint; otherwise, subtract the flex container’s
231    //    margin, border, and padding from the space available to the flex container
232    //    in that dimension and use that value. This might result in an infinite value.
233
234    let avaliable_main =
235        node.main_size(node.flex_direction).resolve(main_constraint.max, main_constraint.max - margin_main)
236            - padding_main
237            - border_main;
238
239    let avaliable_cross =
240        node.cross_size(node.flex_direction).resolve(cross_constraint.max, cross_constraint.max - margin_cross)
241            - padding_cross
242            - border_cross;
243
244    // 3. Determine the flex base size and hypothetical main size of each item:
245    for child in &mut flex_items {
246        // A. If the item has a definite used flex basis, that’s the flex base size.
247
248        let flex_basis = child.node.flex_basis.resolve(avaliable_main, f32::NAN);
249        if flex_basis.is_finite() {
250            child.flex_basis = flex_basis;
251            continue;
252        }
253
254        // B. If the flex item has an intrinsic aspect ratio,
255        //    a used flex basis of content, and a definite cross size,
256        //    then the flex base size is calculated from its inner
257        //    cross size and the flex item’s intrinsic aspect ratio.
258
259        if let Some(ratio) = child.node.aspect_ratio {
260            let cross_size = child.node.cross_size(node.flex_direction).resolve(avaliable_cross, f32::NAN);
261
262            if intrinsic_cross_size.is_finite() && child.node.flex_basis == style::Dimension::Auto {
263                child.flex_basis = ratio * cross_size;
264                continue;
265            }
266        }
267
268        // C. If the used flex basis is content or depends on its available space,
269        //    and the flex container is being sized under a min-content or max-content
270        //    constraint (e.g. when performing automatic table layout [CSS21]),
271        //    size the item under that constraint. The flex base size is the item’s
272        //    resulting main size.
273
274        if main_constraint.min.is_finite() || main_constraint.max.is_finite() {
275            let size = compute_internal(
276                child.node,
277                if node.flex_direction.is_row() { main_constraint } else { SizeConstraint::undefined() },
278                if node.flex_direction.is_column() { main_constraint } else { SizeConstraint::undefined() },
279            ).size;
280            child.flex_basis = size.main(node.flex_direction);
281            continue;
282        }
283
284        // D. Otherwise, if the used flex basis is content or depends on its
285        //    available space, the available main size is infinite, and the flex item’s
286        //    inline axis is parallel to the main axis, lay the item out using the rules
287        //    for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
288        //    is the item’s max-content main size.
289
290        if avaliable_main.is_nan() {
291            let size = compute_internal(child.node, SizeConstraint::undefined(), SizeConstraint::undefined()).size;
292            child.flex_basis = size.main(node.flex_direction);
293            continue;
294        }
295
296        // E. Otherwise, size the item into the available space using its used flex basis
297        //    in place of its main size, treating a value of content as max-content.
298        //    If a cross size is needed to determine the main size (e.g. when the
299        //    flex item’s main size is in its block axis) and the flex item’s cross size
300        //    is auto and not definite, in this calculation use fit-content as the
301        //    flex item’s cross size. The flex base size is the item’s resulting main size.
302
303        let size = compute_internal(
304            child.node,
305            if node.flex_direction.is_row() {
306                SizeConstraint::at_most(avaliable_main)
307            } else {
308                SizeConstraint::at_most(avaliable_cross)
309            },
310            if node.flex_direction.is_column() {
311                SizeConstraint::at_most(avaliable_main)
312            } else {
313                SizeConstraint::at_most(avaliable_cross)
314            },
315        ).size;
316        child.flex_basis = size.main(node.flex_direction);
317    }
318
319    // The hypothetical main size is the item’s flex base size clamped according to its
320    // used min and max main sizes (and flooring the content box size at zero).
321
322    for child in &mut flex_items {
323        let padding_start = child.node.main_padding_start(node.flex_direction).resolve(avaliable_main, 0.0);
324        let padding_end = child.node.main_padding_end(node.flex_direction).resolve(avaliable_main, 0.0);
325        let border_start = child.node.main_border_start(node.flex_direction).resolve(avaliable_main, 0.0);
326        let border_end = child.node.main_border_end(node.flex_direction).resolve(avaliable_main, 0.0);
327        child.inner_flex_basis = child.flex_basis - (padding_start + padding_end + border_start + border_end);
328
329        child.hypothetical_inner_main_size = child
330            .flex_basis
331            .max(child.node.min_main_size(node.flex_direction).resolve(avaliable_main, f32::MIN))
332            .min(child.node.max_main_size(node.flex_direction).resolve(avaliable_main, f32::MAX))
333            .max(0.0);
334
335        child.hypothetical_outer_main_size = child.hypothetical_inner_main_size
336            + child.node.main_margin_start(node.flex_direction).resolve(avaliable_main, 0.0)
337            + child.node.main_margin_end(node.flex_direction).resolve(avaliable_main, 0.0);
338    }
339
340    // 9.3. Main Size Determination
341
342    // 5. Collect flex items into flex lines:
343    //    - If the flex container is single-line, collect all the flex items into
344    //      a single flex line.
345    //    - Otherwise, starting from the first uncollected item, collect consecutive
346    //      items one by one until the first time that the next collected item would
347    //      not fit into the flex container’s inner main size (or until a forced break
348    //      is encountered, see §10 Fragmenting Flex Layout). If the very first
349    //      uncollected item wouldn’t fit, collect just it into the line.
350    //
351    //      For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.)
352    //      Repeat until all flex items have been collected into flex lines
353    //
354    //      Note that the "collect as many" line will collect zero-sized flex items onto
355    //      the end of the previous line even if the last non-zero item exactly "filled up" the line.
356
357    let mut flex_lines = {
358        let mut lines: Vec<FlexLine> = vec![];
359        let mut line_length = 0.0;
360
361        if node.flexWrap == style::FlexWrap::NoWrap {
362            lines.push(FlexLine { items: flex_items, cross_size: 0.0, offset_cross: 0.0 });
363        } else {
364            let mut line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 };
365
366            for child in flex_items {
367                line_length += child.hypothetical_outer_main_size;
368
369                if line_length > avaliable_main && line.items.len() > 0 {
370                    line_length = child.hypothetical_outer_main_size;
371                    lines.push(line);
372                    line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 };
373                }
374
375                line.items.push(child);
376            }
377
378            lines.push(line);
379        }
380
381        lines
382    };
383
384    // 6. Resolve the flexible lengths of all the flex items to find their used main size.
385    //    See §9.7 Resolving Flexible Lengths.
386    //
387    // 9.7. Resolving Flexible Lengths
388
389    for line in &mut flex_lines {
390        // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
391        //    items on the line. If the sum is less than the flex container’s inner main size,
392        //    use the flex grow factor for the rest of this algorithm; otherwise, use the
393        //    flex shrink factor.
394
395        let used_flex_factor: f32 = line.items.iter().map(|child| child.hypothetical_outer_main_size).sum();
396        let growing = used_flex_factor < avaliable_main;
397        let shrinking = !growing;
398
399        // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
400        //    - Any item that has a flex factor of zero
401        //    - If using the flex grow factor: any item that has a flex base size
402        //      greater than its hypothetical main size
403        //    - If using the flex shrink factor: any item that has a flex base size
404        //      smaller than its hypothetical main size
405
406        for child in line.items.iter_mut() {
407            child.target_main_size = child.hypothetical_inner_main_size;
408
409            if (child.node.flex_grow == 0.0 && child.node.flex_shrink == 0.0)
410                || (growing && child.flex_basis > child.hypothetical_inner_main_size)
411                || (shrinking && child.flex_basis < child.hypothetical_inner_main_size)
412            {
413                child.frozen = true;
414            }
415        }
416
417        // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
418        //    and subtract this from the flex container’s inner main size. For frozen items,
419        //    use their outer target main size; for other items, use their outer flex base size.
420
421        let used_space: f32 = line
422            .items
423            .iter()
424            .map(|child| {
425                child.node.main_margin_start(node.flex_direction).resolve(avaliable_main, 0.0)
426                    + child.node.main_margin_end(node.flex_direction).resolve(avaliable_main, 0.0)
427                    + if child.frozen { child.target_main_size } else { child.flex_basis }
428            }).sum();
429
430        let initial_free_space = avaliable_main - used_space;
431
432        // 4. Loop
433
434        loop {
435            // a. Check for flexible items. If all the flex items on the line are frozen,
436            //    free space has been distributed; exit this loop.
437
438            let mut frozen: Vec<&mut FlexItem> = vec![];
439            let mut unfrozen: Vec<&mut FlexItem> = vec![];
440
441            for child in line.items.iter_mut() {
442                if child.frozen {
443                    frozen.push(child);
444                } else {
445                    unfrozen.push(child);
446                }
447            }
448
449            if unfrozen.len() == 0 {
450                break;
451            }
452
453            // b. Calculate the remaining free space as for initial free space, above.
454            //    If the sum of the unfrozen flex items’ flex factors is less than one,
455            //    multiply the initial free space by this sum. If the magnitude of this
456            //    value is less than the magnitude of the remaining free space, use this
457            //    as the remaining free space.
458
459            let used_space: f32 = Iterator::chain(frozen.iter(), unfrozen.iter())
460                .map(|child| {
461                    child.node.main_margin_start(node.flex_direction).resolve(avaliable_main, 0.0)
462                        + child.node.main_margin_end(node.flex_direction).resolve(avaliable_main, 0.0)
463                        + if child.frozen { child.target_main_size } else { child.flex_basis }
464                }).sum();
465
466            let sum_flex_grow: f32 = unfrozen.iter().map(|item| item.node.flex_grow).sum();
467            let sum_flex_shrink: f32 = unfrozen.iter().map(|item| item.node.flex_shrink).sum();
468
469            let free_space = if growing && sum_flex_grow < 1.0 {
470                (initial_free_space * sum_flex_grow).min(avaliable_main - used_space)
471            } else if shrinking && sum_flex_shrink < 1.0 {
472                (initial_free_space * sum_flex_shrink).max(avaliable_main - used_space)
473            } else {
474                avaliable_main - used_space
475            };
476
477            // c. Distribute free space proportional to the flex factors.
478            //    - If the remaining free space is zero
479            //        Do Nothing
480            //    - If using the flex grow factor
481            //        Find the ratio of the item’s flex grow factor to the sum of the
482            //        flex grow factors of all unfrozen items on the line. Set the item’s
483            //        target main size to its flex base size plus a fraction of the remaining
484            //        free space proportional to the ratio.
485            //    - If using the flex shrink factor
486            //        For every unfrozen item on the line, multiply its flex shrink factor by
487            //        its inner flex base size, and note this as its scaled flex shrink factor.
488            //        Find the ratio of the item’s scaled flex shrink factor to the sum of the
489            //        scaled flex shrink factors of all unfrozen items on the line. Set the item’s
490            //        target main size to its flex base size minus a fraction of the absolute value
491            //        of the remaining free space proportional to the ratio. Note this may result
492            //        in a negative inner main size; it will be corrected in the next step.
493            //    - Otherwise
494            //        Do Nothing
495
496            if free_space > 0.0 && growing && sum_flex_grow > 0.0 {
497                for child in &mut unfrozen {
498                    child.target_main_size = child.flex_basis + free_space * (child.node.flex_grow / sum_flex_grow);
499                }
500            } else if free_space < 0.0 && shrinking && sum_flex_shrink > 0.0 {
501                let sum_scaled_shrink_factor: f32 =
502                    unfrozen.iter().map(|child| child.inner_flex_basis * child.node.flex_shrink).sum();
503
504                for child in &mut unfrozen {
505                    let scaled_shrink_factor = child.inner_flex_basis * child.node.flex_shrink;
506                    child.target_main_size =
507                        child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor);
508
509                    println!("child.flex_basis: {}, scaled_shrink_factor: {}, sum_scaled_shrink_factor: {}, child.inner_flex_basis: {}", child.flex_basis, scaled_shrink_factor, sum_scaled_shrink_factor, child.inner_flex_basis);
510                }
511            }
512
513            // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
514            //    used min and max main sizes and floor its content-box size at zero. If the
515            //    item’s target main size was made smaller by this, it’s a max violation.
516            //    If the item’s target main size was made larger by this, it’s a min violation.
517
518            let mut total_violation = 0.0;
519            for child in &mut unfrozen {
520                let max = child.node.max_main_size(node.flex_direction).resolve(avaliable_main, f32::MAX);
521                let min = child.node.min_main_size(node.flex_direction).resolve(avaliable_main, f32::MIN).max(0.0);
522
523                let clamped = if child.target_main_size > max {
524                    max
525                } else if child.target_main_size < min {
526                    min
527                } else {
528                    child.target_main_size
529                };
530
531                child.violation = clamped - child.target_main_size;
532                total_violation += child.violation;
533                child.target_main_size = clamped;
534            }
535
536            // e. Freeze over-flexed items. The total violation is the sum of the adjustments
537            //    from the previous step ∑(clamped size - unclamped size). If the total violation is:
538            //    - Zero
539            //        Freeze all items.
540            //    - Positive
541            //        Freeze all the items with min violations.
542            //    - Negative
543            //        Freeze all the items with max violations.
544
545            if total_violation > 0.0 {
546                for child in &mut unfrozen {
547                    child.frozen = child.violation > 0.0;
548                }
549            } else if total_violation < 0.0 {
550                for child in &mut unfrozen {
551                    child.frozen = child.violation < 0.0;
552                }
553            } else {
554                for child in &mut unfrozen {
555                    child.frozen = true;
556                }
557            }
558
559            // f. Return to the start of this loop.
560        }
561    }
562
563    // Not part of the spec from what i can see but seems correct
564    let container_main_size = if intrinsic_main_size.is_finite() {
565        intrinsic_main_size.min(main_constraint.max).max(main_constraint.min)
566    } else {
567        let mut longest_line = f32::MIN;
568
569        for line in &flex_lines {
570            let length: f32 = line.items.iter().map(|item| item.target_main_size).sum();
571
572            longest_line = if length > longest_line { length } else { longest_line };
573        }
574
575        (longest_line + padding_main + border_main).min(main_constraint.max).max(main_constraint.min)
576    };
577
578    // 9.4. Cross Size Determination
579
580    // 7. Determine the hypothetical cross size of each item by performing layout with the
581    //    used main size and the available space, treating auto as fit-content.
582
583    for line in &mut flex_lines {
584        for child in &mut line.items {
585            let size = compute_internal(
586                child.node,
587                if node.flex_direction.is_row() {
588                    SizeConstraint::exactly(child.target_main_size)
589                } else {
590                    SizeConstraint::between(cross_constraint.min, avaliable_cross)
591                },
592                if node.flex_direction.is_column() {
593                    SizeConstraint::exactly(child.target_main_size)
594                } else {
595                    SizeConstraint::between(cross_constraint.min, avaliable_cross)
596                },
597            ).size;
598
599            child.hypothetical_inner_cross_size = size
600                .cross(node.flex_direction)
601                .max(child.node.min_cross_size(node.flex_direction).resolve(avaliable_cross, f32::MIN))
602                .min(child.node.max_cross_size(node.flex_direction).resolve(avaliable_cross, f32::MAX))
603                .max(0.0);
604
605            child.hypothetical_outer_cross_size = child.hypothetical_inner_cross_size
606                + child.node.cross_margin_start(node.flex_direction).resolve(avaliable_cross, 0.0)
607                + child.node.cross_margin_end(node.flex_direction).resolve(avaliable_cross, 0.0);
608        }
609    }
610
611    // 8. Calculate the cross size of each flex line.
612    //    If the flex container is single-line and has a definite cross size, the cross size
613    //    of the flex line is the flex container’s inner cross size. Otherwise, for each flex line:
614    //
615    //    1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
616    //       align-self is baseline, and whose cross-axis margins are both non-auto. Find the
617    //       largest of the distances between each item’s baseline and its hypothetical outer
618    //       cross-start edge, and the largest of the distances between each item’s baseline
619    //       and its hypothetical outer cross-end edge, and sum these two values.
620    //    2. Among all the items not collected by the previous step, find the largest
621    //       outer hypothetical cross size.
622    //    3. The used cross-size of the flex line is the largest of the numbers found in the
623    //       previous two steps and zero.
624    //
625    //    If the flex container is single-line, then clamp the line’s cross-size to be within
626    //    the container’s computed min and max cross sizes. Note that if CSS 2.1’s definition
627    //    of min/max-width/height applied more generally, this behavior would fall out automatically.
628
629    if flex_lines.len() == 1 && intrinsic_cross_size.is_finite() {
630        flex_lines[0].cross_size = inner_intrinsic_cross_size.min(cross_constraint.max).max(cross_constraint.min);
631    } else {
632        for line in &mut flex_lines {
633            // TODO handle baseline (1)
634
635            line.cross_size =
636                line.items.iter().map(|child| child.hypothetical_outer_cross_size).fold(0.0, |acc, x| acc.max(x));
637        }
638    }
639
640    // 9. Handle 'align-content: stretch'. If the flex container has a definite cross size,
641    //    align-content is stretch, and the sum of the flex lines' cross sizes is less than
642    //    the flex container’s inner cross size, increase the cross size of each flex line
643    //    by equal amounts such that the sum of their cross sizes exactly equals the
644    //    flex container’s inner cross size.
645
646    if node.align_content == style::AlignContent::Stretch && intrinsic_cross_size.is_finite() {
647        let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
648
649        if total_cross < inner_intrinsic_cross_size {
650            let remaining = inner_intrinsic_cross_size - total_cross;
651            let addition = remaining / flex_lines.len() as f32;
652
653            for line in &mut flex_lines {
654                line.cross_size += addition;
655            }
656        }
657    }
658
659    // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
660    //     note the cross size of the line they’re in as the item’s strut size, and restart
661    //     layout from the beginning.
662    //
663    //     In this second layout round, when collecting items into lines, treat the collapsed
664    //     items as having zero main size. For the rest of the algorithm following that step,
665    //     ignore the collapsed items entirely (as if they were display:none) except that after
666    //     calculating the cross size of the lines, if any line’s cross size is less than the
667    //     largest strut size among all the collapsed items in the line, set its cross size to
668    //     that strut size.
669    //
670    //     Skip this step in the second layout round.
671
672    // TODO implement once (if ever) we support visibility:collapse
673
674    // 11. Determine the used cross size of each flex item. If a flex item has align-self: stretch,
675    //     its computed cross size property is auto, and neither of its cross-axis margins are auto,
676    //     the used outer cross size is the used cross size of its flex line, clamped according to
677    //     the item’s used min and max cross sizes. Otherwise, the used cross size is the item’s
678    //     hypothetical cross size.
679    //
680    //     If the flex item has align-self: stretch, redo layout for its contents, treating this
681    //     used size as its definite cross size so that percentage-sized children can be resolved.
682    //
683    //     Note that this step does not affect the main size of the flex item, even if it has an
684    //     intrinsic aspect ratio.
685
686    for line in &mut flex_lines {
687        for child in &mut line.items {
688            let is_stretch = child.node.align_self(node) == style::AlignSelf::Stretch;
689
690            if is_stretch
691                && child.node.cross_margin_start(node.flex_direction) != style::Dimension::Auto
692                && child.node.cross_margin_end(node.flex_direction) != style::Dimension::Auto
693                && child.node.cross_size(node.flex_direction) == style::Dimension::Auto
694            {
695                child.target_cross_size = line
696                    .cross_size
697                    .min(child.node.max_cross_size(node.flex_direction).resolve(avaliable_cross, f32::MAX))
698                    .max(child.node.min_cross_size(node.flex_direction).resolve(avaliable_cross, f32::MIN));
699            } else {
700                child.target_cross_size = child.hypothetical_inner_cross_size;
701            }
702
703            if is_stretch {
704                // TODO use result somehow
705                compute_internal(
706                    child.node,
707                    if node.flex_direction.is_row() {
708                        SizeConstraint::exactly(child.target_main_size)
709                    } else {
710                        SizeConstraint::exactly(child.target_cross_size)
711                    },
712                    if node.flex_direction.is_column() {
713                        SizeConstraint::exactly(child.target_main_size)
714                    } else {
715                        SizeConstraint::exactly(child.target_cross_size)
716                    },
717                );
718            }
719        }
720    }
721
722    // 9.5. Main-Axis Alignment
723
724    // 12. Distribute any remaining free space. For each flex line:
725    //     1. If the remaining free space is positive and at least one main-axis margin on this
726    //        line is auto, distribute the free space equally among these margins. Otherwise,
727    //        set all auto margins to zero.
728    //     2. Align the items along the main-axis per justify-content.
729
730    for line in &mut flex_lines {
731        let used_space: f32 = line.items.iter().map(|child| child.target_main_size).sum();
732        let free_space = avaliable_main - used_space;
733
734        if free_space > 0.0 {
735            let mut num_auto_margins = 0;
736
737            for child in &mut line.items {
738                if child.node.main_margin_start(node.flex_direction) == style::Dimension::Auto {
739                    num_auto_margins += 1;
740                }
741                if child.node.main_margin_end(node.flex_direction) == style::Dimension::Auto {
742                    num_auto_margins += 1;
743                }
744            }
745
746            if num_auto_margins > 0 {
747                let margin = free_space / num_auto_margins as f32;
748
749                for child in &mut line.items {
750                    if child.node.main_margin_start(node.flex_direction) == style::Dimension::Auto {
751                        child.main_margin_start = margin;
752                    }
753                    if child.node.main_margin_end(node.flex_direction) == style::Dimension::Auto {
754                        child.main_margin_end = margin;
755                    }
756                }
757            } else {
758                let num_items = line.items.len();
759                let mut is_first = true;
760
761                for child in &mut line.items {
762                    child.main_margin_start =
763                        child.node.main_margin_start(node.flex_direction).resolve(container_main_size, 0.0);
764                    child.main_margin_end =
765                        child.node.main_margin_end(node.flex_direction).resolve(container_main_size, 0.0);
766
767                    child.offset_main = match node.justify_content {
768                        style::JustifyContent::FlexStart => 0.0,
769                        style::JustifyContent::Center => if is_first {
770                            free_space / 2.0
771                        } else {
772                            0.0
773                        },
774                        style::JustifyContent::FlexEnd => if is_first {
775                            free_space
776                        } else {
777                            0.0
778                        },
779                        style::JustifyContent::SpaceBetween => if is_first {
780                            0.0
781                        } else {
782                            free_space / (num_items - 1) as f32
783                        },
784                        style::JustifyContent::SpaceAround => if is_first {
785                            (free_space / num_items as f32) / 2.0
786                        } else {
787                            free_space / num_items as f32
788                        },
789                        style::JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32,
790                    };
791
792                    is_first = false;
793                }
794            }
795        }
796    }
797
798    // 9.6. Cross-Axis Alignment
799
800    // 13. Resolve cross-axis auto margins. If a flex item has auto cross-axis margins:
801    //     - If its outer cross size (treating those auto margins as zero) is less than the
802    //       cross size of its flex line, distribute the difference in those sizes equally
803    //       to the auto margins.
804    //     - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis)
805    //       is auto, set it to zero. Set the opposite margin so that the outer cross size of the
806    //       item equals the cross size of its flex line.
807
808    for line in &mut flex_lines {
809        for child in &mut line.items {
810            if child.target_cross_size < line.cross_size {
811                let free_space = line.cross_size - child.target_cross_size;
812
813                if child.node.cross_margin_start(node.flex_direction) == style::Dimension::Auto
814                    && child.node.cross_margin_end(node.flex_direction) == style::Dimension::Auto
815                {
816                    child.cross_margin_start = free_space / 2.0;
817                    child.cross_margin_end = free_space / 2.0;
818                } else if child.node.cross_margin_start(node.flex_direction) == style::Dimension::Auto {
819                    child.cross_margin_start = free_space;
820                } else if child.node.cross_margin_end(node.flex_direction) == style::Dimension::Auto {
821                    child.cross_margin_end = free_space;
822                } else {
823                    child.cross_margin_start =
824                        child.node.cross_margin_start(node.flex_direction).resolve(line.cross_size, 0.0);
825                    child.cross_margin_end =
826                        child.node.cross_margin_end(node.flex_direction).resolve(line.cross_size, 0.0);
827
828                    // 14. Align all flex items along the cross-axis per align-self, if neither of the item’s
829                    //     cross-axis margins are auto.
830
831                    child.offset_cross = match child.node.align_self(node) {
832                        style::AlignSelf::Auto => 0.0, // Should never happen
833                        style::AlignSelf::FlexStart => if wrap_reverse {
834                            free_space
835                        } else {
836                            0.0
837                        },
838                        style::AlignSelf::FlexEnd => if wrap_reverse {
839                            0.0
840                        } else {
841                            free_space
842                        },
843                        style::AlignSelf::Center => free_space / 2.0,
844                        style::AlignSelf::Baseline => free_space / 2.0, // Treat as center for now until we have baseline support
845                        style::AlignSelf::Stretch => if wrap_reverse {
846                            free_space
847                        } else {
848                            0.0
849                        },
850                    };
851                }
852            }
853        }
854    }
855
856    // 15. Determine the flex container’s used cross size:
857    //     - If the cross size property is a definite size, use that, clamped by the used
858    //       min and max cross sizes of the flex container.
859    //     - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used
860    //       min and max cross sizes of the flex container.
861
862    let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
863    let container_cross_size = if intrinsic_cross_size.is_finite() {
864        intrinsic_cross_size.min(cross_constraint.max).max(cross_constraint.min)
865    } else {
866        (total_cross_size + padding_cross + border_cross).min(cross_constraint.max).max(cross_constraint.min)
867    };
868
869    // 16. Align all flex lines per align-content.
870
871    let free_space = container_cross_size - total_cross_size;
872    let num_lines = flex_lines.len();
873    let mut is_first = true;
874
875    let align_line = |line: &mut FlexLine| {
876        line.offset_cross = match node.align_content {
877            style::AlignContent::FlexStart => if is_first && wrap_reverse {
878                free_space
879            } else {
880                0.0
881            },
882            style::AlignContent::FlexEnd => if is_first && !wrap_reverse {
883                free_space
884            } else {
885                0.0
886            },
887            style::AlignContent::Center => if is_first {
888                free_space / 2.0
889            } else {
890                0.0
891            },
892            style::AlignContent::Stretch => 0.0,
893            style::AlignContent::SpaceBetween => if is_first {
894                0.0
895            } else {
896                free_space / (num_lines - 1) as f32
897            },
898            style::AlignContent::SpaceAround => if is_first {
899                (free_space / num_lines as f32) / 2.0
900            } else {
901                free_space / num_lines as f32
902            },
903        };
904        is_first = false;
905    };
906
907    if wrap_reverse {
908        flex_lines.iter_mut().rev().for_each(align_line);
909    } else {
910        flex_lines.iter_mut().for_each(align_line);
911    }
912
913    // Do a final layout pass and gather the resulting layouts
914
915    let mut children: Vec<layout::Node> = {
916        let mut lines: Vec<Vec<layout::Node>> = vec![];
917        let mut total_offset_cross = padding_cross_start + border_cross_start;
918
919        {
920            let layout_line = |line: &mut FlexLine| {
921                let mut children: Vec<layout::Node> = vec![];
922                let mut total_offset_main = padding_main_start + border_main_start;
923
924                for child in &mut line.items {
925                    let result = compute_internal(
926                        child.node,
927                        if node.flex_direction.is_row() {
928                            SizeConstraint::exactly(child.target_main_size)
929                        } else {
930                            SizeConstraint::exactly(child.target_cross_size)
931                        },
932                        if node.flex_direction.is_column() {
933                            SizeConstraint::exactly(child.target_main_size)
934                        } else {
935                            SizeConstraint::exactly(child.target_cross_size)
936                        },
937                    );
938
939                    let offset_main = {
940                        total_offset_main
941                            + child.offset_main
942                            + child.main_margin_start
943                            + (child.node.main_start(node.flex_direction).resolve(container_main_size, 0.0)
944                                - child.node.main_end(node.flex_direction).resolve(container_main_size, 0.0))
945                    };
946
947                    let offset_cross = {
948                        total_offset_cross
949                            + child.offset_cross
950                            + line.offset_cross
951                            + child.cross_margin_start
952                            + (child.node.cross_start(node.flex_direction).resolve(container_main_size, 0.0)
953                                - child.node.cross_end(node.flex_direction).resolve(container_main_size, 0.0))
954                    };
955
956                    children.push(layout::Node {
957                        width: result.size.width,
958                        height: result.size.height,
959                        x: if node.flex_direction.is_row() { offset_main } else { offset_cross },
960                        y: if node.flex_direction.is_column() { offset_main } else { offset_cross },
961                        children: result.children,
962                    });
963
964                    total_offset_main = offset_main + result.size.main(node.flex_direction) + child.main_margin_end;
965                }
966
967                total_offset_cross += line.offset_cross + line.cross_size;
968                lines.push(children);
969            };
970
971            if wrap_reverse {
972                flex_lines.iter_mut().rev().for_each(layout_line);
973            } else {
974                flex_lines.iter_mut().for_each(layout_line);
975            }
976        }
977
978        if wrap_reverse {
979            lines.into_iter().rev().flat_map(|x| x).collect()
980        } else {
981            lines.into_iter().flat_map(|x| x).collect()
982        }
983    };
984
985    // Before returning we perform absolute layout on all absolutely positioned children
986
987    let absolute_children: Vec<&style::Node> =
988        node.children.iter().filter(|child| child.position == style::Position::Absolute).collect();
989
990    for child in absolute_children {
991        let start_main = child.main_start(node.flex_direction).resolve(container_main_size, f32::NAN);
992        let end_main = child.main_end(node.flex_direction).resolve(container_main_size, f32::NAN);
993        let main = if start_main.is_finite() && end_main.is_finite() {
994            container_main_size - start_main - end_main
995        } else { f32::NAN };
996
997        let start_cross = child.cross_start(node.flex_direction).resolve(container_cross_size, f32::NAN);
998        let end_cross = child.cross_end(node.flex_direction).resolve(container_cross_size, f32::NAN);
999        let cross = if start_cross.is_finite() && end_cross.is_finite() { 
1000            container_cross_size - start_cross - end_cross
1001         } else { f32::NAN };
1002
1003        let result = compute_internal(
1004            child,
1005            if node.flex_direction.is_row() { SizeConstraint::exactly(main) } else { SizeConstraint::exactly(cross) },
1006            if node.flex_direction.is_column() {
1007                SizeConstraint::exactly(main)
1008            } else {
1009                SizeConstraint::exactly(cross)
1010            },
1011        );
1012
1013        let offset_main = if start_main.is_finite() {
1014            start_main
1015        } else if end_main.is_finite() {
1016            container_main_size - end_main - result.size.main(node.flex_direction)
1017        } else {
1018            match node.justify_content {
1019                style::JustifyContent::SpaceBetween | style::JustifyContent::FlexStart => 0.0,
1020                style::JustifyContent::FlexEnd => container_main_size - result.size.main(node.flex_direction),
1021                style::JustifyContent::SpaceEvenly
1022                | style::JustifyContent::SpaceAround
1023                | style::JustifyContent::Center => {
1024                    container_main_size / 2.0 - result.size.main(node.flex_direction) / 2.0
1025                }
1026            }
1027        };
1028
1029        let free_space = container_cross_size - result.size.cross(node.flex_direction);
1030
1031        let offset_cross = if start_cross.is_finite() {
1032            start_cross
1033        } else if end_cross.is_finite() {
1034            free_space - end_cross
1035        } else {
1036            match child.align_self(node) {
1037                style::AlignSelf::Auto => 0.0, // Should never happen
1038                style::AlignSelf::FlexStart => if wrap_reverse {
1039                    free_space
1040                } else {
1041                    0.0
1042                },
1043                style::AlignSelf::FlexEnd => if wrap_reverse {
1044                    0.0
1045                } else {
1046                    free_space
1047                },
1048                style::AlignSelf::Center => free_space / 2.0,
1049                style::AlignSelf::Baseline => free_space / 2.0, // Treat as center for now until we have baseline support
1050                style::AlignSelf::Stretch => if wrap_reverse {
1051                    free_space
1052                } else {
1053                    0.0
1054                },
1055            }
1056        };
1057
1058        children.push(layout::Node {
1059            width: result.size.width,
1060            height: result.size.height,
1061            x: if node.flex_direction.is_row() { offset_main } else { offset_cross },
1062            y: if node.flex_direction.is_column() { offset_main } else { offset_cross },
1063            children: result.children,
1064        });
1065    }
1066
1067    ComputeResult {
1068        size: FlexSize {
1069            width: if node.flex_direction.is_row() { container_main_size } else { container_cross_size },
1070            height: if node.flex_direction.is_column() { container_main_size } else { container_cross_size },
1071        },
1072        children,
1073    }
1074}