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/inline.py
CHANGED
|
@@ -3,25 +3,27 @@
|
|
|
3
3
|
import unicodedata
|
|
4
4
|
from math import inf
|
|
5
5
|
|
|
6
|
-
from ..css import
|
|
7
|
-
from ..css.
|
|
6
|
+
from ..css import Pending
|
|
7
|
+
from ..css.properties import INHERITED
|
|
8
8
|
from ..formatting_structure import boxes, build
|
|
9
|
-
from ..text.line_break import can_break_text, create_layout, split_first_line
|
|
10
9
|
from .absolute import AbsolutePlaceholder, absolute_layout
|
|
11
10
|
from .flex import flex_layout
|
|
12
11
|
from .float import avoid_collisions, float_layout
|
|
13
12
|
from .grid import grid_layout
|
|
14
13
|
from .leader import handle_leader
|
|
15
14
|
from .min_max import handle_min_max_width
|
|
16
|
-
from .percent import resolve_one_percentage, resolve_percentages
|
|
15
|
+
from .percent import percentage, resolve_one_percentage, resolve_percentages
|
|
17
16
|
from .preferred import inline_min_content_width, shrink_to_fit, trailing_whitespace_size
|
|
18
17
|
from .replaced import inline_replaced_box_layout
|
|
19
18
|
from .table import find_in_flow_baseline, table_wrapper_width
|
|
20
19
|
|
|
20
|
+
from ..text.line_break import ( # isort:skip
|
|
21
|
+
can_break_text, character_ratio, create_layout, split_first_line, strut)
|
|
22
|
+
|
|
21
23
|
|
|
22
24
|
def iter_line_boxes(context, box, position_y, bottom_space, skip_stack,
|
|
23
25
|
containing_block, absolute_boxes, fixed_boxes,
|
|
24
|
-
first_letter_style):
|
|
26
|
+
first_letter_style, first_line_style):
|
|
25
27
|
"""Return an iterator of ``(line, resume_at)``.
|
|
26
28
|
|
|
27
29
|
``line`` is a laid-out LineBox with as much content as possible that
|
|
@@ -30,14 +32,15 @@ def iter_line_boxes(context, box, position_y, bottom_space, skip_stack,
|
|
|
30
32
|
"""
|
|
31
33
|
resolve_percentages(box, containing_block)
|
|
32
34
|
if skip_stack is None:
|
|
33
|
-
# TODO: wrong, see
|
|
35
|
+
# TODO: wrong, see issue #679.
|
|
34
36
|
resolve_one_percentage(box, 'text_indent', containing_block.width)
|
|
35
37
|
else:
|
|
36
38
|
box.text_indent = 0
|
|
37
39
|
while True:
|
|
38
40
|
line, resume_at = get_next_linebox(
|
|
39
|
-
context, box, position_y, bottom_space, skip_stack,
|
|
40
|
-
|
|
41
|
+
context, box, position_y, bottom_space, skip_stack, containing_block,
|
|
42
|
+
absolute_boxes, fixed_boxes, first_letter_style, first_line_style)
|
|
43
|
+
first_line_style = None
|
|
41
44
|
if line:
|
|
42
45
|
handle_leader(context, line, containing_block)
|
|
43
46
|
position_y = line.position_y + line.height
|
|
@@ -53,8 +56,16 @@ def iter_line_boxes(context, box, position_y, bottom_space, skip_stack,
|
|
|
53
56
|
|
|
54
57
|
def get_next_linebox(context, linebox, position_y, bottom_space, skip_stack,
|
|
55
58
|
containing_block, absolute_boxes, fixed_boxes,
|
|
56
|
-
first_letter_style):
|
|
57
|
-
"""Return
|
|
59
|
+
first_letter_style, first_line_style):
|
|
60
|
+
"""Return next line from given linebox.
|
|
61
|
+
|
|
62
|
+
Return ``(line, resume_at)``, where ``line`` is a new linebox copied from the
|
|
63
|
+
original one, with replaced children.
|
|
64
|
+
|
|
65
|
+
This function takes care of excluded floating shapes to avoid collisions.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
|
|
58
69
|
skip_stack = skip_first_whitespace(linebox, skip_stack)
|
|
59
70
|
if skip_stack == 'continue':
|
|
60
71
|
return None, None
|
|
@@ -67,7 +78,7 @@ def get_next_linebox(context, linebox, position_y, bottom_space, skip_stack,
|
|
|
67
78
|
# Width and height must be calculated to avoid floats
|
|
68
79
|
linebox.width = inline_min_content_width(
|
|
69
80
|
context, linebox, skip_stack=skip_stack, first_line=True)
|
|
70
|
-
linebox.height, _ =
|
|
81
|
+
linebox.height, _ = strut(linebox.style)
|
|
71
82
|
else:
|
|
72
83
|
# No float, width and height will be set by the lines
|
|
73
84
|
linebox.width = linebox.height = 0
|
|
@@ -95,7 +106,7 @@ def get_next_linebox(context, linebox, position_y, bottom_space, skip_stack,
|
|
|
95
106
|
last_letter, float_width) = split_inline_box(
|
|
96
107
|
context, linebox, position_x, max_x, bottom_space, skip_stack,
|
|
97
108
|
containing_block, line_absolutes, line_fixed, line_placeholders,
|
|
98
|
-
waiting_floats, line_children)
|
|
109
|
+
waiting_floats, line_children, first_letter_style, first_line_style)
|
|
99
110
|
linebox.width, linebox.height = line.width, line.height
|
|
100
111
|
|
|
101
112
|
if is_phantom_linebox(line) and not preserved_line_break:
|
|
@@ -126,7 +137,7 @@ def get_next_linebox(context, linebox, position_y, bottom_space, skip_stack,
|
|
|
126
137
|
line.translate(offset_x, offset_y)
|
|
127
138
|
# Avoid floating point errors, as position_y - top + top != position_y
|
|
128
139
|
# Removing this line breaks the position == linebox.position test below
|
|
129
|
-
# See
|
|
140
|
+
# See issue #583.
|
|
130
141
|
line.position_y = position_y
|
|
131
142
|
|
|
132
143
|
if line.height <= candidate_height:
|
|
@@ -137,6 +148,16 @@ def get_next_linebox(context, linebox, position_y, bottom_space, skip_stack,
|
|
|
137
148
|
context.excluded_shapes = excluded_shapes
|
|
138
149
|
position_x, position_y, available_width = avoid_collisions(
|
|
139
150
|
context, line, containing_block, outer=False)
|
|
151
|
+
|
|
152
|
+
if first_line_style:
|
|
153
|
+
first_line_box = line.copy_with_children(line.children)
|
|
154
|
+
first_line_box.element_tag += '::first-line'
|
|
155
|
+
first_line_box.style = first_line_box.style.copy()
|
|
156
|
+
for key, value in first_line_style.items():
|
|
157
|
+
first_line_box.style[key] = value
|
|
158
|
+
line.children = [first_line_box]
|
|
159
|
+
_adjust_line_height(first_line_box)
|
|
160
|
+
|
|
140
161
|
if containing_block.style['direction'] == 'ltr':
|
|
141
162
|
condition = (position_x, position_y) == (
|
|
142
163
|
original_position_x, original_position_y)
|
|
@@ -169,8 +190,9 @@ def get_next_linebox(context, linebox, position_y, bottom_space, skip_stack,
|
|
|
169
190
|
fixed_boxes, bottom_space, skip_stack=None)
|
|
170
191
|
float_children.append(new_waiting_float)
|
|
171
192
|
if waiting_float_resume_at:
|
|
172
|
-
context.
|
|
173
|
-
waiting_float, containing_block,
|
|
193
|
+
context.add_broken_out_of_flow(
|
|
194
|
+
new_waiting_float, waiting_float, containing_block,
|
|
195
|
+
waiting_float_resume_at)
|
|
174
196
|
if float_children:
|
|
175
197
|
line.children += tuple(float_children)
|
|
176
198
|
|
|
@@ -280,8 +302,9 @@ def first_letter_to_box(box, skip_stack, first_letter_style):
|
|
|
280
302
|
first_letter = ''
|
|
281
303
|
child = box.children[0]
|
|
282
304
|
if isinstance(child, boxes.TextBox):
|
|
283
|
-
letter_style =
|
|
284
|
-
|
|
305
|
+
letter_style = box.style.copy()
|
|
306
|
+
for key, value in first_letter_style.items():
|
|
307
|
+
letter_style[key] = value
|
|
285
308
|
if child.element_tag.endswith('::first-letter'):
|
|
286
309
|
letter_box = boxes.InlineBox(
|
|
287
310
|
f'{box.element_tag}::first-letter', letter_style,
|
|
@@ -308,10 +331,10 @@ def first_letter_to_box(box, skip_stack, first_letter_style):
|
|
|
308
331
|
# "This type of initial letter is similar to an
|
|
309
332
|
# inline-level element if its 'float' property is 'none',
|
|
310
333
|
# otherwise it is similar to a floated element."
|
|
311
|
-
if
|
|
334
|
+
if letter_style['float'] == 'none':
|
|
312
335
|
letter_box = boxes.InlineBox(
|
|
313
336
|
f'{box.element_tag}::first-letter',
|
|
314
|
-
|
|
337
|
+
letter_style, box.element, [])
|
|
315
338
|
text_box = boxes.TextBox(
|
|
316
339
|
f'{box.element_tag}::first-letter', letter_style,
|
|
317
340
|
box.element, first_letter)
|
|
@@ -320,8 +343,7 @@ def first_letter_to_box(box, skip_stack, first_letter_style):
|
|
|
320
343
|
else:
|
|
321
344
|
letter_box = boxes.BlockBox(
|
|
322
345
|
f'{box.element_tag}::first-letter',
|
|
323
|
-
|
|
324
|
-
letter_box.first_letter_style = None
|
|
346
|
+
letter_style, box.element, [])
|
|
325
347
|
line_box = boxes.LineBox(
|
|
326
348
|
f'{box.element_tag}::first-letter', letter_style,
|
|
327
349
|
box.element, [])
|
|
@@ -334,10 +356,8 @@ def first_letter_to_box(box, skip_stack, first_letter_style):
|
|
|
334
356
|
build.process_text_transform(text_box)
|
|
335
357
|
if skip_stack and child_skip_stack:
|
|
336
358
|
index, = skip_stack
|
|
337
|
-
(child_index, grandchild_skip_stack), = (
|
|
338
|
-
|
|
339
|
-
skip_stack = {
|
|
340
|
-
index: {child_index + 1: grandchild_skip_stack}}
|
|
359
|
+
(child_index, grandchild_skip_stack), = child_skip_stack.items()
|
|
360
|
+
skip_stack = {index: {child_index + 1: grandchild_skip_stack}}
|
|
341
361
|
elif isinstance(child, boxes.ParentBox):
|
|
342
362
|
if skip_stack:
|
|
343
363
|
child_skip_stack, = skip_stack.values()
|
|
@@ -395,10 +415,9 @@ def inline_block_box_layout(context, box, position_x, skip_stack,
|
|
|
395
415
|
box.position_x = position_x
|
|
396
416
|
box.position_y = 0
|
|
397
417
|
box, _, _, _, _, _ = block_container_layout(
|
|
398
|
-
context, box, bottom_space=-inf, skip_stack=skip_stack,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
max_lines=None)
|
|
418
|
+
context, box, bottom_space=-inf, skip_stack=skip_stack, page_is_empty=True,
|
|
419
|
+
absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes, adjoining_margins=None,
|
|
420
|
+
first_letter_style=None, first_line_style=None, discard=False, max_lines=None)
|
|
402
421
|
box.baseline = inline_block_baseline(box)
|
|
403
422
|
return box
|
|
404
423
|
|
|
@@ -438,19 +457,24 @@ def inline_block_width(box, context, containing_block):
|
|
|
438
457
|
def split_inline_level(context, box, position_x, max_x, bottom_space,
|
|
439
458
|
skip_stack, containing_block, absolute_boxes,
|
|
440
459
|
fixed_boxes, line_placeholders, waiting_floats,
|
|
441
|
-
line_children):
|
|
460
|
+
line_children, first_letter_style, first_line_style):
|
|
442
461
|
"""Fit as much content as possible from an inline-level box in a width.
|
|
443
462
|
|
|
444
|
-
Return ``(new_box, resume_at, preserved_line_break, first_letter,
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
where we left off.
|
|
463
|
+
Return ``(new_box, resume_at, preserved_line_break, first_letter, last_letter)``.
|
|
464
|
+
``resume_at`` is ``None`` if all of the content fits. Otherwise it can be passed as
|
|
465
|
+
a ``skip_stack`` parameter to resume where we left off.
|
|
448
466
|
|
|
449
467
|
``new_box`` is non-empty (unless the box is empty) and as big as possible
|
|
450
|
-
while
|
|
451
|
-
is no split is possible.)
|
|
468
|
+
while respecting ``max_x``, if possible (may overflow is no split is possible.)
|
|
452
469
|
|
|
453
470
|
"""
|
|
471
|
+
if first_line_style:
|
|
472
|
+
box = box.copy()
|
|
473
|
+
box.style = box.style.copy()
|
|
474
|
+
for key, value in first_line_style.items():
|
|
475
|
+
if key in INHERITED:
|
|
476
|
+
box.style[key] = value
|
|
477
|
+
build.process_text_transform(box)
|
|
454
478
|
resolve_percentages(box, containing_block)
|
|
455
479
|
float_widths = {'left': 0, 'right': 0}
|
|
456
480
|
if isinstance(box, boxes.TextBox):
|
|
@@ -488,7 +512,7 @@ def split_inline_level(context, box, position_x, max_x, bottom_space,
|
|
|
488
512
|
last_letter, float_widths) = split_inline_box(
|
|
489
513
|
context, box, position_x, max_x, bottom_space, skip_stack,
|
|
490
514
|
containing_block, absolute_boxes, fixed_boxes, line_placeholders,
|
|
491
|
-
waiting_floats, line_children)
|
|
515
|
+
waiting_floats, line_children, first_letter_style, first_line_style)
|
|
492
516
|
elif isinstance(box, boxes.AtomicInlineLevelBox):
|
|
493
517
|
new_box = atomic_box(
|
|
494
518
|
context, box, position_x, skip_stack, containing_block,
|
|
@@ -568,8 +592,8 @@ def _out_of_flow_layout(context, box, containing_block, index, child,
|
|
|
568
592
|
context, child, containing_block, absolute_boxes, fixed_boxes,
|
|
569
593
|
bottom_space, skip_stack=None)
|
|
570
594
|
if float_resume_at:
|
|
571
|
-
context.
|
|
572
|
-
child, containing_block, float_resume_at)
|
|
595
|
+
context.add_broken_out_of_flow(
|
|
596
|
+
child, child, containing_block, float_resume_at)
|
|
573
597
|
waiting_children.append((index, new_child, child))
|
|
574
598
|
child = new_child
|
|
575
599
|
|
|
@@ -602,10 +626,10 @@ def _out_of_flow_layout(context, box, containing_block, index, child,
|
|
|
602
626
|
context.running_elements[running_name][page].append(child)
|
|
603
627
|
|
|
604
628
|
|
|
605
|
-
def _break_waiting_children(context, box, max_x, bottom_space,
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
629
|
+
def _break_waiting_children(context, box, max_x, bottom_space, initial_skip_stack,
|
|
630
|
+
absolute_boxes, fixed_boxes, line_placeholders,
|
|
631
|
+
waiting_floats, line_children, children, waiting_children,
|
|
632
|
+
first_letter_style, first_line_style):
|
|
609
633
|
if waiting_children:
|
|
610
634
|
# Too wide, try to cut inside waiting children, starting from the end.
|
|
611
635
|
# TODO: we should take care of children added into absolute_boxes,
|
|
@@ -631,7 +655,7 @@ def _break_waiting_children(context, box, max_x, bottom_space,
|
|
|
631
655
|
context, original_child, child.position_x, max_x,
|
|
632
656
|
bottom_space, child_skip_stack, box, absolute_boxes,
|
|
633
657
|
fixed_boxes, line_placeholders, waiting_floats,
|
|
634
|
-
line_children)
|
|
658
|
+
line_children, first_letter_style, first_line_style)
|
|
635
659
|
if child_resume_at:
|
|
636
660
|
break
|
|
637
661
|
max_x -= 1
|
|
@@ -654,10 +678,38 @@ def _break_waiting_children(context, box, max_x, bottom_space,
|
|
|
654
678
|
return {children[-1][0] + 1: None}
|
|
655
679
|
|
|
656
680
|
|
|
681
|
+
def _adjust_line_height(box):
|
|
682
|
+
"""Set margins to the half leading to respect line height.
|
|
683
|
+
|
|
684
|
+
Also compensate for borders and padding, we want margin_height() == line_height.
|
|
685
|
+
|
|
686
|
+
"""
|
|
687
|
+
line_height, box.baseline = strut(box.style)
|
|
688
|
+
box.height = box.style['font_size']
|
|
689
|
+
half_leading = (line_height - box.height) / 2
|
|
690
|
+
box.margin_top = half_leading - box.border_top_width - box.padding_top
|
|
691
|
+
box.margin_bottom = half_leading - box.border_bottom_width - box.padding_bottom
|
|
692
|
+
|
|
693
|
+
|
|
657
694
|
def split_inline_box(context, box, position_x, max_x, bottom_space, skip_stack,
|
|
658
|
-
containing_block, absolute_boxes, fixed_boxes,
|
|
659
|
-
|
|
660
|
-
|
|
695
|
+
containing_block, absolute_boxes, fixed_boxes, line_placeholders,
|
|
696
|
+
waiting_floats, line_children, first_letter_style,
|
|
697
|
+
first_line_style):
|
|
698
|
+
"""Fit as much content as possible from an inline box in a width.
|
|
699
|
+
|
|
700
|
+
Return ``(new_box, resume_at, preserved_line_break, first_letter, last_letter)``.
|
|
701
|
+
``resume_at`` is ``None`` if all of the content fits. Otherwise it can be passed as
|
|
702
|
+
a ``skip_stack`` parameter to resume where we left off.
|
|
703
|
+
|
|
704
|
+
``new_box`` is non-empty (unless the box is empty) and as big as possible while
|
|
705
|
+
respecting ``max_x``, if possible (may overflow is no split is possible.)
|
|
706
|
+
|
|
707
|
+
This is the recursive step that loops over the children of the box; base steps of
|
|
708
|
+
recursion are handled by ``split_inline_level()``.
|
|
709
|
+
|
|
710
|
+
In this phase, excluded floating shapes are ignored.
|
|
711
|
+
|
|
712
|
+
"""
|
|
661
713
|
|
|
662
714
|
# In some cases (shrink-to-fit result being the preferred width)
|
|
663
715
|
# max_x is coming from Pango itself,
|
|
@@ -715,7 +767,8 @@ def split_inline_box(context, box, position_x, max_x, bottom_space, skip_stack,
|
|
|
715
767
|
split_inline_level(
|
|
716
768
|
context, child, position_x, available_width, bottom_space,
|
|
717
769
|
skip_stack, containing_block, absolute_boxes, fixed_boxes,
|
|
718
|
-
line_placeholders, child_waiting_floats, line_children
|
|
770
|
+
line_placeholders, child_waiting_floats, line_children,
|
|
771
|
+
first_letter_style, first_line_style))
|
|
719
772
|
if box.style['direction'] == 'rtl':
|
|
720
773
|
end_spacing = left_spacing
|
|
721
774
|
max_x -= new_float_widths['left']
|
|
@@ -730,7 +783,11 @@ def split_inline_box(context, box, position_x, max_x, bottom_space, skip_stack,
|
|
|
730
783
|
split_inline_level(
|
|
731
784
|
context, child, position_x, available_width, bottom_space,
|
|
732
785
|
skip_stack, containing_block, absolute_boxes, fixed_boxes,
|
|
733
|
-
line_placeholders, child_waiting_floats, line_children
|
|
786
|
+
line_placeholders, child_waiting_floats, line_children,
|
|
787
|
+
first_letter_style, first_line_style))
|
|
788
|
+
|
|
789
|
+
float_widths['left'] = max(float_widths['left'], new_float_widths['left'])
|
|
790
|
+
float_widths['right'] = max(float_widths['right'], new_float_widths['right'])
|
|
734
791
|
|
|
735
792
|
skip_stack = None
|
|
736
793
|
if preserved:
|
|
@@ -767,19 +824,19 @@ def split_inline_box(context, box, position_x, max_x, bottom_space, skip_stack,
|
|
|
767
824
|
# May be None where we have an empty TextBox.
|
|
768
825
|
assert isinstance(child, boxes.TextBox)
|
|
769
826
|
else:
|
|
827
|
+
# Store lines to get previous break points.
|
|
770
828
|
if isinstance(box, boxes.LineBox):
|
|
771
829
|
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
830
|
|
|
778
|
-
|
|
831
|
+
# Check that text doesn’t overflow.
|
|
832
|
+
new_position_x = new_child.position_x + new_child.margin_width()
|
|
833
|
+
if new_position_x - trailing_whitespace_size(context, new_child) > max_x:
|
|
834
|
+
# Text overflows, find previous break point.
|
|
779
835
|
previous_resume_at = _break_waiting_children(
|
|
780
836
|
context, containing_block, max_x, bottom_space, initial_skip_stack,
|
|
781
|
-
absolute_boxes, fixed_boxes, line_placeholders,
|
|
782
|
-
|
|
837
|
+
absolute_boxes, fixed_boxes, line_placeholders, waiting_floats,
|
|
838
|
+
line_children, children, waiting_children, first_letter_style,
|
|
839
|
+
first_line_style)
|
|
783
840
|
if previous_resume_at:
|
|
784
841
|
resume_at = previous_resume_at
|
|
785
842
|
break
|
|
@@ -836,15 +893,7 @@ def split_inline_box(context, box, position_x, max_x, bottom_space, skip_stack,
|
|
|
836
893
|
new_box.width = position_x - content_box_left
|
|
837
894
|
new_box.translate(dx=float_widths['left'], ignore_floats=True)
|
|
838
895
|
|
|
839
|
-
|
|
840
|
-
new_box.height = box.style['font_size']
|
|
841
|
-
half_leading = (line_height - new_box.height) / 2
|
|
842
|
-
# Set margins to the half leading but also compensate for borders and
|
|
843
|
-
# paddings. We want margin_height() == line_height
|
|
844
|
-
new_box.margin_top = (
|
|
845
|
-
half_leading - new_box.border_top_width - new_box.padding_top)
|
|
846
|
-
new_box.margin_bottom = (
|
|
847
|
-
half_leading - new_box.border_bottom_width - new_box.padding_bottom)
|
|
896
|
+
_adjust_line_height(new_box)
|
|
848
897
|
|
|
849
898
|
if new_box.style['position'] == 'relative':
|
|
850
899
|
for absolute_box in absolute_boxes:
|
|
@@ -900,7 +949,7 @@ def split_text_box(context, box, available_width, skip, is_line_start=True):
|
|
|
900
949
|
# "only the 'line-height' is used when calculating the height
|
|
901
950
|
# of the line box."
|
|
902
951
|
# Set margins so that margin_height() == line_height
|
|
903
|
-
line_height, _ =
|
|
952
|
+
line_height, _ = strut(box.style)
|
|
904
953
|
half_leading = (line_height - height) / 2
|
|
905
954
|
box.margin_top = half_leading
|
|
906
955
|
box.margin_bottom = half_leading
|
|
@@ -1019,7 +1068,7 @@ def inline_box_verticality(box, top_bottom_subtrees, baseline_y):
|
|
|
1019
1068
|
if vertical_align == 'baseline':
|
|
1020
1069
|
child_baseline_y = baseline_y
|
|
1021
1070
|
elif vertical_align == 'middle':
|
|
1022
|
-
one_ex = box.style['font_size'] * character_ratio(box.style, '
|
|
1071
|
+
one_ex = box.style['font_size'] * character_ratio(box.style, 'ex')
|
|
1023
1072
|
top = baseline_y - (one_ex + child.margin_height()) / 2
|
|
1024
1073
|
child_baseline_y = top + child.baseline
|
|
1025
1074
|
elif vertical_align == 'text-top':
|
|
@@ -1037,6 +1086,10 @@ def inline_box_verticality(box, top_bottom_subtrees, baseline_y):
|
|
|
1037
1086
|
# Later, we will assume for this subtree that its baseline
|
|
1038
1087
|
# is at y=0.
|
|
1039
1088
|
child_baseline_y = 0
|
|
1089
|
+
elif isinstance(vertical_align, Pending):
|
|
1090
|
+
height, _ = strut(box.style)
|
|
1091
|
+
child_baseline_y = baseline_y - percentage(
|
|
1092
|
+
vertical_align, box.style, height)
|
|
1040
1093
|
else:
|
|
1041
1094
|
# Numeric value: The child’s baseline is `vertical_align` above
|
|
1042
1095
|
# (lower y) the parent’s baseline.
|
weasyprint/layout/page.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Layout for pages and CSS3 margin boxes."""
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
-
from collections import namedtuple
|
|
4
|
+
from collections import defaultdict, namedtuple
|
|
5
5
|
from math import inf
|
|
6
6
|
|
|
7
|
-
from ..css import
|
|
7
|
+
from ..css import AnonymousStyle
|
|
8
8
|
from ..formatting_structure import boxes, build
|
|
9
9
|
from ..logger import PROGRESS_LOGGER
|
|
10
10
|
from .absolute import absolute_box_layout, absolute_layout
|
|
@@ -377,8 +377,7 @@ def make_margin_boxes(context, page, state):
|
|
|
377
377
|
style = context.style_for(page.page_type, at_keyword)
|
|
378
378
|
if style is None:
|
|
379
379
|
# doesn't affect counters
|
|
380
|
-
style =
|
|
381
|
-
element=None, cascaded={}, parent_style=page.style)
|
|
380
|
+
style = AnonymousStyle(page.style)
|
|
382
381
|
_standardize_page_based_counters(style, at_keyword)
|
|
383
382
|
box = boxes.MarginBox(at_keyword, style)
|
|
384
383
|
# Empty boxes should not be generated, but they may be needed for
|
|
@@ -493,7 +492,8 @@ def margin_box_content_layout(context, page, box):
|
|
|
493
492
|
box, resume_at, next_page, _, _, _ = block_container_layout(
|
|
494
493
|
context, box, bottom_space=-inf, skip_stack=None, page_is_empty=True,
|
|
495
494
|
absolute_boxes=positioned_boxes, fixed_boxes=positioned_boxes,
|
|
496
|
-
adjoining_margins=None,
|
|
495
|
+
adjoining_margins=None, first_letter_style=None, first_line_style=None,
|
|
496
|
+
discard=False, max_lines=None)
|
|
497
497
|
assert resume_at is None
|
|
498
498
|
for absolute_box in positioned_boxes:
|
|
499
499
|
absolute_layout(
|
|
@@ -622,38 +622,71 @@ 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
|
|
628
630
|
out_of_flow_boxes = []
|
|
631
|
+
excluded_shapes = defaultdict(list)
|
|
629
632
|
broken_out_of_flow = {}
|
|
630
633
|
context_out_of_flow = context.broken_out_of_flow.values()
|
|
631
634
|
context.broken_out_of_flow = broken_out_of_flow
|
|
632
|
-
for box, containing_block, skip_stack in context_out_of_flow:
|
|
635
|
+
for box, containing_block, context_box, skip_stack in context_out_of_flow:
|
|
636
|
+
if context_box:
|
|
637
|
+
context.create_block_formatting_context(context_box)
|
|
633
638
|
box.position_y = root_box.content_box_y()
|
|
634
639
|
if box.is_floated():
|
|
635
640
|
out_of_flow_box, out_of_flow_resume_at = float_layout(
|
|
636
641
|
context, box, containing_block, positioned_boxes,
|
|
637
642
|
positioned_boxes, 0, skip_stack)
|
|
643
|
+
excluded_shapes[context_box].append(out_of_flow_box)
|
|
638
644
|
else:
|
|
639
645
|
assert box.is_absolutely_positioned()
|
|
640
646
|
out_of_flow_box, out_of_flow_resume_at = absolute_box_layout(
|
|
641
647
|
context, box, containing_block, positioned_boxes, 0,
|
|
642
648
|
skip_stack)
|
|
643
649
|
out_of_flow_boxes.append(out_of_flow_box)
|
|
650
|
+
page_is_empty = False
|
|
644
651
|
if out_of_flow_resume_at:
|
|
645
|
-
|
|
646
|
-
box, containing_block, out_of_flow_resume_at)
|
|
652
|
+
context.add_broken_out_of_flow(
|
|
653
|
+
out_of_flow_box, box, containing_block, out_of_flow_resume_at)
|
|
654
|
+
if context_box:
|
|
655
|
+
context.finish_block_formatting_context()
|
|
656
|
+
|
|
657
|
+
# Set excluded shapes from broken out-of-flow for in-flow content.
|
|
658
|
+
for context_box, shapes in excluded_shapes.items():
|
|
659
|
+
context._excluded_shapes[context_box] = shapes
|
|
660
|
+
|
|
661
|
+
# Display in-flow content.
|
|
662
|
+
initial_root_box = root_box
|
|
663
|
+
initial_resume_at = resume_at
|
|
647
664
|
root_box, resume_at, next_page, _, _, _ = block_level_layout(
|
|
648
665
|
context, root_box, 0, resume_at, initial_containing_block,
|
|
649
666
|
page_is_empty, positioned_boxes, positioned_boxes, adjoining_margins)
|
|
650
|
-
|
|
667
|
+
if not root_box:
|
|
668
|
+
# In-flow page rendering didn’t progress, only out-of-flow did. Render empty box
|
|
669
|
+
# at skip_stack and force fragmentation to make the root box and its descendants
|
|
670
|
+
# cover the whole page height.
|
|
671
|
+
assert not page_is_empty
|
|
672
|
+
box = parent = initial_root_box = initial_root_box.deepcopy()
|
|
673
|
+
skip_stack = initial_resume_at
|
|
674
|
+
while skip_stack and len(skip_stack) == 1:
|
|
675
|
+
(skip, skip_stack), = skip_stack.items()
|
|
676
|
+
box, parent = box.children[skip], box
|
|
677
|
+
parent.children = []
|
|
678
|
+
parent.force_fragmentation = True
|
|
679
|
+
root_box, _, _, _, _, _ = block_level_layout(
|
|
680
|
+
context, initial_root_box, 0, initial_resume_at, initial_containing_block,
|
|
681
|
+
True, positioned_boxes, positioned_boxes, adjoining_margins)
|
|
682
|
+
resume_at = initial_resume_at
|
|
651
683
|
root_box.children = out_of_flow_boxes + root_box.children
|
|
652
684
|
|
|
653
685
|
footnote_area = build.create_anonymous_boxes(footnote_area.deepcopy())
|
|
654
686
|
footnote_area = block_level_layout(
|
|
655
|
-
context, footnote_area,
|
|
656
|
-
|
|
687
|
+
context, footnote_area, bottom_space=-inf, skip_stack=None,
|
|
688
|
+
containing_block=footnote_area.page, page_is_empty=True,
|
|
689
|
+
absolute_boxes=positioned_boxes, fixed_boxes=positioned_boxes)[0]
|
|
657
690
|
footnote_area.translate(dy=-footnote_area.margin_height())
|
|
658
691
|
|
|
659
692
|
page.fixed_boxes = [
|
|
@@ -664,7 +697,7 @@ def make_page(context, root_box, page_type, resume_at, page_number,
|
|
|
664
697
|
context, absolute_box, page, positioned_boxes, bottom_space=0,
|
|
665
698
|
skip_stack=None)
|
|
666
699
|
|
|
667
|
-
context.finish_block_formatting_context(
|
|
700
|
+
context.finish_block_formatting_context()
|
|
668
701
|
|
|
669
702
|
page.children = [root_box, footnote_area]
|
|
670
703
|
|
weasyprint/layout/percent.py
CHANGED
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from math import inf
|
|
4
4
|
|
|
5
|
+
from ..css import resolve_math
|
|
6
|
+
from ..css.functions import check_math
|
|
5
7
|
from ..formatting_structure import boxes
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
def percentage(value, refer_to):
|
|
10
|
+
def percentage(value, computed, refer_to):
|
|
9
11
|
"""Return the percentage of the reference value, or the value unchanged.
|
|
10
12
|
|
|
11
|
-
``refer_to`` is the length for 100%.
|
|
12
|
-
just replaces percentages.
|
|
13
|
+
``refer_to`` is the length for 100%.
|
|
13
14
|
|
|
14
15
|
"""
|
|
16
|
+
if check_math(value):
|
|
17
|
+
value = resolve_math(value, computed, refer_to=refer_to)
|
|
15
18
|
if value is None or value == 'auto':
|
|
16
19
|
return value
|
|
17
|
-
elif value.unit == 'px':
|
|
20
|
+
elif value.unit.lower() == 'px':
|
|
18
21
|
return value.value
|
|
19
22
|
else:
|
|
20
23
|
assert value.unit == '%'
|
|
@@ -31,7 +34,7 @@ def resolve_one_percentage(box, property_name, refer_to):
|
|
|
31
34
|
# box.style has computed values
|
|
32
35
|
value = box.style[property_name]
|
|
33
36
|
# box attributes are used values
|
|
34
|
-
percent = percentage(value, refer_to)
|
|
37
|
+
percent = percentage(value, box.style, refer_to)
|
|
35
38
|
setattr(box, property_name, percent)
|
|
36
39
|
if property_name in ('min_width', 'min_height') and percent == 'auto':
|
|
37
40
|
setattr(box, property_name, 0)
|
|
@@ -75,10 +78,10 @@ def resolve_percentages(box, containing_block):
|
|
|
75
78
|
# Special handling when the height of the containing block
|
|
76
79
|
# depends on its content.
|
|
77
80
|
height = box.style['height']
|
|
78
|
-
if height == 'auto' or height.unit == '%':
|
|
81
|
+
if height == 'auto' or check_math(height) or height.unit == '%':
|
|
79
82
|
box.height = 'auto'
|
|
80
83
|
else:
|
|
81
|
-
assert height.unit == 'px'
|
|
84
|
+
assert height.unit.lower() == 'px'
|
|
82
85
|
box.height = height.value
|
|
83
86
|
resolve_one_percentage(box, 'min_height', 0)
|
|
84
87
|
resolve_one_percentage(box, 'max_height', inf)
|
|
@@ -104,7 +107,8 @@ def resolve_percentages(box, containing_block):
|
|
|
104
107
|
def resolve_radii_percentages(box):
|
|
105
108
|
for corner in ('top_left', 'top_right', 'bottom_right', 'bottom_left'):
|
|
106
109
|
property_name = f'border_{corner}_radius'
|
|
107
|
-
|
|
110
|
+
computed = box.style[property_name]
|
|
111
|
+
rx, ry = computed
|
|
108
112
|
|
|
109
113
|
# Short track for common case
|
|
110
114
|
if (0, 'px') in (rx, ry):
|
|
@@ -116,8 +120,8 @@ def resolve_radii_percentages(box):
|
|
|
116
120
|
setattr(box, property_name, (0, 0))
|
|
117
121
|
break
|
|
118
122
|
else:
|
|
119
|
-
rx = percentage(rx, box.border_width())
|
|
120
|
-
ry = percentage(ry, box.border_height())
|
|
123
|
+
rx = percentage(rx, box.style, box.border_width())
|
|
124
|
+
ry = percentage(ry, box.style, box.border_height())
|
|
121
125
|
setattr(box, property_name, (rx, ry))
|
|
122
126
|
|
|
123
127
|
|