tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5364Rust 86Markdown 12TOML 91822 Definiciones

⎇ 348ac760233c53cb615d2d28fa5a6a63565c9eae src/algo.rs Rust · 1115 líneas · 48.6 KB Historia Quién tocó qué crudo

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