tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Definitions

⎇ bd78e4a192d77d56ec577a1e5a707be4f4cf08f7 src/algo.rs Rust · 704 lines · 27.9 KB History Who touched what raw

initial commit. Something works bd78e4a19 · Emil Sjölander ·

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