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.
Files changed (67) hide show
  1. weasyprint/__init__.py +17 -7
  2. weasyprint/__main__.py +21 -10
  3. weasyprint/anchors.py +4 -4
  4. weasyprint/css/__init__.py +732 -67
  5. weasyprint/css/computed_values.py +65 -170
  6. weasyprint/css/counters.py +1 -1
  7. weasyprint/css/functions.py +206 -0
  8. weasyprint/css/html5_ua.css +3 -7
  9. weasyprint/css/html5_ua_form.css +2 -2
  10. weasyprint/css/media_queries.py +3 -1
  11. weasyprint/css/properties.py +6 -2
  12. weasyprint/css/{utils.py → tokens.py} +306 -397
  13. weasyprint/css/units.py +91 -0
  14. weasyprint/css/validation/__init__.py +1 -1
  15. weasyprint/css/validation/descriptors.py +47 -19
  16. weasyprint/css/validation/expanders.py +7 -8
  17. weasyprint/css/validation/properties.py +341 -357
  18. weasyprint/document.py +20 -19
  19. weasyprint/draw/__init__.py +56 -63
  20. weasyprint/draw/border.py +121 -69
  21. weasyprint/draw/color.py +1 -1
  22. weasyprint/draw/text.py +60 -41
  23. weasyprint/formatting_structure/boxes.py +24 -5
  24. weasyprint/formatting_structure/build.py +33 -45
  25. weasyprint/images.py +76 -62
  26. weasyprint/layout/__init__.py +32 -26
  27. weasyprint/layout/absolute.py +7 -6
  28. weasyprint/layout/background.py +7 -7
  29. weasyprint/layout/block.py +195 -152
  30. weasyprint/layout/column.py +19 -24
  31. weasyprint/layout/flex.py +54 -26
  32. weasyprint/layout/float.py +12 -7
  33. weasyprint/layout/grid.py +284 -90
  34. weasyprint/layout/inline.py +121 -68
  35. weasyprint/layout/page.py +45 -12
  36. weasyprint/layout/percent.py +14 -10
  37. weasyprint/layout/preferred.py +105 -63
  38. weasyprint/layout/replaced.py +9 -6
  39. weasyprint/layout/table.py +16 -9
  40. weasyprint/pdf/__init__.py +58 -18
  41. weasyprint/pdf/anchors.py +3 -4
  42. weasyprint/pdf/fonts.py +126 -69
  43. weasyprint/pdf/metadata.py +36 -4
  44. weasyprint/pdf/pdfa.py +19 -3
  45. weasyprint/pdf/pdfua.py +7 -115
  46. weasyprint/pdf/pdfx.py +83 -0
  47. weasyprint/pdf/stream.py +57 -49
  48. weasyprint/pdf/tags.py +307 -0
  49. weasyprint/stacking.py +14 -15
  50. weasyprint/svg/__init__.py +59 -32
  51. weasyprint/svg/bounding_box.py +4 -2
  52. weasyprint/svg/defs.py +4 -9
  53. weasyprint/svg/images.py +11 -3
  54. weasyprint/svg/text.py +11 -2
  55. weasyprint/svg/utils.py +15 -8
  56. weasyprint/text/constants.py +1 -1
  57. weasyprint/text/ffi.py +4 -3
  58. weasyprint/text/fonts.py +13 -5
  59. weasyprint/text/line_break.py +146 -43
  60. weasyprint/urls.py +41 -13
  61. {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/METADATA +5 -6
  62. weasyprint-67.0.dist-info/RECORD +77 -0
  63. weasyprint/draw/stack.py +0 -13
  64. weasyprint-65.1.dist-info/RECORD +0 -74
  65. {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/WHEEL +0 -0
  66. {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/entry_points.txt +0 -0
  67. {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,8 +7,8 @@ from .percent import percentage, resolve_percentages
7
7
 
8
8
 
9
9
  def columns_layout(context, box, bottom_space, skip_stack, containing_block,
10
- page_is_empty, absolute_boxes, fixed_boxes,
11
- adjoining_margins):
10
+ page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins,
11
+ first_letter_style, first_line_style):
12
12
  """Lay out a multi-column ``box``."""
13
13
  from .block import ( # isort:skip
14
14
  block_box_layout, block_level_layout, block_level_width,
@@ -26,11 +26,11 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
26
26
  absolute_boxes = []
27
27
 
28
28
  box = box.copy_with_children(box.children)
29
- box.position_y += collapse_margin(adjoining_margins) - box.margin_top
29
+ box.position_y += collapse_margin(adjoining_margins)
30
30
 
31
31
  # Set height if defined
32
32
  if height != 'auto' and height.unit != '%':
33
- assert height.unit == 'px'
33
+ assert height.unit.lower() == 'px'
34
34
  height_defined = True
35
35
  empty_space = context.page_bottom - box.content_box_y() - height.value
36
36
  bottom_space = max(bottom_space, empty_space)
@@ -45,7 +45,7 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
45
45
  # 1em because in column context
46
46
  gap = style['font_size']
47
47
  else:
48
- gap = percentage(style['column_gap'], box.width)
48
+ gap = percentage(style['column_gap'], box.style, box.width)
49
49
 
50
50
  # Define the number of columns and their widths
51
51
  if width == 'auto' and count != 'auto':
@@ -118,9 +118,9 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
118
118
  block.position_y = current_position_y
119
119
  new_child, resume_at, next_page, adjoining_margins, _, _ = (
120
120
  block_level_layout(
121
- context, block, original_bottom_space, skip_stack,
122
- containing_block, page_is_empty, absolute_boxes,
123
- fixed_boxes, adjoining_margins))
121
+ context, block, original_bottom_space, skip_stack, containing_block,
122
+ page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins,
123
+ first_letter_style, first_line_style))
124
124
  skip_stack = None
125
125
  if new_child is None:
126
126
  last_loop = True
@@ -171,8 +171,8 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
171
171
  context, column_box,
172
172
  context.page_bottom - current_position_y - height,
173
173
  column_skip_stack, containing_block,
174
- page_is_empty or not balancing, [], [], [],
175
- discard=False, max_lines=None)
174
+ page_is_empty or not balancing, [], [], [], first_letter_style,
175
+ first_line_style, discard=False, max_lines=None)
176
176
  if new_box is None:
177
177
  # We didn't render anything, retry
178
178
  column_skip_stack = {0: None}
@@ -190,20 +190,20 @@ 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
196
+ next_box_height = 0
195
197
  if column_skip_stack:
196
198
  next_box = block_box_layout(
197
199
  context, column_box, inf, column_skip_stack,
198
- containing_block, True, [], [], [],
199
- discard=False, max_lines=None)[0]
200
+ containing_block, True, [], [], [], first_letter_style,
201
+ first_line_style, discard=False, max_lines=None)[0]
200
202
  for child in next_box.children:
201
203
  if child.is_in_normal_flow():
202
204
  next_box_height = child.margin_height()
203
205
  break
204
206
  remove_placeholders(context, [next_box], [], [])
205
- else:
206
- next_box_height = 0
207
207
  else:
208
208
  consumed_height = empty_space = next_box_height = 0
209
209
 
@@ -265,7 +265,6 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
265
265
  if everything_fits:
266
266
  # Everything fits, start expanding columns at the average
267
267
  # of the column heights
268
- max_height -= last_footnotes_height
269
268
  if (style['column_fill'] == 'balance' or
270
269
  index < columns_and_blocks[-1][0]):
271
270
  balancing = True
@@ -275,11 +274,6 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
275
274
  else:
276
275
  # Content overflows even at maximum height, stop now and
277
276
  # let the columns continue on the next page
278
- height += footnote_area_heights[-1]
279
- if len(footnote_area_heights) > 2:
280
- last_footnotes_height = min(
281
- last_footnotes_height, footnote_area_heights[-1])
282
- height -= last_footnotes_height
283
277
  stop_rendering = True
284
278
  break
285
279
 
@@ -301,9 +295,10 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
301
295
  column_box.position_x += i * (width + gap)
302
296
  new_child, column_skip_stack, column_next_page, _, _, _ = (
303
297
  block_box_layout(
304
- context, column_box, bottom_space, skip_stack,
305
- containing_block, original_page_is_empty, absolute_boxes,
306
- fixed_boxes, None, discard=False, max_lines=None))
298
+ context, column_box, bottom_space, skip_stack, containing_block,
299
+ original_page_is_empty, absolute_boxes, fixed_boxes, None,
300
+ first_letter_style, first_line_style, discard=False,
301
+ max_lines=None))
307
302
  if new_child is None:
308
303
  columns = []
309
304
  break_page = True
@@ -337,7 +332,7 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
337
332
  break
338
333
 
339
334
  # Report footnotes above the defined footnotes height
340
- _report_footnotes(context, last_footnotes_height)
335
+ _report_footnotes(context, footnote_area_heights[-1])
341
336
 
342
337
  if box.children and not new_children:
343
338
  # The box has children but none can be drawn, let's skip the whole box
weasyprint/layout/flex.py CHANGED
@@ -20,7 +20,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
20
20
  from . import block
21
21
 
22
22
  # TODO: merge this with block_container_layout.
23
- context.create_flex_formatting_context()
23
+ context.create_flex_formatting_context(box)
24
24
  resume_at = None
25
25
 
26
26
  is_start = skip_stack is None
@@ -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:
@@ -211,7 +219,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
211
219
  flex_basis = 'content'
212
220
  else:
213
221
  flex_basis = percent.percentage(
214
- child.style['flex_basis'], available_main_space)
222
+ child.style['flex_basis'], child.style, available_main_space)
215
223
  if flex_basis == 'auto':
216
224
  if (flex_basis := getattr(child, main)) == 'auto':
217
225
  flex_basis = 'content'
@@ -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
- child.flex_base_size = max_content_width(context, child, outer=False)
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
- new_child = child.copy()
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)[0]
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)
@@ -430,7 +452,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
430
452
  if not child.frozen:
431
453
  min_size = getattr(child, f'min_{main}')
432
454
  max_size = getattr(child, f'max_{main}')
433
- min_size = max(min_size , min(child.target_main_size, max_size))
455
+ min_size = max(min_size, min(child.target_main_size, max_size))
434
456
  if child.target_main_size < min_size:
435
457
  child.adjustment = min_size - child.target_main_size
436
458
  child.target_main_size = min_size
@@ -471,14 +493,16 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
471
493
  new_child = child.copy()
472
494
  new_child, _, _, adjoining_margins, _, _ = block.block_level_layout_switch(
473
495
  context, new_child, -inf, child_skip_stack, parent_box, page_is_empty,
474
- absolute_boxes, fixed_boxes, [], discard, None)
496
+ absolute_boxes, fixed_boxes, adjoining_margins=[],
497
+ first_letter_style=None, first_line_style=None, discard=discard,
498
+ max_lines=None)
475
499
  child._baseline = find_in_flow_baseline(new_child) or 0
476
500
  if cross == 'height':
477
501
  child.height = new_child.height
478
502
  # As flex items margins never collapse (with other flex items or
479
503
  # with the flex container), we can add the adjoining margins to the
480
- # child bottom margin.
481
- child.margin_bottom += block.collapse_margin(adjoining_margins)
504
+ # child height.
505
+ child.height += block.collapse_margin(adjoining_margins)
482
506
  else:
483
507
  if child.width == 'auto':
484
508
  min_width = min_content_width(context, child, outer=False)
@@ -591,9 +615,9 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
591
615
  align_self = align_items
592
616
  if 'stretch' in align_self and child.style[cross] == 'auto':
593
617
  cross_margins = (
594
- (child.margin_top, child.margin_bottom)
595
- if cross == 'height'
596
- else (child.margin_left, child.margin_right))
618
+ (child.style['margin_top'], child.style['margin_bottom'])
619
+ if cross == 'height' else
620
+ (child.style['margin_left'], child.style['margin_right']))
597
621
  if 'auto' not in cross_margins:
598
622
  cross_size = line.cross_size
599
623
  if cross == 'height':
@@ -690,6 +714,8 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
690
714
  position_main += free_space / (len(line) + 1)
691
715
 
692
716
  growths = sum(child.style['flex_grow'] for child in children)
717
+ if box.style['direction'] == 'rtl' and main == 'width':
718
+ main_gap *= -1
693
719
  for i, (index, child) in enumerate(line):
694
720
  if i:
695
721
  position_main += main_gap
@@ -733,8 +759,9 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
733
759
  line.lower_baseline = line[0][1]._baseline if line else 0
734
760
  for index, child in line:
735
761
  cross_margins = (
736
- (child.margin_top, child.margin_bottom) if cross == 'height'
737
- else (child.margin_left, child.margin_right))
762
+ (child.style['margin_top'], child.style['margin_bottom'])
763
+ if cross == 'height' else
764
+ (child.style['margin_left'], child.style['margin_right']))
738
765
  auto_margins = sum([margin == 'auto' for margin in cross_margins])
739
766
  # If a flex item has auto cross-axis margins…
740
767
  if auto_margins:
@@ -755,14 +782,14 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
755
782
  # If its outer cross size is less than the cross size…
756
783
  extra_cross /= auto_margins
757
784
  if cross == 'height':
758
- if child.margin_top == 'auto':
785
+ if child.style['margin_top'] == 'auto':
759
786
  child.margin_top = extra_cross
760
- if child.margin_bottom == 'auto':
787
+ if child.style['margin_bottom'] == 'auto':
761
788
  child.margin_bottom = extra_cross
762
789
  else:
763
- if child.margin_left == 'auto':
790
+ if child.style['margin_left'] == 'auto':
764
791
  child.margin_left = extra_cross
765
- if child.margin_right == 'auto':
792
+ if child.style['margin_right'] == 'auto':
766
793
  child.margin_right = extra_cross
767
794
  else:
768
795
  # Otherwise…
@@ -815,9 +842,6 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
815
842
  margins += (
816
843
  child.border_left_width + child.border_right_width +
817
844
  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
845
  position_cross += line.cross_size
822
846
 
823
847
  # 15 Determine the flex container’s used cross size.
@@ -877,7 +901,8 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
877
901
  # TODO: Don't use block_level_layout_switch.
878
902
  new_child, child_resume_at = block.block_level_layout_switch(
879
903
  context, child, bottom_space, child_skip_stack, box, page_is_empty,
880
- absolute_boxes, fixed_boxes, adjoining_margins=[], discard=discard,
904
+ absolute_boxes, fixed_boxes, adjoining_margins=[],
905
+ first_letter_style=None, first_line_style=None, discard=discard,
881
906
  max_lines=None)[:2]
882
907
  if new_child is None:
883
908
  if resume_at:
@@ -912,6 +937,9 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
912
937
  absolute_layout(
913
938
  context, absolute_box, box, fixed_boxes, bottom_space, skip_stack=None)
914
939
 
940
+ for child in box.children:
941
+ block.relative_positioning(child, (box.width, box.height))
942
+
915
943
  # TODO: Use real algorithm, see https://www.w3.org/TR/css-flexbox-1/#flex-baselines.
916
944
  if isinstance(box, boxes.InlineFlexBox):
917
945
  if main == 'width': # and main text direction is horizontal
@@ -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
@@ -56,13 +58,11 @@ def float_layout(context, box, containing_block, absolute_boxes, fixed_boxes,
56
58
  table_wrapper_width(context, box, (cb_width, cb_height))
57
59
 
58
60
  if isinstance(box, boxes.BlockContainerBox):
59
- context.create_block_formatting_context()
60
61
  box, resume_at, _, _, _, _ = block_container_layout(
61
- context, box, bottom_space=bottom_space,
62
- skip_stack=skip_stack, page_is_empty=True,
63
- absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
64
- adjoining_margins=None, discard=False, max_lines=None)
65
- context.finish_block_formatting_context(box)
62
+ context, box, bottom_space=bottom_space, skip_stack=skip_stack,
63
+ page_is_empty=True, absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
64
+ adjoining_margins=None, first_letter_style=None, first_line_style=None,
65
+ discard=False, max_lines=None)
66
66
  elif isinstance(box, boxes.FlexContainerBox):
67
67
  box, resume_at, _, _, _ = flex_layout(
68
68
  context, box, bottom_space=bottom_space,
@@ -115,9 +115,14 @@ def find_float_position(context, box, containing_block):
115
115
 
116
116
  def get_clearance(context, box, collapsed_margin=0):
117
117
  """Return None if there is no clearance, otherwise the clearance value."""
118
+ # Box should be after shape that’s broken on this page.
119
+ for broken_shape in context.broken_out_of_flow:
120
+ if broken_shape.is_floated():
121
+ if box.style['clear'] in (broken_shape.style['float'], 'both'):
122
+ return inf
123
+ # Hypothetical position is the position of the top border edge
118
124
  clearance = None
119
125
  hypothetical_position = box.position_y + collapsed_margin
120
- # Hypothetical position is the position of the top border edge
121
126
  for excluded_shape in context.excluded_shapes:
122
127
  if box.style['clear'] in (excluded_shape.style['float'], 'both'):
123
128
  y, h = excluded_shape.position_y, excluded_shape.margin_height()