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}