weasyprint 64.1__py3-none-any.whl → 65.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
weasyprint/layout/flex.py CHANGED
@@ -5,126 +5,88 @@ from math import inf, log10
5
5
 
6
6
  from ..css.properties import Dimension
7
7
  from ..formatting_structure import boxes
8
+ from . import percent
8
9
  from .absolute import AbsolutePlaceholder, absolute_layout
9
- from .percent import resolve_one_percentage, resolve_percentages
10
- from .preferred import max_content_width, min_content_width
11
- from .table import find_in_flow_baseline
10
+ from .preferred import max_content_width, min_content_width, min_max
11
+ from .table import find_in_flow_baseline, table_wrapper_width
12
12
 
13
13
 
14
14
  class FlexLine(list):
15
- pass
15
+ """Flex container line."""
16
16
 
17
17
 
18
- def flex_layout(context, box, bottom_space, skip_stack, containing_block,
19
- page_is_empty, absolute_boxes, fixed_boxes):
20
- from . import block, preferred
18
+ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_is_empty,
19
+ absolute_boxes, fixed_boxes, discard):
20
+ from . import block
21
21
 
22
- context.create_block_formatting_context()
22
+ # TODO: merge this with block_container_layout.
23
+ context.create_flex_formatting_context()
23
24
  resume_at = None
24
25
 
26
+ is_start = skip_stack is None
27
+ box.remove_decoration(start=not is_start, end=False)
28
+
29
+ discard |= box.style['continue'] == 'discard'
30
+ draw_bottom_decoration = discard or box.style['box_decoration_break'] == 'clone'
31
+
32
+ row_gap, column_gap = box.style['row_gap'], box.style['column_gap']
33
+
34
+ if draw_bottom_decoration:
35
+ bottom_space += box.padding_bottom + box.border_bottom_width + box.margin_bottom
36
+
25
37
  if box.style['position'] == 'relative':
26
38
  # New containing block, use a new absolute list
27
39
  absolute_boxes = []
28
40
 
29
- # 9.x references are to: https://www.w3.org/TR/css-flexbox-1/#layout-algorithm.
41
+ # References are to: https://www.w3.org/TR/css-flexbox-1/#layout-algorithm.
42
+
43
+ # 1 Initial setup, done in formatting_structure.build.
30
44
 
31
- # 9.1. Generate anonymous flex items, done in formatting_structure.boxes.
32
- # 9.2. Line length determination.
45
+ # 2 Determine the available main and cross space for the flex items.
33
46
  if box.style['flex_direction'].startswith('row'):
34
- axis, cross = 'width', 'height'
47
+ main, cross = 'width', 'height'
35
48
  else:
36
- axis, cross = 'height', 'width'
49
+ main, cross = 'height', 'width'
37
50
 
38
51
  margin_left = 0 if box.margin_left == 'auto' else box.margin_left
39
52
  margin_right = 0 if box.margin_right == 'auto' else box.margin_right
40
- margin_top = 0 if box.margin_top == 'auto' else box.margin_top
41
- margin_bottom = 0 if box.margin_bottom == 'auto' else box.margin_bottom
42
53
 
43
- # 9.2.2 For each dimension, if that dimension of the flex
44
- # container’s content box is a definite size, use that.
45
- if getattr(box, axis) != 'auto':
46
- available_main_space = getattr(box, axis)
54
+ # Define available main space.
55
+ # TODO: min- and max-content not implemented.
56
+ if getattr(box, main) != 'auto':
57
+ # If that dimension of the flex container’s content box is a definite size…
58
+ available_main_space = getattr(box, main)
47
59
  else:
48
- # 9.2.2 If that dimension of the flex container is being sized
49
- # under a min or max-content constraint, the available space
50
- # in that dimension is that constraint.
51
- # TODO: not implemented (at least, not correctly).
52
-
53
- # 9.2.2 Otherwise, subtract the flex container’s margin,
54
- # border, and padding from the space available to the flex
55
- # container in that dimension and use that value. This might
56
- # result in an infinite value.
57
- if axis == 'width':
60
+ # Otherwise, subtract the flex container’s margin, border, and padding…
61
+ if main == 'width':
58
62
  available_main_space = (
59
63
  containing_block.width -
60
64
  margin_left - margin_right -
61
65
  box.padding_left - box.padding_right -
62
66
  box.border_left_width - box.border_right_width)
63
67
  else:
64
- main_space = context.page_bottom - bottom_space - box.position_y
65
- if containing_block.height != 'auto':
66
- if isinstance(containing_block.height, Dimension):
67
- assert containing_block.height.unit == 'px'
68
- main_space = min(main_space, containing_block.height.value)
69
- else:
70
- main_space = min(main_space, containing_block.height)
71
- available_main_space = (
72
- main_space -
73
- margin_top - margin_bottom -
74
- box.padding_top - box.padding_bottom -
75
- box.border_top_width - box.border_bottom_width)
68
+ available_main_space = inf
76
69
 
77
- # Same as above for available cross space
70
+ # Same as above for available cross space.
71
+ # TODO: min- and max-content not implemented.
78
72
  if getattr(box, cross) != 'auto':
79
73
  available_cross_space = getattr(box, cross)
80
74
  else:
81
- # TODO: As above, min- and max-content not implemented (at least, not
82
- # correctly).
83
- if cross == 'height':
84
- main_space = (
85
- context.page_bottom - bottom_space - box.content_box_y())
86
- if containing_block.height != 'auto':
87
- if isinstance(containing_block.height, Dimension):
88
- assert containing_block.height.unit == 'px'
89
- main_space = min(main_space, containing_block.height.value)
90
- else:
91
- main_space = min(main_space, containing_block.height)
92
- available_cross_space = (
93
- main_space -
94
- margin_top - margin_bottom -
95
- box.padding_top - box.padding_bottom -
96
- box.border_top_width - box.border_bottom_width)
97
- else:
75
+ if cross == 'width':
98
76
  available_cross_space = (
99
77
  containing_block.width -
100
78
  margin_left - margin_right -
101
79
  box.padding_left - box.padding_right -
102
80
  box.border_left_width - box.border_right_width)
81
+ else:
82
+ available_cross_space = inf
103
83
 
104
- # 9.2.3 Determine the flex base size and hypothetical main size of each
105
- # item.
106
- # TODO: Check that we really want to drop already calculated used values.
107
- children = box.children
108
- parent_box = box.copy_with_children(children)
109
- resolve_percentages(parent_box, containing_block)
110
- # We remove auto margins from parent_box (which is a throwaway) only but
111
- # keep them on box itself. They will get computed later, once we have done
112
- # some layout.
113
- if parent_box.margin_top == 'auto':
114
- parent_box.margin_top = 0
115
- if parent_box.margin_bottom == 'auto':
116
- parent_box.margin_bottom = 0
117
- if parent_box.margin_left == 'auto':
118
- parent_box.margin_left = 0
119
- if parent_box.margin_right == 'auto':
120
- parent_box.margin_right = 0
121
- if isinstance(parent_box, boxes.FlexBox):
122
- block.block_level_width(parent_box, containing_block)
123
- else:
124
- parent_box.width = preferred.flex_max_content_width(
125
- context, parent_box)
84
+ # 3 Determine the flex base size and hypothetical main size of each item.
85
+ parent_box = box.copy()
86
+ percent.resolve_percentages(parent_box, containing_block)
87
+ block.block_level_width(parent_box, containing_block)
88
+ children = sorted(box.children, key=lambda item: item.style['order'])
126
89
  original_skip_stack = skip_stack
127
- children = sorted(children, key=lambda item: item.style['order'])
128
90
  if skip_stack is not None:
129
91
  (skip, skip_stack), = skip_stack.items()
130
92
  if box.style['flex_direction'].endswith('-reverse'):
@@ -133,16 +95,46 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
133
95
  children = children[skip:]
134
96
  skip_stack = skip_stack
135
97
  else:
136
- skip = 0
137
- skip_stack = None
98
+ skip, skip_stack = 0, None
138
99
  child_skip_stack = skip_stack
139
100
 
101
+ if row_gap == 'normal':
102
+ row_gap = 0
103
+ elif row_gap.unit == '%':
104
+ if box.height == 'auto':
105
+ row_gap = 0
106
+ else:
107
+ row_gap = row_gap.value / 100 * box.height
108
+ else:
109
+ row_gap = row_gap.value
110
+ if column_gap == 'normal':
111
+ column_gap = 0
112
+ elif column_gap.unit == '%':
113
+ if box.width == 'auto':
114
+ column_gap = 0
115
+ else:
116
+ column_gap = column_gap.value / 100 * box.width
117
+ else:
118
+ column_gap = column_gap.value
119
+ if main == 'width':
120
+ main_gap, cross_gap = column_gap, row_gap
121
+ else:
122
+ main_gap, cross_gap = row_gap, column_gap
123
+
124
+ position_x = (
125
+ parent_box.position_x + parent_box.border_left_width + parent_box.padding_left)
126
+ if parent_box.margin_left != 'auto':
127
+ position_x += parent_box.margin_left
128
+ position_y = (
129
+ parent_box.position_y + parent_box.border_top_width + parent_box.padding_top)
130
+ if parent_box.margin_top != 'auto':
131
+ position_y += parent_box.margin_top
140
132
  for index, child in enumerate(children):
141
133
  if not child.is_flex_item:
142
134
  # Absolute child layout: create placeholder.
143
135
  if child.is_absolutely_positioned():
144
- child.position_x = box.content_box_x()
145
- child.position_y = box.content_box_y()
136
+ child.position_x = position_x
137
+ child.position_y = position_y
146
138
  new_child = placeholder = AbsolutePlaceholder(child)
147
139
  placeholder.index = index
148
140
  children[index] = placeholder
@@ -156,200 +148,161 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
156
148
  context.running_elements[running_name][page].append(child)
157
149
  continue
158
150
  # See https://www.w3.org/TR/css-flexbox-1/#min-size-auto.
159
- if child.style['overflow'] == 'visible':
160
- main_flex_direction = axis
151
+ if main == 'width':
152
+ child_containing_block = (available_main_space, parent_box.height)
161
153
  else:
162
- main_flex_direction = None
163
- resolve_percentages(child, parent_box, main_flex_direction)
164
- child.position_x = parent_box.content_box_x()
165
- child.position_y = parent_box.content_box_y()
166
- if child.min_width == 'auto':
167
- specified_size = child.width if child.width != 'auto' else inf
168
- if isinstance(child, boxes.ParentBox):
169
- new_child = child.copy_with_children(child.children)
170
- else:
171
- new_child = child.copy()
154
+ child_containing_block = (parent_box.width, available_main_space)
155
+ percent.resolve_percentages(child, child_containing_block)
156
+ if child.is_table_wrapper:
157
+ table_wrapper_width(context, child, child_containing_block)
158
+ child.position_x = position_x
159
+ child.position_y = position_y
160
+ if child.style['min_width'] == 'auto':
161
+ specified_size = child.width
162
+ new_child = child.copy()
172
163
  new_child.style = child.style.copy()
173
164
  new_child.style['width'] = 'auto'
174
165
  new_child.style['min_width'] = Dimension(0, 'px')
175
166
  new_child.style['max_width'] = Dimension(inf, 'px')
176
167
  content_size = min_content_width(context, new_child, outer=False)
177
- child.min_width = min(specified_size, content_size)
178
- elif child.min_height == 'auto':
179
- # TODO: Find a way to get min-content-height.
180
- specified_size = child.height if child.height != 'auto' else inf
181
- if isinstance(child, boxes.ParentBox):
182
- new_child = child.copy_with_children(child.children)
168
+ transferred_size = None
169
+ if isinstance(child, boxes.ReplacedBox):
170
+ image = child.replacement
171
+ _, intrinsic_height, intrinsic_ratio = image.get_intrinsic_size(
172
+ child.style['image_resolution'], child.style['font_size'])
173
+ if intrinsic_ratio and intrinsic_height:
174
+ transferred_size = intrinsic_height * intrinsic_ratio
175
+ if specified_size != 'auto':
176
+ child.min_width = min(specified_size, content_size)
177
+ elif transferred_size is not None:
178
+ child.min_width = min(transferred_size, content_size)
183
179
  else:
184
- new_child = child.copy()
180
+ child.min_width = content_size
181
+ if child.style['min_height'] == 'auto':
182
+ # TODO: avoid calling block_level_layout, write min_content_height instead.
183
+ specified_size = child.height
184
+ new_child = child.copy()
185
185
  new_child.style = child.style.copy()
186
+ if new_child.style['width'] == 'auto':
187
+ new_child_width = max_content_width(context, new_child)
188
+ new_child.style['width'] = Dimension(new_child_width, 'px')
186
189
  new_child.style['height'] = 'auto'
187
190
  new_child.style['min_height'] = Dimension(0, 'px')
188
191
  new_child.style['max_height'] = Dimension(inf, 'px')
189
192
  new_child = block.block_level_layout(
190
- context, new_child, -inf, child_skip_stack, parent_box,
193
+ context, new_child, bottom_space, child_skip_stack, parent_box,
191
194
  page_is_empty)[0]
192
- content_size = new_child.height
193
- child.min_height = min(specified_size, content_size)
194
-
195
- child.style = child.style.copy()
195
+ content_size = new_child.height if new_child else 0
196
+ transferred_size = None
197
+ if isinstance(child, boxes.ReplacedBox):
198
+ image = child.replacement
199
+ intrinsic_width, _, intrinsic_ratio = image.get_intrinsic_size(
200
+ child.style['image_resolution'], child.style['font_size'])
201
+ if intrinsic_ratio and intrinsic_width:
202
+ transferred_size = intrinsic_width / intrinsic_ratio
203
+ if specified_size != 'auto':
204
+ child.min_height = min(specified_size, content_size)
205
+ elif transferred_size is not None:
206
+ child.min_height = min(transferred_size, content_size)
207
+ else:
208
+ child.min_height = content_size
196
209
 
197
210
  if child.style['flex_basis'] == 'content':
198
- flex_basis = child.flex_basis = 'content'
211
+ flex_basis = 'content'
199
212
  else:
200
- resolve_one_percentage(child, 'flex_basis', available_main_space)
201
- flex_basis = child.flex_basis
202
-
203
- # "If a value would resolve to auto for width, it instead resolves
204
- # to content for flex-basis." Let's do this for height too.
205
- # See https://www.w3.org/TR/css-flexbox-1/#propdef-flex-basis.
206
- resolve_one_percentage(child, axis, available_main_space)
207
- if flex_basis == 'auto':
208
- if child.style[axis] == 'auto':
209
- flex_basis = 'content'
210
- else:
211
- if axis == 'width':
212
- flex_basis = child.border_width()
213
- if child.margin_left != 'auto':
214
- flex_basis += child.margin_left
215
- if child.margin_right != 'auto':
216
- flex_basis += child.margin_right
217
- else:
218
- flex_basis = child.border_height()
219
- if child.margin_top != 'auto':
220
- flex_basis += child.margin_top
221
- if child.margin_bottom != 'auto':
222
- flex_basis += child.margin_bottom
213
+ flex_basis = percent.percentage(
214
+ child.style['flex_basis'], available_main_space)
215
+ if flex_basis == 'auto':
216
+ if (flex_basis := getattr(child, main)) == 'auto':
217
+ flex_basis = 'content'
223
218
 
224
- # 9.2.3.A If the item has a definite used flex basis, that’s the flex
225
- # base size.
219
+ # 3.A If the item has a definite used flex basis
226
220
  if flex_basis != 'content':
227
221
  child.flex_base_size = flex_basis
222
+ if main == 'width':
223
+ child.main_outer_extra = (
224
+ child.border_left_width + child.border_right_width +
225
+ child.padding_left + child.padding_right)
226
+ if child.margin_left != 'auto':
227
+ child.main_outer_extra += child.margin_left
228
+ if child.margin_right != 'auto':
229
+ child.main_outer_extra += child.margin_right
230
+ else:
231
+ child.main_outer_extra = (
232
+ child.border_top_width + child.border_bottom_width +
233
+ child.padding_top + child.padding_bottom)
234
+ if child.margin_top != 'auto':
235
+ child.main_outer_extra += child.margin_top
236
+ if child.margin_bottom != 'auto':
237
+ child.main_outer_extra += child.margin_bottom
228
238
  elif False:
229
- # TODO: 9.2.3.B If the flex item has an intrinsic aspect ratio, a
230
- # used flex basis of 'content', and a definite cross size, then the
231
- # flex base size is calculated from its inner cross size and the
232
- # flex item’s intrinsic aspect ratio.
233
- pass
234
- elif False:
235
- # TODO: 9.2.3.C If the used flex basis is 'content' or depends on
236
- # its available space, and the flex container is being sized under
237
- # a 'min-content' or 'max-content' constraint (e.g. when performing
238
- # automatic table layout [CSS21]), size the item under that
239
- # constraint. The flex base size is the item’s resulting main size.
240
- pass
241
- elif False:
242
- # TODO: 9.2.3.D Otherwise, if the used flex basis is 'content' or
243
- # depends on its available space, the available main size is
244
- # infinite, and the flex item’s inline axis is parallel to the main
245
- # axis, lay the item out using the rules for a box in an orthogonal
246
- # flow [CSS3-WRITING-MODES]. The flex base size is the item’s
247
- # max-content main size. This case occurs, for example, in an
248
- # English document (horizontal writing mode) containing a column
249
- # flex container containing a vertical Japanese (vertical writing
250
- # mode) flex item. We *can* have infinite main size (only for
251
- # flex-direction: column - see
252
- # https://www.w3.org/TR/css-flexbox-1/#pagination), but we don't
253
- # support vertical writing mode (or writing-mode in general) yet,
254
- # so this isn't relevant.
239
+ # TODO: 3.B If the flex item has an intrinsic aspect ratio
240
+ # TODO: 3.C If the used flex basis is 'content'
241
+ # TODO: 3.D Otherwise, if the used flex basis is 'content'…
255
242
  pass
256
243
  else:
257
- # 9.2.3.E Otherwise, size the item into the available
258
- # space using its used flex basis in place of its main size,
259
- # treating a value of 'content' as 'max-content'. If a cross size
260
- # is needed to determine the main size (e.g. when the flex item’s
261
- # main size is in its block axis) and the flex item’s cross size is
262
- # 'auto' and not definite, in this calculation use 'fit-content' as
263
- # the flex item’s cross size. The flex base size is the item’s
264
- # resulting main size.
265
- child.style[axis] = 'max-content'
266
-
267
- # TODO: Don't set style value, support *-content values instead.
268
- if child.style[axis] == 'max-content':
269
- child.style[axis] = 'auto'
270
- if axis == 'width':
271
- child.flex_base_size = max_content_width(context, child)
272
- else:
273
- if isinstance(child, boxes.ParentBox):
274
- new_child = child.copy_with_children(child.children)
275
- else:
276
- new_child = child.copy()
277
- new_child.width = inf
278
- new_child = block.block_level_layout(
279
- context, new_child, -inf, child_skip_stack, parent_box,
280
- page_is_empty, absolute_boxes, fixed_boxes)[0]
281
- child.flex_base_size = new_child.margin_height()
282
- elif child.style[axis] == 'min-content':
283
- child.style[axis] = 'auto'
284
- if axis == 'width':
285
- child.flex_base_size = min_content_width(context, child)
286
- else:
287
- if isinstance(child, boxes.ParentBox):
288
- new_child = child.copy_with_children(child.children)
289
- else:
290
- new_child = child.copy()
291
- new_child.width = 0
292
- new_child = block.block_level_layout(
293
- context, new_child, -inf, child_skip_stack, parent_box,
294
- page_is_empty, absolute_boxes, fixed_boxes)[0]
295
- child.flex_base_size = new_child.margin_height()
244
+ # 3.E Otherwise
245
+ if main == 'width':
246
+ child.flex_base_size = max_content_width(context, child, outer=False)
247
+ child.main_outer_extra = (
248
+ max_content_width(context, child) - child.flex_base_size)
296
249
  else:
297
- assert child.style[axis].unit == 'px'
298
- # TODO: To be consistent with the above we may wish to add
299
- # padding, border, and margins here (but it's not done
300
- # consistently above either, and may not be correct).
301
- child.flex_base_size = child.style[axis].value
250
+ new_child = child.copy()
251
+ new_child.width = inf
252
+ new_child = block.block_level_layout(
253
+ context, new_child, bottom_space, child_skip_stack, parent_box,
254
+ page_is_empty, absolute_boxes, fixed_boxes)[0]
255
+ if new_child:
256
+ child.flex_base_size = new_child.height
257
+ child.main_outer_extra = (
258
+ new_child.margin_height() - new_child.height)
259
+ else:
260
+ child.flex_base_size = child.main_outer_extra = 0
261
+
262
+ if main == 'width':
263
+ position_x += child.flex_base_size + child.main_outer_extra
264
+ else:
265
+ position_y += child.flex_base_size + child.main_outer_extra
302
266
 
267
+ min_size = getattr(child, f'min_{main}')
268
+ max_size = getattr(child, f'max_{main}')
303
269
  child.hypothetical_main_size = max(
304
- getattr(child, f'min_{axis}'), min(
305
- child.flex_base_size, getattr(child, f'max_{axis}')))
270
+ min_size, min(child.flex_base_size, max_size))
306
271
 
307
272
  # Skip stack is only for the first child.
308
273
  child_skip_stack = None
309
274
 
310
- # 9.2.4 Determine the main size of the flex container
311
- # using the rules of the formatting context in which it
312
- # participates. For this computation, auto margins on flex items
313
- # are treated as 0.
314
- # TODO: The whole step has to be fixed.
315
- if axis == 'width':
275
+ # 4 Determine the main size of the flex container using the rules of the formatting
276
+ # context in which it participates.
277
+ if main == 'width':
316
278
  block.block_level_width(box, containing_block)
317
279
  else:
318
- if box.style['height'] != 'auto' and box.style['height'].unit != '%':
319
- box.height = box.style['height'].value
320
- elif box.style['height'] != 'auto' and containing_block.height != 'auto':
321
- box.height = box.style['height'].value / 100 * containing_block.height
322
- else:
280
+ if box.height == 'auto':
323
281
  box.height = 0
324
- for index, child in enumerate(children):
325
- if not child.is_flex_item:
326
- continue
327
- child_height = (
328
- child.hypothetical_main_size +
329
- child.border_top_width + child.border_bottom_width +
330
- child.padding_top + child.padding_bottom)
331
- if getattr(box, axis) == 'auto' and (
332
- child_height + box.height > available_main_space):
333
- resume_at = {index: None}
334
- children = children[:index + 1]
335
- break
336
- box.height += child_height
337
-
338
- # 9.3.5 If the flex container is single-line, collect all the flex items
339
- # into a single flex line.
282
+ flex_items = (child for child in children if child.is_flex_item)
283
+ for i, child in enumerate(flex_items):
284
+ box.height += child.hypothetical_main_size + child.main_outer_extra
285
+ if i:
286
+ box.height += main_gap
287
+ box.height = max(box.min_height, min(box.height, box.max_height))
288
+
289
+ # 5 If the flex container is single-line, collect all the flex items into a single
290
+ # flex line.
340
291
  flex_lines = []
341
292
  line = []
342
293
  line_size = 0
343
- axis_size = getattr(box, axis)
294
+ main_size = getattr(box, main)
344
295
  for i, child in enumerate(children, start=skip):
345
296
  if not child.is_flex_item:
346
297
  continue
347
- line_size += child.hypothetical_main_size
348
- if box.style['flex_wrap'] != 'nowrap' and line_size > axis_size:
298
+ line_size += child.hypothetical_main_size + child.main_outer_extra
299
+ if i > skip:
300
+ line_size += main_gap
301
+ if box.style['flex_wrap'] != 'nowrap' and line_size > main_size:
349
302
  if line:
350
303
  flex_lines.append(FlexLine(line))
351
304
  line = [(i, child)]
352
- line_size = child.hypothetical_main_size
305
+ line_size = child.hypothetical_main_size + child.main_outer_extra
353
306
  else:
354
307
  line.append((i, child))
355
308
  flex_lines.append(FlexLine(line))
@@ -367,72 +320,58 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
367
320
  for line in flex_lines:
368
321
  line.reverse()
369
322
 
370
- # 9.3.6 Resolve the flexible lengths of all the flex items to find their
371
- # used main size.
372
- # See https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths.
373
- available_main_space = getattr(box, axis)
323
+ # 6 Resolve the flexible lengths of all the flex items to find their used main size.
324
+ available_main_space = getattr(box, main)
374
325
  for line in flex_lines:
375
- # 9.7.1 Determine the used flex factor. Sum the outer hypothetical main
376
- # sizes of all items on the line. If the sum is less than the flex
377
- # container’s inner main size, use the flex grow factor for the rest of
378
- # this algorithm; otherwise, use the flex shrink factor.
326
+ # 9.7.1 Determine the used flex factor.
379
327
  hypothetical_main_size = sum(
380
- child.hypothetical_main_size for index, child in line)
328
+ child.hypothetical_main_size + child.main_outer_extra
329
+ for index, child in line)
381
330
  if hypothetical_main_size < available_main_space:
382
331
  flex_factor_type = 'grow'
383
332
  else:
384
333
  flex_factor_type = 'shrink'
385
334
 
386
- # 9.7.2 Size inflexible items. Freeze, setting its target main size to
387
- # its hypothetical main size…
388
- # - any item that has a flex factor of zero,
389
- # - if using the flex grow factor: any item that has a flex base size
390
- # greater than its hypothetical main size,
391
- # - if using the flex shrink factor: any item that has a flex base
392
- # size smaller than its hypothetical main size.
335
+ # 9.7.3 Size inflexible items.
393
336
  for index, child in line:
394
337
  if flex_factor_type == 'grow':
395
338
  child.flex_factor = child.style['flex_grow']
339
+ flex_condition = child.flex_base_size > child.hypothetical_main_size
396
340
  else:
397
341
  child.flex_factor = child.style['flex_shrink']
398
- if (child.flex_factor == 0 or
399
- (flex_factor_type == 'grow' and
400
- child.flex_base_size > child.hypothetical_main_size) or
401
- (flex_factor_type == 'shrink' and
402
- child.flex_base_size < child.hypothetical_main_size)):
342
+ flex_condition = child.flex_base_size < child.hypothetical_main_size
343
+ if child.flex_factor == 0 or flex_condition:
403
344
  child.target_main_size = child.hypothetical_main_size
404
345
  child.frozen = True
405
346
  else:
406
347
  child.frozen = False
407
348
 
408
- # 9.7.3 Calculate initial free space. Sum the outer sizes of all items
409
- # on the line, and subtract this from the flex container’s inner main
410
- # size. For frozen items, use their outer target main size; for other
411
- # items, use their outer flex base size.
349
+ # 9.7.4 Calculate initial free space.
412
350
  initial_free_space = available_main_space
413
- for index, child in line:
351
+ for i, (index, child) in enumerate(line):
414
352
  if child.frozen:
415
- initial_free_space -= child.target_main_size
353
+ initial_free_space -= child.target_main_size + child.main_outer_extra
416
354
  else:
417
- initial_free_space -= child.flex_base_size
355
+ initial_free_space -= child.flex_base_size + child.main_outer_extra
356
+ if i:
357
+ initial_free_space -= main_gap
418
358
 
419
- # 9.7.4.a Check for flexible items. If all the flex items on the line
420
- # are frozen, free space has been distributed; exit this loop.
359
+ # 9.7.5.a Check for flexible items.
421
360
  while not all(child.frozen for index, child in line):
422
361
  unfrozen_factor_sum = 0
423
362
  remaining_free_space = available_main_space
424
363
 
425
- # 9.7.4.b Calculate the remaining free space as for initial free
426
- # space, above. If the sum of the unfrozen flex items’ flex factors
427
- # is less than one, multiply the initial free space by this sum. If
428
- # the magnitude of this value is less than the magnitude of the
429
- # remaining free space, use this as the remaining free space.
430
- for index, child in line:
364
+ # 9.7.5.b Calculate the remaining free space.
365
+ for i, (index, child) in enumerate(line):
431
366
  if child.frozen:
432
- remaining_free_space -= child.target_main_size
367
+ remaining_free_space -= (
368
+ child.target_main_size + child.main_outer_extra)
433
369
  else:
434
- remaining_free_space -= child.flex_base_size
370
+ remaining_free_space -= (
371
+ child.flex_base_size + child.main_outer_extra)
435
372
  unfrozen_factor_sum += child.flex_factor
373
+ if i:
374
+ remaining_free_space -= main_gap
436
375
 
437
376
  if unfrozen_factor_sum < 1:
438
377
  initial_free_space *= unfrozen_factor_sum
@@ -443,19 +382,16 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
443
382
  remaining_free_space = sys.maxsize
444
383
 
445
384
  initial_magnitude = (
446
- int(log10(initial_free_space)) if initial_free_space > 0
447
- else -inf)
385
+ int(log10(initial_free_space)) if initial_free_space > 0 else -inf)
448
386
  remaining_magnitude = (
449
- int(log10(remaining_free_space)) if remaining_free_space > 0
450
- else -inf)
387
+ int(log10(remaining_free_space)) if remaining_free_space > 0 else -inf)
451
388
  if initial_magnitude < remaining_magnitude:
452
389
  remaining_free_space = initial_free_space
453
390
 
454
- # 9.7.4.c Distribute free space proportional to the flex factors.
391
+ # 9.7.5.c Distribute free space proportional to the flex factors.
455
392
  if remaining_free_space == 0:
456
- # If the remaining free space is zero: "Do nothing", but we at
457
- # least set the flex_base_size as target_main_size for next
458
- # step.
393
+ # If the remaining free space is zero: "Do nothing", but we at least set
394
+ # the flex_base_size as target_main_size for next step.
459
395
  for index, child in line:
460
396
  if not child.frozen:
461
397
  child.target_main_size = child.flex_base_size
@@ -471,30 +407,12 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
471
407
  flex_grow_factors_sum += child.style['flex_grow']
472
408
  for index, child in line:
473
409
  if not child.frozen:
474
- # If using the flex grow factor: "Find the ratio of the
475
- # item’s flex grow factor to the sum of the flex grow
476
- # factors of all unfrozen items on the line. Set the
477
- # item’s target main size to its flex base size plus a
478
- # fraction of the remaining free space proportional to
479
- # the ratio."
410
+ # If using the flex grow factor
480
411
  if flex_factor_type == 'grow':
481
- ratio = (
482
- child.style['flex_grow'] /
483
- flex_grow_factors_sum)
412
+ ratio = child.style['flex_grow'] / flex_grow_factors_sum
484
413
  child.target_main_size = (
485
- child.flex_base_size +
486
- remaining_free_space * ratio)
487
- # If using the flex shrink factor: "For every unfrozen
488
- # item on the line, multiply its flex shrink factor by
489
- # its inner flex base size, and note this as its scaled
490
- # flex shrink factor. Find the ratio of the item’s
491
- # scaled flex shrink factor to the sum of the scaled
492
- # flex shrink factors of all unfrozen items on the
493
- # line. Set the item’s target main size to its flex
494
- # base size minus a fraction of the absolute value of
495
- # the remaining free space proportional to the ratio.
496
- # Note this may result in a negative inner main size;
497
- # it will be corrected in the next step."
414
+ child.flex_base_size + remaining_free_space * ratio)
415
+ # If using the flex shrink factor…
498
416
  elif flex_factor_type == 'shrink':
499
417
  if scaled_flex_shrink_factors_sum == 0:
500
418
  child.target_main_size = child.flex_base_size
@@ -503,24 +421,21 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
503
421
  child.scaled_flex_shrink_factor /
504
422
  scaled_flex_shrink_factors_sum)
505
423
  child.target_main_size = (
506
- child.flex_base_size +
507
- remaining_free_space * ratio)
508
-
509
- # 9.7.4.d Fix min/max violations. Clamp each non-frozen item’s
510
- # target main size by its used min and max main sizes and floor its
511
- # content-box size at zero. If the item’s target main size was made
512
- # smaller by this, it’s a max violation. If the item’s target main
513
- # size was made larger by this, it’s a min violation.
514
- # TODO: First part of this step is useless until 3.E is correct.
424
+ child.flex_base_size + remaining_free_space * ratio)
425
+ child.target_main_size = min_max(child, child.target_main_size)
426
+
427
+ # 9.7.5.d Fix min/max violations.
515
428
  for index, child in line:
516
429
  child.adjustment = 0
517
- if not child.frozen and child.target_main_size < 0:
518
- child.adjustment = -child.target_main_size
519
- child.target_main_size = 0
520
-
521
- # 9.7.4.e Freeze over-flexed items. The total violation is the sum
522
- # of the adjustments from the previous step
523
- # ∑(clamped size - unclamped size). If the total violation is:
430
+ if not child.frozen:
431
+ min_size = getattr(child, f'min_{main}')
432
+ max_size = getattr(child, f'max_{main}')
433
+ min_size = max(min_size , min(child.target_main_size, max_size))
434
+ if child.target_main_size < min_size:
435
+ child.adjustment = min_size - child.target_main_size
436
+ child.target_main_size = min_size
437
+
438
+ # 9.7.5.e Freeze over-flexed items.
524
439
  adjustments = sum(child.adjustment for index, child in line)
525
440
  for index, child in line:
526
441
  # Zero: Freeze all items.
@@ -533,72 +448,44 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
533
448
  elif adjustments < 0 and child.adjustment < 0:
534
449
  child.frozen = True
535
450
 
536
- # 9.7.5 Set each item’s used main size to its target main size.
451
+ # 9.7.6 Set each item’s used main size to its target main size.
537
452
  for index, child in line:
538
- if axis == 'width':
539
- child.width = (
540
- child.target_main_size -
541
- child.padding_left - child.padding_right -
542
- child.border_left_width - child.border_right_width)
543
- if child.margin_left != 'auto':
544
- child.width -= child.margin_left
545
- if child.margin_right != 'auto':
546
- child.width -= child.margin_right
453
+ if main == 'width':
454
+ child.width = child.target_main_size
547
455
  else:
548
- child.height = (
549
- child.target_main_size -
550
- child.padding_top - child.padding_bottom -
551
- child.border_top_width - child.border_top_width)
552
- if child.margin_top != 'auto':
553
- child.height -= child.margin_top
554
- if child.margin_bottom != 'auto':
555
- child.height -= child.margin_bottom
456
+ child.height = child.target_main_size
556
457
 
557
- # 9.4. Cross Size Determination.
558
- # TODO: Fix TODO in build.flex_children.
458
+ # 7 Determine the hypothetical cross size of each item.
559
459
  # TODO: Handle breaks.
560
460
  new_flex_lines = []
561
461
  child_skip_stack = skip_stack
562
- # 9.4.7 Determine the hypothetical cross size of each item by performing
563
- # layout with the used main size and the available space, treating auto as
564
- # fit-content.
565
462
  for line in flex_lines:
566
463
  new_flex_line = FlexLine()
567
464
  for index, child in line:
568
- # TODO: This *should* do the right thing for auto?
569
- # TODO: Find another way than calling block_level_layout_switch to
570
- # get baseline and child.height.
465
+ # TODO: Fix this value, see test_flex_item_auto_margin_cross.
571
466
  if child.margin_top == 'auto':
572
467
  child.margin_top = 0
573
468
  if child.margin_bottom == 'auto':
574
469
  child.margin_bottom = 0
575
- if isinstance(child, boxes.ParentBox):
576
- child_copy = child.copy_with_children(child.children)
577
- else:
578
- child_copy = child.copy()
579
- block.block_level_width(child_copy, parent_box)
580
- # TODO: We set the style here above, otherwise used main size will
581
- # get clobbered by resolve_percentages() somewhere down the line,
582
- # particularly if the child is itself a flex container. Is this the
583
- # right approach?
584
- child_copy.style[axis] = Dimension(getattr(child, axis), 'px')
585
- new_child, _, _, adjoining_margins, _, _ = (
586
- block.block_level_layout_switch(
587
- context, child_copy, -inf, child_skip_stack, parent_box,
588
- page_is_empty, absolute_boxes, fixed_boxes,
589
- [], False, None))
590
-
470
+ # TODO: Find another way than calling block_level_layout_switch.
471
+ new_child = child.copy()
472
+ new_child, _, _, adjoining_margins, _, _ = block.block_level_layout_switch(
473
+ context, new_child, -inf, child_skip_stack, parent_box, page_is_empty,
474
+ absolute_boxes, fixed_boxes, [], discard, None)
591
475
  child._baseline = find_in_flow_baseline(new_child) or 0
592
476
  if cross == 'height':
593
477
  child.height = new_child.height
594
- # As flex items margins never collapse (with other flex items
595
- # or with the flex container), we can add the adjoining margins
596
- # to the child bottom margin.
478
+ # As flex items margins never collapse (with other flex items or
479
+ # with the flex container), we can add the adjoining margins to the
480
+ # child bottom margin.
597
481
  child.margin_bottom += block.collapse_margin(adjoining_margins)
598
482
  else:
599
- # TODO: We had min_content_width here but I have no idea under
600
- # what circumstance that would be correct.
601
- child.width = new_child.width
483
+ if child.width == 'auto':
484
+ min_width = min_content_width(context, child, outer=False)
485
+ max_width = max_content_width(context, child, outer=False)
486
+ child.width = min(max(min_width, new_child.width), max_width)
487
+ else:
488
+ child.width = new_child.width
602
489
 
603
490
  new_flex_line.append((index, child))
604
491
 
@@ -609,33 +496,25 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
609
496
  new_flex_lines.append(new_flex_line)
610
497
  flex_lines = new_flex_lines
611
498
 
499
+ # 8 Calculate the cross size of each flex line.
612
500
  cross_size = getattr(box, cross)
613
501
  if len(flex_lines) == 1 and cross_size != 'auto':
614
- # 9.4.8 If the flex container is single-line and has a
615
- # definite cross size, the cross size of the flex line is the
616
- # flex container’s inner cross size.
502
+ # If the flex container is single-line
617
503
  flex_lines[0].cross_size = cross_size
618
- else: # 9.4.8 Otherwise, for each flex line:
619
- # 9.4.8.1 Collect all the flex items whose inline-axis is parallel to
620
- # the main-axis, whose align-self is baseline, and whose cross-axis
621
- # margins are both non-auto. Find the largest of the distances between
622
- # each item’s baseline and its hypothetical outer cross-start edge, and
623
- # the largest of the distances between each item’s baseline and its
624
- # hypothetical outer cross-end edge, and sum these two values.
504
+ else:
505
+ # Otherwise, for each flex line…
506
+ # 8.1 Collect all the flex items whose inline-axis is parallel to the main-axis
625
507
  for line in flex_lines:
626
508
  collected_items = []
627
509
  not_collected_items = []
628
510
  for index, child in line:
629
511
  align_self = child.style['align_self']
630
- if (box.style['flex_direction'].startswith('row') and
631
- 'baseline' in align_self and
632
- child.margin_top != 'auto' and
633
- child.margin_bottom != 'auto'):
634
- collected_items.append(child)
635
- else:
636
- not_collected_items.append(child)
637
- cross_start_distance = 0
638
- cross_end_distance = 0
512
+ collect = (
513
+ box.style['flex_direction'].startswith('row') and
514
+ 'baseline' in align_self and
515
+ 'auto' not in (child.margin_top, child.margin_bottom))
516
+ (collected_items if collect else not_collected_items).append(child)
517
+ cross_start_distance = cross_end_distance = 0
639
518
  for child in collected_items:
640
519
  baseline = child._baseline - child.position_y
641
520
  cross_start_distance = max(cross_start_distance, baseline)
@@ -643,8 +522,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
643
522
  cross_end_distance, child.margin_height() - baseline)
644
523
  collected_cross_size = cross_start_distance + cross_end_distance
645
524
  non_collected_cross_size = 0
646
- # 9.4.8.2 Among all the items not collected by the previous step,
647
- # find the largest outer hypothetical cross size.
525
+ # 8.2 Find the largest outer hypothetical cross size.
648
526
  if not_collected_items:
649
527
  non_collected_cross_size = -inf
650
528
  for child in not_collected_items:
@@ -662,15 +540,10 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
662
540
  child_cross_size += child.margin_right
663
541
  non_collected_cross_size = max(
664
542
  child_cross_size, non_collected_cross_size)
665
- # 9.4.8.3 The used cross-size of the flex line is the largest of
666
- # the numbers found in the previous two steps and zero.
667
- line.cross_size = max(
668
- collected_cross_size, non_collected_cross_size)
669
-
670
- # 9.4.8.3 If the flex container is single-line, then clamp the line’s
671
- # cross-size to be within the container’s computed min and max cross sizes.
672
- # Note that if CSS 2.1’s definition of min/max-width/height applied more
673
- # generally, this behavior would fall out automatically.
543
+ # 8.3 Set the used cross-size of the flex line.
544
+ line.cross_size = max(collected_cross_size, non_collected_cross_size)
545
+
546
+ # 8.3 If the flex container is single-line…
674
547
  if len(flex_lines) == 1:
675
548
  line, = flex_lines
676
549
  min_cross_size = getattr(box, f'min_{cross}')
@@ -679,15 +552,9 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
679
552
  max_cross_size = getattr(box, f'max_{cross}')
680
553
  if max_cross_size == 'auto':
681
554
  max_cross_size = inf
682
- line.cross_size = max(
683
- min_cross_size, min(line.cross_size, max_cross_size))
684
-
685
- # 9.4.9 Handle 'align-content: stretch'. If the flex container has a
686
- # definite cross size, align-content is stretch, and the sum of the flex
687
- # lines' cross sizes is less than the flex container’s inner cross size,
688
- # increase the cross size of each flex line by equal amounts such that the
689
- # sum of their cross sizes exactly equals the flex container’s inner cross
690
- # size.
555
+ line.cross_size = max(min_cross_size, min(line.cross_size, max_cross_size))
556
+
557
+ # 9 Handle 'align-content: stretch'.
691
558
  align_content = box.style['align_content']
692
559
  if 'normal' in align_content:
693
560
  align_content = ('stretch',)
@@ -702,38 +569,16 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
702
569
  else:
703
570
  definite_cross_size = box.width
704
571
  if definite_cross_size is not None:
705
- extra_cross_size = definite_cross_size - sum(
706
- line.cross_size for line in flex_lines)
572
+ extra_cross_size = definite_cross_size
573
+ extra_cross_size -= sum(line.cross_size for line in flex_lines)
574
+ extra_cross_size -= (len(flex_lines) - 1) * cross_gap
707
575
  if extra_cross_size:
708
576
  for line in flex_lines:
709
577
  line.cross_size += extra_cross_size / len(flex_lines)
710
578
 
711
- # TODO: 9.4.10 Collapse visibility:collapse items. If any flex items have
712
- # visibility: collapse, note the cross size of the line they’re in as the
713
- # item’s strut size, and restart layout from the beginning.
714
- #
715
- # In this second layout round, when collecting items into lines, treat the
716
- # collapsed items as having zero main size. For the rest of the algorithm
717
- # following that step, ignore the collapsed items entirely (as if they were
718
- # display:none) except that after calculating the cross size of the lines,
719
- # if any line’s cross size is less than the largest strut size among all
720
- # the collapsed items in the line, set its cross size to that strut size.
721
- #
722
- # Skip this step in the second layout round.
723
-
724
- # Step 11 - 9.4.11 Determine the used cross size of each flex item. If a
725
- # flex item has align-self: stretch, its computed cross size property is
726
- # auto, and neither of its cross-axis margins are auto, the used outer
727
- # cross size is the used cross size of its flex line, clamped according to
728
- # the item’s used min and max cross sizes. Otherwise, the used cross size
729
- # is the item’s hypothetical cross size.
730
- #
731
- # If the flex item has align-self: stretch, redo layout for its contents,
732
- # treating this used size as its definite cross size so that
733
- # percentage-sized children can be resolved.
734
- #
735
- # Note that this step does not affect the main size of the flex item, even
736
- # if it has an intrinsic aspect ratio.
579
+ # TODO: 10 Collapse 'visibility: collapse' items.
580
+
581
+ # 11 Determine the used cross size of each flex item.
737
582
  align_items = box.style['align_items']
738
583
  if 'normal' in align_items:
739
584
  align_items = ('stretch',)
@@ -764,12 +609,11 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
764
609
  child.border_left_width +
765
610
  child.border_right_width)
766
611
  setattr(child, cross, cross_size)
767
- # TODO: redo layout?
768
612
  # else: Cross size has been set by step 7.
769
613
 
770
- # 9.5 Main-Axis Alignment.
771
- original_position_axis = (
772
- box.content_box_x() if axis == 'width'
614
+ # 12 Distribute any remaining free space.
615
+ original_position_main = (
616
+ box.content_box_x() if main == 'width'
773
617
  else box.content_box_y())
774
618
  justify_content = box.style['justify_content']
775
619
  if 'normal' in justify_content:
@@ -777,13 +621,16 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
777
621
  if box.style['flex_direction'].endswith('-reverse'):
778
622
  if 'flex-start' in justify_content:
779
623
  justify_content = ('flex-end',)
780
- elif justify_content == 'flex-end':
624
+ elif 'flex-end' in justify_content:
781
625
  justify_content = ('flex-start',)
626
+ elif 'start' in justify_content:
627
+ justify_content = ('end',)
628
+ elif 'end' in justify_content:
629
+ justify_content = ('start',)
782
630
 
783
- # 9.5.12 Distribute any remaining free space. For each flex line:
784
631
  for line in flex_lines:
785
- position_axis = original_position_axis
786
- if axis == 'width':
632
+ position_main = original_position_main
633
+ if main == 'width':
787
634
  free_space = box.width
788
635
  for index, child in line:
789
636
  free_space -= child.border_width()
@@ -799,13 +646,12 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
799
646
  free_space -= child.margin_top
800
647
  if child.margin_bottom != 'auto':
801
648
  free_space -= child.margin_bottom
649
+ free_space -= (len(line) - 1) * main_gap
802
650
 
803
- # 9.5.12.1 If the remaining free space is positive and at least one
804
- # main-axis margin on this line is auto, distribute the free space
805
- # equally among these margins. Otherwise, set all auto margins to zero.
651
+ # 12.1 If the remaining free space is positive
806
652
  margins = 0
807
653
  for index, child in line:
808
- if axis == 'width':
654
+ if main == 'width':
809
655
  if child.margin_left == 'auto':
810
656
  margins += 1
811
657
  if child.margin_right == 'auto':
@@ -818,7 +664,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
818
664
  if margins:
819
665
  free_space /= margins
820
666
  for index, child in line:
821
- if axis == 'width':
667
+ if main == 'width':
822
668
  if child.margin_left == 'auto':
823
669
  child.margin_left = free_space
824
670
  if child.margin_right == 'auto':
@@ -830,68 +676,67 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
830
676
  child.margin_bottom = free_space
831
677
  free_space = 0
832
678
 
833
- if box.style['direction'] == 'rtl' and axis == 'width':
679
+ if box.style['direction'] == 'rtl' and main == 'width':
834
680
  free_space *= -1
835
681
 
836
- # 9.5.12.2 Align the items along the main-axis per justify-content.
682
+ # 12.2 Align the items along the main-axis per justify-content.
837
683
  if {'end', 'flex-end', 'right'} & set(justify_content):
838
- position_axis += free_space
684
+ position_main += free_space
839
685
  elif 'center' in justify_content:
840
- position_axis += free_space / 2
686
+ position_main += free_space / 2
841
687
  elif 'space-around' in justify_content:
842
- position_axis += free_space / len(line) / 2
688
+ position_main += free_space / len(line) / 2
843
689
  elif 'space-evenly' in justify_content:
844
- position_axis += free_space / (len(line) + 1)
845
-
846
- for index, child in line:
847
- if axis == 'width':
848
- child.position_x = position_axis
849
- if 'stretch' in justify_content:
850
- child.width += free_space / len(line)
690
+ position_main += free_space / (len(line) + 1)
691
+
692
+ growths = sum(child.style['flex_grow'] for child in children)
693
+ for i, (index, child) in enumerate(line):
694
+ if i:
695
+ position_main += main_gap
696
+ if main == 'width':
697
+ child.position_x = position_main
698
+ if 'stretch' in justify_content and growths:
699
+ child.width += free_space * child.style['flex_grow'] / growths
851
700
  else:
852
- child.position_y = position_axis
853
- margin_axis = (
854
- child.margin_width() if axis == 'width'
855
- else child.margin_height())
856
- if box.style['direction'] == 'rtl' and axis == 'width':
857
- margin_axis *= -1
858
- position_axis += margin_axis
701
+ child.position_y = position_main
702
+ margin_main = (
703
+ child.margin_width() if main == 'width' else child.margin_height())
704
+ if box.style['direction'] == 'rtl' and main == 'width':
705
+ margin_main *= -1
706
+ position_main += margin_main
859
707
  if 'space-around' in justify_content:
860
- position_axis += free_space / len(line)
708
+ position_main += free_space / len(line)
861
709
  elif 'space-between' in justify_content:
862
710
  if len(line) > 1:
863
- position_axis += free_space / (len(line) - 1)
711
+ position_main += free_space / (len(line) - 1)
864
712
  elif 'space-evenly' in justify_content:
865
- position_axis += free_space / (len(line) + 1)
713
+ position_main += free_space / (len(line) + 1)
866
714
 
867
- # 9.6. Cross-Axis Alignment.
868
- # Make sure width/margins are no longer "auto", as we did not do it above
869
- # in step 9.2.4.
715
+ # 13 Resolve cross-axis auto margins.
870
716
  if cross == 'width':
717
+ # Make sure width/margins are no longer "auto", as we did not do it above in
718
+ # step 4.
871
719
  block.block_level_width(box, containing_block)
872
- position_cross = (
873
- box.content_box_y() if cross == 'height'
874
- else box.content_box_x())
720
+ position_cross = box.content_box_y() if cross == 'height' else box.content_box_x()
875
721
  for line in flex_lines:
876
722
  line.lower_baseline = -inf
877
- # TODO: don't duplicate this loop.
723
+ # TODO: Don't duplicate this loop.
878
724
  for index, child in line:
879
725
  align_self = child.style['align_self']
880
726
  if 'auto' in align_self:
881
727
  align_self = align_items
882
- if 'baseline' in align_self and axis == 'width':
728
+ if 'baseline' in align_self and main == 'width':
883
729
  # TODO: handle vertical text.
884
730
  child.baseline = child._baseline - position_cross
885
731
  line.lower_baseline = max(line.lower_baseline, child.baseline)
886
732
  if line.lower_baseline == -inf:
887
733
  line.lower_baseline = line[0][1]._baseline if line else 0
888
- # 9.6.13 Resolve cross-axis auto margins.
889
734
  for index, child in line:
890
735
  cross_margins = (
891
736
  (child.margin_top, child.margin_bottom) if cross == 'height'
892
737
  else (child.margin_left, child.margin_right))
893
738
  auto_margins = sum([margin == 'auto' for margin in cross_margins])
894
- # 9.6.13 If a flex item has auto cross-axis margins:
739
+ # If a flex item has auto cross-axis margins
895
740
  if auto_margins:
896
741
  extra_cross = line.cross_size
897
742
  if cross == 'height':
@@ -907,10 +752,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
907
752
  if child.margin_right != 'auto':
908
753
  extra_cross -= child.margin_right
909
754
  if extra_cross > 0:
910
- # 9.6.13 If its outer cross size (treating those auto
911
- # margins as zero) is less than the cross size of its flex
912
- # line, distribute the difference in those sizes equally to
913
- # the auto margins.
755
+ # If its outer cross size is less than the cross size…
914
756
  extra_cross /= auto_margins
915
757
  if cross == 'height':
916
758
  if child.margin_top == 'auto':
@@ -923,10 +765,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
923
765
  if child.margin_right == 'auto':
924
766
  child.margin_right = extra_cross
925
767
  else:
926
- # 9.6.13 Otherwise, if the block-start or inline-start
927
- # margin (whichever is in the cross axis) is auto, set it
928
- # to zero. Set the opposite margin so that the outer cross
929
- # size of the item equals the cross size of its flex line.
768
+ # Otherwise
930
769
  if cross == 'height':
931
770
  if child.margin_top == 'auto':
932
771
  child.margin_top = 0
@@ -936,9 +775,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
936
775
  child.margin_left = 0
937
776
  child.margin_right = extra_cross
938
777
  else:
939
- # 9.6.14 Align all flex items along the cross-axis per
940
- # align-self, if neither of the item’s cross-axis margins are
941
- # auto.
778
+ # 14 Align all flex items along the cross-axis.
942
779
  align_self = child.style['align_self']
943
780
  if 'normal' in align_self:
944
781
  align_self = ('stretch',)
@@ -948,24 +785,20 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
948
785
  setattr(child, position, position_cross)
949
786
  if {'end', 'self-end', 'flex-end'} & set(align_self):
950
787
  if cross == 'height':
951
- child.position_y += (
952
- line.cross_size - child.margin_height())
788
+ child.position_y += line.cross_size - child.margin_height()
953
789
  else:
954
- child.position_x += (
955
- line.cross_size - child.margin_width())
790
+ child.position_x += line.cross_size - child.margin_width()
956
791
  elif 'center' in align_self:
957
792
  if cross == 'height':
958
793
  child.position_y += (
959
794
  line.cross_size - child.margin_height()) / 2
960
795
  else:
961
- child.position_x += (
962
- line.cross_size - child.margin_width()) / 2
796
+ child.position_x += (line.cross_size - child.margin_width()) / 2
963
797
  elif 'baseline' in align_self:
964
798
  if cross == 'height':
965
- child.position_y += (
966
- line.lower_baseline - child.baseline)
799
+ child.position_y += line.lower_baseline - child.baseline
967
800
  else:
968
- # Handle vertical text.
801
+ # TODO: Handle vertical text.
969
802
  pass
970
803
  elif 'stretch' in align_self:
971
804
  if child.style[cross] == 'auto':
@@ -976,78 +809,76 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
976
809
  if child.style['box_sizing'] == 'content-box':
977
810
  if cross == 'height':
978
811
  margins += (
979
- child.border_top_width +
980
- child.border_bottom_width +
812
+ child.border_top_width + child.border_bottom_width +
981
813
  child.padding_top + child.padding_bottom)
982
814
  else:
983
815
  margins += (
984
- child.border_left_width +
985
- child.border_right_width +
816
+ child.border_left_width + child.border_right_width +
986
817
  child.padding_left + child.padding_right)
987
- # TODO: Don't set style width, find a way to avoid
988
- # width re-calculation after 9.6.16.
818
+ # TODO: Don't set style width, find a way to avoid width
819
+ # re-calculation after 16.
989
820
  child.style[cross] = Dimension(line.cross_size - margins, 'px')
990
821
  position_cross += line.cross_size
991
822
 
992
- # 9.6.15 Determine the flex container’s used cross size:
823
+ # 15 Determine the flex container’s used cross size.
824
+ # TODO: Use the updated algorithm.
993
825
  if getattr(box, cross) == 'auto':
994
- # 9.6.15 Otherwise, use the sum of the flex lines' cross sizes, clamped
995
- # by the used min and max cross sizes of the flex container.
826
+ # Otherwise, use the sum of the flex lines' cross sizes
996
827
  # TODO: Handle min-max.
997
828
  # TODO: What about align-content here?
998
- setattr(box, cross, sum(line.cross_size for line in flex_lines))
999
- elif len(flex_lines) > 1:
1000
- # 9.6.15 If the cross size property is a definite size, use that,
1001
- # clamped by the used min and max cross sizes of the flex container.
1002
- extra_cross_size = getattr(box, cross) - sum(
1003
- line.cross_size for line in flex_lines)
1004
- # 9.6.16 Align all flex lines per align-content.
829
+ cross_size = sum(line.cross_size for line in flex_lines)
830
+ cross_size += (len(flex_lines) - 1) * cross_gap
831
+ setattr(box, cross, cross_size)
832
+
833
+ if len(flex_lines) > 1:
834
+ # 15 If the cross size property is a definite size, use that…
835
+ extra_cross_size = getattr(box, cross)
836
+ extra_cross_size -= sum(line.cross_size for line in flex_lines)
837
+ extra_cross_size -= (len(flex_lines) - 1) * cross_gap
838
+ # 16 Align all flex lines per align-content.
839
+ cross_translate = 0
1005
840
  direction = 'position_y' if cross == 'height' else 'position_x'
1006
- if extra_cross_size > 0:
1007
- cross_translate = 0
1008
- for line in flex_lines:
1009
- for index, child in line:
1010
- if child.is_flex_item:
1011
- current_value = getattr(child, direction)
1012
- current_value += cross_translate
1013
- setattr(child, direction, current_value)
1014
- if {'flex-end', 'end'} & set(align_content):
1015
- setattr(
1016
- child, direction,
1017
- current_value + extra_cross_size)
1018
- elif 'center' in align_content:
1019
- setattr(
1020
- child, direction,
1021
- current_value + extra_cross_size / 2)
1022
- elif 'space-around' in align_content:
1023
- setattr(
1024
- child, direction,
1025
- current_value + extra_cross_size /
1026
- len(flex_lines) / 2)
1027
- elif 'space-evenly' in align_content:
1028
- setattr(
1029
- child, direction,
1030
- current_value + extra_cross_size /
1031
- (len(flex_lines) + 1))
1032
- if 'space-between' in align_content:
1033
- cross_translate += extra_cross_size / (len(flex_lines) - 1)
841
+ for i, line in enumerate(flex_lines):
842
+ flex_items = tuple(child for _, child in line if child.is_flex_item)
843
+ if i:
844
+ cross_translate += cross_gap
845
+ for child in flex_items:
846
+ current_value = getattr(child, direction) + cross_translate
847
+ setattr(child, direction, current_value)
848
+ if extra_cross_size == 0:
849
+ continue
850
+ for child in flex_items:
851
+ if {'flex-end', 'end'} & set(align_content):
852
+ setattr(child, direction, current_value + extra_cross_size)
853
+ elif 'center' in align_content:
854
+ setattr(child, direction, current_value + extra_cross_size / 2)
1034
855
  elif 'space-around' in align_content:
1035
- cross_translate += extra_cross_size / len(flex_lines)
856
+ setattr(
857
+ child, direction,
858
+ current_value + extra_cross_size / len(flex_lines) / 2)
1036
859
  elif 'space-evenly' in align_content:
1037
- cross_translate += extra_cross_size / (len(flex_lines) + 1)
860
+ setattr(
861
+ child, direction,
862
+ current_value + extra_cross_size / (len(flex_lines) + 1))
863
+ if 'space-between' in align_content:
864
+ cross_translate += extra_cross_size / (len(flex_lines) - 1)
865
+ elif 'space-around' in align_content:
866
+ cross_translate += extra_cross_size / len(flex_lines)
867
+ elif 'space-evenly' in align_content:
868
+ cross_translate += extra_cross_size / (len(flex_lines) + 1)
1038
869
 
1039
870
  # Now we are no longer in the flex algorithm.
1040
- # TODO: Don't use block_box_level_layout_switch.
1041
871
  box = box.copy_with_children(
1042
872
  [child for child in children if child.is_absolutely_positioned()])
1043
873
  child_skip_stack = skip_stack
1044
874
  for line in flex_lines:
1045
875
  for index, child in line:
1046
876
  if child.is_flex_item:
877
+ # TODO: Don't use block_level_layout_switch.
1047
878
  new_child, child_resume_at = block.block_level_layout_switch(
1048
- context, child, bottom_space, child_skip_stack, box,
1049
- page_is_empty, absolute_boxes, fixed_boxes,
1050
- adjoining_margins=[], discard=False, max_lines=None)[:2]
879
+ context, child, bottom_space, child_skip_stack, box, page_is_empty,
880
+ absolute_boxes, fixed_boxes, adjoining_margins=[], discard=discard,
881
+ max_lines=None)[:2]
1051
882
  if new_child is None:
1052
883
  if resume_at:
1053
884
  resume_index, = resume_at
@@ -1079,29 +910,23 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
1079
910
  # New containing block, resolve the layout of the absolute descendants.
1080
911
  for absolute_box in absolute_boxes:
1081
912
  absolute_layout(
1082
- context, absolute_box, box, fixed_boxes, bottom_space,
1083
- skip_stack=None)
1084
-
1085
- # Set box height.
1086
- # TODO: This is probably useless because of 9.6.15.
1087
- if axis == 'width' and box.height == 'auto':
1088
- if flex_lines:
1089
- box.height = sum(line.cross_size for line in flex_lines)
1090
- else:
1091
- box.height = 0
913
+ context, absolute_box, box, fixed_boxes, bottom_space, skip_stack=None)
1092
914
 
1093
- # Set baseline.
1094
- # See https://www.w3.org/TR/css-flexbox-1/#flex-baselines
1095
- # TODO: Use the real algorithm.
915
+ # TODO: Use real algorithm, see https://www.w3.org/TR/css-flexbox-1/#flex-baselines.
1096
916
  if isinstance(box, boxes.InlineFlexBox):
1097
- if axis == 'width': # and main text direction is horizontal
917
+ if main == 'width': # and main text direction is horizontal
1098
918
  box.baseline = flex_lines[0].lower_baseline if flex_lines else 0
1099
919
  else:
1100
- box.baseline = ((
1101
- find_in_flow_baseline(box.children[0])
1102
- if box.children else 0) or 0)
920
+ for child in box.children:
921
+ if child.is_in_normal_flow():
922
+ box.baseline = find_in_flow_baseline(child) or 0
923
+ break
924
+ else:
925
+ box.baseline = 0
926
+
927
+ box.remove_decoration(start=False, end=resume_at and not discard)
1103
928
 
1104
- context.finish_block_formatting_context(box)
929
+ context.finish_flex_formatting_context(box)
1105
930
 
1106
931
  # TODO: Check these returned values.
1107
932
  return box, resume_at, {'break': 'any', 'page': None}, [], False