weasyprint 65.1__py3-none-any.whl → 67.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/__init__.py +17 -7
- weasyprint/__main__.py +21 -10
- weasyprint/anchors.py +4 -4
- weasyprint/css/__init__.py +732 -67
- weasyprint/css/computed_values.py +65 -170
- weasyprint/css/counters.py +1 -1
- weasyprint/css/functions.py +206 -0
- weasyprint/css/html5_ua.css +3 -7
- weasyprint/css/html5_ua_form.css +2 -2
- weasyprint/css/media_queries.py +3 -1
- weasyprint/css/properties.py +6 -2
- weasyprint/css/{utils.py → tokens.py} +306 -397
- weasyprint/css/units.py +91 -0
- weasyprint/css/validation/__init__.py +1 -1
- weasyprint/css/validation/descriptors.py +47 -19
- weasyprint/css/validation/expanders.py +7 -8
- weasyprint/css/validation/properties.py +341 -357
- weasyprint/document.py +20 -19
- weasyprint/draw/__init__.py +56 -63
- weasyprint/draw/border.py +121 -69
- weasyprint/draw/color.py +1 -1
- weasyprint/draw/text.py +60 -41
- weasyprint/formatting_structure/boxes.py +24 -5
- weasyprint/formatting_structure/build.py +33 -45
- weasyprint/images.py +76 -62
- weasyprint/layout/__init__.py +32 -26
- weasyprint/layout/absolute.py +7 -6
- weasyprint/layout/background.py +7 -7
- weasyprint/layout/block.py +195 -152
- weasyprint/layout/column.py +19 -24
- weasyprint/layout/flex.py +54 -26
- weasyprint/layout/float.py +12 -7
- weasyprint/layout/grid.py +284 -90
- weasyprint/layout/inline.py +121 -68
- weasyprint/layout/page.py +45 -12
- weasyprint/layout/percent.py +14 -10
- weasyprint/layout/preferred.py +105 -63
- weasyprint/layout/replaced.py +9 -6
- weasyprint/layout/table.py +16 -9
- weasyprint/pdf/__init__.py +58 -18
- weasyprint/pdf/anchors.py +3 -4
- weasyprint/pdf/fonts.py +126 -69
- weasyprint/pdf/metadata.py +36 -4
- weasyprint/pdf/pdfa.py +19 -3
- weasyprint/pdf/pdfua.py +7 -115
- weasyprint/pdf/pdfx.py +83 -0
- weasyprint/pdf/stream.py +57 -49
- weasyprint/pdf/tags.py +307 -0
- weasyprint/stacking.py +14 -15
- weasyprint/svg/__init__.py +59 -32
- weasyprint/svg/bounding_box.py +4 -2
- weasyprint/svg/defs.py +4 -9
- weasyprint/svg/images.py +11 -3
- weasyprint/svg/text.py +11 -2
- weasyprint/svg/utils.py +15 -8
- weasyprint/text/constants.py +1 -1
- weasyprint/text/ffi.py +4 -3
- weasyprint/text/fonts.py +13 -5
- weasyprint/text/line_break.py +146 -43
- weasyprint/urls.py +41 -13
- {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/METADATA +5 -6
- weasyprint-67.0.dist-info/RECORD +77 -0
- weasyprint/draw/stack.py +0 -13
- weasyprint-65.1.dist-info/RECORD +0 -74
- {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/WHEEL +0 -0
- {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/entry_points.txt +0 -0
- {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/licenses/LICENSE +0 -0
weasyprint/layout/grid.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Layout for grid containers and grid-items."""
|
|
2
2
|
|
|
3
|
+
from collections import defaultdict
|
|
3
4
|
from itertools import count, cycle
|
|
4
5
|
from math import inf
|
|
5
6
|
|
|
@@ -12,11 +13,11 @@ from .table import find_in_flow_baseline
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def _is_length(sizing):
|
|
15
|
-
return isinstance(sizing, Dimension) and sizing.unit != 'fr'
|
|
16
|
+
return isinstance(sizing, Dimension) and sizing.unit.lower() != 'fr'
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def _is_fr(sizing):
|
|
19
|
-
return isinstance(sizing, Dimension) and sizing.unit == 'fr'
|
|
20
|
+
return isinstance(sizing, Dimension) and sizing.unit.lower() == 'fr'
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _intersect(position_1, size_1, position_2, size_2):
|
|
@@ -227,10 +228,9 @@ def _get_template_tracks(tracks):
|
|
|
227
228
|
return tracks_list
|
|
228
229
|
|
|
229
230
|
|
|
230
|
-
def _distribute_extra_space(affected_sizes, affected_tracks_types,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
context, containing_block):
|
|
231
|
+
def _distribute_extra_space(affected_sizes, affected_tracks_types, size_contribution,
|
|
232
|
+
tracks_children, sizing_functions, tracks_sizes, span,
|
|
233
|
+
direction, context):
|
|
234
234
|
assert affected_sizes in ('min', 'max')
|
|
235
235
|
assert affected_tracks_types in (
|
|
236
236
|
'intrinsic', 'content-based', 'max-content')
|
|
@@ -243,7 +243,14 @@ def _distribute_extra_space(affected_sizes, affected_tracks_types,
|
|
|
243
243
|
# 2. Distribute space.
|
|
244
244
|
affected_tracks = []
|
|
245
245
|
affected_size_index = 0 if affected_sizes == 'min' else 1
|
|
246
|
-
|
|
246
|
+
current_span = 0
|
|
247
|
+
for children, functions in zip(tracks_children, sizing_functions):
|
|
248
|
+
if children:
|
|
249
|
+
current_span = span
|
|
250
|
+
if not current_span:
|
|
251
|
+
affected_tracks.append(False)
|
|
252
|
+
continue
|
|
253
|
+
current_span -= 1
|
|
247
254
|
function = functions[affected_size_index]
|
|
248
255
|
if affected_tracks_types == 'intrinsic':
|
|
249
256
|
if (function in ('min-content', 'max-content', 'auto') or
|
|
@@ -262,7 +269,7 @@ def _distribute_extra_space(affected_sizes, affected_tracks_types,
|
|
|
262
269
|
for i, children in enumerate(tracks_children):
|
|
263
270
|
if not children:
|
|
264
271
|
continue
|
|
265
|
-
for item in children:
|
|
272
|
+
for item, parent in children:
|
|
266
273
|
# 2.1 Find the space distribution.
|
|
267
274
|
# TODO: Differenciate minimum and min-content values.
|
|
268
275
|
# TODO: Find a better way to get height.
|
|
@@ -278,8 +285,7 @@ def _distribute_extra_space(affected_sizes, affected_tracks_types,
|
|
|
278
285
|
item.position_y = 0
|
|
279
286
|
item, _, _, _, _, _ = block_level_layout(
|
|
280
287
|
context, item, bottom_space=-inf, skip_stack=None,
|
|
281
|
-
containing_block=
|
|
282
|
-
absolute_boxes=[], fixed_boxes=[])
|
|
288
|
+
containing_block=parent)
|
|
283
289
|
space = item.margin_height()
|
|
284
290
|
for sizes in tracks_sizes[i:i+span]:
|
|
285
291
|
space -= sizes[affected_size_index]
|
|
@@ -329,17 +335,21 @@ def _distribute_extra_space(affected_sizes, affected_tracks_types,
|
|
|
329
335
|
for k, extra in enumerate(item_incurred_increases):
|
|
330
336
|
if extra > planned_increases[k]:
|
|
331
337
|
planned_increases[k] = extra
|
|
338
|
+
|
|
332
339
|
# 3. Update the tracks’ affected size.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
340
|
+
iterator = zip(affected_tracks, tracks_sizes, planned_increases)
|
|
341
|
+
for affected, track_sizes, increase in iterator:
|
|
342
|
+
if not affected:
|
|
343
|
+
continue
|
|
344
|
+
if affected_sizes == 'max' and track_sizes[1] is inf:
|
|
345
|
+
track_sizes[1] = track_sizes[0] + increase
|
|
336
346
|
else:
|
|
337
|
-
|
|
347
|
+
track_sizes[affected_size_index] += increase
|
|
338
348
|
|
|
339
349
|
|
|
340
350
|
def _resolve_tracks_sizes(sizing_functions, box_size, children_positions,
|
|
341
|
-
implicit_start, direction, gap, context,
|
|
342
|
-
|
|
351
|
+
implicit_start, direction, gap, context, containing_block,
|
|
352
|
+
orthogonal_sizes=None):
|
|
343
353
|
assert direction in 'xy'
|
|
344
354
|
tracks_sizes = []
|
|
345
355
|
# TODO: Check that auto box size is 0 for percentages.
|
|
@@ -348,13 +358,15 @@ def _resolve_tracks_sizes(sizing_functions, box_size, children_positions,
|
|
|
348
358
|
for min_function, max_function in sizing_functions:
|
|
349
359
|
base_size = None
|
|
350
360
|
if _is_length(min_function):
|
|
351
|
-
base_size = percentage(
|
|
361
|
+
base_size = percentage(
|
|
362
|
+
min_function, containing_block.style, percent_box_size)
|
|
352
363
|
elif (min_function in ('min-content', 'max-content', 'auto') or
|
|
353
364
|
min_function[0] == 'fit-content()'):
|
|
354
365
|
base_size = 0
|
|
355
366
|
growth_limit = None
|
|
356
367
|
if _is_length(max_function):
|
|
357
|
-
growth_limit = percentage(
|
|
368
|
+
growth_limit = percentage(
|
|
369
|
+
max_function, containing_block.style, percent_box_size)
|
|
358
370
|
elif (max_function in ('min-content', 'max-content', 'auto') or
|
|
359
371
|
max_function[0] == 'fit-content()' or _is_fr(max_function)):
|
|
360
372
|
growth_limit = inf
|
|
@@ -381,13 +393,12 @@ def _resolve_tracks_sizes(sizing_functions, box_size, children_positions,
|
|
|
381
393
|
from .block import block_level_layout
|
|
382
394
|
height = 0
|
|
383
395
|
for child in children:
|
|
384
|
-
x,
|
|
396
|
+
x, y, width, _ = children_positions[child]
|
|
385
397
|
width = sum(orthogonal_sizes[x:x+width])
|
|
386
398
|
child = child.deepcopy()
|
|
387
399
|
child.position_x = 0
|
|
388
400
|
child.position_y = 0
|
|
389
|
-
parent = boxes.BlockContainerBox.anonymous_from(
|
|
390
|
-
containing_block, ())
|
|
401
|
+
parent = boxes.BlockContainerBox.anonymous_from(containing_block, ())
|
|
391
402
|
resolve_percentages(parent, containing_block)
|
|
392
403
|
parent.position_x = child.position_x
|
|
393
404
|
parent.position_y = child.position_y
|
|
@@ -396,8 +407,7 @@ def _resolve_tracks_sizes(sizing_functions, box_size, children_positions,
|
|
|
396
407
|
bottom_space = -inf
|
|
397
408
|
child, _, _, _, _, _ = block_level_layout(
|
|
398
409
|
context, child, bottom_space, skip_stack=None,
|
|
399
|
-
containing_block=parent
|
|
400
|
-
absolute_boxes=[], fixed_boxes=[])
|
|
410
|
+
containing_block=parent)
|
|
401
411
|
height = max(height, child.margin_height())
|
|
402
412
|
if min_function in ('min-content', 'max_content', 'auto'):
|
|
403
413
|
sizes[0] = height
|
|
@@ -442,48 +452,35 @@ def _resolve_tracks_sizes(sizing_functions, box_size, children_positions,
|
|
|
442
452
|
if _is_fr(max_function):
|
|
443
453
|
break
|
|
444
454
|
else:
|
|
445
|
-
|
|
455
|
+
parent = boxes.BlockContainerBox.anonymous_from(containing_block, ())
|
|
456
|
+
resolve_percentages(parent, containing_block)
|
|
457
|
+
if direction == 'y':
|
|
458
|
+
parent.width = sum(orthogonal_sizes[x:x+width])
|
|
459
|
+
tracks_children[coord - implicit_start].append((child, parent))
|
|
446
460
|
# 1.2.3.1 For intrinsic minimums.
|
|
447
461
|
# TODO: Respect min-/max-content constraint.
|
|
448
462
|
_distribute_extra_space(
|
|
449
463
|
'min', 'intrinsic', 'minimum', tracks_children,
|
|
450
|
-
sizing_functions, tracks_sizes, span, direction, context
|
|
451
|
-
containing_block)
|
|
464
|
+
sizing_functions, tracks_sizes, span, direction, context)
|
|
452
465
|
# 1.2.3.2 For content-based minimums.
|
|
453
466
|
_distribute_extra_space(
|
|
454
467
|
'min', 'content-based', 'min-content', tracks_children,
|
|
455
|
-
sizing_functions, tracks_sizes, span, direction, context
|
|
456
|
-
containing_block)
|
|
468
|
+
sizing_functions, tracks_sizes, span, direction, context)
|
|
457
469
|
# 1.2.3.3 For max-content minimums.
|
|
458
470
|
# TODO: Respect max-content constraint.
|
|
459
471
|
_distribute_extra_space(
|
|
460
472
|
'min', 'max-content', 'max-content', tracks_children,
|
|
461
|
-
sizing_functions, tracks_sizes, span, direction, context
|
|
462
|
-
containing_block)
|
|
473
|
+
sizing_functions, tracks_sizes, span, direction, context)
|
|
463
474
|
# 1.2.3.4 Increase growth limit.
|
|
464
|
-
|
|
465
|
-
if None not in sizes:
|
|
466
|
-
sizes[1] = max(sizes)
|
|
467
|
-
iterable = enumerate(children_positions.items())
|
|
468
|
-
for i, (child, (x, y, width, height)) in iterable:
|
|
469
|
-
coord, size = (x, width) if direction == 'x' else (y, height)
|
|
470
|
-
if size != span:
|
|
471
|
-
continue
|
|
472
|
-
for _, max_function in sizing_functions[i:i+span+1]:
|
|
473
|
-
if _is_fr(max_function):
|
|
474
|
-
break
|
|
475
|
-
else:
|
|
476
|
-
tracks_children[coord - implicit_start].append(child)
|
|
475
|
+
# TODO: Increase growth limit.
|
|
477
476
|
# 1.2.3.5 For intrinsic maximums.
|
|
478
477
|
_distribute_extra_space(
|
|
479
478
|
'max', 'intrinsic', 'min-content', tracks_children,
|
|
480
|
-
sizing_functions, tracks_sizes, span, direction, context
|
|
481
|
-
containing_block)
|
|
479
|
+
sizing_functions, tracks_sizes, span, direction, context)
|
|
482
480
|
# 1.2.3.6 For max-content maximums.
|
|
483
481
|
_distribute_extra_space(
|
|
484
482
|
'max', 'max-content', 'max-content', tracks_children,
|
|
485
|
-
sizing_functions, tracks_sizes, span, direction, context
|
|
486
|
-
containing_block)
|
|
483
|
+
sizing_functions, tracks_sizes, span, direction, context)
|
|
487
484
|
# 1.2.4 Increase sizes to accommodate items spanning flexible tracks.
|
|
488
485
|
# TODO: Support spans for flexible tracks.
|
|
489
486
|
# 1.2.5 Fix infinite growth limits.
|
|
@@ -577,7 +574,14 @@ def _resolve_tracks_sizes(sizing_functions, box_size, children_positions,
|
|
|
577
574
|
|
|
578
575
|
def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
579
576
|
page_is_empty, absolute_boxes, fixed_boxes):
|
|
580
|
-
context.create_block_formatting_context()
|
|
577
|
+
context.create_block_formatting_context(box)
|
|
578
|
+
|
|
579
|
+
if skip_stack and box.style['box_decoration_break'] != 'clone':
|
|
580
|
+
box.remove_decoration(start=True, end=False)
|
|
581
|
+
|
|
582
|
+
if box.style['position'] == 'relative':
|
|
583
|
+
# New containing block, use a new absolute list
|
|
584
|
+
absolute_boxes = []
|
|
581
585
|
|
|
582
586
|
# Define explicit grid
|
|
583
587
|
grid_areas = box.style['grid_template_areas']
|
|
@@ -591,13 +595,13 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
591
595
|
column_gap = 0
|
|
592
596
|
else:
|
|
593
597
|
refer_to = containing_block.width if box.width == 'auto' else box.width
|
|
594
|
-
column_gap = percentage(column_gap, refer_to)
|
|
598
|
+
column_gap = percentage(column_gap, box.style, refer_to)
|
|
595
599
|
row_gap = box.style['row_gap']
|
|
596
600
|
if row_gap == 'normal':
|
|
597
601
|
row_gap = 0
|
|
598
602
|
else:
|
|
599
603
|
refer_to = 0 if box.height == 'auto' else box.height
|
|
600
|
-
row_gap = percentage(row_gap, refer_to)
|
|
604
|
+
row_gap = percentage(row_gap, box.style, refer_to)
|
|
601
605
|
|
|
602
606
|
if grid_areas == 'none':
|
|
603
607
|
grid_areas = ((None,),)
|
|
@@ -660,8 +664,9 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
660
664
|
second_tracks = rows if second_flow == 'row' else columns
|
|
661
665
|
|
|
662
666
|
# 1.1 Position anything that’s not auto-positioned.
|
|
667
|
+
children = sorted(box.children, key=lambda item: item.style['order'])
|
|
663
668
|
children_positions = {}
|
|
664
|
-
for child in
|
|
669
|
+
for child in children:
|
|
665
670
|
column_start = child.style['grid_column_start']
|
|
666
671
|
column_end = child.style['grid_column_end']
|
|
667
672
|
row_start = child.style['grid_row_start']
|
|
@@ -677,7 +682,6 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
677
682
|
children_positions[child] = (x, y, width, height)
|
|
678
683
|
|
|
679
684
|
# 1.2 Process the items locked to a given row (resp. column).
|
|
680
|
-
children = sorted(box.children, key=lambda item: item.style['order'])
|
|
681
685
|
for child in children:
|
|
682
686
|
if child in children_positions:
|
|
683
687
|
continue
|
|
@@ -980,6 +984,21 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
980
984
|
rows.append(next(auto_rows))
|
|
981
985
|
rows.append([])
|
|
982
986
|
|
|
987
|
+
page_breaks_by_row = defaultdict(
|
|
988
|
+
lambda: {'before': 'auto', 'after': 'auto', 'inside': 'auto'})
|
|
989
|
+
for child, (x, y, width, height) in children_positions.items():
|
|
990
|
+
row_page_break = page_breaks_by_row[y]
|
|
991
|
+
if child.style['break_inside'] in ('avoid', 'avoid-page'):
|
|
992
|
+
row_page_break['inside'] = 'avoid'
|
|
993
|
+
for side in ('before', 'after'):
|
|
994
|
+
if child.style[f'break_{side}'] in ('avoid', 'avoid-page'):
|
|
995
|
+
if row_page_break[side] != 'page':
|
|
996
|
+
row_page_break[side] = 'avoid'
|
|
997
|
+
elif child.style[f'break_{side}'] in ('page', 'always'):
|
|
998
|
+
row_page_break[side] = 'page'
|
|
999
|
+
elif child.style[f'break_{side}'] in ('left', 'right', 'recto', 'verso'):
|
|
1000
|
+
row_page_break[side] = child.style[f'break_{side}']
|
|
1001
|
+
|
|
983
1002
|
# 2. Find the size of the grid container.
|
|
984
1003
|
|
|
985
1004
|
if isinstance(box, boxes.GridBox):
|
|
@@ -1006,9 +1025,10 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
1006
1025
|
'x', column_gap, context, box)
|
|
1007
1026
|
|
|
1008
1027
|
# 3.2 Resolve the sizes of the grid rows.
|
|
1028
|
+
vertical_sizes = [size for size, _ in columns_sizes]
|
|
1009
1029
|
rows_sizes = _resolve_tracks_sizes(
|
|
1010
1030
|
row_sizing_functions, box.height, children_positions, implicit_y1, 'y',
|
|
1011
|
-
row_gap, context, box,
|
|
1031
|
+
row_gap, context, box, vertical_sizes)
|
|
1012
1032
|
|
|
1013
1033
|
# 3.3 Re-resolve the sizes of the grid columns with min-/max-content.
|
|
1014
1034
|
# TODO: Re-resolve.
|
|
@@ -1099,57 +1119,132 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
1099
1119
|
y += size + row_gap
|
|
1100
1120
|
|
|
1101
1121
|
# 4. Lay out the grid items into their respective containing blocks.
|
|
1122
|
+
|
|
1102
1123
|
# Find resume_at row.
|
|
1103
1124
|
this_page_children = []
|
|
1104
1125
|
resume_row = None
|
|
1105
1126
|
if skip_stack:
|
|
1106
|
-
|
|
1127
|
+
from .block import block_level_layout
|
|
1128
|
+
first_skip_row = min(skip_stack)
|
|
1129
|
+
last_skip_row = max(skip_stack)
|
|
1107
1130
|
skip_height = (
|
|
1108
|
-
sum(size for size, _ in rows_sizes[:
|
|
1109
|
-
(len(rows_sizes[:
|
|
1131
|
+
sum(size for size, _ in rows_sizes[:last_skip_row]) +
|
|
1132
|
+
(len(rows_sizes[:last_skip_row]) - 1) * row_gap)
|
|
1133
|
+
extra_skip_height = 0
|
|
1134
|
+
for child, (x, y, width, height) in children_positions.items():
|
|
1135
|
+
if (advancement := box.advancements.get((x, y))) is None:
|
|
1136
|
+
continue
|
|
1137
|
+
span = _get_span(child.style['grid_row_start'])
|
|
1138
|
+
span_height = (
|
|
1139
|
+
sum(size for size, _ in rows_sizes[y:y+span]) +
|
|
1140
|
+
(span - 1) * row_gap)
|
|
1141
|
+
index = tuple(children_positions).index(child)
|
|
1142
|
+
width = sum(vertical_sizes[x:x+width])
|
|
1143
|
+
child = child.deepcopy()
|
|
1144
|
+
child.position_x = 0
|
|
1145
|
+
child.position_y = 0
|
|
1146
|
+
if y in skip_stack:
|
|
1147
|
+
child_skip_stack = skip_stack[y].get(index)
|
|
1148
|
+
else:
|
|
1149
|
+
child_skip_stack = None
|
|
1150
|
+
parent = boxes.BlockContainerBox.anonymous_from(containing_block, ())
|
|
1151
|
+
resolve_percentages(parent, containing_block)
|
|
1152
|
+
parent.position_x = 0
|
|
1153
|
+
parent.position_y = 0
|
|
1154
|
+
parent.width = width
|
|
1155
|
+
parent.height = 0
|
|
1156
|
+
child, _, _, _, _, _ = block_level_layout(
|
|
1157
|
+
context, child, bottom_space=-inf, skip_stack=child_skip_stack,
|
|
1158
|
+
containing_block=parent)
|
|
1159
|
+
skip_stack_advancement = span_height - child.margin_height()
|
|
1160
|
+
if skip_stack_advancement < advancement:
|
|
1161
|
+
extra_skip_height = max(
|
|
1162
|
+
extra_skip_height, advancement - skip_stack_advancement)
|
|
1163
|
+
for (x, y), advancement in box.advancements.items():
|
|
1164
|
+
if y != last_skip_row:
|
|
1165
|
+
continue
|
|
1166
|
+
skip_height += advancement
|
|
1167
|
+
break
|
|
1168
|
+
else:
|
|
1169
|
+
extra_skip_height = 0
|
|
1170
|
+
skip_height -= extra_skip_height
|
|
1110
1171
|
else:
|
|
1111
|
-
|
|
1112
|
-
skip_height = 0
|
|
1172
|
+
first_skip_row = last_skip_row = skip_height = 0
|
|
1113
1173
|
resume_at = None
|
|
1114
1174
|
total_height = (
|
|
1115
|
-
sum(size for size, _ in rows_sizes[
|
|
1116
|
-
(len(rows_sizes[
|
|
1175
|
+
sum(size for size, _ in rows_sizes[last_skip_row:]) +
|
|
1176
|
+
(len(rows_sizes[last_skip_row:]) - 1) * row_gap)
|
|
1177
|
+
|
|
1178
|
+
def _add_page_children(max_row=inf):
|
|
1179
|
+
for j, child in enumerate(children):
|
|
1180
|
+
_, y, _, _ = children_positions[child]
|
|
1181
|
+
if not first_skip_row <= y < max_row:
|
|
1182
|
+
# Item in previous or next rows.
|
|
1183
|
+
continue
|
|
1184
|
+
if skip_stack is None:
|
|
1185
|
+
# No skip stack, draw on this page.
|
|
1186
|
+
this_page_children.append((j, child))
|
|
1187
|
+
elif y > last_skip_row:
|
|
1188
|
+
# Row after the skip stack, draw on this page.
|
|
1189
|
+
this_page_children.append((j, child))
|
|
1190
|
+
elif y in skip_stack:
|
|
1191
|
+
child_skip_stack = skip_stack[y]
|
|
1192
|
+
if child_skip_stack is None or j in child_skip_stack:
|
|
1193
|
+
# Child in skip stack, draw on this page.
|
|
1194
|
+
this_page_children.append((j, child))
|
|
1195
|
+
|
|
1117
1196
|
row_lines_positions = (
|
|
1118
|
-
rows_positions[
|
|
1119
|
-
for i, row_y in enumerate(row_lines_positions, start=
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1197
|
+
[*rows_positions[first_skip_row + 1:], box.content_box_y() + total_height])
|
|
1198
|
+
for i, row_y in enumerate(row_lines_positions, start=first_skip_row):
|
|
1199
|
+
# TODO: handle break-before and break-after for rows.
|
|
1200
|
+
if not context.overflows_page(bottom_space, row_y - skip_height):
|
|
1201
|
+
page_is_empty = False
|
|
1202
|
+
continue
|
|
1203
|
+
resume_row = i
|
|
1204
|
+
if box.style['break_inside'] == 'avoid' and not page_is_empty:
|
|
1205
|
+
# Avoid breaks inside grid container, break before.
|
|
1206
|
+
context.finish_block_formatting_context(box)
|
|
1207
|
+
return None, None, {'break': 'any', 'page': None}, [], False
|
|
1208
|
+
if page_breaks_by_row[i]['inside'] == 'avoid' and not page_is_empty:
|
|
1209
|
+
# Break before current row.
|
|
1210
|
+
if resume_row == 0:
|
|
1211
|
+
# First row, break before grid container.
|
|
1212
|
+
context.finish_block_formatting_context(box)
|
|
1213
|
+
return None, None, {'break': 'any', 'page': None}, [], False
|
|
1214
|
+
# Mark all children before and in current row as drawn on the page.
|
|
1215
|
+
_add_page_children(resume_row)
|
|
1216
|
+
resume_at = {resume_row: None}
|
|
1217
|
+
else:
|
|
1218
|
+
# Break inside current row.
|
|
1219
|
+
# Mark all children before current row as drawn on the page.
|
|
1220
|
+
page_is_empty = False
|
|
1221
|
+
_add_page_children(resume_row + 1)
|
|
1222
|
+
break
|
|
1132
1223
|
else:
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
if skip_row <= y:
|
|
1136
|
-
this_page_children.append(child)
|
|
1224
|
+
# Mark all children as drawn on the page.
|
|
1225
|
+
_add_page_children()
|
|
1137
1226
|
if box.height == 'auto':
|
|
1138
1227
|
box.height = (
|
|
1139
|
-
sum(size for size, _ in rows_sizes[
|
|
1140
|
-
(len(rows_sizes[
|
|
1228
|
+
sum(size for size, _ in rows_sizes[:resume_row]) +
|
|
1229
|
+
(len(rows_sizes[:resume_row]) - 1) * row_gap) - skip_height
|
|
1230
|
+
|
|
1141
1231
|
# Lay out grid items.
|
|
1142
1232
|
justify_items = set(box.style['justify_items'])
|
|
1143
1233
|
align_items = set(box.style['align_items'])
|
|
1144
1234
|
new_children = []
|
|
1235
|
+
new_children_by_rows = defaultdict(list)
|
|
1145
1236
|
baseline = None
|
|
1146
1237
|
next_page = {'break': 'any', 'page': None}
|
|
1147
1238
|
from .block import block_level_layout
|
|
1148
|
-
for child in this_page_children:
|
|
1239
|
+
for i, child in this_page_children:
|
|
1149
1240
|
x, y, width, height = children_positions[child]
|
|
1150
|
-
index =
|
|
1151
|
-
if skip_stack and skip_stack.get(y)
|
|
1152
|
-
|
|
1241
|
+
index = children.index(child)
|
|
1242
|
+
if skip_stack and skip_stack.get(y):
|
|
1243
|
+
if index in skip_stack[y]:
|
|
1244
|
+
child_skip_stack = skip_stack[y][index]
|
|
1245
|
+
else:
|
|
1246
|
+
assert isinstance(child, boxes.ParentBox)
|
|
1247
|
+
child_skip_stack = {len(child.children): None}
|
|
1153
1248
|
else:
|
|
1154
1249
|
child_skip_stack = None
|
|
1155
1250
|
child = child.deepcopy()
|
|
@@ -1162,6 +1257,9 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
1162
1257
|
height = (
|
|
1163
1258
|
sum(size for size, _ in rows_sizes[y:y+height]) +
|
|
1164
1259
|
(height - 1) * row_gap)
|
|
1260
|
+
if skip_stack and (x, y) in box.advancements:
|
|
1261
|
+
child.position_y += box.advancements[x, y] - extra_skip_height
|
|
1262
|
+
height -= box.advancements[x, y] - extra_skip_height
|
|
1165
1263
|
|
|
1166
1264
|
# TODO: Apply auto margin.
|
|
1167
1265
|
if child.margin_top == 'auto':
|
|
@@ -1177,20 +1275,24 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
1177
1275
|
child.margin_left + child.border_left_width + child.padding_left +
|
|
1178
1276
|
child.margin_right + child.border_right_width + child.padding_right)
|
|
1179
1277
|
child_height = height - (
|
|
1180
|
-
child.margin_top + child.border_top_width + child.padding_top +
|
|
1181
1278
|
child.margin_bottom + child.border_bottom_width + child.padding_bottom)
|
|
1279
|
+
if not child_skip_stack or child.style['box_decoration_break'] == 'clone':
|
|
1280
|
+
child_height -= (
|
|
1281
|
+
child.margin_top + child.border_top_width + child.padding_top)
|
|
1182
1282
|
|
|
1183
1283
|
justify_self = set(child.style['justify_self'])
|
|
1184
1284
|
if justify_self & {'auto'}:
|
|
1185
1285
|
justify_self = justify_items
|
|
1186
1286
|
if justify_self & {'normal', 'stretch'}:
|
|
1187
1287
|
if child.style['width'] == 'auto':
|
|
1288
|
+
child.style = child.style.copy()
|
|
1188
1289
|
child.style['width'] = Dimension(child_width, 'px')
|
|
1189
1290
|
align_self = set(child.style['align_self'])
|
|
1190
1291
|
if align_self & {'auto'}:
|
|
1191
1292
|
align_self = align_items
|
|
1192
1293
|
if align_self & {'normal', 'stretch'}:
|
|
1193
1294
|
if child.style['height'] == 'auto':
|
|
1295
|
+
child.style = child.style.copy()
|
|
1194
1296
|
child.style['height'] = Dimension(child_height, 'px')
|
|
1195
1297
|
|
|
1196
1298
|
# TODO: Find a better solution for the layout.
|
|
@@ -1205,15 +1307,41 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
1205
1307
|
page_is_empty, absolute_boxes, fixed_boxes)[:3]
|
|
1206
1308
|
if new_child:
|
|
1207
1309
|
page_is_empty = False
|
|
1208
|
-
|
|
1310
|
+
broken_child = False
|
|
1311
|
+
span = _get_span(child.style['grid_row_start'])
|
|
1312
|
+
if child_resume_at:
|
|
1313
|
+
broken_child = True
|
|
1314
|
+
elif resume_row is not None and y + span >= resume_row + 1:
|
|
1315
|
+
broken_child = True
|
|
1316
|
+
if broken_child:
|
|
1317
|
+
# Child is broken, add row to resume_at.
|
|
1318
|
+
if resume_at is None:
|
|
1319
|
+
resume_at = {}
|
|
1320
|
+
if y not in resume_at:
|
|
1321
|
+
resume_at[y] = {}
|
|
1322
|
+
if child_resume_at:
|
|
1323
|
+
# There is some content left for next page, save the cell’s resume_at.
|
|
1324
|
+
resume_at[y][i] = child_resume_at
|
|
1325
|
+
elif broken_child:
|
|
1326
|
+
# Everything fits but the cell overflows. Only display the bottom of an
|
|
1327
|
+
# empty cell on next page, set the cell’s resume_at after the cell’s
|
|
1328
|
+
# last child.
|
|
1329
|
+
assert isinstance(new_child, boxes.ParentBox)
|
|
1330
|
+
previous_skip_child = max(child_skip_stack) if child_skip_stack else 0
|
|
1331
|
+
resume_at[y][i] = {previous_skip_child + len(new_child.children): None}
|
|
1209
1332
|
else:
|
|
1210
|
-
|
|
1333
|
+
if resume_at is None:
|
|
1334
|
+
resume_at = {}
|
|
1335
|
+
if y not in resume_at:
|
|
1336
|
+
resume_at[y] = {}
|
|
1337
|
+
resume_at[y][i] = None
|
|
1211
1338
|
continue
|
|
1212
1339
|
|
|
1213
1340
|
if justify_self & {'normal', 'stretch'}:
|
|
1214
1341
|
new_child.width = max(child_width, new_child.width)
|
|
1215
1342
|
else:
|
|
1216
|
-
new_child.width
|
|
1343
|
+
if new_child.style['width'] == 'auto':
|
|
1344
|
+
new_child.width = max_content_width(context, new_child, outer=False)
|
|
1217
1345
|
diff = child_width - new_child.width
|
|
1218
1346
|
if justify_self & {'center'}:
|
|
1219
1347
|
new_child.translate(diff / 2, 0)
|
|
@@ -1230,17 +1358,83 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
1230
1358
|
elif align_self & {'end', 'flex-end', 'self-end'}:
|
|
1231
1359
|
new_child.translate(0, diff)
|
|
1232
1360
|
|
|
1233
|
-
# TODO: Take care of page fragmentation.
|
|
1234
1361
|
new_children.append(new_child)
|
|
1362
|
+
new_children_by_rows[y].append((x, new_child))
|
|
1235
1363
|
if baseline is None and y == implicit_y1:
|
|
1236
1364
|
baseline = find_in_flow_baseline(new_child)
|
|
1237
1365
|
|
|
1366
|
+
# Abort whole grid rendering if no child fits.
|
|
1367
|
+
if this_page_children and not new_children:
|
|
1368
|
+
context.finish_block_formatting_context(box)
|
|
1369
|
+
return None, None, {'break': 'any', 'page': None}, [], False
|
|
1370
|
+
|
|
1371
|
+
old_advancements = box.advancements or {}
|
|
1372
|
+
advancements = box.advancements = {}
|
|
1238
1373
|
box = box.copy_with_children(new_children)
|
|
1239
1374
|
if isinstance(box, boxes.InlineGridBox):
|
|
1240
1375
|
# TODO: Synthetize a real baseline value.
|
|
1241
1376
|
LOGGER.warning('Inline grids are not supported')
|
|
1242
1377
|
box.baseline = baseline or 0
|
|
1243
1378
|
|
|
1244
|
-
|
|
1379
|
+
from .absolute import absolute_layout
|
|
1380
|
+
from .block import relative_positioning
|
|
1245
1381
|
|
|
1382
|
+
if box.style['position'] == 'relative':
|
|
1383
|
+
# New containing block, resolve the layout of the absolute descendants
|
|
1384
|
+
for absolute_box in absolute_boxes:
|
|
1385
|
+
absolute_layout(
|
|
1386
|
+
context, absolute_box, box, fixed_boxes, bottom_space,
|
|
1387
|
+
skip_stack=None)
|
|
1388
|
+
|
|
1389
|
+
for child in box.children:
|
|
1390
|
+
relative_positioning(child, (box.width, box.height))
|
|
1391
|
+
|
|
1392
|
+
# Resume early when there’s no resume_at.
|
|
1393
|
+
if not resume_at:
|
|
1394
|
+
context.finish_block_formatting_context(box)
|
|
1395
|
+
return box, resume_at, next_page, [], False
|
|
1396
|
+
|
|
1397
|
+
# Set broken rows’ bottom at the bottom of the page.
|
|
1398
|
+
last_page_row = max(new_children_by_rows)
|
|
1399
|
+
next_page_first_row = min(resume_at)
|
|
1400
|
+
next_page_last_row = max(resume_at)
|
|
1401
|
+
extra_advancement = 0
|
|
1402
|
+
for y in range(last_page_row, next_page_first_row - 1, -1):
|
|
1403
|
+
for x, child in new_children_by_rows[y]:
|
|
1404
|
+
span = _get_span(child.style['grid_row_start'])
|
|
1405
|
+
if y + span < last_page_row + 1:
|
|
1406
|
+
# Child finishing before the last row, do nothing.
|
|
1407
|
+
continue
|
|
1408
|
+
broken_child = y + span >= next_page_last_row + 1
|
|
1409
|
+
if broken_child and child.style['box_decoration_break'] != 'clone':
|
|
1410
|
+
child.remove_decoration(start=False, end=True)
|
|
1411
|
+
child.height = (
|
|
1412
|
+
context.page_bottom - bottom_space - child.position_y -
|
|
1413
|
+
child.margin_top - child.border_top_width - child.padding_top -
|
|
1414
|
+
child.margin_bottom - child.border_bottom_width - child.padding_bottom)
|
|
1415
|
+
if broken_child:
|
|
1416
|
+
# Child not fully drawn, keep advancement.
|
|
1417
|
+
advancements[x, y] = child.margin_height()
|
|
1418
|
+
if (x, y) in old_advancements:
|
|
1419
|
+
advancements[x, y] += old_advancements[x, y] - extra_skip_height
|
|
1420
|
+
else:
|
|
1421
|
+
# Child fully drawn, save the extra height added to reach the bottom of
|
|
1422
|
+
# the page to substract it from the advancements.
|
|
1423
|
+
extra_advancement = max(extra_advancement, child.height - child_height)
|
|
1424
|
+
|
|
1425
|
+
# Substract the extra height added to reach the bottom of the page from all the
|
|
1426
|
+
# advancements.
|
|
1427
|
+
if extra_advancement:
|
|
1428
|
+
for x, y in advancements:
|
|
1429
|
+
advancements[x, y] -= extra_advancement
|
|
1430
|
+
|
|
1431
|
+
# Set box height and remove bottom decoration.
|
|
1432
|
+
box.height = (
|
|
1433
|
+
context.page_bottom - bottom_space - box.position_y -
|
|
1434
|
+
box.margin_top - box.border_top_width - box.padding_top)
|
|
1435
|
+
if box.style['box_decoration_break'] != 'clone':
|
|
1436
|
+
box.remove_decoration(start=False, end=True)
|
|
1437
|
+
box.height -= box.margin_bottom + box.border_bottom_width + box.padding_bottom
|
|
1438
|
+
|
|
1439
|
+
context.finish_block_formatting_context(box)
|
|
1246
1440
|
return box, resume_at, next_page, [], False
|