weasyprint 64.1__py3-none-any.whl → 65.0__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,155 @@ 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 = box.replacement
171
+ _, intrinsic_height, intrinsic_ratio = image.get_intrinsic_size(
172
+ box.style['image_resolution'], box.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 = box.replacement
199
+ intrinsic_width, _, intrinsic_ratio = image.get_intrinsic_size(
200
+ box.style['image_resolution'], box.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
+ if child.margin_left != 'auto':
226
+ child.main_outer_extra += child.margin_left
227
+ if child.margin_right != 'auto':
228
+ child.main_outer_extra += child.margin_right
229
+ else:
230
+ child.main_outer_extra = (
231
+ child.border_top_width + child.border_bottom_width)
232
+ if child.margin_top != 'auto':
233
+ child.main_outer_extra += child.margin_top
234
+ if child.margin_bottom != 'auto':
235
+ child.main_outer_extra += child.margin_bottom
228
236
  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.
237
+ # TODO: 3.B If the flex item has an intrinsic aspect ratio
238
+ # TODO: 3.C If the used flex basis is 'content'
239
+ # TODO: 3.D Otherwise, if the used flex basis is 'content'…
255
240
  pass
256
241
  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()
242
+ # 3.E Otherwise
243
+ if main == 'width':
244
+ child.flex_base_size = max_content_width(context, child, outer=False)
245
+ child.main_outer_extra = (
246
+ max_content_width(context, child) - child.flex_base_size)
296
247
  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
248
+ new_child = child.copy()
249
+ new_child.width = inf
250
+ new_child = block.block_level_layout(
251
+ context, new_child, bottom_space, child_skip_stack, parent_box,
252
+ page_is_empty, absolute_boxes, fixed_boxes)[0]
253
+ child.flex_base_size = new_child.height
254
+ child.main_outer_extra = new_child.margin_height() - new_child.height
255
+
256
+ if main == 'width':
257
+ position_x += child.flex_base_size + child.main_outer_extra
258
+ else:
259
+ position_y += child.flex_base_size + child.main_outer_extra
302
260
 
261
+ min_size = getattr(child, f'min_{main}')
262
+ max_size = getattr(child, f'max_{main}')
303
263
  child.hypothetical_main_size = max(
304
- getattr(child, f'min_{axis}'), min(
305
- child.flex_base_size, getattr(child, f'max_{axis}')))
264
+ min_size, min(child.flex_base_size, max_size))
306
265
 
307
266
  # Skip stack is only for the first child.
308
267
  child_skip_stack = None
309
268
 
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':
269
+ # 4 Determine the main size of the flex container using the rules of the formatting
270
+ # context in which it participates.
271
+ if main == 'width':
316
272
  block.block_level_width(box, containing_block)
317
273
  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:
274
+ if box.height == 'auto':
323
275
  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.
276
+ flex_items = (child for child in children if child.is_flex_item)
277
+ for i, child in enumerate(flex_items):
278
+ box.height += child.hypothetical_main_size + child.main_outer_extra
279
+ if i:
280
+ box.height += main_gap
281
+ box.height = max(box.min_height, min(box.height, box.max_height))
282
+
283
+ # 5 If the flex container is single-line, collect all the flex items into a single
284
+ # flex line.
340
285
  flex_lines = []
341
286
  line = []
342
287
  line_size = 0
343
- axis_size = getattr(box, axis)
288
+ main_size = getattr(box, main)
344
289
  for i, child in enumerate(children, start=skip):
345
290
  if not child.is_flex_item:
346
291
  continue
347
- line_size += child.hypothetical_main_size
348
- if box.style['flex_wrap'] != 'nowrap' and line_size > axis_size:
292
+ line_size += child.hypothetical_main_size + child.main_outer_extra
293
+ if i > skip:
294
+ line_size += main_gap
295
+ if box.style['flex_wrap'] != 'nowrap' and line_size > main_size:
349
296
  if line:
350
297
  flex_lines.append(FlexLine(line))
351
298
  line = [(i, child)]
352
- line_size = child.hypothetical_main_size
299
+ line_size = child.hypothetical_main_size + child.main_outer_extra
353
300
  else:
354
301
  line.append((i, child))
355
302
  flex_lines.append(FlexLine(line))
@@ -367,72 +314,58 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
367
314
  for line in flex_lines:
368
315
  line.reverse()
369
316
 
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)
317
+ # 6 Resolve the flexible lengths of all the flex items to find their used main size.
318
+ available_main_space = getattr(box, main)
374
319
  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.
320
+ # 9.7.1 Determine the used flex factor.
379
321
  hypothetical_main_size = sum(
380
- child.hypothetical_main_size for index, child in line)
322
+ child.hypothetical_main_size + child.main_outer_extra
323
+ for index, child in line)
381
324
  if hypothetical_main_size < available_main_space:
382
325
  flex_factor_type = 'grow'
383
326
  else:
384
327
  flex_factor_type = 'shrink'
385
328
 
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.
329
+ # 9.7.3 Size inflexible items.
393
330
  for index, child in line:
394
331
  if flex_factor_type == 'grow':
395
332
  child.flex_factor = child.style['flex_grow']
333
+ flex_condition = child.flex_base_size > child.hypothetical_main_size
396
334
  else:
397
335
  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)):
336
+ flex_condition = child.flex_base_size < child.hypothetical_main_size
337
+ if child.flex_factor == 0 or flex_condition:
403
338
  child.target_main_size = child.hypothetical_main_size
404
339
  child.frozen = True
405
340
  else:
406
341
  child.frozen = False
407
342
 
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.
343
+ # 9.7.4 Calculate initial free space.
412
344
  initial_free_space = available_main_space
413
- for index, child in line:
345
+ for i, (index, child) in enumerate(line):
414
346
  if child.frozen:
415
- initial_free_space -= child.target_main_size
347
+ initial_free_space -= child.target_main_size + child.main_outer_extra
416
348
  else:
417
- initial_free_space -= child.flex_base_size
349
+ initial_free_space -= child.flex_base_size + child.main_outer_extra
350
+ if i:
351
+ initial_free_space -= main_gap
418
352
 
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.
353
+ # 9.7.5.a Check for flexible items.
421
354
  while not all(child.frozen for index, child in line):
422
355
  unfrozen_factor_sum = 0
423
356
  remaining_free_space = available_main_space
424
357
 
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:
358
+ # 9.7.5.b Calculate the remaining free space.
359
+ for i, (index, child) in enumerate(line):
431
360
  if child.frozen:
432
- remaining_free_space -= child.target_main_size
361
+ remaining_free_space -= (
362
+ child.target_main_size + child.main_outer_extra)
433
363
  else:
434
- remaining_free_space -= child.flex_base_size
364
+ remaining_free_space -= (
365
+ child.flex_base_size + child.main_outer_extra)
435
366
  unfrozen_factor_sum += child.flex_factor
367
+ if i:
368
+ remaining_free_space -= main_gap
436
369
 
437
370
  if unfrozen_factor_sum < 1:
438
371
  initial_free_space *= unfrozen_factor_sum
@@ -443,19 +376,16 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
443
376
  remaining_free_space = sys.maxsize
444
377
 
445
378
  initial_magnitude = (
446
- int(log10(initial_free_space)) if initial_free_space > 0
447
- else -inf)
379
+ int(log10(initial_free_space)) if initial_free_space > 0 else -inf)
448
380
  remaining_magnitude = (
449
- int(log10(remaining_free_space)) if remaining_free_space > 0
450
- else -inf)
381
+ int(log10(remaining_free_space)) if remaining_free_space > 0 else -inf)
451
382
  if initial_magnitude < remaining_magnitude:
452
383
  remaining_free_space = initial_free_space
453
384
 
454
- # 9.7.4.c Distribute free space proportional to the flex factors.
385
+ # 9.7.5.c Distribute free space proportional to the flex factors.
455
386
  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.
387
+ # If the remaining free space is zero: "Do nothing", but we at least set
388
+ # the flex_base_size as target_main_size for next step.
459
389
  for index, child in line:
460
390
  if not child.frozen:
461
391
  child.target_main_size = child.flex_base_size
@@ -471,30 +401,12 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
471
401
  flex_grow_factors_sum += child.style['flex_grow']
472
402
  for index, child in line:
473
403
  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."
404
+ # If using the flex grow factor
480
405
  if flex_factor_type == 'grow':
481
- ratio = (
482
- child.style['flex_grow'] /
483
- flex_grow_factors_sum)
406
+ ratio = child.style['flex_grow'] / flex_grow_factors_sum
484
407
  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."
408
+ child.flex_base_size + remaining_free_space * ratio)
409
+ # If using the flex shrink factor…
498
410
  elif flex_factor_type == 'shrink':
499
411
  if scaled_flex_shrink_factors_sum == 0:
500
412
  child.target_main_size = child.flex_base_size
@@ -503,24 +415,21 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
503
415
  child.scaled_flex_shrink_factor /
504
416
  scaled_flex_shrink_factors_sum)
505
417
  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.
418
+ child.flex_base_size + remaining_free_space * ratio)
419
+ child.target_main_size = min_max(child, child.target_main_size)
420
+
421
+ # 9.7.5.d Fix min/max violations.
515
422
  for index, child in line:
516
423
  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:
424
+ if not child.frozen:
425
+ min_size = getattr(child, f'min_{main}')
426
+ max_size = getattr(child, f'max_{main}')
427
+ min_size = max(min_size , min(child.target_main_size, max_size))
428
+ if child.target_main_size < min_size:
429
+ child.adjustment = min_size - child.target_main_size
430
+ child.target_main_size = min_size
431
+
432
+ # 9.7.5.e Freeze over-flexed items.
524
433
  adjustments = sum(child.adjustment for index, child in line)
525
434
  for index, child in line:
526
435
  # Zero: Freeze all items.
@@ -533,72 +442,44 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
533
442
  elif adjustments < 0 and child.adjustment < 0:
534
443
  child.frozen = True
535
444
 
536
- # 9.7.5 Set each item’s used main size to its target main size.
445
+ # 9.7.6 Set each item’s used main size to its target main size.
537
446
  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
447
+ if main == 'width':
448
+ child.width = child.target_main_size
547
449
  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
450
+ child.height = child.target_main_size
556
451
 
557
- # 9.4. Cross Size Determination.
558
- # TODO: Fix TODO in build.flex_children.
452
+ # 7 Determine the hypothetical cross size of each item.
559
453
  # TODO: Handle breaks.
560
454
  new_flex_lines = []
561
455
  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
456
  for line in flex_lines:
566
457
  new_flex_line = FlexLine()
567
458
  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.
459
+ # TODO: Fix this value, see test_flex_item_auto_margin_cross.
571
460
  if child.margin_top == 'auto':
572
461
  child.margin_top = 0
573
462
  if child.margin_bottom == 'auto':
574
463
  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
-
464
+ # TODO: Find another way than calling block_level_layout_switch.
465
+ new_child = child.copy()
466
+ new_child, _, _, adjoining_margins, _, _ = block.block_level_layout_switch(
467
+ context, new_child, -inf, child_skip_stack, parent_box, page_is_empty,
468
+ absolute_boxes, fixed_boxes, [], discard, None)
591
469
  child._baseline = find_in_flow_baseline(new_child) or 0
592
470
  if cross == 'height':
593
471
  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.
472
+ # As flex items margins never collapse (with other flex items or
473
+ # with the flex container), we can add the adjoining margins to the
474
+ # child bottom margin.
597
475
  child.margin_bottom += block.collapse_margin(adjoining_margins)
598
476
  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
477
+ if child.width == 'auto':
478
+ min_width = min_content_width(context, child, outer=False)
479
+ max_width = max_content_width(context, child, outer=False)
480
+ child.width = min(max(min_width, new_child.width), max_width)
481
+ else:
482
+ child.width = new_child.width
602
483
 
603
484
  new_flex_line.append((index, child))
604
485
 
@@ -609,33 +490,25 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
609
490
  new_flex_lines.append(new_flex_line)
610
491
  flex_lines = new_flex_lines
611
492
 
493
+ # 8 Calculate the cross size of each flex line.
612
494
  cross_size = getattr(box, cross)
613
495
  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.
496
+ # If the flex container is single-line
617
497
  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.
498
+ else:
499
+ # Otherwise, for each flex line…
500
+ # 8.1 Collect all the flex items whose inline-axis is parallel to the main-axis
625
501
  for line in flex_lines:
626
502
  collected_items = []
627
503
  not_collected_items = []
628
504
  for index, child in line:
629
505
  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
506
+ collect = (
507
+ box.style['flex_direction'].startswith('row') and
508
+ 'baseline' in align_self and
509
+ 'auto' not in (child.margin_top, child.margin_bottom))
510
+ (collected_items if collect else not_collected_items).append(child)
511
+ cross_start_distance = cross_end_distance = 0
639
512
  for child in collected_items:
640
513
  baseline = child._baseline - child.position_y
641
514
  cross_start_distance = max(cross_start_distance, baseline)
@@ -643,8 +516,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
643
516
  cross_end_distance, child.margin_height() - baseline)
644
517
  collected_cross_size = cross_start_distance + cross_end_distance
645
518
  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.
519
+ # 8.2 Find the largest outer hypothetical cross size.
648
520
  if not_collected_items:
649
521
  non_collected_cross_size = -inf
650
522
  for child in not_collected_items:
@@ -662,15 +534,10 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
662
534
  child_cross_size += child.margin_right
663
535
  non_collected_cross_size = max(
664
536
  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.
537
+ # 8.3 Set the used cross-size of the flex line.
538
+ line.cross_size = max(collected_cross_size, non_collected_cross_size)
539
+
540
+ # 8.3 If the flex container is single-line…
674
541
  if len(flex_lines) == 1:
675
542
  line, = flex_lines
676
543
  min_cross_size = getattr(box, f'min_{cross}')
@@ -679,15 +546,9 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
679
546
  max_cross_size = getattr(box, f'max_{cross}')
680
547
  if max_cross_size == 'auto':
681
548
  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.
549
+ line.cross_size = max(min_cross_size, min(line.cross_size, max_cross_size))
550
+
551
+ # 9 Handle 'align-content: stretch'.
691
552
  align_content = box.style['align_content']
692
553
  if 'normal' in align_content:
693
554
  align_content = ('stretch',)
@@ -702,38 +563,16 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
702
563
  else:
703
564
  definite_cross_size = box.width
704
565
  if definite_cross_size is not None:
705
- extra_cross_size = definite_cross_size - sum(
706
- line.cross_size for line in flex_lines)
566
+ extra_cross_size = definite_cross_size
567
+ extra_cross_size -= sum(line.cross_size for line in flex_lines)
568
+ extra_cross_size -= (len(flex_lines) - 1) * cross_gap
707
569
  if extra_cross_size:
708
570
  for line in flex_lines:
709
571
  line.cross_size += extra_cross_size / len(flex_lines)
710
572
 
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.
573
+ # TODO: 10 Collapse 'visibility: collapse' items.
574
+
575
+ # 11 Determine the used cross size of each flex item.
737
576
  align_items = box.style['align_items']
738
577
  if 'normal' in align_items:
739
578
  align_items = ('stretch',)
@@ -764,12 +603,11 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
764
603
  child.border_left_width +
765
604
  child.border_right_width)
766
605
  setattr(child, cross, cross_size)
767
- # TODO: redo layout?
768
606
  # else: Cross size has been set by step 7.
769
607
 
770
- # 9.5 Main-Axis Alignment.
771
- original_position_axis = (
772
- box.content_box_x() if axis == 'width'
608
+ # 12 Distribute any remaining free space.
609
+ original_position_main = (
610
+ box.content_box_x() if main == 'width'
773
611
  else box.content_box_y())
774
612
  justify_content = box.style['justify_content']
775
613
  if 'normal' in justify_content:
@@ -777,13 +615,16 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
777
615
  if box.style['flex_direction'].endswith('-reverse'):
778
616
  if 'flex-start' in justify_content:
779
617
  justify_content = ('flex-end',)
780
- elif justify_content == 'flex-end':
618
+ elif 'flex-end' in justify_content:
781
619
  justify_content = ('flex-start',)
620
+ elif 'start' in justify_content:
621
+ justify_content = ('end',)
622
+ elif 'end' in justify_content:
623
+ justify_content = ('start',)
782
624
 
783
- # 9.5.12 Distribute any remaining free space. For each flex line:
784
625
  for line in flex_lines:
785
- position_axis = original_position_axis
786
- if axis == 'width':
626
+ position_main = original_position_main
627
+ if main == 'width':
787
628
  free_space = box.width
788
629
  for index, child in line:
789
630
  free_space -= child.border_width()
@@ -799,13 +640,12 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
799
640
  free_space -= child.margin_top
800
641
  if child.margin_bottom != 'auto':
801
642
  free_space -= child.margin_bottom
643
+ free_space -= (len(line) - 1) * main_gap
802
644
 
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.
645
+ # 12.1 If the remaining free space is positive
806
646
  margins = 0
807
647
  for index, child in line:
808
- if axis == 'width':
648
+ if main == 'width':
809
649
  if child.margin_left == 'auto':
810
650
  margins += 1
811
651
  if child.margin_right == 'auto':
@@ -818,7 +658,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
818
658
  if margins:
819
659
  free_space /= margins
820
660
  for index, child in line:
821
- if axis == 'width':
661
+ if main == 'width':
822
662
  if child.margin_left == 'auto':
823
663
  child.margin_left = free_space
824
664
  if child.margin_right == 'auto':
@@ -830,68 +670,67 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
830
670
  child.margin_bottom = free_space
831
671
  free_space = 0
832
672
 
833
- if box.style['direction'] == 'rtl' and axis == 'width':
673
+ if box.style['direction'] == 'rtl' and main == 'width':
834
674
  free_space *= -1
835
675
 
836
- # 9.5.12.2 Align the items along the main-axis per justify-content.
676
+ # 12.2 Align the items along the main-axis per justify-content.
837
677
  if {'end', 'flex-end', 'right'} & set(justify_content):
838
- position_axis += free_space
678
+ position_main += free_space
839
679
  elif 'center' in justify_content:
840
- position_axis += free_space / 2
680
+ position_main += free_space / 2
841
681
  elif 'space-around' in justify_content:
842
- position_axis += free_space / len(line) / 2
682
+ position_main += free_space / len(line) / 2
843
683
  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)
684
+ position_main += free_space / (len(line) + 1)
685
+
686
+ growths = sum(child.style['flex_grow'] for child in children)
687
+ for i, (index, child) in enumerate(line):
688
+ if i:
689
+ position_main += main_gap
690
+ if main == 'width':
691
+ child.position_x = position_main
692
+ if 'stretch' in justify_content and growths:
693
+ child.width += free_space * child.style['flex_grow'] / growths
851
694
  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
695
+ child.position_y = position_main
696
+ margin_main = (
697
+ child.margin_width() if main == 'width' else child.margin_height())
698
+ if box.style['direction'] == 'rtl' and main == 'width':
699
+ margin_main *= -1
700
+ position_main += margin_main
859
701
  if 'space-around' in justify_content:
860
- position_axis += free_space / len(line)
702
+ position_main += free_space / len(line)
861
703
  elif 'space-between' in justify_content:
862
704
  if len(line) > 1:
863
- position_axis += free_space / (len(line) - 1)
705
+ position_main += free_space / (len(line) - 1)
864
706
  elif 'space-evenly' in justify_content:
865
- position_axis += free_space / (len(line) + 1)
707
+ position_main += free_space / (len(line) + 1)
866
708
 
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.
709
+ # 13 Resolve cross-axis auto margins.
870
710
  if cross == 'width':
711
+ # Make sure width/margins are no longer "auto", as we did not do it above in
712
+ # step 4.
871
713
  block.block_level_width(box, containing_block)
872
- position_cross = (
873
- box.content_box_y() if cross == 'height'
874
- else box.content_box_x())
714
+ position_cross = box.content_box_y() if cross == 'height' else box.content_box_x()
875
715
  for line in flex_lines:
876
716
  line.lower_baseline = -inf
877
- # TODO: don't duplicate this loop.
717
+ # TODO: Don't duplicate this loop.
878
718
  for index, child in line:
879
719
  align_self = child.style['align_self']
880
720
  if 'auto' in align_self:
881
721
  align_self = align_items
882
- if 'baseline' in align_self and axis == 'width':
722
+ if 'baseline' in align_self and main == 'width':
883
723
  # TODO: handle vertical text.
884
724
  child.baseline = child._baseline - position_cross
885
725
  line.lower_baseline = max(line.lower_baseline, child.baseline)
886
726
  if line.lower_baseline == -inf:
887
727
  line.lower_baseline = line[0][1]._baseline if line else 0
888
- # 9.6.13 Resolve cross-axis auto margins.
889
728
  for index, child in line:
890
729
  cross_margins = (
891
730
  (child.margin_top, child.margin_bottom) if cross == 'height'
892
731
  else (child.margin_left, child.margin_right))
893
732
  auto_margins = sum([margin == 'auto' for margin in cross_margins])
894
- # 9.6.13 If a flex item has auto cross-axis margins:
733
+ # If a flex item has auto cross-axis margins
895
734
  if auto_margins:
896
735
  extra_cross = line.cross_size
897
736
  if cross == 'height':
@@ -907,10 +746,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
907
746
  if child.margin_right != 'auto':
908
747
  extra_cross -= child.margin_right
909
748
  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.
749
+ # If its outer cross size is less than the cross size…
914
750
  extra_cross /= auto_margins
915
751
  if cross == 'height':
916
752
  if child.margin_top == 'auto':
@@ -923,10 +759,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
923
759
  if child.margin_right == 'auto':
924
760
  child.margin_right = extra_cross
925
761
  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.
762
+ # Otherwise
930
763
  if cross == 'height':
931
764
  if child.margin_top == 'auto':
932
765
  child.margin_top = 0
@@ -936,9 +769,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
936
769
  child.margin_left = 0
937
770
  child.margin_right = extra_cross
938
771
  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.
772
+ # 14 Align all flex items along the cross-axis.
942
773
  align_self = child.style['align_self']
943
774
  if 'normal' in align_self:
944
775
  align_self = ('stretch',)
@@ -948,24 +779,20 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
948
779
  setattr(child, position, position_cross)
949
780
  if {'end', 'self-end', 'flex-end'} & set(align_self):
950
781
  if cross == 'height':
951
- child.position_y += (
952
- line.cross_size - child.margin_height())
782
+ child.position_y += line.cross_size - child.margin_height()
953
783
  else:
954
- child.position_x += (
955
- line.cross_size - child.margin_width())
784
+ child.position_x += line.cross_size - child.margin_width()
956
785
  elif 'center' in align_self:
957
786
  if cross == 'height':
958
787
  child.position_y += (
959
788
  line.cross_size - child.margin_height()) / 2
960
789
  else:
961
- child.position_x += (
962
- line.cross_size - child.margin_width()) / 2
790
+ child.position_x += (line.cross_size - child.margin_width()) / 2
963
791
  elif 'baseline' in align_self:
964
792
  if cross == 'height':
965
- child.position_y += (
966
- line.lower_baseline - child.baseline)
793
+ child.position_y += line.lower_baseline - child.baseline
967
794
  else:
968
- # Handle vertical text.
795
+ # TODO: Handle vertical text.
969
796
  pass
970
797
  elif 'stretch' in align_self:
971
798
  if child.style[cross] == 'auto':
@@ -976,78 +803,76 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
976
803
  if child.style['box_sizing'] == 'content-box':
977
804
  if cross == 'height':
978
805
  margins += (
979
- child.border_top_width +
980
- child.border_bottom_width +
806
+ child.border_top_width + child.border_bottom_width +
981
807
  child.padding_top + child.padding_bottom)
982
808
  else:
983
809
  margins += (
984
- child.border_left_width +
985
- child.border_right_width +
810
+ child.border_left_width + child.border_right_width +
986
811
  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.
812
+ # TODO: Don't set style width, find a way to avoid width
813
+ # re-calculation after 16.
989
814
  child.style[cross] = Dimension(line.cross_size - margins, 'px')
990
815
  position_cross += line.cross_size
991
816
 
992
- # 9.6.15 Determine the flex container’s used cross size:
817
+ # 15 Determine the flex container’s used cross size.
818
+ # TODO: Use the updated algorithm.
993
819
  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.
820
+ # Otherwise, use the sum of the flex lines' cross sizes
996
821
  # TODO: Handle min-max.
997
822
  # 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.
823
+ cross_size = sum(line.cross_size for line in flex_lines)
824
+ cross_size += (len(flex_lines) - 1) * cross_gap
825
+ setattr(box, cross, cross_size)
826
+
827
+ if len(flex_lines) > 1:
828
+ # 15 If the cross size property is a definite size, use that…
829
+ extra_cross_size = getattr(box, cross)
830
+ extra_cross_size -= sum(line.cross_size for line in flex_lines)
831
+ extra_cross_size -= (len(flex_lines) - 1) * cross_gap
832
+ # 16 Align all flex lines per align-content.
833
+ cross_translate = 0
1005
834
  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)
835
+ for i, line in enumerate(flex_lines):
836
+ flex_items = tuple(child for _, child in line if child.is_flex_item)
837
+ if i:
838
+ cross_translate += cross_gap
839
+ for child in flex_items:
840
+ current_value = getattr(child, direction) + cross_translate
841
+ setattr(child, direction, current_value)
842
+ if extra_cross_size == 0:
843
+ continue
844
+ for child in flex_items:
845
+ if {'flex-end', 'end'} & set(align_content):
846
+ setattr(child, direction, current_value + extra_cross_size)
847
+ elif 'center' in align_content:
848
+ setattr(child, direction, current_value + extra_cross_size / 2)
1034
849
  elif 'space-around' in align_content:
1035
- cross_translate += extra_cross_size / len(flex_lines)
850
+ setattr(
851
+ child, direction,
852
+ current_value + extra_cross_size / len(flex_lines) / 2)
1036
853
  elif 'space-evenly' in align_content:
1037
- cross_translate += extra_cross_size / (len(flex_lines) + 1)
854
+ setattr(
855
+ child, direction,
856
+ current_value + extra_cross_size / (len(flex_lines) + 1))
857
+ if 'space-between' in align_content:
858
+ cross_translate += extra_cross_size / (len(flex_lines) - 1)
859
+ elif 'space-around' in align_content:
860
+ cross_translate += extra_cross_size / len(flex_lines)
861
+ elif 'space-evenly' in align_content:
862
+ cross_translate += extra_cross_size / (len(flex_lines) + 1)
1038
863
 
1039
864
  # Now we are no longer in the flex algorithm.
1040
- # TODO: Don't use block_box_level_layout_switch.
1041
865
  box = box.copy_with_children(
1042
866
  [child for child in children if child.is_absolutely_positioned()])
1043
867
  child_skip_stack = skip_stack
1044
868
  for line in flex_lines:
1045
869
  for index, child in line:
1046
870
  if child.is_flex_item:
871
+ # TODO: Don't use block_level_layout_switch.
1047
872
  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]
873
+ context, child, bottom_space, child_skip_stack, box, page_is_empty,
874
+ absolute_boxes, fixed_boxes, adjoining_margins=[], discard=discard,
875
+ max_lines=None)[:2]
1051
876
  if new_child is None:
1052
877
  if resume_at:
1053
878
  resume_index, = resume_at
@@ -1079,29 +904,23 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
1079
904
  # New containing block, resolve the layout of the absolute descendants.
1080
905
  for absolute_box in absolute_boxes:
1081
906
  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
907
+ context, absolute_box, box, fixed_boxes, bottom_space, skip_stack=None)
1092
908
 
1093
- # Set baseline.
1094
- # See https://www.w3.org/TR/css-flexbox-1/#flex-baselines
1095
- # TODO: Use the real algorithm.
909
+ # TODO: Use real algorithm, see https://www.w3.org/TR/css-flexbox-1/#flex-baselines.
1096
910
  if isinstance(box, boxes.InlineFlexBox):
1097
- if axis == 'width': # and main text direction is horizontal
911
+ if main == 'width': # and main text direction is horizontal
1098
912
  box.baseline = flex_lines[0].lower_baseline if flex_lines else 0
1099
913
  else:
1100
- box.baseline = ((
1101
- find_in_flow_baseline(box.children[0])
1102
- if box.children else 0) or 0)
914
+ for child in box.children:
915
+ if child.is_in_normal_flow():
916
+ box.baseline = find_in_flow_baseline(child) or 0
917
+ break
918
+ else:
919
+ box.baseline = 0
920
+
921
+ box.remove_decoration(start=False, end=resume_at and not discard)
1103
922
 
1104
- context.finish_block_formatting_context(box)
923
+ context.finish_flex_formatting_context(box)
1105
924
 
1106
925
  # TODO: Check these returned values.
1107
926
  return box, resume_at, {'break': 'any', 'page': None}, [], False