tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1077HTML 1008Markdown 12TOML 83209 Definitions

⎇ 076aca1b7e6f8d292ac3a13f1565ac94aaf1f5bd src/algo.rs Rust · 913 lines · 35.5 KB History Who touched what raw

Basic test generation working 076aca1b7 · Emil Sjölander ·

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