weasyprint 65.1__py3-none-any.whl → 66.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 +4 -1
- weasyprint/__main__.py +2 -0
- weasyprint/css/__init__.py +12 -4
- weasyprint/css/computed_values.py +8 -2
- weasyprint/css/html5_ua.css +2 -7
- weasyprint/css/html5_ua_form.css +1 -1
- weasyprint/css/utils.py +1 -1
- weasyprint/document.py +2 -10
- weasyprint/draw/__init__.py +51 -57
- weasyprint/draw/border.py +120 -66
- weasyprint/draw/text.py +1 -2
- weasyprint/formatting_structure/boxes.py +3 -2
- weasyprint/formatting_structure/build.py +32 -42
- weasyprint/images.py +8 -15
- weasyprint/layout/__init__.py +5 -2
- weasyprint/layout/absolute.py +4 -1
- weasyprint/layout/block.py +60 -29
- weasyprint/layout/column.py +1 -0
- weasyprint/layout/flex.py +41 -21
- weasyprint/layout/float.py +8 -1
- weasyprint/layout/grid.py +1 -1
- weasyprint/layout/inline.py +7 -8
- weasyprint/layout/page.py +23 -1
- weasyprint/layout/preferred.py +59 -32
- weasyprint/layout/table.py +8 -4
- weasyprint/pdf/__init__.py +13 -6
- weasyprint/pdf/anchors.py +2 -2
- weasyprint/pdf/pdfua.py +7 -115
- weasyprint/pdf/stream.py +40 -49
- weasyprint/pdf/tags.py +305 -0
- weasyprint/stacking.py +14 -15
- weasyprint/svg/__init__.py +22 -11
- weasyprint/svg/bounding_box.py +4 -2
- weasyprint/svg/defs.py +4 -9
- weasyprint/svg/utils.py +9 -5
- weasyprint/text/fonts.py +1 -1
- weasyprint/text/line_break.py +45 -26
- weasyprint/urls.py +21 -10
- {weasyprint-65.1.dist-info → weasyprint-66.0.dist-info}/METADATA +1 -1
- weasyprint-66.0.dist-info/RECORD +74 -0
- weasyprint/draw/stack.py +0 -13
- weasyprint-65.1.dist-info/RECORD +0 -74
- {weasyprint-65.1.dist-info → weasyprint-66.0.dist-info}/WHEEL +0 -0
- {weasyprint-65.1.dist-info → weasyprint-66.0.dist-info}/entry_points.txt +0 -0
- {weasyprint-65.1.dist-info → weasyprint-66.0.dist-info}/licenses/LICENSE +0 -0
weasyprint/layout/block.py
CHANGED
|
@@ -298,9 +298,13 @@ def _out_of_flow_layout(context, box, index, child, new_children,
|
|
|
298
298
|
return stop, resume_at, new_child, out_of_flow_resume_at
|
|
299
299
|
|
|
300
300
|
|
|
301
|
-
def _break_line(context, box, line, new_children,
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
def _break_line(context, box, line, new_children, next_lines, page_is_empty, index,
|
|
302
|
+
skip_stack, resume_at, absolute_boxes, fixed_boxes):
|
|
303
|
+
"""Break line where allowed by orphans and widows.
|
|
304
|
+
|
|
305
|
+
Return (abort, stop, resume_at).
|
|
306
|
+
|
|
307
|
+
"""
|
|
304
308
|
over_orphans = len(new_children) - box.style['orphans']
|
|
305
309
|
if over_orphans < 0 and not page_is_empty:
|
|
306
310
|
# Reached the bottom of the page before we had
|
|
@@ -309,12 +313,7 @@ def _break_line(context, box, line, new_children, lines_iterator,
|
|
|
309
313
|
return True, False, resume_at
|
|
310
314
|
# How many lines we need on the next page to satisfy widows
|
|
311
315
|
# -1 for the current line.
|
|
312
|
-
needed = box.style['widows'] - 1
|
|
313
|
-
if needed:
|
|
314
|
-
for _ in lines_iterator:
|
|
315
|
-
needed -= 1
|
|
316
|
-
if needed == 0:
|
|
317
|
-
break
|
|
316
|
+
needed = max(box.style['widows'] - 1 - next_lines, 0)
|
|
318
317
|
if needed > over_orphans and not page_is_empty:
|
|
319
318
|
# Total number of lines < orphans + widows
|
|
320
319
|
remove_placeholders(context, line.children, absolute_boxes, fixed_boxes)
|
|
@@ -366,24 +365,52 @@ def _linebox_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
366
365
|
else:
|
|
367
366
|
offset_y = 0
|
|
368
367
|
|
|
369
|
-
# Allow overflow if the first line of the page is higher
|
|
370
|
-
#
|
|
371
|
-
# page and can advance in the context.
|
|
368
|
+
# Allow overflow if the first line of the page is higher than the page itself so
|
|
369
|
+
# that we put *something* on this page and can advance in the context.
|
|
372
370
|
overflow = (
|
|
373
371
|
(new_children or not page_is_empty) and
|
|
374
372
|
context.overflows_page(bottom_space, new_position_y + offset_y))
|
|
375
373
|
if overflow:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
374
|
+
# If we couldn’t break the line before but can break now, first try to
|
|
375
|
+
# report footnotes and see if we don’t overflow.
|
|
376
|
+
could_break_before = can_break_now = True
|
|
377
|
+
next_lines = len(tuple(lines_iterator))
|
|
378
|
+
if len(new_children) + 1 < box.style['orphans']:
|
|
379
|
+
can_break_now = False
|
|
380
|
+
elif next_lines < box.style['widows']:
|
|
381
|
+
can_break_now = False
|
|
382
|
+
if len(new_children) < box.style['orphans']:
|
|
383
|
+
could_break_before = False
|
|
384
|
+
elif next_lines + 1 < box.style['widows']:
|
|
385
|
+
could_break_before = False
|
|
386
|
+
report = not context.in_column and can_break_now and not could_break_before
|
|
387
|
+
reported_footnotes = 0
|
|
388
|
+
while report and context.current_page_footnotes:
|
|
389
|
+
context.report_footnote(context.current_page_footnotes[-1])
|
|
390
|
+
reported_footnotes += 1
|
|
391
|
+
if not context.overflows_page(bottom_space, new_position_y + offset_y):
|
|
392
|
+
new_children.append(line)
|
|
393
|
+
stop = True
|
|
394
|
+
break
|
|
395
|
+
else:
|
|
396
|
+
abort, stop, resume_at = _break_line(
|
|
397
|
+
context, box, line, new_children, next_lines,
|
|
398
|
+
page_is_empty, index, skip_stack, resume_at, absolute_boxes,
|
|
399
|
+
fixed_boxes)
|
|
400
|
+
|
|
401
|
+
# Revert reported footnotes, as they’ve been reported starting from the last
|
|
402
|
+
# one.
|
|
403
|
+
if reported_footnotes >= 2:
|
|
404
|
+
extra = context.reported_footnotes[-1:-reported_footnotes-1:-1]
|
|
405
|
+
context.reported_footnotes[-reported_footnotes:] = extra
|
|
406
|
+
|
|
380
407
|
break
|
|
381
408
|
|
|
382
409
|
# TODO: this is incomplete.
|
|
383
410
|
# See https://drafts.csswg.org/css-page-3/#allowed-pg-brk
|
|
384
411
|
# "When an unforced page break occurs here, both the adjoining
|
|
385
412
|
# ‘margin-top’ and ‘margin-bottom’ are set to zero."
|
|
386
|
-
# See
|
|
413
|
+
# See issue #115.
|
|
387
414
|
elif page_is_empty and context.overflows_page(
|
|
388
415
|
bottom_space, new_position_y):
|
|
389
416
|
# Remove the top border when a page is empty and the box is
|
|
@@ -413,9 +440,10 @@ def _linebox_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
413
440
|
# even try.
|
|
414
441
|
if new_children or not page_is_empty:
|
|
415
442
|
if footnote.style['footnote_policy'] == 'line':
|
|
443
|
+
next_lines = len(tuple(lines_iterator))
|
|
416
444
|
abort, stop, resume_at = _break_line(
|
|
417
445
|
context, box, line, new_children,
|
|
418
|
-
|
|
446
|
+
next_lines, page_is_empty, index,
|
|
419
447
|
skip_stack, resume_at, absolute_boxes,
|
|
420
448
|
fixed_boxes)
|
|
421
449
|
break_linebox = True
|
|
@@ -502,7 +530,6 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
502
530
|
if box.is_table_wrapper: # should not be a special case
|
|
503
531
|
collapsed_margin = collapse_margin(adjoining_margins)
|
|
504
532
|
child.position_y += collapsed_margin
|
|
505
|
-
position_y += collapsed_margin
|
|
506
533
|
adjoining_margins = []
|
|
507
534
|
elif not isinstance(child, boxes.BlockBox): # blocks handle that themselves
|
|
508
535
|
if child.style['margin_top'] == 'auto':
|
|
@@ -512,7 +539,6 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
512
539
|
adjoining_margins.append(margin_top)
|
|
513
540
|
offset_y = collapse_margin(adjoining_margins) - margin_top
|
|
514
541
|
child.position_y += offset_y
|
|
515
|
-
position_y += offset_y
|
|
516
542
|
adjoining_margins = []
|
|
517
543
|
|
|
518
544
|
page_is_empty_with_no_children = page_is_empty and not any(
|
|
@@ -677,8 +703,7 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
677
703
|
|
|
678
704
|
new_children = []
|
|
679
705
|
next_page = {'break': 'any', 'page': None}
|
|
680
|
-
|
|
681
|
-
broken_out_of_flow = {}
|
|
706
|
+
broken_out_of_flow = []
|
|
682
707
|
|
|
683
708
|
last_in_flow_child = None
|
|
684
709
|
|
|
@@ -704,8 +729,13 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
704
729
|
absolute_boxes, fixed_boxes, adjoining_margins,
|
|
705
730
|
bottom_space))
|
|
706
731
|
if out_of_flow_resume_at:
|
|
707
|
-
broken_out_of_flow[new_child] = (
|
|
732
|
+
context.broken_out_of_flow[new_child] = (
|
|
708
733
|
child, box, out_of_flow_resume_at)
|
|
734
|
+
broken_out_of_flow.append(new_child)
|
|
735
|
+
if child.is_outside_marker:
|
|
736
|
+
new_child.position_x = box.border_box_x()
|
|
737
|
+
if child.style['direction'] == 'rtl':
|
|
738
|
+
new_child.position_x += box.width + box.padding_right
|
|
709
739
|
|
|
710
740
|
elif isinstance(child, boxes.LineBox):
|
|
711
741
|
(abort, stop, resume_at, position_y,
|
|
@@ -716,7 +746,6 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
716
746
|
draw_bottom_decoration, max_lines)
|
|
717
747
|
draw_bottom_decoration |= resume_at is None
|
|
718
748
|
adjoining_margins = []
|
|
719
|
-
all_footnotes += new_footnotes
|
|
720
749
|
|
|
721
750
|
else:
|
|
722
751
|
(abort, stop, resume_at, position_y, adjoining_margins,
|
|
@@ -766,7 +795,7 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
766
795
|
else:
|
|
767
796
|
resume_at = None
|
|
768
797
|
|
|
769
|
-
box_is_fragmented = resume_at is not None
|
|
798
|
+
box_is_fragmented = resume_at is not None or box.force_fragmentation
|
|
770
799
|
if box.style['continue'] == 'discard':
|
|
771
800
|
resume_at = None
|
|
772
801
|
|
|
@@ -775,11 +804,10 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
775
804
|
not page_is_empty):
|
|
776
805
|
remove_placeholders(
|
|
777
806
|
context, [*new_children, *box.children[skip:]], absolute_boxes, fixed_boxes)
|
|
807
|
+
for child in broken_out_of_flow:
|
|
808
|
+
del context.broken_out_of_flow[child]
|
|
778
809
|
return None, None, {'break': 'any', 'page': None}, [], False, max_lines
|
|
779
810
|
|
|
780
|
-
for key, value in broken_out_of_flow.items():
|
|
781
|
-
context.broken_out_of_flow[key] = value
|
|
782
|
-
|
|
783
811
|
if collapsing_with_children:
|
|
784
812
|
box.position_y += (
|
|
785
813
|
collapse_margin(this_box_adjoining_margins) - box.margin_top)
|
|
@@ -831,7 +859,10 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
831
859
|
float_box.position_y + float_box.margin_height()
|
|
832
860
|
for float_box in context.excluded_shapes)
|
|
833
861
|
position_y = max(max_float_position_y, position_y)
|
|
834
|
-
|
|
862
|
+
if position_y == new_box.content_box_y() == inf:
|
|
863
|
+
new_box.height = 0
|
|
864
|
+
else:
|
|
865
|
+
new_box.height = position_y - new_box.content_box_y()
|
|
835
866
|
|
|
836
867
|
if new_box.style['position'] == 'relative':
|
|
837
868
|
# New containing block, resolve the layout of the absolute descendants
|
weasyprint/layout/column.py
CHANGED
|
@@ -190,6 +190,7 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
190
190
|
in_flow_children[-1].margin_height() +
|
|
191
191
|
in_flow_children[-1].position_y - current_position_y)
|
|
192
192
|
empty_space = height - consumed_height
|
|
193
|
+
consumed_height -= in_flow_children[-1].margin_bottom
|
|
193
194
|
|
|
194
195
|
# Get the minimum size needed to render the next box
|
|
195
196
|
if column_skip_stack:
|
weasyprint/layout/flex.py
CHANGED
|
@@ -172,6 +172,8 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
172
172
|
child.style['image_resolution'], child.style['font_size'])
|
|
173
173
|
if intrinsic_ratio and intrinsic_height:
|
|
174
174
|
transferred_size = intrinsic_height * intrinsic_ratio
|
|
175
|
+
content_size = max(
|
|
176
|
+
child.min_width, min(child.max_width, content_size))
|
|
175
177
|
if specified_size != 'auto':
|
|
176
178
|
child.min_width = min(specified_size, content_size)
|
|
177
179
|
elif transferred_size is not None:
|
|
@@ -183,12 +185,12 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
183
185
|
specified_size = child.height
|
|
184
186
|
new_child = child.copy()
|
|
185
187
|
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')
|
|
189
188
|
new_child.style['height'] = 'auto'
|
|
190
189
|
new_child.style['min_height'] = Dimension(0, 'px')
|
|
191
190
|
new_child.style['max_height'] = Dimension(inf, 'px')
|
|
191
|
+
if new_child.style['width'] == 'auto':
|
|
192
|
+
new_child_width = max_content_width(context, new_child)
|
|
193
|
+
new_child.style['width'] = Dimension(new_child_width, 'px')
|
|
192
194
|
new_child = block.block_level_layout(
|
|
193
195
|
context, new_child, bottom_space, child_skip_stack, parent_box,
|
|
194
196
|
page_is_empty)[0]
|
|
@@ -200,6 +202,12 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
200
202
|
child.style['image_resolution'], child.style['font_size'])
|
|
201
203
|
if intrinsic_ratio and intrinsic_width:
|
|
202
204
|
transferred_size = intrinsic_width / intrinsic_ratio
|
|
205
|
+
content_size = max(
|
|
206
|
+
child.min_height, min(child.max_height, content_size))
|
|
207
|
+
elif not intrinsic_width:
|
|
208
|
+
# TODO: wrongly set by block_level_layout, would be OK with
|
|
209
|
+
# min_content_height.
|
|
210
|
+
content_size = 0
|
|
203
211
|
if specified_size != 'auto':
|
|
204
212
|
child.min_height = min(specified_size, content_size)
|
|
205
213
|
elif transferred_size is not None:
|
|
@@ -242,17 +250,31 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
242
250
|
pass
|
|
243
251
|
else:
|
|
244
252
|
# 3.E Otherwise…
|
|
253
|
+
new_child = child.copy()
|
|
254
|
+
new_child.style = child.style.copy()
|
|
245
255
|
if main == 'width':
|
|
246
|
-
|
|
256
|
+
# … the item’s min and max main sizes are ignored.
|
|
257
|
+
new_child.style['min_width'] = Dimension(0, 'px')
|
|
258
|
+
new_child.style['max_width'] = Dimension(inf, 'px')
|
|
259
|
+
|
|
260
|
+
child.flex_base_size = max_content_width(
|
|
261
|
+
context, new_child, outer=False)
|
|
247
262
|
child.main_outer_extra = (
|
|
248
263
|
max_content_width(context, child) - child.flex_base_size)
|
|
249
264
|
else:
|
|
250
|
-
|
|
265
|
+
# … the item’s min and max main sizes are ignored.
|
|
266
|
+
new_child.style['min_height'] = Dimension(0, 'px')
|
|
267
|
+
new_child.style['max_height'] = Dimension(inf, 'px')
|
|
268
|
+
|
|
251
269
|
new_child.width = inf
|
|
252
|
-
new_child = block.block_level_layout(
|
|
270
|
+
new_child, _, _, adjoining_margins, _, _ = block.block_level_layout(
|
|
253
271
|
context, new_child, bottom_space, child_skip_stack, parent_box,
|
|
254
|
-
page_is_empty, absolute_boxes, fixed_boxes)
|
|
272
|
+
page_is_empty, absolute_boxes, fixed_boxes)
|
|
255
273
|
if new_child:
|
|
274
|
+
# As flex items margins never collapse (with other flex items or
|
|
275
|
+
# with the flex container), we can add the adjoining margins to the
|
|
276
|
+
# child height.
|
|
277
|
+
new_child.height += block.collapse_margin(adjoining_margins)
|
|
256
278
|
child.flex_base_size = new_child.height
|
|
257
279
|
child.main_outer_extra = (
|
|
258
280
|
new_child.margin_height() - new_child.height)
|
|
@@ -477,8 +499,8 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
477
499
|
child.height = new_child.height
|
|
478
500
|
# As flex items margins never collapse (with other flex items or
|
|
479
501
|
# with the flex container), we can add the adjoining margins to the
|
|
480
|
-
# child
|
|
481
|
-
child.
|
|
502
|
+
# child height.
|
|
503
|
+
child.height += block.collapse_margin(adjoining_margins)
|
|
482
504
|
else:
|
|
483
505
|
if child.width == 'auto':
|
|
484
506
|
min_width = min_content_width(context, child, outer=False)
|
|
@@ -591,9 +613,9 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
591
613
|
align_self = align_items
|
|
592
614
|
if 'stretch' in align_self and child.style[cross] == 'auto':
|
|
593
615
|
cross_margins = (
|
|
594
|
-
(child.margin_top, child.margin_bottom)
|
|
595
|
-
if cross == 'height'
|
|
596
|
-
|
|
616
|
+
(child.style['margin_top'], child.style['margin_bottom'])
|
|
617
|
+
if cross == 'height' else
|
|
618
|
+
(child.style['margin_left'], child.style['margin_right']))
|
|
597
619
|
if 'auto' not in cross_margins:
|
|
598
620
|
cross_size = line.cross_size
|
|
599
621
|
if cross == 'height':
|
|
@@ -733,8 +755,9 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
733
755
|
line.lower_baseline = line[0][1]._baseline if line else 0
|
|
734
756
|
for index, child in line:
|
|
735
757
|
cross_margins = (
|
|
736
|
-
(child.margin_top, child.margin_bottom)
|
|
737
|
-
|
|
758
|
+
(child.style['margin_top'], child.style['margin_bottom'])
|
|
759
|
+
if cross == 'height' else
|
|
760
|
+
(child.style['margin_left'], child.style['margin_right']))
|
|
738
761
|
auto_margins = sum([margin == 'auto' for margin in cross_margins])
|
|
739
762
|
# If a flex item has auto cross-axis margins…
|
|
740
763
|
if auto_margins:
|
|
@@ -755,14 +778,14 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
755
778
|
# If its outer cross size is less than the cross size…
|
|
756
779
|
extra_cross /= auto_margins
|
|
757
780
|
if cross == 'height':
|
|
758
|
-
if child.margin_top == 'auto':
|
|
781
|
+
if child.style['margin_top'] == 'auto':
|
|
759
782
|
child.margin_top = extra_cross
|
|
760
|
-
if child.margin_bottom == 'auto':
|
|
783
|
+
if child.style['margin_bottom'] == 'auto':
|
|
761
784
|
child.margin_bottom = extra_cross
|
|
762
785
|
else:
|
|
763
|
-
if child.margin_left == 'auto':
|
|
786
|
+
if child.style['margin_left'] == 'auto':
|
|
764
787
|
child.margin_left = extra_cross
|
|
765
|
-
if child.margin_right == 'auto':
|
|
788
|
+
if child.style['margin_right'] == 'auto':
|
|
766
789
|
child.margin_right = extra_cross
|
|
767
790
|
else:
|
|
768
791
|
# Otherwise…
|
|
@@ -815,9 +838,6 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
|
|
|
815
838
|
margins += (
|
|
816
839
|
child.border_left_width + child.border_right_width +
|
|
817
840
|
child.padding_left + child.padding_right)
|
|
818
|
-
# TODO: Don't set style width, find a way to avoid width
|
|
819
|
-
# re-calculation after 16.
|
|
820
|
-
child.style[cross] = Dimension(line.cross_size - margins, 'px')
|
|
821
841
|
position_cross += line.cross_size
|
|
822
842
|
|
|
823
843
|
# 15 Determine the flex container’s used cross size.
|
weasyprint/layout/float.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Layout for floating boxes."""
|
|
2
2
|
|
|
3
|
+
from math import inf
|
|
4
|
+
|
|
3
5
|
from ..formatting_structure import boxes
|
|
4
6
|
from .min_max import handle_min_max_width
|
|
5
7
|
from .percent import resolve_percentages, resolve_position_percentages
|
|
@@ -115,9 +117,14 @@ def find_float_position(context, box, containing_block):
|
|
|
115
117
|
|
|
116
118
|
def get_clearance(context, box, collapsed_margin=0):
|
|
117
119
|
"""Return None if there is no clearance, otherwise the clearance value."""
|
|
120
|
+
# Box should be after shape that’s broken on this page.
|
|
121
|
+
for broken_shape in context.broken_out_of_flow:
|
|
122
|
+
if broken_shape.is_floated():
|
|
123
|
+
if box.style['clear'] in (broken_shape.style['float'], 'both'):
|
|
124
|
+
return inf
|
|
125
|
+
# Hypothetical position is the position of the top border edge
|
|
118
126
|
clearance = None
|
|
119
127
|
hypothetical_position = box.position_y + collapsed_margin
|
|
120
|
-
# Hypothetical position is the position of the top border edge
|
|
121
128
|
for excluded_shape in context.excluded_shapes:
|
|
122
129
|
if box.style['clear'] in (excluded_shape.style['float'], 'both'):
|
|
123
130
|
y, h = excluded_shape.position_y, excluded_shape.margin_height()
|
weasyprint/layout/grid.py
CHANGED
|
@@ -1115,7 +1115,7 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
1115
1115
|
sum(size for size, _ in rows_sizes[skip_row:]) +
|
|
1116
1116
|
(len(rows_sizes[skip_row:]) - 1) * row_gap)
|
|
1117
1117
|
row_lines_positions = (
|
|
1118
|
-
rows_positions[skip_row + 1:]
|
|
1118
|
+
[*rows_positions[skip_row + 1:], box.content_box_y() + total_height])
|
|
1119
1119
|
for i, row_y in enumerate(row_lines_positions, start=skip_row + 1):
|
|
1120
1120
|
if context.overflows_page(bottom_space, row_y - skip_height):
|
|
1121
1121
|
if not page_is_empty:
|
weasyprint/layout/inline.py
CHANGED
|
@@ -30,7 +30,7 @@ def iter_line_boxes(context, box, position_y, bottom_space, skip_stack,
|
|
|
30
30
|
"""
|
|
31
31
|
resolve_percentages(box, containing_block)
|
|
32
32
|
if skip_stack is None:
|
|
33
|
-
# TODO: wrong, see
|
|
33
|
+
# TODO: wrong, see issue #679.
|
|
34
34
|
resolve_one_percentage(box, 'text_indent', containing_block.width)
|
|
35
35
|
else:
|
|
36
36
|
box.text_indent = 0
|
|
@@ -126,7 +126,7 @@ def get_next_linebox(context, linebox, position_y, bottom_space, skip_stack,
|
|
|
126
126
|
line.translate(offset_x, offset_y)
|
|
127
127
|
# Avoid floating point errors, as position_y - top + top != position_y
|
|
128
128
|
# Removing this line breaks the position == linebox.position test below
|
|
129
|
-
# See
|
|
129
|
+
# See issue #583.
|
|
130
130
|
line.position_y = position_y
|
|
131
131
|
|
|
132
132
|
if line.height <= candidate_height:
|
|
@@ -767,15 +767,14 @@ def split_inline_box(context, box, position_x, max_x, bottom_space, skip_stack,
|
|
|
767
767
|
# May be None where we have an empty TextBox.
|
|
768
768
|
assert isinstance(child, boxes.TextBox)
|
|
769
769
|
else:
|
|
770
|
+
# Store lines to get previous break points.
|
|
770
771
|
if isinstance(box, boxes.LineBox):
|
|
771
772
|
line_children.append((index, new_child))
|
|
772
|
-
trailing_whitespace = (
|
|
773
|
-
isinstance(new_child, boxes.TextBox) and
|
|
774
|
-
new_child.text and
|
|
775
|
-
unicodedata.category(new_child.text[-1]) == 'Zs')
|
|
776
|
-
new_position_x = new_child.position_x + new_child.margin_width()
|
|
777
773
|
|
|
778
|
-
|
|
774
|
+
# Check that text doesn’t overflow.
|
|
775
|
+
new_position_x = new_child.position_x + new_child.margin_width()
|
|
776
|
+
if new_position_x - trailing_whitespace_size(context, new_child) > max_x:
|
|
777
|
+
# Text overflows, find previous break point.
|
|
779
778
|
previous_resume_at = _break_waiting_children(
|
|
780
779
|
context, containing_block, max_x, bottom_space, initial_skip_stack,
|
|
781
780
|
absolute_boxes, fixed_boxes, line_placeholders,
|
weasyprint/layout/page.py
CHANGED
|
@@ -622,6 +622,8 @@ def make_page(context, root_box, page_type, resume_at, page_number,
|
|
|
622
622
|
context.reported_footnotes = reported_footnotes[i:]
|
|
623
623
|
break
|
|
624
624
|
|
|
625
|
+
# Display out-of-flow boxes broken on the previous page.
|
|
626
|
+
# TODO: we shouldn’t separate broken in-flow and out-of-flow layout.
|
|
625
627
|
page_is_empty = True
|
|
626
628
|
adjoining_margins = []
|
|
627
629
|
positioned_boxes = [] # Mixed absolute and fixed
|
|
@@ -641,13 +643,33 @@ def make_page(context, root_box, page_type, resume_at, page_number,
|
|
|
641
643
|
context, box, containing_block, positioned_boxes, 0,
|
|
642
644
|
skip_stack)
|
|
643
645
|
out_of_flow_boxes.append(out_of_flow_box)
|
|
646
|
+
page_is_empty = False
|
|
644
647
|
if out_of_flow_resume_at:
|
|
645
648
|
broken_out_of_flow[out_of_flow_box] = (
|
|
646
649
|
box, containing_block, out_of_flow_resume_at)
|
|
650
|
+
|
|
651
|
+
# Display in-flow content.
|
|
652
|
+
initial_root_box = root_box
|
|
653
|
+
initial_resume_at = resume_at
|
|
647
654
|
root_box, resume_at, next_page, _, _, _ = block_level_layout(
|
|
648
655
|
context, root_box, 0, resume_at, initial_containing_block,
|
|
649
656
|
page_is_empty, positioned_boxes, positioned_boxes, adjoining_margins)
|
|
650
|
-
|
|
657
|
+
if not root_box:
|
|
658
|
+
# In-flow page rendering didn’t progress, only out-of-flow did. Render empty box
|
|
659
|
+
# at skip_stack and force fragmentation to make the root box and its descendants
|
|
660
|
+
# cover the whole page height.
|
|
661
|
+
assert not page_is_empty
|
|
662
|
+
box = parent = initial_root_box = initial_root_box.deepcopy()
|
|
663
|
+
skip_stack = initial_resume_at
|
|
664
|
+
while skip_stack and len(skip_stack) == 1:
|
|
665
|
+
(skip, skip_stack), = skip_stack.items()
|
|
666
|
+
box, parent = box.children[skip], box
|
|
667
|
+
parent.children = []
|
|
668
|
+
parent.force_fragmentation = True
|
|
669
|
+
root_box, _, _, _, _, _ = block_level_layout(
|
|
670
|
+
context, initial_root_box, 0, initial_resume_at, initial_containing_block,
|
|
671
|
+
page_is_empty, positioned_boxes, positioned_boxes, adjoining_margins)
|
|
672
|
+
resume_at = initial_resume_at
|
|
651
673
|
root_box.children = out_of_flow_boxes + root_box.children
|
|
652
674
|
|
|
653
675
|
footnote_area = build.create_anonymous_boxes(footnote_area.deepcopy())
|
weasyprint/layout/preferred.py
CHANGED
|
@@ -270,37 +270,43 @@ def table_cell_min_max_content_width(context, box, outer=True):
|
|
|
270
270
|
|
|
271
271
|
def inline_line_widths(context, box, outer, is_line_start, minimum, skip_stack=None,
|
|
272
272
|
first_line=False):
|
|
273
|
+
"""Yield line width for each line."""
|
|
274
|
+
|
|
275
|
+
# Set text indent.
|
|
276
|
+
text_indent = 0
|
|
273
277
|
if isinstance(box, boxes.LineBox) and box.style['text_indent'].unit != '%':
|
|
274
278
|
text_indent = box.style['text_indent'].value
|
|
275
|
-
else:
|
|
276
|
-
text_indent = 0
|
|
277
279
|
|
|
280
|
+
# Yield widths for each line.
|
|
278
281
|
current_line = 0
|
|
279
282
|
if skip_stack is None:
|
|
280
283
|
skip = 0
|
|
281
284
|
else:
|
|
282
285
|
(skip, skip_stack), = skip_stack.items()
|
|
283
286
|
for child in box.children[skip:]:
|
|
287
|
+
# Skip absolutely positioned elements.
|
|
284
288
|
if child.is_absolutely_positioned():
|
|
285
|
-
continue
|
|
289
|
+
continue
|
|
286
290
|
|
|
291
|
+
# None is used in "lines" to track line breaks, transformed to 0 when yielded.
|
|
287
292
|
if isinstance(child, boxes.InlineBox):
|
|
293
|
+
# Inline box, call function recursively.
|
|
288
294
|
lines = inline_line_widths(
|
|
289
|
-
context, child, outer, is_line_start, minimum, skip_stack,
|
|
290
|
-
first_line)
|
|
295
|
+
context, child, outer, is_line_start, minimum, skip_stack, first_line)
|
|
291
296
|
if first_line:
|
|
292
|
-
lines = [next(lines)]
|
|
297
|
+
lines = [next(lines) or None]
|
|
293
298
|
else:
|
|
294
|
-
lines =
|
|
299
|
+
lines = [line or None for line in lines]
|
|
295
300
|
if len(lines) == 1:
|
|
296
|
-
lines[0] = adjust(child, outer, lines[0])
|
|
301
|
+
lines[0] = adjust(child, outer, lines[0] or 0)
|
|
297
302
|
else:
|
|
298
|
-
lines[0] = adjust(child, outer, lines[0], right=False)
|
|
299
|
-
lines[-1] = adjust(child, outer, lines[-1], left=False)
|
|
303
|
+
lines[0] = adjust(child, outer, lines[0] or 0, right=False) or None
|
|
304
|
+
lines[-1] = adjust(child, outer, lines[-1] or 0, left=False) or None
|
|
300
305
|
elif isinstance(child, boxes.TextBox):
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
# Text box, split into lines.
|
|
307
|
+
white_space = child.style['white_space']
|
|
308
|
+
space_collapse = white_space in ('normal', 'nowrap', 'pre-line')
|
|
309
|
+
text_wrap = white_space in ('normal', 'pre-wrap', 'pre-line')
|
|
304
310
|
if skip_stack is None:
|
|
305
311
|
skip = 0
|
|
306
312
|
else:
|
|
@@ -318,18 +324,21 @@ def inline_line_widths(context, box, outer, is_line_start, minimum, skip_stack=N
|
|
|
318
324
|
child_text[resume_index:].decode(), child.style, context, max_width,
|
|
319
325
|
child.justification_spacing, is_line_start=is_line_start,
|
|
320
326
|
minimum=True)
|
|
321
|
-
lines.append(width)
|
|
327
|
+
lines.append(width or None)
|
|
322
328
|
if first_line:
|
|
323
329
|
break
|
|
324
330
|
if first_line and new_resume_index:
|
|
325
|
-
|
|
331
|
+
# We only need the first line, break early.
|
|
332
|
+
current_line += lines[0] or 0
|
|
326
333
|
break
|
|
327
334
|
# TODO: use the real next character instead of 'a' to detect line breaks.
|
|
328
|
-
|
|
329
|
-
|
|
335
|
+
last_letter = child_text.decode()[-1:]
|
|
336
|
+
can_break = can_break_text(last_letter + 'a', child.style['lang'])
|
|
330
337
|
if minimum and text_wrap and can_break:
|
|
331
|
-
|
|
338
|
+
# Add all possible line breaks for minimal width.
|
|
339
|
+
lines.append(None)
|
|
332
340
|
else:
|
|
341
|
+
# Replaced elements, inline blocks…
|
|
333
342
|
# https://www.w3.org/TR/css-text-3/#overflow-wrap
|
|
334
343
|
# "The line breaking behavior of a replaced element
|
|
335
344
|
# or other atomic inline is equivalent to that
|
|
@@ -338,20 +347,20 @@ def inline_line_widths(context, box, outer, is_line_start, minimum, skip_stack=N
|
|
|
338
347
|
# "By default, there is a break opportunity
|
|
339
348
|
# both before and after any inline object."
|
|
340
349
|
if minimum:
|
|
341
|
-
lines = [
|
|
350
|
+
lines = [None, min_content_width(context, child), None]
|
|
342
351
|
else:
|
|
343
352
|
lines = [max_content_width(context, child)]
|
|
344
|
-
# The first text line goes on the current line
|
|
345
|
-
current_line += lines[0]
|
|
353
|
+
# The first text line goes on the current line.
|
|
354
|
+
current_line += lines[0] or 0
|
|
346
355
|
if len(lines) > 1:
|
|
347
|
-
# Forced line break
|
|
356
|
+
# Forced line break(s).
|
|
348
357
|
yield current_line + text_indent
|
|
349
358
|
text_indent = 0
|
|
350
359
|
if len(lines) > 2:
|
|
351
360
|
for line in lines[1:-1]:
|
|
352
|
-
yield line
|
|
353
|
-
current_line = lines[-1]
|
|
354
|
-
is_line_start = lines[-1]
|
|
361
|
+
yield line or 0
|
|
362
|
+
current_line = lines[-1] or 0
|
|
363
|
+
is_line_start = lines[-1] is None
|
|
355
364
|
skip_stack = None
|
|
356
365
|
yield current_line + text_indent
|
|
357
366
|
|
|
@@ -743,17 +752,30 @@ def trailing_whitespace_size(context, box):
|
|
|
743
752
|
"""Return the size of the trailing whitespace of ``box``."""
|
|
744
753
|
from .inline import split_first_line, split_text_box
|
|
745
754
|
|
|
755
|
+
# Find last box child, keep last parent to remove nested trailing spaces.
|
|
756
|
+
last_parent = None
|
|
746
757
|
while isinstance(box, (boxes.InlineBox, boxes.LineBox)):
|
|
747
758
|
if not box.children:
|
|
748
759
|
return 0
|
|
749
|
-
box = box.children[-1]
|
|
750
|
-
|
|
751
|
-
|
|
760
|
+
last_parent, box = box, box.children[-1]
|
|
761
|
+
|
|
762
|
+
# Return early if possible.
|
|
763
|
+
if not isinstance(box, boxes.TextBox) or not box.text:
|
|
764
|
+
# There’s no text in last child.
|
|
752
765
|
return 0
|
|
753
|
-
|
|
754
|
-
|
|
766
|
+
elif box.style['white_space'] not in ('normal', 'nowrap', 'pre-line'):
|
|
767
|
+
# Spaces don’t collapse.
|
|
755
768
|
return 0
|
|
756
|
-
|
|
769
|
+
elif box.style['font_size'] == 0:
|
|
770
|
+
# Trailing spaces take no space.
|
|
771
|
+
return 0
|
|
772
|
+
elif not box.text.endswith(' '):
|
|
773
|
+
# No trailing space.
|
|
774
|
+
return 0
|
|
775
|
+
|
|
776
|
+
# Strip text.
|
|
777
|
+
if stripped_text := box.text.rstrip(' '):
|
|
778
|
+
# Stripped text is not empty, calculate width difference.
|
|
757
779
|
resume = 0
|
|
758
780
|
while resume is not None:
|
|
759
781
|
old_resume = resume
|
|
@@ -763,12 +785,17 @@ def trailing_whitespace_size(context, box):
|
|
|
763
785
|
stripped_box, resume, _ = split_text_box(
|
|
764
786
|
context, stripped_box, None, old_resume)
|
|
765
787
|
if stripped_box is None:
|
|
766
|
-
#
|
|
788
|
+
# Old box is split just before the trailing spaces.
|
|
767
789
|
return old_box.width
|
|
768
790
|
else:
|
|
791
|
+
# Return difference between old width and stripped width.
|
|
769
792
|
assert resume is None
|
|
770
793
|
return old_box.width - stripped_box.width
|
|
771
794
|
else:
|
|
795
|
+
# Stripped text is empty, render spaces to get width.
|
|
772
796
|
_, _, _, width, _, _ = split_first_line(
|
|
773
797
|
box.text, box.style, context, None, box.justification_spacing)
|
|
798
|
+
# Remove possible trailing spaces from previous child.
|
|
799
|
+
if last_parent and len(last_parent.children) >= 2:
|
|
800
|
+
width += trailing_whitespace_size(context, last_parent.children[-2])
|
|
774
801
|
return width
|
weasyprint/layout/table.py
CHANGED
|
@@ -414,9 +414,13 @@ def table_layout(context, table, bottom_space, skip_stack, containing_block,
|
|
|
414
414
|
if avoid_page_break(page_break, context):
|
|
415
415
|
earlier_page_break = find_earlier_page_break(
|
|
416
416
|
context, new_table_children, absolute_boxes, fixed_boxes)
|
|
417
|
-
if earlier_page_break is
|
|
418
|
-
|
|
419
|
-
|
|
417
|
+
if earlier_page_break is None:
|
|
418
|
+
remove_placeholders(
|
|
419
|
+
context, new_table_children, absolute_boxes,
|
|
420
|
+
fixed_boxes)
|
|
421
|
+
return None, None, next_page, position_y
|
|
422
|
+
new_table_children, resume_at = earlier_page_break
|
|
423
|
+
break
|
|
420
424
|
resume_at = {index_group: None}
|
|
421
425
|
else:
|
|
422
426
|
return None, None, next_page, position_y
|
|
@@ -782,7 +786,7 @@ def auto_table_layout(context, box, containing_block):
|
|
|
782
786
|
|
|
783
787
|
if assignable_width < sum(max_content_guess):
|
|
784
788
|
# Default values shouldn't be used, but we never know.
|
|
785
|
-
# See
|
|
789
|
+
# See issue #770.
|
|
786
790
|
lower_guess = guesses[0]
|
|
787
791
|
upper_guess = guesses[-1]
|
|
788
792
|
|