tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5464Rust 87Markdown 12TOML 91903 Sutichaykuna

⎇ 6df8c338557a995e2ca93a3e4c725921a029633f src/algo.rs Rust · 1092 siq'ikuna · 47.5 KB Ñawpa willay Pi imata llamkarqan ch'uya

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