weasyprint 64.1__py3-none-any.whl → 65.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- weasyprint/__init__.py +2 -1
- weasyprint/css/computed_values.py +1 -1
- weasyprint/css/html5_ph.css +65 -178
- weasyprint/css/html5_ua.css +249 -749
- weasyprint/css/html5_ua_form.css +3 -13
- weasyprint/css/validation/descriptors.py +9 -0
- weasyprint/draw/border.py +4 -4
- weasyprint/draw/text.py +12 -8
- weasyprint/formatting_structure/build.py +17 -17
- weasyprint/images.py +2 -2
- weasyprint/layout/__init__.py +17 -0
- weasyprint/layout/absolute.py +1 -1
- weasyprint/layout/block.py +23 -20
- weasyprint/layout/column.py +2 -3
- weasyprint/layout/flex.py +396 -571
- weasyprint/layout/float.py +6 -4
- weasyprint/layout/inline.py +23 -27
- weasyprint/layout/page.py +22 -18
- weasyprint/layout/percent.py +41 -46
- weasyprint/layout/preferred.py +13 -16
- weasyprint/pdf/fonts.py +22 -23
- weasyprint/stacking.py +2 -2
- weasyprint/svg/__init__.py +6 -3
- weasyprint/text/constants.py +5 -0
- weasyprint/text/ffi.py +12 -0
- weasyprint/text/fonts.py +12 -3
- weasyprint/text/line_break.py +8 -7
- {weasyprint-64.1.dist-info → weasyprint-65.1.dist-info}/METADATA +2 -2
- {weasyprint-64.1.dist-info → weasyprint-65.1.dist-info}/RECORD +32 -32
- {weasyprint-64.1.dist-info → weasyprint-65.1.dist-info}/WHEEL +1 -1
- {weasyprint-64.1.dist-info → weasyprint-65.1.dist-info}/entry_points.txt +0 -0
- {weasyprint-64.1.dist-info → weasyprint-65.1.dist-info}/licenses/LICENSE +0 -0
weasyprint/css/html5_ua_form.css
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
/* Default stylesheet for PDF forms */
|
|
2
2
|
|
|
3
|
-
button, input, select, textarea {
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
select option,
|
|
8
|
-
select:not([multiple])::before,
|
|
9
|
-
input:not([type="submit"])::before {
|
|
10
|
-
visibility: hidden;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
textarea {
|
|
14
|
-
color: transparent;
|
|
15
|
-
}
|
|
3
|
+
button, input, select, textarea { appearance: auto }
|
|
4
|
+
select option, select:not([multiple])::before, input:not([type="submit"])::before {visibility: hidden }
|
|
5
|
+
textarea { text-indent: 10000% } /* Hide text but don’t change color used by PDF form */
|
|
@@ -208,6 +208,15 @@ def font_variant(tokens):
|
|
|
208
208
|
return values
|
|
209
209
|
|
|
210
210
|
|
|
211
|
+
@descriptor('font-face')
|
|
212
|
+
@comma_separated_list
|
|
213
|
+
@single_token
|
|
214
|
+
def unicode_range(token):
|
|
215
|
+
"""``unicode_range`` descriptor validation."""
|
|
216
|
+
if token.type == 'unicode-range':
|
|
217
|
+
return token
|
|
218
|
+
|
|
219
|
+
|
|
211
220
|
@descriptor('counter-style')
|
|
212
221
|
def system(tokens):
|
|
213
222
|
"""``system`` descriptor validation."""
|
weasyprint/draw/border.py
CHANGED
|
@@ -419,9 +419,9 @@ def clip_border_segment(stream, style, width, side, border_box,
|
|
|
419
419
|
else:
|
|
420
420
|
# 2x - 1/2 dashes
|
|
421
421
|
dash = length / (dash_length + dash_length % 2 - 0.5)
|
|
422
|
-
dashes1 =
|
|
423
|
-
dashes2 =
|
|
424
|
-
line =
|
|
422
|
+
dashes1 = ceil((chl1 - dash / 2) / dash)
|
|
423
|
+
dashes2 = ceil((chl2 - dash / 2) / dash)
|
|
424
|
+
line = floor(line_length / dash)
|
|
425
425
|
|
|
426
426
|
def draw_dots(dashes, line, way, x, y, px, py, chl):
|
|
427
427
|
if not dashes:
|
|
@@ -483,7 +483,7 @@ def clip_border_segment(stream, style, width, side, border_box,
|
|
|
483
483
|
stream.end()
|
|
484
484
|
dash = length / (
|
|
485
485
|
round(length / dash) - (round(length / dash) + 1) % 2) or 1
|
|
486
|
-
for i in range(0,
|
|
486
|
+
for i in range(0, round(length / dash), 2):
|
|
487
487
|
if side == 'top':
|
|
488
488
|
stream.rectangle(bbx + i * dash, bby, dash, width)
|
|
489
489
|
elif side == 'right':
|
weasyprint/draw/text.py
CHANGED
|
@@ -134,7 +134,6 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
|
|
|
134
134
|
first_line, index = textbox.pango_layout.get_first_line()
|
|
135
135
|
|
|
136
136
|
utf8_text = textbox.pango_layout.text.encode()
|
|
137
|
-
previous_utf8_position = 0
|
|
138
137
|
stream.set_text_matrix(*matrix.values)
|
|
139
138
|
previous_pango_font = None
|
|
140
139
|
string = ''
|
|
@@ -152,8 +151,11 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
|
|
|
152
151
|
clusters = glyph_string.log_clusters
|
|
153
152
|
|
|
154
153
|
# Get positions of the glyphs in the UTF-8 string.
|
|
155
|
-
utf8_positions = [offset + clusters[i] for i in range(
|
|
156
|
-
|
|
154
|
+
utf8_positions = [offset + clusters[i] for i in range(num_glyphs)]
|
|
155
|
+
if glyph_item.item.analysis.level % 2:
|
|
156
|
+
utf8_positions.insert(0, offset + glyph_item.item.length) # rtl
|
|
157
|
+
else:
|
|
158
|
+
utf8_positions.append(offset + glyph_item.item.length) # ltr
|
|
157
159
|
|
|
158
160
|
pango_font = glyph_item.item.analysis.font
|
|
159
161
|
if pango_font != previous_pango_font:
|
|
@@ -161,6 +163,10 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
|
|
|
161
163
|
previous_pango_font = pango_font
|
|
162
164
|
font, font_size = stream.add_font(pango_font)
|
|
163
165
|
|
|
166
|
+
# Workaround for https://gitlab.gnome.org/GNOME/pango/-/issues/530.
|
|
167
|
+
if pango.pango_version() < 14802:
|
|
168
|
+
font_size = textbox.style['font_size']
|
|
169
|
+
|
|
164
170
|
# Go through the run glyphs.
|
|
165
171
|
if string:
|
|
166
172
|
stream.show_text(string)
|
|
@@ -175,7 +181,6 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
|
|
|
175
181
|
glyph & pango.PANGO_GLYPH_UNKNOWN_FLAG):
|
|
176
182
|
string += f'>{-width / font_size}<'
|
|
177
183
|
continue
|
|
178
|
-
utf8_position = utf8_positions[i]
|
|
179
184
|
|
|
180
185
|
offset = glyph_info.geometry.x_offset / font_size
|
|
181
186
|
rise = glyph_info.geometry.y_offset / 1000
|
|
@@ -202,8 +207,8 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
|
|
|
202
207
|
if glyph not in font.widths:
|
|
203
208
|
pango.pango_font_get_glyph_extents(
|
|
204
209
|
pango_font, glyph, stream.ink_rect, stream.logical_rect)
|
|
205
|
-
font.widths[glyph] =
|
|
206
|
-
stream.logical_rect.width * 1000 * FROM_UNITS / font_size)
|
|
210
|
+
font.widths[glyph] = round(
|
|
211
|
+
stream.logical_rect.width * 1000 * FROM_UNITS / font_size)
|
|
207
212
|
|
|
208
213
|
# Set kerning, word spacing, letter spacing.
|
|
209
214
|
kerning = int(
|
|
@@ -213,9 +218,8 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
|
|
|
213
218
|
|
|
214
219
|
# Create mapping between glyphs and characters.
|
|
215
220
|
if glyph not in font.cmap:
|
|
216
|
-
utf8_slice = slice(
|
|
221
|
+
utf8_slice = slice(*sorted(utf8_positions[i:i+2]))
|
|
217
222
|
font.cmap[glyph] = utf8_text[utf8_slice].decode()
|
|
218
|
-
previous_utf8_position = utf8_position
|
|
219
223
|
|
|
220
224
|
# Create list of emojis.
|
|
221
225
|
if font.svg:
|
|
@@ -724,9 +724,7 @@ def is_whitespace(box, _has_non_whitespace=re.compile('\\S').search):
|
|
|
724
724
|
|
|
725
725
|
|
|
726
726
|
def wrap_improper(box, children, wrapper_type, test=None):
|
|
727
|
-
"""
|
|
728
|
-
Wrap consecutive children that do not pass ``test`` in a box of type
|
|
729
|
-
``wrapper_type``.
|
|
727
|
+
"""Wrap consecutive children that do not pass ``test`` in a ``wrapper_type`` box.
|
|
730
728
|
|
|
731
729
|
``test`` defaults to children being of the same type as ``wrapper_type``.
|
|
732
730
|
|
|
@@ -744,17 +742,7 @@ def wrap_improper(box, children, wrapper_type, test=None):
|
|
|
744
742
|
improper = []
|
|
745
743
|
yield child
|
|
746
744
|
else:
|
|
747
|
-
|
|
748
|
-
# so there is no need to take special care with the definition
|
|
749
|
-
# of "consecutive".
|
|
750
|
-
if isinstance(box, boxes.FlexContainerBox):
|
|
751
|
-
# The display value of a flex item must be "blockified", see
|
|
752
|
-
# https://www.w3.org/TR/css-flexbox-1/#flex-items
|
|
753
|
-
# TODO: These blocks are currently ignored, we should
|
|
754
|
-
# "blockify" them and their children.
|
|
755
|
-
pass
|
|
756
|
-
else:
|
|
757
|
-
improper.append(child)
|
|
745
|
+
improper.append(child)
|
|
758
746
|
if improper:
|
|
759
747
|
wrapper = wrapper_type.anonymous_from(box, children=[])
|
|
760
748
|
# Apply the rules again on the new wrapper
|
|
@@ -1023,6 +1011,7 @@ def flex_children(box, children):
|
|
|
1023
1011
|
if isinstance(box, boxes.FlexContainerBox):
|
|
1024
1012
|
flex_children = []
|
|
1025
1013
|
for child in children:
|
|
1014
|
+
child.is_floated = lambda: False
|
|
1026
1015
|
if child.is_in_normal_flow():
|
|
1027
1016
|
child.is_flex_item = True
|
|
1028
1017
|
if isinstance(child, boxes.TextBox) and not child.text.strip(' '):
|
|
@@ -1030,8 +1019,14 @@ def flex_children(box, children):
|
|
|
1030
1019
|
# affected by the white-space property"
|
|
1031
1020
|
# https://www.w3.org/TR/css-flexbox-1/#flex-items
|
|
1032
1021
|
continue
|
|
1033
|
-
if isinstance(child, boxes.
|
|
1034
|
-
anonymous = boxes.BlockBox.anonymous_from(
|
|
1022
|
+
if isinstance(child, boxes.InlineBlockBox):
|
|
1023
|
+
anonymous = boxes.BlockBox.anonymous_from(child, child.children)
|
|
1024
|
+
anonymous.style = child.style
|
|
1025
|
+
anonymous.is_flex_item = True
|
|
1026
|
+
flex_children.append(anonymous)
|
|
1027
|
+
elif isinstance(child, boxes.InlineLevelBox):
|
|
1028
|
+
anonymous = boxes.BlockBox.anonymous_from(child, [child])
|
|
1029
|
+
anonymous.style = child.style
|
|
1035
1030
|
anonymous.is_flex_item = True
|
|
1036
1031
|
flex_children.append(anonymous)
|
|
1037
1032
|
else:
|
|
@@ -1069,7 +1064,12 @@ def grid_children(box, children):
|
|
|
1069
1064
|
# affected by the white-space property"
|
|
1070
1065
|
# https://drafts.csswg.org/css-grid-2/#grid-item
|
|
1071
1066
|
continue
|
|
1072
|
-
if isinstance(child, boxes.
|
|
1067
|
+
if isinstance(child, boxes.InlineBlockBox):
|
|
1068
|
+
anonymous = boxes.BlockBox.anonymous_from(child, child.children)
|
|
1069
|
+
anonymous.style = child.style
|
|
1070
|
+
anonymous.is_grid_item = True
|
|
1071
|
+
grid_children.append(anonymous)
|
|
1072
|
+
elif isinstance(child, boxes.InlineLevelBox):
|
|
1073
1073
|
anonymous = boxes.BlockBox.anonymous_from(child, [child])
|
|
1074
1074
|
anonymous.style = child.style
|
|
1075
1075
|
child.is_grid_item = False
|
weasyprint/images.py
CHANGED
|
@@ -127,8 +127,8 @@ class RasterImage:
|
|
|
127
127
|
width, height = self.width, self.height
|
|
128
128
|
else:
|
|
129
129
|
thumbnail = Image.open(io.BytesIO(self.image_data.data))
|
|
130
|
-
width = max(1,
|
|
131
|
-
height = max(1,
|
|
130
|
+
width = max(1, round(self.width * dpi_ratio))
|
|
131
|
+
height = max(1, round(self.height * dpi_ratio))
|
|
132
132
|
thumbnail.thumbnail((width, height))
|
|
133
133
|
image_file = io.BytesIO()
|
|
134
134
|
thumbnail.save(
|
weasyprint/layout/__init__.py
CHANGED
|
@@ -215,6 +215,12 @@ def layout_document(html, root_box, context, max_loops=8):
|
|
|
215
215
|
yield page
|
|
216
216
|
|
|
217
217
|
|
|
218
|
+
class FakeList(list):
|
|
219
|
+
"""List in which you can’t append objects."""
|
|
220
|
+
def append(self, item):
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
|
|
218
224
|
class LayoutContext:
|
|
219
225
|
def __init__(self, style_for, get_image_from_uri, font_config,
|
|
220
226
|
counter_style, target_collector):
|
|
@@ -271,6 +277,17 @@ class LayoutContext:
|
|
|
271
277
|
else:
|
|
272
278
|
self.excluded_shapes = None
|
|
273
279
|
|
|
280
|
+
def create_flex_formatting_context(self):
|
|
281
|
+
self.excluded_shapes = FakeList()
|
|
282
|
+
self._excluded_shapes_lists.append(self.excluded_shapes)
|
|
283
|
+
|
|
284
|
+
def finish_flex_formatting_context(self, root_box):
|
|
285
|
+
self._excluded_shapes_lists.pop()
|
|
286
|
+
if self._excluded_shapes_lists:
|
|
287
|
+
self.excluded_shapes = self._excluded_shapes_lists[-1]
|
|
288
|
+
else:
|
|
289
|
+
self.excluded_shapes = None
|
|
290
|
+
|
|
274
291
|
def get_string_set_for(self, page, name, keyword='first'):
|
|
275
292
|
"""Resolve value of string function."""
|
|
276
293
|
return self.get_string_or_element_for(
|
weasyprint/layout/absolute.py
CHANGED
|
@@ -205,7 +205,7 @@ def absolute_block(context, box, containing_block, fixed_boxes, bottom_space,
|
|
|
205
205
|
new_box, resume_at, _, _, _ = flex_layout(
|
|
206
206
|
context, box, bottom_space, skip_stack, containing_block,
|
|
207
207
|
page_is_empty=True, absolute_boxes=absolute_boxes,
|
|
208
|
-
fixed_boxes=fixed_boxes)
|
|
208
|
+
fixed_boxes=fixed_boxes, discard=False)
|
|
209
209
|
elif isinstance(box, (boxes.GridContainerBox)):
|
|
210
210
|
new_box, resume_at, _, _, _ = grid_layout(
|
|
211
211
|
context, box, bottom_space, skip_stack, containing_block,
|
weasyprint/layout/block.py
CHANGED
|
@@ -10,7 +10,7 @@ from .float import avoid_collisions, float_layout, get_clearance
|
|
|
10
10
|
from .grid import grid_layout
|
|
11
11
|
from .inline import iter_line_boxes
|
|
12
12
|
from .min_max import handle_min_max_width
|
|
13
|
-
from .percent import resolve_percentages, resolve_position_percentages
|
|
13
|
+
from .percent import percentage, resolve_percentages, resolve_position_percentages
|
|
14
14
|
from .replaced import block_replaced_box_layout
|
|
15
15
|
from .table import table_layout, table_wrapper_width
|
|
16
16
|
|
|
@@ -81,7 +81,7 @@ def block_level_layout_switch(context, box, bottom_space, skip_stack,
|
|
|
81
81
|
elif isinstance(box, boxes.FlexBox):
|
|
82
82
|
result = flex_layout(
|
|
83
83
|
context, box, bottom_space, skip_stack, containing_block,
|
|
84
|
-
page_is_empty, absolute_boxes, fixed_boxes)
|
|
84
|
+
page_is_empty, absolute_boxes, fixed_boxes, discard)
|
|
85
85
|
elif isinstance(box, boxes.GridBox):
|
|
86
86
|
result = grid_layout(
|
|
87
87
|
context, box, bottom_space, skip_stack, containing_block,
|
|
@@ -496,11 +496,24 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
496
496
|
adjoining_margins = []
|
|
497
497
|
position_y = box.content_box_y()
|
|
498
498
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
499
|
+
# TODO: Merge this with block_container_layout, block_level_layout, _in_flow_layout,
|
|
500
|
+
# and check code above.
|
|
501
|
+
if adjoining_margins:
|
|
502
|
+
if box.is_table_wrapper: # should not be a special case
|
|
503
|
+
collapsed_margin = collapse_margin(adjoining_margins)
|
|
504
|
+
child.position_y += collapsed_margin
|
|
505
|
+
position_y += collapsed_margin
|
|
506
|
+
adjoining_margins = []
|
|
507
|
+
elif not isinstance(child, boxes.BlockBox): # blocks handle that themselves
|
|
508
|
+
if child.style['margin_top'] == 'auto':
|
|
509
|
+
margin_top = 0
|
|
510
|
+
else:
|
|
511
|
+
margin_top = percentage(child.style['margin_top'], box.width)
|
|
512
|
+
adjoining_margins.append(margin_top)
|
|
513
|
+
offset_y = collapse_margin(adjoining_margins) - margin_top
|
|
514
|
+
child.position_y += offset_y
|
|
515
|
+
position_y += offset_y
|
|
516
|
+
adjoining_margins = []
|
|
504
517
|
|
|
505
518
|
page_is_empty_with_no_children = page_is_empty and not any(
|
|
506
519
|
child for child in new_children
|
|
@@ -515,15 +528,6 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
515
528
|
fixed_boxes, adjoining_margins, discard, max_lines)
|
|
516
529
|
|
|
517
530
|
if new_child is not None:
|
|
518
|
-
# We need to do this after the child layout to have the
|
|
519
|
-
# used value for margin_top (eg. it might be a percentage.)
|
|
520
|
-
if not isinstance(new_child, (boxes.BlockBox, boxes.TableBox)):
|
|
521
|
-
adjoining_margins.append(new_child.margin_top)
|
|
522
|
-
offset_y = (
|
|
523
|
-
collapse_margin(adjoining_margins) - new_child.margin_top)
|
|
524
|
-
new_child.translate(0, offset_y)
|
|
525
|
-
# else: blocks handle that themselves.
|
|
526
|
-
|
|
527
531
|
if not collapsing_through:
|
|
528
532
|
new_content_position_y = (
|
|
529
533
|
new_child.content_box_y() + new_child.height)
|
|
@@ -629,13 +633,12 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
629
633
|
page_is_empty, absolute_boxes, fixed_boxes,
|
|
630
634
|
adjoining_margins, discard, max_lines):
|
|
631
635
|
"""Set the ``box`` height."""
|
|
632
|
-
|
|
633
|
-
# block_container_layout, there's probably a better solution.
|
|
634
|
-
assert isinstance(box, (boxes.BlockContainerBox, boxes.FlexBox))
|
|
636
|
+
assert isinstance(box, boxes.BlockContainerBox)
|
|
635
637
|
|
|
636
638
|
if box.establishes_formatting_context():
|
|
637
639
|
context.create_block_formatting_context()
|
|
638
640
|
|
|
641
|
+
# TODO: merge this with _in_flow_layout, flex_layout…
|
|
639
642
|
is_start = skip_stack is None
|
|
640
643
|
box.remove_decoration(start=not is_start, end=False)
|
|
641
644
|
|
|
@@ -753,7 +756,7 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
753
756
|
max_lines)
|
|
754
757
|
elif stop:
|
|
755
758
|
if box.height != 'auto':
|
|
756
|
-
if context.overflows(box.position_y + box.
|
|
759
|
+
if context.overflows(box.position_y + box.border_height(), position_y):
|
|
757
760
|
# Box heigh is fixed and it doesn’t overflow page, forget
|
|
758
761
|
# overflowing children.
|
|
759
762
|
resume_at = None
|
weasyprint/layout/column.py
CHANGED
|
@@ -51,11 +51,10 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
|
51
51
|
if width == 'auto' and count != 'auto':
|
|
52
52
|
width = max(0, box.width - (count - 1) * gap) / count
|
|
53
53
|
elif width != 'auto' and count == 'auto':
|
|
54
|
-
count = max(1,
|
|
54
|
+
count = max(1, floor((box.width + gap) / (width + gap)))
|
|
55
55
|
width = (box.width + gap) / count - gap
|
|
56
56
|
else: # overconstrained, with width != 'auto' and count != 'auto'
|
|
57
|
-
count = max(
|
|
58
|
-
1, min(count, int(floor((box.width + gap) / (width + gap)))))
|
|
57
|
+
count = max(1, min(count, floor((box.width + gap) / (width + gap))))
|
|
59
58
|
width = (box.width + gap) / count - gap
|
|
60
59
|
|
|
61
60
|
# Handle column-span property with the following structure:
|